Some time ago I blogged about a custom template for the vSRX 3.0. One drawback was, that the config-export would no longer work, since this is a custom template but I took this drawback because the speed improvements were very good and I usually export my config manually. But I understand, that some of you use this feature in and out so I tried to generate a config-export-script. With the help of Alain from EVE-NG I was able to map the config-script to the new template 🙂 Huge thanks to Alain 🙂
Here’s what you need to do:
1.) Copy this script with the name config_vsrx30.py to /opt/unetlab/scripts/ – here’s my script, that will use the username ‘root’ and the password ‘Juniper’:
#!/usr/bin/env python3
# scripts/config_vsrx30.py
#
# Import/Export script for vsrx30.
#
# @author Andrea Dainese <andrea.dainese@gmail.com>
# @author Alain Degreffe <eczema@ecze.com>
# @copyright 2014-2016 Andrea Dainese
# @copyright 2017-2018 Alain Degreffe
# @copyright 2019-2020 Christian Scholz
# @license BSD-3-Clause https://github.com/dainok/unetlab/blob/master/LICENSE
# @link http://www.eve-ng.net/
# @version 20200422
import getopt, multiprocessing, os, pexpect, re, sys, time
username = 'root'
password = 'Juniper'
conntimeout = 3 # Maximum time for console connection
expctimeout = 3 # Maximum time for each short expect
longtimeout = 30 # Maximum time for each long expect
timeout = 60 # Maximum run time (conntimeout is included)
def node_login(handler):
# Send an empty line, and wait for the login prompt
i = -1
while i == -1:
try:
handler.sendline('\r\n')
i = handler.expect([
'login:',
'root@.*%',
'root>',
'root@.*>',
'root#',
'root@.*#'], timeout = 5)
except:
i = -1
if i == 0:
# Need to send username and password
handler.sendline(username)
try:
j = handler.expect(['root@.*#', 'Password:'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root@.*%", "password:"] prompt.')
node_quit(handler)
return False
if j == 0:
# Nothing to do
return True
elif j == 1:
handler.sendline(password)
try:
handler.expect('root@.*#', timeout = longtimeout)
except:
print('ERROR: error waiting for "root@.*%" prompt.')
node_quit(handler)
return False
return True
else:
# Unexpected output
node_quit(handler)
return False
elif i == 1:
# Nothing to do
return True
elif i == 2 or i == 3:
# Exit from CLI mode
handler.sendline('exit')
try:
handler.expect('root@.*%', timeout = expctimeout)
except:
print('ERROR: error waiting for "root@.*%" prompt.')
node_quit(handler)
return False
return True
elif i == 4:
# Exit from configuration mode
handler.sendline('exit')
try:
handler.expect(['root>', 'root@.*>'], timeout = expctimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
# Exit from CLI mode
handler.sendline('exit')
try:
handler.expect('root.*%', timeout = expctimeout)
except:
print('ERROR: error waiting for "root@.*%" prompt.')
node_quit(handler)
return False
return True
elif i == 5:
return True
else:
# Unexpected output
node_quit(handler)
return False
def node_quit(handler):
if handler.isalive() == True:
handler.sendline('exit\n')
handler.close()
def config_get(handler):
# Clearing all "expect" buffer
while True:
try:
handler.expect('root@.*#', timeout = 0.1)
except:
break
# Go into CLI mode
handler.sendline('cli')
try:
handler.expect(['root>', 'root@.*>'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
# Disable paging
handler.sendline('set cli screen-length 0')
try:
handler.expect(['root>', 'root@.*>'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
# Getting the config
handler.sendline('show configuration | display set')
try:
handler.expect(['root>', 'root@.*>'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
config = handler.before.decode()
# Exit from config mode
handler.sendline('exit')
try:
handler.expect('root@.*#', timeout = longtimeout)
except:
print('ERROR: error waiting for "root@.*%" prompt.')
node_quit(handler)
return False
# Manipulating the config
config = re.sub('\r', '', config, flags=re.DOTALL) # Unix style
config = re.sub('.*show configuration \| display set', '', config, flags=re.DOTALL) # Header
config = re.sub('\nroot.*>.*', '\n', config, flags=re.DOTALL) # Footer
return config
def config_put(handler, config):
# mount drive
handler.sendline('mount -t cd9660 /dev/cd0 /mnt')
try:
handler.expect(['root>', 'root@.*#'], timeout = expctimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*%"] prompt.')
node_quit(handler)
return False
# Go into CLI mode
handler.sendline('cli')
try:
handler.expect(['root>', 'root@.*>'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
# Got to configure mode
handler.sendline('configure')
try:
handler.expect(['root#', 'root@.*#'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root#", "root@.*#"] prompt.')
node_quit(handler)
return False
# Start the load mode
handler.sendline('load set /mnt/juniper.conf')
try:
handler.expect('load complete', timeout = longtimeout)
except:
print('ERROR: error waiting for "load complete" prompt.')
node_quit(handler)
return False
# Save
handler.sendline('commit')
try:
handler.expect(['root#', 'root@.*#'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root#", "root@.*#"] prompt.')
node_quit(handler)
return False
handler.sendline('exit')
try:
handler.expect(['root>', 'root@.*>'], timeout = longtimeout)
except:
print('ERROR: error waiting for ["root>", "root@.*>"] prompt.')
node_quit(handler)
return False
return True
def usage():
print('Usage: %s <standard options>' %(sys.argv[0]));
print('Standard Options:');
print('-a <s> *Action can be:')
print(' - get: get the startup-configuration and push it to a file')
print(' - put: put the file as startup-configuration')
print('-f <s> *File');
print('-p <n> *Console port');
print('-t <n> Timeout (default = %i)' %(timeout));
print('* Mandatory option')
def now():
# Return current UNIX time in milliseconds
return int(round(time.time() * 1000))
def main(action, fiename, port):
try:
# Connect to the device
tmp = conntimeout
while (tmp > 0):
handler = pexpect.spawn('telnet 127.0.0.1 %i' %(port))
time.sleep(0.1)
tmp = tmp - 0.1
if handler.isalive() == True:
break
if (handler.isalive() != True):
print('ERROR: cannot connect to port "%i".' %(port))
node_quit(handler)
sys.exit(1)
# Login to the device and get a privileged prompt
rc = node_login(handler)
if rc != True:
print('ERROR: failed to login.')
node_quit(handler)
sys.exit(1)
if action == 'get':
config = config_get(handler)
if config in [False, None]:
print('ERROR: failed to retrieve config.')
node_quit(handler)
sys.exit(1)
try:
fd = open(filename, 'a')
fd.write(config)
fd.close()
except:
print('ERROR: cannot write config to file.')
node_quit(handler)
sys.exit(1)
elif action == 'put':
try:
fd = open(filename, 'r')
config = fd.read()
fd.close()
except:
print('ERROR: cannot read config from file.')
node_quit(handler)
sys.exit(1)
rc = config_put(handler, config)
if rc != True:
print('ERROR: failed to push config.')
node_quit(handler)
sys.exit(1)
# Remove lock file
lock = '%s/.lock' %(os.path.dirname(filename))
if os.path.exists(lock):
os.remove(lock)
# Mark as configured
configured = '%s/.configured' %(os.path.dirname(filename))
if not os.path.exists(configured):
open(configured, 'a').close()
node_quit(handler)
sys.exit(0)
except Exception as e:
print('ERROR: got an exception')
print(type(e)) # the exception instance
print(e.args) # arguments stored in .args
print(e) # __str__ allows args to be printed directly,
node_quit(handler)
return False
if __name__ == "__main__":
action = None
filename = None
port = None
# Getting parameters from command line
try:
opts, args = getopt.getopt(sys.argv[1:], 'a:p:t:f:', ['action=', 'port=', 'timeout=', 'file='])
except getopt.GetoptError as e:
usage()
sys.exit(3)
for o, a in opts:
if o in ('-a', '--action'):
action = a
elif o in ('-f', '--file'):
filename = a
elif o in ('-p', '--port'):
try:
port = int(a)
except:
port = -1
elif o in ('-t', '--timeout'):
try:
timeout = int(a) * 1000
except:
timeout = -1
else:
print('ERROR: invalid parameter.')
# Checking mandatory parameters
if action == None or port == None or filename == None:
usage()
print('ERROR: missing mandatory parameters.')
sys.exit(1)
if action not in ['get', 'put']:
usage()
print('ERROR: invalid action.')
sys.exit(1)
if timeout < 0:
usage()
print('ERROR: timeout must be 0 or higher.')
sys.exit(1)
if port < 0:
usage()
print('ERROR: port must be 32768 or higher.')
sys.exit(1)
if action == 'get' and os.path.exists(filename):
usage()
print('ERROR: destination file already exists.')
sys.exit(1)
if action == 'put' and not os.path.exists(filename):
usage()
print('ERROR: source file does not already exist.')
sys.exit(1)
if action == 'put':
try:
fd = open(filename, 'r')
config = fd.read()
fd.close()
except:
usage()
print('ERROR: cannot read from file.')
sys.exit(1)
# Backgrounding the script
end_before = now() + timeout
p = multiprocessing.Process(target=main, name="Main", args=(action, filename, port))
p.start()
while (p.is_alive() and now() < end_before):
# Waiting for the child process to end
time.sleep(1)
if p.is_alive():
# Timeout occurred
print('ERROR: timeout occurred.')
p.terminate()
sys.exit(127)
if p.exitcode != 0:
sys.exit(127)
sys.exit(0)
Now that we have our python script under /opt/unetlab/scripts/config_vsrx30.py we need to add a line to our vSRX 3.0 template located at /opt/unetlab/html/templates/intel/vsrx30.yml
# Copyright (c) 2016, Andrea Dainese
# Copyright (c) 2016, Alain Degreffe
# Copyright (c) 2017, Alain Degreffe
# Copyright (c) 2019, Christian Scholz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the UNetLab Ltd nor the name of EVE-NG Ltd nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
type: qemu
name: vSRX3-0
config_script: config_vsrx30.py
cpulimit: 1
icon: JuniperSRX.png
cpu: 2
ram: 4096
eth_name:
- fxp0/mgmt
eth_format: ge-0/0/{0}
ethernet: 6
qemu_nic: virtio-net-pci
console: telnet
qemu_arch: x86_64
qemu_options: -machine type=pc-1.0,accel=kvm -cpu qemu64,+ssse3,+sse4.1,+sse4.2,+x2apic,+aes,pclmulqdq -serial
mon:stdio -nographic -nodefconfig -nodefaults -rtc base=utc
...
As you can see, the line “config_script: config_vsrx30.py” has been added, so that the server knows, what script to trigger when using the vSRX3.0 template. After I added this line and saved it, I could immediately export without having to reboot the server or wipe a device 🙂
As you can see, it’s now possible to export your vSRX3.0 configs even with the custom template 🙂 Enjoy!
Hi – great tip.
I came across an issue (of my own creation) – I made my script in windows and this causes an error when running:
Extract from the unl_wrapper.txt log
Apr 24 13:39:24 ERROR: Failed to export (80060).
Apr 24 13:39:24 INFO: exporting /opt/unetlab/scripts/config_vsrx30.py -a get -p 32788 -f /tmp/unl_cfg_20_JV5zaR -t 15
/usr/bin/env: ‘python3\r’: No such file or directory
After a bit of a Google I found a similar issue. The python script will have used Windows style line ending characters:
https://askubuntu.com/a/896880
“Your file was created or edited on a Windows system and uses Windows/DOS-style line endings (CR+LF), whereas Linux systems like Ubuntu require Unix-style line endings (LF).”
So I used the app in the post and fixed the script.
Obviosuly if the script had have been created on box or another Linux system then this would not have been an issue!
Cheers
P.S. Is the EVE team going to add your custom template and script into EVE?
Hi SImon,
thanks a lot for the tip – yes – sometimes WIndows messes with the formatting.
Maybe they will – there are also custom templates for JATP, Space PE, Space LC and more 😉