Python.
Python
Python offers support to interact with our network devices for management and administration by using different sets of libraries. On machine with python install we can use the raw python interpreter from Windows cmd or terminal for Mac and Linux to send commands and retrieve information or send configuration to devices. Python also comes with an IDE which can be used as well. In order to be able to interface with the devices we need to have appropriate libraries installed. Think of libraries as add-ons that adds features to the native python. Python by default ships with core modules for basic functionality however you can install additional modules to extend the functionality. Generally we can interact with our devices by using the native CLI using telnet and SSH or by using APIs such as NETCONF and RESTCONF. Luckily there are libraries for all these methods in python.CLI methods
On this section we’ll cover popular libraries available in python that supports native raw interaction with the network device CLI as regular ssh or telnet clients. Some of the popular libraries include- Telnetlib
- Paramiko
- Netmiko
- Pyntc
Telnetlib
The telnetlib uses the telnet protocol to communicate to devices and send raw cli commands. The telnetlib comes with standard python installation and does not require a separate install, as soon as you got python installed on your workstation you are good to go. See demonstration below on how it works Pre-requisite: telnet must be enabled on the remote device.Telnetlib demo
On this demo we'll use telnet lib to run some show commands on a router. Will be using ipython for the python interpreterbasondole@box$ ipython Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.
- Import the telnetlib in python interpreter
- Create a function that will be used to establish connection
- Create a function that will be used to send commands to the device
- Define login parameters
- Connect to the device
- Send sample command to the device
- Send another sample command to the device
- End the session and neatly exist.
In [1]: import telnetlib
In [2]: def telnet_connection(host,username,password): session = telnetlib.Telnet(host) session.read until('Username:'.encode()) session.write(username.encode()+ '\n'.encode()) session.read until('Password:'.encode()) session.write(password.encode()+'\n'.encode()) session.read until(b'#') return session
In [3]: def send_command (session,command): session.write(command.encode()) session.write(b'\n') output = session.read_until(b'#') print (output.decode())
In [4]: username = 'fisi' password = 'fisi123' host = '192.168.56.26'
In [5]: open_session = telnet_connection(host,username,password)
In [6]: send command(open_session, 'show ip interface brief') show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 unassigned YES manual down down GigabitEthernet1.4094 unassigned YES unset down down GigabitEthernet2 unassigned YES manual up up GigabitEthernet2.4094 unassigned YES unset up up GigabitEthernet3 192.168.56.26 YES manual up up KH16#
In [7]: send command(open_session, 'show ver | i up') show ver | i up Technical Support: http://www.cisco.com/techsupport KH16 uptime is 23 hours, 54 minutes KH16#
In [8]: open_session.close()
import telnetlib def telnet_connection(host,username,password): session = telnetlib.Telnet(host) session.read until('Username:'.encode()) session.write(username.encode()+ '\n'.encode()) session.read until('Password:'.encode()) session.write(password.encode()+'\n'.encode()) session.read until(b'#') return session def send_command (session,command): session.write(command.encode()) session.write(b'\n') output = session.read_until(b'#') print (output.decode()) username = 'fisi' password = 'fisi123' host = '192.168.56.26' open_session = telnet_connection(host,username,password) send command(open_session, 'show ip interface brief') send command(open_session, 'show ver | i up') open_session.close()
Paramiko
Paramiko uses the SSH protocol to communicate with devices and sends raw cli commands as it will be shown in the demonstration. In very light language paramiko does what the telnetlib does but with SSH instead of telnet however paramiko is not a standard python library and must be installed separately.To install paramiko on machine with python and pip installed do:
pip install paramiko
Pre-requisite:
- ssh is enabled on the remote device
- paramiko library is installed on the local working station
Paramiko demo
- Import the paramiko module
In [1]: import paramiko
- Create a paramiko ssh client object
In [2]: session = paramiko.SSHClient()
- Specify the policy for missing host keys from the ssh server
In [3]: session.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- Define login parameters
In [4]: username = 'fisi' password = 'fisi123' host = '192.168.56.26'
- Connect to the device
In [5]: session.connect(host,username=username,password=password)
- Invoke the device shell to interact with
In [6]: shell = session.invoke_shell()
- Send command the output shows 24 bytes have been sent to the device
In [7]: shell.send('show ip interface brief\n') Out[7]: 24
- Retrieve the output from the device
In [8]: print(shell.recv(65535).decode()) KH16#show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 unassigned YES manual down down GigabitEthernet1.4094 unassigned YES unset down down GigabitEthernet2 unassigned YES manual up up GigabitEthernet2.4094 unassigned YES unset up up GigabitEthernet3 192.168.56.26 YES manual up up KH16#
- Send another sample command
In [9]: shell.send('show ver | i up\n') Out[9]: 16
- Retrieve the output from the device
In [10]: print(shell.recv(65535).decode()) show ver | i up Technical Support: http://www.cisco.com/techsupport KH16 uptime is 1 day, 24 minutes KH16#
- Close the session gracefully
In [11] session.close()
import paramiko session = paramiko.SSHClient() session.set_missing_host_key_policy(paramiko.AutoAddPolicy()) username = 'fisi' password = 'fisi123' host = '192.168.56.26' session.connect(host,username=username,password=password) shell = session.invoke_shell() shell.send('show ip interface brief\n') print(shell.recv(65535).decode()) shell.send('show ver | i up\n') print(shell. recv(65535).decode()) session.close()Once you know a little Python and you start doing more, you're going to start getting annoyed by all the screen scraping and RegEx you have to deal with. This is because the data you get from CLI "show commands" is not structured data, so it's time to look at some nice modules to help you.
Genie
Genie is both a library framework and a test harness that facilitates rapid development, encourage re-usable and simplify writing test automation. Genie was initially developed internally in Cisco, and is now available to the general public. Genie offers both a python library and a cli based tool you can use directly from your command prompt (or terminal) without running the python interpreter. Learn more about Genie here
Genie uses the idea of testbeds where basically a testbed is where you define parameters of your device such as IP address, hostnames, login credential and transport protocol. Basically it is an equivalent of ansible inventory or routers.db for rancid respectively if you have worked with those before. Ansible is gonna be covered later in this write up, for rancid learn more here
Genie supports both telnet and ssh as transport protocols the caveat is however, similar to rancid and ansible, genie does not natively run on windows and since it also a non-standard python to install genie on a non-windows machine with python and pip installed do: pip install genie
basondole@box$ cat testbed.yaml testbed: name: sample tacacs: username: fisi passwords: tacacs: fisil23 services: KH16: os: iosxe type: IOSv connections: defaults: class: unicon.Unicon console: ip: 192.168.56.26 protocol: sshImportant: the hostname used in the testbed must be the exact match with the device configured hostname. I had so much problems before figuring this out :)
Genie demo
In this demo we are going to extract interface information from the device and record in a csv file the interface names and their mac addresses For the syntax in the python interpreter where each numbered action is executed jump to the snapshot thereafter. First I switchover to a linux box (wsl and run ipython) since genie is not supported on windows- Import the genie module
In [1]: from genie.conf import Genie
- Load the testbed file
In [2]: testbed = Genie.init('testbed.yaml')
- Verify which devices are in the testbed and we only have KH16 in our testbed
In [3]: testbed.devices Out[3]: TopologyDict({'KH16': <Device KH16 at 0x7f293e53df98>})
- Create an object for the device
In [4]: router = testbed.devices['KH16']
- Use the object to connect to the device. This will display the commands that have been issued by genie to the device which I will cut out of the snapshot
In [5]: router.connect() [2019-08-25 15:02:15,852] +++ KH16 logfile /tmp/KH16-cli-20190825T150215851.log +++ [2019-08-25 15:02:15,853] +++ Unicon plugin iosxe +++ /home/baggy/.local/bin/ipython:1: DeprecationWarning: Arguments ‘username', ‘enable_password', ‘tac acs_password' and 'line_password' are now deprecated and replaced by ‘credentials’. #!/usr/bin/python3 Password: [2019-08-25 15:02:18,415] +++ connection to spawn: ssh -l fisi 192.168.56.26, id: 139815100067176 +++ [2019-08-25 15:02:18,417] connection to KH16 [2019-08-25 15:02:18,482] +++ initializing handle +++ [2019-08-25 15:02:18,484] +++ KH16: executing command ‘term length 0' +++ . . {long output omitted}
- Verify the connection
In [6]: router.connected Out[6]: True
- Get interface information from device. This will display the commands that have been issued by genie to the device together with the output which I will cut out of the snapshot
In [7]: interfaces = router.parse('show interfaces') [2019-08-25 15:02:45,786] +++ KH16: executing command 'show interfaces' +++ show interfaces GigabitEthernet1 is down, line protocol is down Hardware is CSR vNIC, address is 000c.29be.12eb (bia 000c.29be.12eb) MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec, reliability 255/255, txload 1/255, rxload 1/255 Encapsulation 802.1Q Virtual LAN, Vlan ID 1., loopback not set Keepalive not set Full Duplex, 1000Mbps, link type is auto, media type is Virtual output flow-control is unsupported, input flow-control is unsupported ARP type: ARPA, ARP Timeout 04:00:00 Last input never, output never, output hang never Last clearing of "show interface" counters never Input queue: 0/375/0/0 (size/max/drops/flushes); Total output drops: 0 Queueing strategy: fifo Output queue: 0/40 (size/max) 5 minute input rate 0 bits/sec, 0 packets/sec 5 minute output rate 0 bits/sec, 0 packets/sec 0 packets input, 0 bytes, 0 no buffer Received 0 broadcasts (0 IP multicasts) 0 runts, 0 giants, 90 throttles 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored 0 watchdog, 0 multicast, 0 pause input 0 packets output, 0 bytes, 0 underruns 0 output errors, 0 collisions, 0 interface resets 0 unknown protocol drops 0 babbles, 0 late collision, 0 deferred 0 lost carrier, 0 no carrier, 0 pause output 0 output buffer failures, 0 output buffers swapped out . . {long output omitted}
- Import the csv module for creating a csv file
In [8]: import csv
- Define the field of the csv file
In [9]: fields = ['interfaces','MAC']
- Write the data to the csv file
In [10]: with open('interfaces.csv,'w') as f: writer = csv.DictWriter(f,fields) writer.writeheader() for interface, details in interfaces.items(): try: writer.writerow({'interfaces':interface, 'MAC':details['mac_address']}) except KeyError: pass
- Close the session gracefully
In [11]: router.disconnect()
- Exit and verify the contents of the file created on step 10 above
In [12]: exit
basondole@box$ ls interfaces.csv testbed. yaml basondole@box$ cat interfaces.csv interface,MAC GigabitEthernet1,000c.29be.12eb GigabitEthernet1.4094,000c.29be.12eb GigabitEthernet2,000c.29be.12f5 GigabitEthernet2.4094,000c.29be.12f5 GigabitEthernet3 ,000c.29be. 12ffComplete script
from genie.conf import Genie testbed = Genie.init('testbed.yaml') testbed.devices router = testbed.devices['KH16'] router.connect() router.connected interfaces = router.parse('show interfaces') import csv fields = ['interfaces','MAC'] with open('interfaces.csv,'w') as f: writer = csv.DictWriter(f,fields) writer.writeheader() for interface, details in interfaces.items(): try: writer.writerow({'interfaces':interface, 'MAC':details['mac_address']}) except KeyError: pass router.disconnect()
Netmiko
Netmiko is yet another option. Essentially it is a multi-vendor library to simplify Paramiko SSH connections to network devices. Learn more about Netmiko herePyntc
Pyntc is a simple open source multi-vendor Python library that establishes a common framework for working with different network APIs & device types. Checkout this self-explanatory repo to learn more about Pyntc hereSummary
In this section we have covered the python libraries that allow us to use conventional CLI commands to interact with the devices. The CLI methods allows user to send raw commands to devices via SSH or telnet as they would on the regular CLI, some libraries offer pre-defined functions that can be used to perform specific tasks an example will be a method that gets the running-config or reboot the device without the user issuing the specific CLI commands and rather just call the method, the commands are generated by the function itself and are sent via SSH to the CLI for execution.API & Remote Procedure Calls method
In this section we cover the basics on interaction with network devices using APIs. This is however only useful when the device supports API interactions. The most popular protocol in this category is NETCONF. NETCONF is a protocol defined by the IETF to install manipulate and delete configuration of network devices. NETCONF operations are realized on top of a Remote Procedure Call using XML encoding. Equivalent protocols include RESTCONF and gRPC all of which make use of the YANG models.
The easiest way to understand this is to think of YANG as an SNMP MIB where as NETCONF and RESTCONF are like SNMP versions. They are responsible to connect to the devices, package and send requests to retrieve info or send configuration details.
YANG is really a data structure to describe properties of an element using models (modules) analogous to SNMP MIB. There are different flavors of data models, these model include:- IETF Models: IETF issues the industry standard models
- Native Models: Models developed by specific vendors such as Cisco or Juniper
NETCONF runs over SSH tcp port 830 where as RESTCONF runs over HTTPS leveraging REST API standards. gRPC is created by google aiming to interface with everything in their data centres from switch, router and servers.
NETCONF
Cisco iosxe netconf config
Issue the commandnetconf-yang
in configuration mode
basondole@box $ ssh fisi@192.168.56.26 KH16#show version | include Software Cisco IOS XE Software, Version 16.09.01 Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2) KH16#config terminal Enter configuration commands, one per line. End with CNTL/Z. KH16(config)#netconf-yang KH16(config)#end KH16#exit Connection to 192.168.56.26 closed.
Cisco iosxr netconf config
RP/0/0/CPU0:ios#show version | utility egrep Software Thu Jan 16 14:22:42.870 UTC Cisco IOS XR Software, Version 6.0.1[Default] RP/0/0/CPU0:ios#conf Thu Jan 16 14:22:53.379 UTC RP/0/0/CPU0:ios(config)#netconf-yang agent ssh RP/0/0/CPU0:ios(config)#commit Thu Jan 16 14:22:59.679 UTC RP/0/0/CPU0:ios(config)#end RP/0/0/CPU0:ios#exit
Junos netconf config
basondole@box $ ssh fisi@192.168.56.36 JUNOS 12.1R1.9 built 2012-03-24 12:52:33 UTC fisi@big> configure Entering configuration mode [edit] fisi@big# set system services netconf ssh [edit] fisi@big# commit and-quit commit complete Exiting configuration mode fisi@big> exit Connection to 192.168.56.36 closed.
To verify the device is listening to NETCONF we open a netconf session via ssh specifying the netconf port -p 830
and the netconf service -s netconf
basondole@box $ ssh fisi@192.168.56.36 -p830 -s netconf fisi@192.168.56.36's password: **** <!-- No zombies were killed during the creation of this user interface --> <!-- user fisi, class j-super-user --> <hello> <capabilities> <capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability> <capability>urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http, ftp,file</capability> <capability>http://xml.juniper.net/netconf/junos/1.0</capability> <capability>http://xml.juniper.net/dmi/system/1.0</capability> </capabilities> <session-id>1900</session-id> </hello> ]]>]]>The session will open and the device will send its capability list in what is called a hello message, which is essentially the models it supports. To complete the hello we have to send our computers capability back to the device in an XML format
<?xml version="1.0" encoding="UTF-8"?> <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <capabilities> <capability>urn:ietf:params:netconf:base:1.0</capability> </capabilities> </hello>]]>]]>Now the hello process is completes we can interact with the device by sending XML commands packaged as RPC (remote procedure calls) which tell the device what to do. However for now we will just issue an XML command that ends the session neatly without leaving a hanging vty session on the router.
<?xml version="1.0" encoding="UTF-8"?> <rpc message-id="1239123" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <close-session /> </rpc> ]]>]]>We get rpc response from the device, confirming session is closed
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos" message-id="1239123" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> <ok/> </rpc-reply> ]]>]]> <!-- session end at 2019-08-25 16:03:58 UTC --> basondole@box $As you can see it is not very user friendly as the regular CLI, this is because NETCONF is intended for writing scripts & network management tools. Mostly we work with NETCONF using a programming language such as python. The value for NETCONF is not replacing the CLI for running one command or one operational task. The value for NETCONF is doing this type of configuration across huge networks of devices not just one at a time.
NETCONF with python
Pre-requisite:- NETCONF is enabled on the network device
- Python and ncclient are installed on the workstation
NETCONF Cisco demo
On this demo we use netconf to get config from the device and also to push config to the device running Cisco IOSXE First we launch the python interpreter. I’m using ipythonbasondole@box$ ipython Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.
- Import the manager module from the netconf client (ncclient library)
Your workstation is the manager whereas the network device is running the agent code
In [1]: from ncclient import manager
- Define the router and its login parameters
In [2]: router = { 'address': '192.168.56.26', 'netconf_port': 830, 'username': 'fisi', 'password': 'fisi123}
- Connect to the router using the netconfclient manager
In [3]: router_manager = manager.connect( host = router['address'], port = router['netconf_port'], username = router['username'], password = router[password], hostkey_verify = False)
- Verify the device is connected
In [4]: router_manager.connected Out[4]: True
- Crete an xml filter to get config of interface GigabitEthernet3 since netconf uses xml format
In [5]: match_if_GigEth3 = ''' <filter> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> <interface> <name>GigabitEthernet3</name> </interface> </interfaces> </filter> '''
- Get the running config from the device and apply the filter from step 5 to get only Gigethernet3 config
In [6]: router_GigEth3_config_xml = router_manager.get_config('running',match_if_GigEth3)
- Verify the operation was successful
In [7]: router_GigEth3_config_xml.ok Out[7] True
- Import a library that will help us convert xml data returned by netconf to a python dictionary which is easier to navigate
In [8]: import xmltodict
- Convert the xml data collected at step 6 to a python dictionary
In [9]: router_GigEth3_config_dict = xmltodict.parse(router_GigEth3_config_xml.xml)
- To extract the actual data, since netconf returns structured data we have to navigate to specific blocks of information in this case we go to the
rpc-reply
section then dive into thedata
section which is a leaf of therpc-reply
In [10]: router_GigEth3_config_data = router_GigEth3_config_dict['rpc-reply']['data']
- Use the dictionary keys (which I already know because I know the data structure) to access the data of interest in this case we are extracting the interface name. The return value datatype is
OrderedDict
the data model isietf
containing#text
data which is the name of the interfaceIn [11]: router_GigEth3_config_data['interfaces']['interface']['name'] Out[11]: OrderedDict([('@xmlns:nc', 'urn:ietf:params:xml:ns:netconf:base:1.01), ('#text', 'GigabitEthernet3'])
- Get the ipv4 address configured on the interface, we follow same paradigm as above except we want the key
ipv4
instead ofname
In [12]: router_GigEth3_config_data['interfaces']['interface']['ipv4'] Out[12]: OrderedDict([('@xmlns', 'urn:ietf:params:xml:ns:yang:ietf-ip'), ('address', OrderedDict([('ip', '192.168.56.26'), ('netmask', '255.255.255.0')]))])
- To send config to the device we create an xml block of our desired config. Here we are creating interface looopback0 and assign an ipv4 address as well as description
In [13]: create_loopback_0_xml = ''' <config> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> <interface> <name>Loopback0</name> <description>LOOPBACK CREATED BY NETCONF</description> <type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type"> ianaift:softwareLoopback </type> <enabled>true</enabled> <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> <address> <ip>10.1.1.1</ip> <netmask>255.255.255.255</netmask> </address> </ipv4> </interface> </interfaces> </config> '''
- Push the config to the device running config
In [14]: send_loopback_0_config = router_manager.edit_config(target='running', config=create_loopback_0_xml)
- Verify the operation was successful
In [15]: send_loopback_0_config.ok Out[15]: True
from ncclient import manager router = { 'address': '192.168.56.26', 'netconf_port': 830, 'username': 'fisi', 'password': 'fisi123} router_manager = manager.connect( host = router['address'], port = router['netconf_port'], username = router['username'], password = router[password], hostkey_verify = False) router_manager.connected match_if_GigEth3 = ''' <filter> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> <interface> <name>GigabitEthernet3</name> </interface> </interfaces> </filter> ''' router_GigEth3_config_xml = router_manager.get_config('running',match_if_GigEth3) router_GigEth3_config_xml.ok import xmltodict router_GigEth3_config_dict = xmltodict.parse(router_GigEth3_config_xml.xml) router_GigEth3_config_data = router_GigEth3_config_dict['rpc-reply']['data'] router_GigEth3_config_data['interfaces']['interface']['name'] router_GigEth3_config_data['interfaces']['interface']['ipv4'] create_loopback_0_xml = ''' <config> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"> <interface> <name>Loopback0</name> <description>LOOPBACK CREATED BY NETCONF</description> <type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type"> ianaift:softwareLoopback </type> <enabled>true</enabled> <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"> <address> <ip>10.1.1.1</ip> <netmask>255.255.255.255</netmask> </address> </ipv4> </interface> </interfaces> </config> ''' send_loopback_0_config = router_manager.edit_config(target='running' , config=create_loopback_0_xml) send_loopback_0_config.ok
Login to the router to verify the change
basondole@box $ ssh fisi@192.168.56.26 KH16#sh logging | begin NETCONF *Aug 3 23:36:32.616: %DMI-5-CONFIG_I: RO/O: nesd: Configured from NETCONF/RESTCONF by fisi, transaction-id 294 *Aug 3 23:36:34.591: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopback0, changed state to up *Aug 3 23:36:34.591: %LINK-3-UPDOWN: Interface Loopback0, changed state to up KH16#sh run int lo0 Building configuration... Current configuration : 105 bytes ! interface Loopback0 description LOOPBACK CREATED BY NETCONF ip address 10.1.1.1 255.255.255.255 end KH16#exit Connection to 192.168.56.26 closed.We see from the logs the config was changed using NETCONF by user
fisi
and the interface loopback0
comes up also we verify the configuration of interface loopback0
which has been created.
NETCONF Juniper demo
In this demo we write a sample code to get logical interfaces from a device First we launch the python interpreter. I’m using ipythonbasondole@box $ ipython Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.
- Import the manager module from the netconfclient (ncclient library) Your workstation is the manager whereas the network device is running the agent code
In [1]: from ncclient import manager
- Import a library that will help us convert xml data returned by netconf to a python dictionary which is easier to navigate
In [2]: import xmltodict
- Define the router and its login parameters
In [3]: junos = {'address':'192.168.56.36' 'netconf_port':830, 'username':'fisi', 'password':'fisi123' }
- Connect to the router
In [4]: junos_man = manager.connect( host = junos['address'], port = junos['netconf_port'], username = junos['username'], password = junos['password'], hostkey_verify = False)
- Get running config from the device and convert the xml to a python dictionary at the same time
In [5]: junos_conf = xmltodict.parse((junos_man.get config('running')).xml)['rpc-reply']['data']
- From the config use the dictionary keys to navigate to the data of interest which is the interface section in this case and print out the interface name and its logical interfaces names
In [6]: for intf in junos_conf['configuration']['interfaces']['interface']: print('└── '+intf['name']) if type(intf['unit'])==list : for unit in intf['unit']: print(' └──'+unit['name']) └── em0 └── em1 └──2 └──7 └──10 └──111 └──222 └──300 └──400 └──450 └──999 └── em2 └──9 └──10 └──100 └──111 └──200 └── em3 └──0 └── lo0
- Gracefully close the session
In [7]: junos_man.close_session() <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:junos="http://xml.juniper.net/ junos/12.1R1/junos" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:f4c e20d2-1d51-4de8-9687-9468b1367f85"> <ok/> </rpc-reply>
from ncclient import manager import xmltodict junos = {'address':'192.168.56.36' 'netconf_port':830, 'username':'fisi', 'password':'fisi123' } junos_man = manager.connect( host = junos['address'], port = junos['netconf_port'], username = junos['username'], password = junos['password'], hostkey_verify = False) junos_conf = xmltodict.parse((junos_man.get config('running')).xml)['rpc-reply']['data'] for intf in junos_conf['configuration']['interfaces']['interface']: print('|__ '+intf['name']) if type(intf['unit'])==list : for unit in intf['unit']: print(' |__'+unit['name']) junos_man.close_session()Important note is that since netconf uses structured data the commands are exactly the same across all vendors regardless of the difference in the cli syntax. Check out the other sample here
PyEZ
PyEZ is a python module that enables management and automation of devices running JunOS. PyEZ uses NETCONF to send remote procedure calls to JunOS devices with CLI support as well. Pre-requisite: Netconf is enabled on the network deviceJunos PyEZ API demo
In this demo we use PyEZ API to send commands to a junos device as well as equivalent CLI command- Import the Device module from junos library
In [1]: from jnpr.junos import Device from lxml import etree
- Define login parameters
In [2]: username = 'fisi' password = 'fisi123' host = '192.168.56.36'
- Open a session with the JunOS device
In [3]: router = Device(host=host,user=username,passwd=password) router.open() Out[3]: Device(192.168.56.36)
- Make an rpc call to get bgp information from the device. The
{‘format’:‘text’}
is used to convert the xml data returned by the device to a human readable format.In [4]: bgp_info = router.rpc.get_bgp_summary_information({'format':'text'})
- Convert the text data into a format as the one displayed when issuing a CLI command
In [5]: bgp_info_txt = etree.tostring(bgp_info).decode()
- Print the output
In [6]: print(bgp_info_txt) <output> Groups: 4 Peers: 5 Down peers: 4 Table Tot Paths Act Paths Suppressed History Damp State Pending inet.0 0 0 0 0 0 0 bgp.l3vpn.0 0 0 0 0 0 0 bgp.l2vpn.0 0 0 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn Statel#Active/Received/Accepted/Damped... 1.1.1.2 64512 0 0 0 0 1d 16:25:40 Idle 10.36.100.2 64518 0 0 0 0 5:53:14 Connect 192.168.0.15 64512 0 0 0 0 1d 16:25:40 Active 192.168.56.18 64512 0 0 0 0 1d 16:25:40 Active 192.168.56.26 64512 134 138 0 0 1:00:15 Establ inet.0: 0/0/0/0 </output>
- Get bgp information using cli command, the
warning=False
clause is to stop the warning message when using the cli method as the writers of PyEZ discourage using this method unless it is for debuggingIn [7]: bgp_info_cli = router.cli('show bgp summary', warning=False)
- Print the results
In [8]: print(bgp_info_cli) Groups: 4 Peers: 5 Down peers: 4 Table Tot Paths Act Paths Suppressed History Damp State Pending inet.0 0 0 0 0 0 0 bgp.l3vpn.0 0 0 0 0 0 0 bgp.l2vpn.0 0 0 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn Statel#Active/Received/Accepted/Damped... 1.1.1.2 64512 0 0 0 0 1d 16:27:56 Idle 10.36.100.2 64518 0 0 0 0 5:55:30 Connect 192.168.0.15 64512 0 0 0 0 1d 16:27:56 Active 192.168.56.18 64512 0 0 0 0 1d 16:27:56 Active 192.168.56.26 64512 134 138 0 0 1:02:31 Establ inet.0: 0/0/0/0
from jnpr.junos import Device from lxml import etree username = 'fisi' password = 'fisi123' host = '192.168.56.36' router = Device(host=host,user=username,passwd=password) router.open() bgp_info = router.rpc.get_bgp_summary_information({'format':'text'}) bgp_info_txt = etree.tostring(bgp_info).decode() print(bgp_info_txt) bgp_info_cli = router.cli('show bgp summary', warning=False) print(bgp_info_cli)From the above demo we can verify we get the same output when using either method. The question now becomes how do we know the rpc equivalent commands since we obviously only know the CLI commands? Next demonstration shows how.
Getting the RPC command equivalent of the CLI command
First im saving my username and password for the router as environment variables on my Ubuntu machine so that I can use the variable in the code and avoid typing the plain text password in the code then launch my python interpreterProcedures:
- Import the Device module from junos library
In [1]: from jnpr.junos import Device
- Import the xml module for converting xml data
In [2]: from lxml import etree
- Import the os module that will be used to access the environment variables for username and password
In [3]: import os
- Create a device object for the router
In [4]: router = Device(host='192.168.56.36',user=os.getenv('JUSER'),passwd=os.getenv('JPASS'))
- Open a session with the JunOS device
In [5]: router.open() Out[5]. Device(192.168.56.36)
- Get the rpc command equivalent for the cli command
show route
In [6]: rpc_command = router.display_xml_rpc('show route',format='text')
- Display the rpc command which is
get-route-information
In [7]: print(rpc_command) <get-route-information> </get-route-information>
- Get the rpc command equivalent for the cli command
show system alarms
In [8]: rpc_command = router.display_xml_rpc('show system alarms',format='text')
- Display the rpc command which is
get-system-alarm-information
In [9]: print(rpc_command) <get-system-alarm-information> </get-system-alarm-information>
- Make an rpc call using the rpc command from step 9. Note "
–
" are replaced by "_
"In [10] alarms = router.rpc.get_system_alarm_information({'format':'text')
- Print the results from the device
In [11]: print((etree.tostring(alarms)).decode()) <output> No alarms currently active </output>
- Close the connection
In [12]: router.close()
basondole@box $ export JUSER=fisi basondole@box $ export JPASS=fisi123 basondole@box $ ipython from jnpr.junos import Device from lxml import etree import os router = Device(host='192.168.56.36',user=os.getenv('JUSER'),passwd=os.getenv('JPASS')) router.open() rpc_command = router.display_xml_rpc('show route',format='text') print(rpc_command) rpc_command = router.display_xml_rpc('show system alarms',format='text') print(rpc_command) alarms = router.rpc.get_system_alarm_information({'format':'text') print((etree.tostring(alarms)).decode()) router.close()
NAPALM
NAPALM stands for Network Automation and Programmability Abstraction Layer with Multivendor support is a Python library build by a community network engineers. It is a wrapper of different libraries that communicate directly with the network device. It also comes with a CLI tool you can use directly from your command prompt (or terminal) without running the python interpreter. The general idea behind this library is to create a standardized, multivendor interface for certain file and get operationsNAPALM with Cisco ios
To enable NAPALM interaction with IOS devices configureip scp server enable
on the device.
This config enables the router to act as a secure copy server which povides ability to copy files to and from the device using ssh which is useful for mimicing diff and rollback features on regular ios
$ ssh fisi@192.168.56.26 KH16#sh version | i Software Cisco IOS XE Software, Version 16.09.01 Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2) KH16# KH16#conf t Enter configuration commands, one per line. End with CNTL/Z. KH16(config)#ip scp server enable KH16(config)#end KH16#exit Connection to 192.168.56.26 closed by remote host. Connection to 192.168.56.26 closed.
NAPALM with Cisco iosxr
RP/0/0/CPU0:pycon-iosxr#sh ver | i Software Fri Jan 17 00:59:07.645 UTC Cisco IOS XR Software, Version 6.0.1[Default] RP/0/0/CPU0:pycon-iosxr# RP/0/0/CPU0:pycon-iosxr#conf Fri Jan 17 00:57:45.790 UTC RP/0/0/CPU0:pycon-iosxr(config)#xml agent tty iteration off RP/0/0/CPU0:pycon-iosxr(config)#commit RP/0/0/CPU0:pycon-iosxr(config)#end RP/0/0/CPU0:pycon-iosxr#
Demo
In the demonstration we use napalm to interact with a Cisco IOS device send configuration to the device and perform a rollback of the config. Note the rollback feature is not natively available on IOS devices but with the use of NAPALM we can easily achieve this.- Import napalm driver
In [1]: from napalm import get_network_driver
- Define login parameters
In [2]: username = 'fisi' password = 'fisi123' iosxe_host = '192.168.56.26'
- Create a napalm driver for the ios. By the time of this writing NAPALM did not have a dedicated
iosxe
driverIn [3]: ios_driver = get_network_driver('ios')
- Create a drive object and connect to the device
In [4]: iosxe_dev = ios_driver(username=username,password=password,hostname=iosxe_host) iosxe dev.open()
- Use napalm to get facts about the device and we see from the list of interfaces we do not have loopback1 (We are goingto create loopback1 in the following steps)
In [5]: iosxe_dev.get_facts() Out[5]: {'uptime': 3480, 'vendor': 'Cisco', 'os version': 'Virtual XE Software (X86 64 LINUX IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2)', 'serial number': '9XZ2CV2FFA4', 'model': 'CSR1000V', 'hostname': 'KH16', 'fqdn': 'Unknown', 'interface_list': ['GigabitEthernet1', 'GigabitEthernet1.4094', 'GigabitEthernet2', 'GigabitEthernet2.4094', 'GigabitEthernet3', 'Loopback0']}
- Create configuration for
loopback1
interface following the the regular CLI syntaxIn [6]: iosxe_loopback1_cfg = ''' int loopback 1 ip add 10.2.2.2 255.255.255.255 desc CONFIGURED BY NAPALM '''
- Load the configuration to the device (without saving the config)
In [7]: iosxe_dev.load_merge_candidate(config=iosxe_loopback1_cfg)
- Do a diff to compare the loaded config and the running config, the
+
sign means we are adding the respective config. Note this feature is not natively available in regular Cisco IOSIn [8]: diff = iosxe_dev.compare_config() print(diff) +int loopback 1 +ip add 10.2.2.2 255.255.255.255 +desc CONFIGURED BY NAPALM
- Commit the configuration on the device
In [9]: iosxe_dev.commit_config()
- Get the facts from the device again, this time we see interface
loopback1
is present since we added it with the commit operation on step 9In [10]: iosxe_dev.get_facts() {'uptime': 3540, 'vendor': 'Cisco', 'os version': 'Virtual XE Software (X86 64 LINUX IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2)', 'serial number': '9XZ2CV2FFA4', 'model': 'CSR1000V', 'hostname': 'KH16', 'fqdn': 'Unknown', 'interface_list': ['GigabitEthernet1', 'GigabitEthernet1.4094', 'GigabitEthernet2', 'GigabitEthernet2.4094', 'GigabitEthernet3', 'Loopback0', 'Loopback1']}
- Issue a rollback operation to delete interface
loopback1
In [11]: iosxe_dev.rollback()
- Close the session
In [12]: iosxe_dev.close()
from napalm import get_network_driver username = 'fisi' password = 'fisi123' iosxe_host = '192.168.56.26' ios_driver = get_network_driver('ios') iosxe_dev = ios_driver(username=username,password=password,hostname=iosxe_host) iosxe_dev.open() iosxe_dev.get_facts() iosxe_loopback1_cfg = ''' int loopback 1 ip add 10.2.2.2 255.255.255.255 desc CONFIGURED BY NAPALM ''' iosxe_dev.load_merge_candidate(config=iosxe_loopback1_cfg) diff = iosxe_dev.compare_config() print(diff) iosxe_dev.commit_config() iosxe_dev.get_facts() iosxe_dev.rollback() iosxe_dev.close()
Verification on the device
The below snapshot shows what was happening in the background as we were configuring the device using napalm. I had opened the ssh session to the device and did a terminal monitor to see the log messages before I started operating on NAPALM.Below is what happened to the device
loopback1
loopback1
comes up
loopback1
that was sent by napalm in the running config
show run int lo1
the interface is not there anymore due to the rollback from NAPALM
$ ssh fisi@192.168.56.26 KH16#sh run int lol ^ % Invalid input detected at '^' marker. KH16#terminal monitor KH16# *Aug 5 23:06:34.225: %SYS-5-CONFIG_I: Configured from console by fisi on vtyl (192.168.56.1) *Aug 5 23:06:36.477: %PARSER-4-BADCFG: Unexpected end of configuration file. *Aug 5 23:06:36.477: %SYS-5-CONFIG_C: Running-config file is Modified *Aug 5 23:06:38.462: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopbackl, changed state to up *Aug 5 23:06:38.463: %LINK-3-UPDOWN: Interface Loopbackl, changed state to up KH16# KH16#sh run int lol Building configuration.. Current configuration : 98 bytes ! interface Loopbackl description CONFIGURED BY NAPALM ip address 10.2.2.2 255.255.255.255 end KH16# *Aug 5 23:07:46.399: Rollback:Acquired Configuration lock. *Aug 5 23:07:46.399: %SYS-5-CONFIG_R: Config Replace is Done *Aug 5 23:07:48.669: %LINEPROTO-5-UPDOWN: Line protocol on Interface Loopbackl, changed state to down *Aug 5 23:07:48.669: %LINK-5-CHANGED: Interface Loopbackl, changed state to administratively down KH16# KH16#sh run int lol ^ % Invalid input detected at '^' marker. KH16#
NAPALM with JunOS
- Import napalm driver
In [1]: from napalm import get_network_driver
- Define login parameters
In [2]: username = 'fisi' password = 'fisi123' junos_host = '192.168.56.36'
- Create a napalm driver for junos, create a driver object and connect to the device
In [3]: junos_driver = get_network_driver('junos') junos_dev = junos_driver(username=username,password=password,hostname=junos_host) junos_dev.open()
- Use napalm to get facts about the device
In [4]: junos_dev.get_facts() Out[4]: {'vendor': 'Juniper', 'model': 'OLIVE', 'serial number': 'None', 'os version': '12.1R1.9', 'hostname': 'big', 'fqdn': 'big', 'uptime': 6189, 'interface list': ['.local.', 'cbp0', 'demux0', 'dsc', 'em0', 'em', 'em2', 'em3', 'gre', 'ipip', 'irb', 'lo0', 'mtun', 'pimd', 'pime', 'pp0']}
- Create configuration for interface em2.555 following the junos CLI syntax
In [5]: junos_em2_555_cfg = ''' interfaces{ em2{ unit 555{ vlan-id 555; description "CONFIGURED BY NAPLAM"; } } } '''
- Load the configuration to the device (without saving the config) and print the diff between the active config and the loaded config
In [6]: junos_dev.load_merge_candidate(config=junos_em2_555_cfg) diff = junos_dev.compare_config() print(diff) [edit interfaces em2] + unit 555 { + description "CONFIGURED BY NAPLAM"; + vlan-id 555; + }
- Commit the config
In [7]: junos_dev.commit_config()
- Use the CLI method to get configuration of interface em2.555
In [8]: em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555'])
- Print the config and see what we have added with the commit on step 7
In [9]: print(em2_555_cf) {'show configuration interfaces em2.555': '\ndescription "CONFIGURED BY NAPLAM";\nvlan-id 555;\n'}
- Rollback the config
In [10] junos dev.rollback()
- Use the CLI method to get configuration of interface em2.555
In [11] em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555'])
- Print the config and we see there is none that’s because we rolled back the config on step 10
In [12]: print(em2_555_cf) {'show configuration interfaces em2.555': ''}
- Close the session.
In [13]: junos_dev.close()
from napalm import get_network_driver username = 'fisi' password = 'fisi123' junos_host = '192.168.56.36' junos_driver = get_network_driver('junos') junos_dev = junos_driver(username=username,password=password,hostname=junos_host) junos_dev.open() junos_dev.get_facts() junos_em2_555_cfg = ''' interfaces{ em2{ unit 555{ vlan-id 555; description "CONFIGURED BY NAPLAM"; } } } ''' junos_dev.load_merge_candidate(config=junos_em2_555_cfg) diff = junos_dev.compare_config() print(diff) junos_dev.commit_config() em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555']) print(em2_555_cf) junos dev.rollback() em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555']) print(em2_555_cf) junos_dev.close()
NORNIR
Nornir is an automation framework written in python with multiple threading capabilities. Nornir has a simple inventory which uses two YAML filesSee below snapshot of the files for my demo setup
basondole@box $ ls groups.yaml hosts.yaml basondole@box $ head * ==> groups.yaml <== --- routers: username: fisi password: fisi123 ==> hosts.yaml <== --- kh16: groups: [routers] hostname: 192.168.56.26 platform: cisco_ios big: groups: [routers] hostname: 192.168.56.36 platform: juniper_junos
Demo
- Import the necessary libraries. In this demo we’ll use the netmiko_send_command in nornir to send commands to devices
In [1]: from nornir import InitNornir from nornir.plugins.tasks.networking import netmiko_send_command from nornir.plugins.functions.text import print_result
- Create a nornir object
In [2] op = InitNornir()
- Run the task using the netmiko_send_command function and specify the command
In [3] res = op.run(task=netmiko_send_command, command_string= 'sh ip int brief')
- Use the nornir print function to print the results. The results are broken for router
big
that is because the command we issued is not a valid junos command. The changed flag is false for both devices meaning nothing was changed on the deviceIn [4] print_result(res) netmiko_send_command************************************************************ * big ** changed : False ******************************************************* vvvv netmiko_send_command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO ^ 'ip' is ambiguous. Possible completions: ipsec Show IP Security information ipv6 Show IP version 6 information fisi@big> show ip int ^ syntax error, expecting <command>. fisi@big> show ipint brief ^ syntax error, expecting <command>. ^^^^ END netmiko send command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * kh16 ** changed : False ****************************************************** vvvv netmiko send command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO Interface IP-Address OK? Method Status Protocol GigabitEthernet1 unassigned YES NVRAM down down GigabitEthernet1.4094 unassigned YES unset down down GigabitEthernet2 unassigned YES NVRAM up up GigabitEthernet2.4094 unassigned YES unset up up GigabitEthernet3 192.168.56.26 YES NVRAM up up Loopback0 10.1.1.1 YES NVRAM up up Loopback100 unassigned YES unset up up ^^^^ END netmiko send command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Issue a command that works across both junos and ios
show bgp summary
In [5]: result = op.run(task = netmiko_send_command,command_string='sh bgp summary')
- Print the output we get clean output from both the routers big running junos and kh16 running ios. Again the change flag is false because nothing was changed in the config
In [6]: print_result(result) netmiko_send_command************************************************************ * big ** changed : False ******************************************************* vvvv netmiko send command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO Y Groups: 2 Peers: 2 Down peers: 1 Table Tot Paths Act Paths Suppressed History Damp State Pending inet.0 1 1 0 0 0 0 inetflow.0 0 0 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn Statel#Active/Received/Accepted/Damped... 10.36.100.2 64518 0 0 0 0 5:21:37 Connect 192.168.56.26 64512 705 723 0 0 5:21:29 Establ inet.0: 1/1/1/0 inetflow.0: 0/0/0/0 ^^^^ END netmiko send command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * kh16 ** changed : False ****************************************************** vvvv netmiko send command ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO % Command accepted but obsolete, unreleased or unsupported; see documentation. BGP router identifier 10.1.1.1, local AS number 64512 BGP table version is 7, main routing table version 7 6 network entries using 1488 bytes of memory 6 path entries using 816 bytes of memory 2/2 BGP path/bestpath attribute entries using 560 bytes of memory 1 BGP AS-PATH entries using 24 bytes of memory 1 BGP extended community entries using 24 bytes of memory 0 BGP route-map cache entries using 0 bytes of memory 0 BGP filter-list cache entries using 0 bytes of memory BGP using 2912 total bytes of memory BGP activity 8/0 prefixes, 8/0 paths, scan interval 60 secs Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd 192.168.56.36 4 64516 723 707 7 0 0 05:21:45 5 192.168.56.63 4 64512 0 0 1 0 0 never Active ^^^^ END netmiko send command ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Import a netmiko send config function for sending config to the device
In [7]: from nornir.plugins.tasks.networking import netmiko_send_config
- Filter the hosts to only hosts running
cisco_ios
In [8]: ios_host = op.filter(platform='cisco_ios')
- Create config for interface
loopback10
In [9]: loopback_10 = '''interface lo10 description CONPTGURED BY NORNIR'''
- Send config to the device
In [10]: result = ios_host.run(task = netmiko_send_config, config_commands=loopback_10)
- Display the results and we see the change flag is now True, this is because we have actually changed the device config
In [11]: print_result(result) netmiko_send_config************************************************************** * kh16 ** changed : True ******************************************************** vvvv netmiko —send config ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO config term Enter configuration commands, one per line. End with CNTL/Z. KH16(config)#interface lo10 KH16(config-if)#description CONFIGURED BY NORNIR KH16(config-if)#end KH16# ^^^^ END netmiko send config ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Close the session.
In [12]: op.close_connections()
Complete scriptfrom nornir import InitNornir from nornir.plugins.tasks.networking import netmiko_send_command from nornir.plugins.functions.text import print_result op = InitNornir() res = op.run(task=netmiko_send_command, command_string= 'sh ip int brief') print_result(res) result = op.run(task = netmiko_send_command,command_string='sh bgp summary') print_result(result) from nornir.plugins.tasks.networking import netmiko_send_config ios_host = op.filter(platform='cisco_ios') loopback_10 = '''interface lo10 description CONPTGURED BY NORNIR''' result = ios_host.run(task = netmiko_send_config, config_commands=loopback_10) print_result(result) op.close_connections()
Command line tools
Napalm
NAPALM offers a command line utility that you can use directly from the command prompt for windows users or terminal for mac and linux users without using the python interpreter. Basically we can use all the get methods NAPALM provides right from the terminal such as getting facts, bgp neighbors and so much more.Demonstration
Using napalm cli to get facts about our cisco and junos devices$ napalm --vendor ios --user fisi --password fisi123 192.168.56.26 call get_facts { "uptime": 28980, "vendor": "Cisco", "os version": "Virtual XE Software (X86 64 LINUX IOSD-UNIVERSALK9-M), Version 16.9.1, "serial number": "9XZ2CV2FFA4", "model": "CSR1000V", "hostname": "KH16", "fqdn": "KH16.baggy.org", "interface list": [ "GigabitEthernetl", "GigabitEthernet1.4094", "GigabitEthernet2", "GigabitEthernet2.4094", "GigabitEthernet3", "Loopback0", "Loopback10", "Loopback100" ] }
$ napalm --vendor junos --user fisi --password fisi123 192.168.56.36 call get_facts { "vendor": "Juniper", "model": "OLIVE", "serial number": "None", "os version": "12.1R1.9", "hostname": "big", "fqdn": "big", "uptime": 22625, "interface list": [ ".local.", "cbp0", "demux0", "dsc", "em0", "em1", "em2", "em3", "gre", "ipip", "irb", "lsi", "mtun", "pimd", "pime", "pp0", "tap" ] }
Ansible
Ansible uses playboooks to perform tasks. A play book consists of one or more tasks to be performed by modules. There are different modules for different operations such as backing up configuration, sending comands etc. These tasks are performed against specified host(s) as specified in the play
In the below sample hosts file for ansible we have two groups of devices namely routers and vm with each group containing one device as displayed.pbasondole baggy@box $ cat hosts [routers] 192.168.56.36 ansible_ssh_user=fisi ansible_ssh _pass=fisi123 ansible_network_os=junos [vm] server ansible_host=192.168.56.1
The playbook is written in yaml as displayed below. This play book provides a method of backing up configuration of junos devices, the backup file is written to thebackup
folder in the playbook directory or role root directory, if playbook is part of an ansible rolepbasondole baggy@box $ cat junos_config_backup.yml --- - hosts: 192.168.56.36 gather_facts: no tasks: name: backup junos configuration connection: local junos_config: backup: yes register: config ...
Running the playbook
First we confirm the contents of our directory by issuing a tree command and we see there is only thehosts
file andjunos_config_backup.yml
file which is the playbook Then we run the playbook specifying the host file with-i
and it will play all the tasks defined in the play-book against the specified host, then we verify by using thetree
command there is a new folder with the backup config for the router with ip192.168.56.36
pbasondole baggy@box $ tree . ├── hosts └── junos_config_backup.yml 0 directories, 2 files pbasondole baggy@box $ ansible-playbook junos_config_backup.yml -i hosts PLAY [192.168.56.36] ***************************************************************** TASK [backup junos configuration] **************************************************** ok: [192.168.56.36] PLAY RECAP *************************************************************************** 192.168.56.36 : ok=1 changed=0 unreachable=0 failed=0 pbasondole baggy@box $ tree . ├── backup | └── 192.168.56.36_config.2019-08-25@22:28:31 ├── hosts └── junos_config_backup.yml 1 directory, 3 files
To learn more about ansible click herePlaybook to configure junos device
The playbook below uses diffrent modules to perform configuration changes on a junos device--- - name: Configuring junos device hosts: 192.168.56.36 gather_facts: no tasks: - name: configuring router interface via junos_config module connection: local # can use "local" or "netconf" if netconf is enabled on remote device junos_config: lines: # each command must begin with set - set interfaces em2.999 description "configured by ansible" - set interfaces em2.999 vlan-id 999 - set interfaces em2.999 family inet address 10.10.20.1/30 - set interfaces em2.999 family inet policer input 2Mbps output 2Mbps comment: configured by ansible junos_config register: junos_config - name: rolling back config done by junos_config module connection: local junos_config: rollback: 1 comment: rollback of the config by ansible junos_config register: rollback when: junos_config.changed == true - name: configuring router interface via junos_command module connection: network cli junos_command: commands: - configure private - edit interface em2.999 - set description "configured by ansible" - set vlan-id 999 - set family inet address 10.10.20.1/30 - set family inet policer input 2Mbps output 2Mbps - top - show | compare - commit and-quit comment "configured by ansible junos_command" - show configuration interfaces em2.999 - quit register: junos_command when: rollback.changed == true - name: print the results for the junos_command module ignore_errors: yes debug: msg={{junos_command.stdout_lines[9]}} when: rollback.changed == true ...
The when statement in the tasks is a conditional. This means this task will only be done if theconfiguring router interface via junos_config
task changes configuration so we need to use when conditional to check if the change flag is true from the first task. We run the playbook as shown belowpbasondole $ ansible-playbook junosconf.yml -i ./hosts PLAY [configuring junos device] *************************************************************** TASK [configuring router interface via junos_config module] *********************************** changed: [192.168.56.36] TASK [rolling back config done by junos_config module] **************************************** changed: [192.168.56.36] TASK [configuring router interface via junos_command module] ********************************** [WARNING]: arguments wait_for, rpcs are not supported when using transport=cli ok: [192.168.56.36] TASK [print the results for the junos_command module] ***************************************** ok: [192.168.56.36] => { "msg": [ "description \"configured by ansible\";", "vlan-id 999;", "family inet {", " policer {", " input 2Mbps;", " output 2Mbps;", " }", " address 10.10.20.1/30;", "}" ] } PLAY RECAP ************************************************************************************ 192.168.56.36 : ok=4 changed=2 unreachable=0 failed=0
On a separate terminal window we log in to the router and check the commit history, we can verify all the three tasks were executed on the device.pbasondole@box $ ssh fisi@192.168.56.36 --- JUNOS 12.1R1.9 built 2012-03-24 12:52:33 UTC fisi@big> show system commit 0 2019-08-17 00:45:38 UTC by fisi via cli configured by ansible junos_command 1 2019-08-17 00:45:33 UTC by fisi via netconf rollback of the config by ansible junos_config 2 2019-08-17 00:45:29 UTC by fisi via netconf configured by ansible junos_config
When we re-run the playbook again no changes are made, this is what is referred to as idempotency which essentially means only make changes when they are needed. Ansible is not the only option in its category, two other popular tools that are used along with Ansible are puppet and chef.Genie
Genie is both a library framework and a test harness that facilitates rapid development, encourage re-usable and simplify writing test automation. Genie was initially developed internally in Cisco, and is now available to the general public. Learn more about Genie here
Genie uses a testbed file to define devices and their respective properties. Below is a sample testbed containing one device with a configured hostname KH16, the name of the device in the testbed must match exactly the configured hostname of the device in question. Note testbeds are written in yaml.pbasondole:baggy@box $ ls testbed.yaml* pbasondole:baggy@box $ cat testbed.yaml testbed: name: sample tacacs: username: fisi passwords: tacacs: fisi123 devices: KH16: os: iosxe type: csr1000v platform: csr1000v connections: defaults: class: unicon.Unicon console: ip: 192.168.56.26 protocol: ssh
To check whether the testbed file is valid use the commandpyats validate testbed testbed-file-path
as shown below. If the testbed file is not valid the errors will be displayed, in this case the testbed file is valid however there is a warning message which we’ll ignore.pbasondole:baggy@box $ pyats validate testbed testbed.yaml Loading testbed file: testbed.yaml ------------------------------------------------------------------------------------------------ Testbed Name: sample Testbed Devices: . `-- KH16 [csr1000v/iosxe/csr1000v] Warning Messages ---------------- - Device 'KH16' has no interface definitions YAML Lint Messages ------------------
Genie offers a learn method which can be used to learn different things from the device(s) specified. In below sample we use learn to gather routing information from the device.pbasondole:baggy@box $ genie learn routing --testbed-file testbed.yaml Learning '['routing']' on devices '['KH16']' /home/baggy/.local/bin/genie:11: DeprecationWarning: Arguments 'username', 'enable_password','tacacs_password' and 'line_password' are now deprecated and replaced by 'credentials'. sys.exit(main()) 100%|████████████████████████████████████████████████████████████████████████████████| 1/1 [00:04<00:00, 4.59s/it] +==============================================================================+ | Genie Learn Summary for device KH16 | +==============================================================================+ | Connected to KH16 | | - Log: ./connection_KH16.txt | |------------------------------------------------------------------------------| | Learnt feature 'routing' | | - Ops structure: ./routing_iosxe_KH16_ops.txt | | - Device Console: ./routing_iosxe_KH16_console.txt | |==============================================================================|
The operation produces three files in the directory. The connection_KH16.txt file contains what genie did as it sets up the connection to the device. Therouting_iosxe_KH16_console.txt
contains all the commands sent to the device cli and their respective outputs and finallyrouting_iosxe_KH16_ops.txt
contains the parsed structured data.pbasondole:baggy@box $ tree . ├── connection_KH16.txt ├── interfaces.csv ├── routing_iosxe_KH16_console.txt ├── routing_iosxe_KH16_ops.txt └── testbed.yaml
Refer below snapshot for the preview of the contents of each of the three filespbasondole:baggy@box $ head *.txt ==> connection_KH16.txt <== [2019-08-25 23:45:58,825] +++ KH16 logfile ./connection_KH16.txt +++ [2019-08-25 23:45:58,826] +++ Unicon plugin iosxe +++ Password: [2019-08-25 23:46:01,505] +++ connection to spawn: ssh -l fisi 192.168.56.26, id: 140588126939064 +++ [2019-08-25 23:46:01,505] connection to KH16 KH16# ==> routing_iosxe_KH16_console.txt <== +++ KH16: executing command 'show vrf detail' +++ show vrf detail VRF management (VRF Id = 1); default RD
; default VPNID New CLI format, supports multiple address-families Flags: 0x1808 No interfaces Address family ipv4 unicast (Table ID = 0x1): Flags: 0x0 No Export VPN route-target communities No Import VPN route-target communities ==> routing_iosxe_KH16_ops.txt <== { "_exclude": [], "attributes": null, "commands": null, "connections": null, "context_manager": {}, "info": { "ipv6_unicast_routing_enabled": true, "vrf": { "default": { Talktous
Talk to us is a command line tool written by Paul S.I. Basondole on top of paramiko library. The tool provides a quick and easy way to send multiple commands to multiple devices and producing an intuitive output. These arguments can be loaded from a file or specified on cli at runtime. Talktous does not support ssh keys however it has the ability to cache ssh login username and password in an encrypted format and thus does not have to type in credentials every time an operation is ran on remote devices. In below demonstration we use talktous to request for bgp summary information from the two devices 192.168.56.36 running junos and 192.168.56.63 running ios, we also see the login credentials were retrieved from a prior execution.C:\Users\u>talktous -commands "show bgp summary" -ipaddress "192.168.56.36:192.168.56.63" INFO: username and password auto-retrieved [192.168.56.63] show bgp summary ---------------- BGP router identifier 192.168.142.63, local AS number 64512 BGP table version is 2, main routing table version 2 1 network entries using 144 bytes of memory 1 path entries using 80 bytes of memory 1/1 BGP path/bestpath attribute entries using 136 bytes of memory 0 BGP route-map cache entries using 0 bytes of memory 0 BGP filter-list cache entries using 0 bytes of memory BGP using 360 total bytes of memory BGP activity 1/0 prefixes, 1/0 paths, scan interval 60 secs Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd 192.168.56.15 4 64512 0 0 1 0 0 never Idle 192.168.56.26 4 64512 0 0 1 0 0 never Idle [192.168.56.36] show bgp summary ---------------- Groups: 4 Peers: 6 Down peers: 6 Table Tot Paths Act Paths Suppressed History Damp State Pending inet.0 0 0 0 0 0 0 bgp.l3vpn.0 0 0 0 0 0 0 bgp.l2vpn.0 0 0 0 0 0 0 inetflow.0 0 0 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped... 10.36.100.2 64512 0 0 0 0 6:49 Connect 10.36.100.2 64518 0 0 0 0 6:49 Connect 192.168.0.15 64512 0 0 0 0 6:49 Connect 192.168.56.2 64512 0 0 0 0 6:49 Connect 192.168.56.18 64512 0 0 0 0 6:49 Connect 192.168.56.26 64512 0 0 0 0 6:49 Connect INFO: Developed by Paul S.I. Basondole
Ansible.
Introduction
Ansible is an open-source automation tool, or platform, used for IT tasks such as configuration management, application deployment, intraservice orchestration and provisioning. Ansible uses playbooks to perform tasks. Essentially a play book consists of one or more tasks to be performed by modules There are different modules for different operations such as copying files, sending shell comands etc These tasks are performed against specified host(s) as specified in the play. Generaly however the hosts must be defined in the ansible hosts file A simple task can also be executed by using ad-hoc command, this means no play book is required and the command can be simply run on terminal specifying the module to use and the hosts.
Creating the invetory file for our devicesbasondole@box $ cat hosts [routers] 192.168.56.36 ansible_network_os=junos ansible_ssh_user=fisi ansible_ssh_password=fisi123 192.168.56.26 ansible_network_os=ios ansible_ssh_user=fisi ansible_ssh_password=fisi123
Ad-hoc commands
In below examples we are issuing theshow system alarms
commnad to a junos device using two different modules
thats is the raw
module and the junos_command
module
$ ansible -m raw -a "show system alarms" 192.168.56.36 -i ./hosts 192.168.56.36 | SUCCESS | rc=0 >> No alarms currently active Shared connection to 192.168.56.36 closed.
$ ansible -m junos_command -a "commands='show sys alarm'" -c network_cli 192.168.56.36 -i ./hosts [WARNING]: arguments wait_for, match, rpcs are not supported when using transport=cli 192.168.56.36 | SUCCESS => { "changed": false, "stdout": [ "show system alarms \nNo alarms currently active" ], "stdout_lines": [ [ "show system alarms ", "No alarms currently active" ] ] }
Playbooks
In the example below a playbook runs and gets the uptime of junos and cisco ios devicebasondole@box $ cat uptime.yml --- - hosts: routers gather_facts: no ignore_errors: yes vars: ansible_ssh_user: fisi ansible_ssh_password: fisi123 tasks: - name: check uptime juniper raw: show system uptime | match boot register: junos_uptime when: ansible_network_os == "junos" - name: check uptime cisco raw: show ver | i uptime register: cisco_uptime when: ansible_network_os == "ios" - name: print the uptime debug: var=junos_uptime.stdout debug: msg="{{ cisco_uptime.stdout }}"
Running the playbook
basondole@box $ ansible-playbook router_uptime_playbook.yml -i hosts -v Using /etc/ansible/ansible.cfg as config file PLAY [routers] ******************************************************************************** TASK [check uptime juniper] ******************************************************************* skipping: [192.168.56.26] => changed=false skip_reason: Conditional result was False changed: [192.168.56.36] => changed=true rc: 0 stderr: |- Shared connection to 41.188.128.120 closed. stdout: |- System booted: 2019-05-21 16:38:27 EAT (12w3d 23:26 ago) stdout_lines:After a playbook completes you get a recap of the tasks doneTASK [check uptime cisco] ********************************************************************* skipping: [192.168.56.36] => changed=false skip_reason: Conditional result was False changed: [192.168.56.26] => changed=true rc: 0 stderr: |- Shared connection to 41.188.128.41 closed. stdout: |- router01 uptime is 22 weeks, 2 days, 6 hours, 27 minutes stdout_lines: PLAY RECAP ************************************************************************************ 192.168.56.36 : ok=1 changed=1 unreachable=0 failed=0 192.168.56.26 : ok=1 changed=1 unreachable=0 failed=0
Note
To get more information about a module$ ansible-doc module-name
To check what documentation is availbale
$ ansible-doc -l
When we re-run the playbook again no changes are made, this is what is referred to as idempotency which essentially means only make changes when they are needed.
Ansible is not the only option in its category, two other popular tools that are used along with Ansible are puppet and chef.
NSO.
NSO
NSO enabled by Tail-f provides end-to-end automation to design and deliver services much faster. It seamlessly integrates all of your infrastructure across different technologies, vendors. Learn more at https://developer.cisco.com/site/nso/
Downloading the NSO
Get the link for downloading nso from Cisco website then download the nso package
basondole@netbox:~$ mkdir nso basondole@netbox:~$ cd nso basondole@netbox:~/nso$ wget "https://devnet-filemedia-download.s3.amazonaws.com/119b2bc7-dbf6-49a1-974d-0a5610e41390/nso-5.1.0.1.linux.x86_64.signed.bin?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXOWDCPZVVCGUYIRZ%2F20191113%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20191113T130414Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=f66d3a819f755e24dfdb08844fb2e9d5fd676f28a518a3ab922347b302cda0b4" -O nso-5.1.0.1 basondole@netbox:~/nso$ ls nso-5.1.0.1 basondole@netbox:~/nso$
Extract the package
basondole@netbox:~/nso$ sh nso-5.1.0.1 Unpacking... Verifying signature... Downloading CA certificate from http://www.cisco.com/security/pki/certs/crcam2.cer ... Successfully downloaded and verified crcam2.cer. Downloading SubCA certificate from http://www.cisco.com/security/pki/certs/innerspace.cer ... Successfully downloaded and verified innerspace.cer. Successfully verified root, subca and end-entity certificate chain. Successfully fetched a public key from tailf.cer. Successfully verified the signature of nso-5.1.0.1.linux.x86_64.installer.bin using tailf.cer basondole@netbox:~/nso$ ls cisco_x509_verify_release.py nso-5.1.0.1.linux.x86_64.installer.bin README.signature nso-5.1.0.1 nso-5.1.0.1.linux.x86_64.installer.bin.signature tailf.cer basondole@netbox:~/nso$
Installation
In this system, we'll install the nso in the home directory
basondole@netbox:~/nso$ sh nso-5.1.0.1.linux.x86_64.installer.bin $HOME/nso-5.1.0.1 INFO Using temporary directory /tmp/ncs_installer.2786 to stage NCS installation bundle INFO Unpacked ncs-5.1.0.1 in /home/basondole/nso-5.1.0.1 INFO Found and unpacked corresponding DOCUMENTATION_PACKAGE INFO Found and unpacked corresponding EXAMPLE_PACKAGE INFO Generating default SSH hostkey (this may take some time) INFO SSH hostkey generated INFO Environment set-up generated in /home/basondole/nso-5.1.0.1/ncsrc INFO NCS installation script finished INFO Found and unpacked corresponding NETSIM_PACKAGE INFO NCS installation complete basondole@netbox:~/nso$
Incase of a python error
basondole@netbox:~/nso$ sh nso-5.1.0.1 Unpacking... ERROR Verification requires Python version 2.7.4 or later. ERRORTo overcome this do a python install then extrat the file again
basondole@netbox:~/nso$ sudo apt install python . . basondole@netbox:~/nso$ sh nso-5.1.0.1.linux.x86_64.installer.bin $HOME/nso-5.1.0.1
Running the nso
basondole@netbox:~/nso$ cd .. basondole@netbox:~$ ls nso nso-5.1.0.1 basondole@netbox:~$ source $HOME/nso-5.1.0.1/ncsrc basondole@netbox:~$ ncs-setup --dest $HOME/ncs-run basondole@netbox:~$ ls ncs-run nso nso-5.1.0.1 basondole@netbox:~$ cd ncs-run/ basondole@netbox:~/ncs-run$ ls logs ncs-cdb ncs.conf packages README.ncs scripts state basondole@netbox:~/ncs-run$ ncs ! takes a minute to start basondole@netbox:~/ncs-run$ ncs --status basondole@netbox:~/ncs-run$ ncs --version 5.1.0.1 basondole@netbox:~/ncs-run$ ncs --status | grep status status: started basondole@netbox:~/ncs-run$
Accessing the nso
The NSO offers a frontend UI which can be accessed via a web browser via http://192.168.56.20:8080/login.html
Where 192.168.56.20
is my server address
The default login credentials:username: admin
password: admin
To access the nso via CLI
basondole@netbox:~/ncs-run$ ncs_cli -u admin -C admin connected from 192.168.56.1 using ssh on netbox admin@ncs# exit basondole@netbox:~/ncs-run$
Configuration
To enable pasting of multiple lines of text in the ncs cli add below lines in the ncs config file
basondole@netbox:~/ncs-run$ nano ncs.conf
<enabled>true</enabled>
<space-completion><enabled>false</enabled></space-completion>
<ignore-leading-whitespace>true</ignore-leading-whitespace>
<auto-wizard><enabled>false</enabled></auto-wizard>
To offer support for a range of multivendor devices, NSO uses Network Element Drivers (NEDs).
Using NEDs, NSO makes device configuration commands available over a network wide, multivendor Command Line Interface (CLI), APIs, and user interface
Learn more at https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/network-services-orchestrator/datasheet-c78-734669.html
To verify the pre installed NEDs on your system
basondole@netbox:~/ncs-run$ cd $NCS_DIR basondole@netbox:~/nso-5.1.0.1$ ls packages/ lsa neds services tools basondole@netbox:~/nso-5.1.0.1$ ls packages/neds/ a10-acos-cli-3.0 cisco-ios-cli-3.0 cisco-iosxr-cli-3.0 cisco-nx-cli-3.0 juniper-junos-nc-3.0 alu-sr-cli-3.4 cisco-ios-cli-3.8 cisco-iosxr-cli-3.5 dell-ftos-cli-3.0 basondole@netbox:~/nso-5.1.0.1$
To verif whether the packages are loaded in the ncs
basondole@netbox:~/ncs-run$ ncs_cli -u admin -C admin connected from 192.168.56.1 using ssh on netbox admin@ncs# show packages % No entries found. admin@ncs# exit
If they are not loaded as seen above you can issue a reload command in the ncs
basondole@netbox:~/ncs-run$ ncs_cli -u admin -C admin connected from 192.168.56.1 using ssh on netbox admin@ncs# show packages % No entries found. admin@ncs# packages reload >>> System upgrade is starting. >>> Sessions in configure mode must exit to operational mode. >>> No configuration changes can be performed until upgrade has completed. >>> System upgrade has been cancelled. Error: User java class "com.tailf.packages.ned.ios.UpgradeNedId" exited with status 127 admin@ncs# show packages % No entries found. admin@ncs# exit
If you run into this error confirm you have java installed and if not install java
basondole@netbox:~/ncs-run$ java -version Command 'java' not found, but can be installed with: sudo apt install default-jre sudo apt install openjdk-11-jre-headless sudo apt install openjdk-8-jre-headless basondole@netbox:~/ncs-run$ sudo apt-get update -y . . basondole@netbox:~/ncs-run$ sudo apt-get install openjdk-11-jre -y . . basondole@netbox:~/ncs-run$ sudo apt-get install ant -y . . basondole@netbox:~/ncs-run$ java -version openjdk version "11.0.5" 2019-10-15 OpenJDK Runtime Environment (build 11.0.5+10-post-Ubuntu-0ubuntu1.118.04) OpenJDK 64-Bit Server VM (build 11.0.5+10-post-Ubuntu-0ubuntu1.118.04, mixed mode, sharing)
Also confirm the packages are available on the directory you are running NSO from
in my case I'm running NSO from ~/ncs-run
basondole@netbox:~/ncs-run$ ls packages/ basondole@netbox:~/ncs-run$
If the package directory is empty copy the NEDs from the $NCS_DIR
directory
basondole@netbox:~/ncs-run$ cp -r ~/nso-5.1.0.1/packages/neds/* ./packages/ basondole@netbox:~/ncs-run$ ls packages/ cisco-ios-cli-3.0 cisco-iosxr-cli-3.0 cisco-nx-cli-3.0 cisco-ios-cli-3.8 cisco-iosxr-cli-3.5 juniper-junos-nc-3.0
Login to the ncs and reload the packages
basondole@netbox:~/ncs-run$ ncs_cli -u admin -C admin@ncs# packages reload . reload-result { package cisco-iosxr-cli-3.5 result false info --ERROR-- } reload-result { package cisco-nx-cli-3.0 result false info --ERROR-- } reload-result { package dell-ftos-cli-3.0 result false info --ERROR-- } reload-result { package juniper-junos-nc-3.0 result true } basondole@ncs# show packages package oper-status PACKAGE PROGRAM META FILE CODE JAVA BAD NCS PACKAGE PACKAGE CIRCULAR DATA LOAD ERROR NAME UP ERROR UNINITIALIZED VERSION NAME VERSION DEPENDENCY ERROR ERROR INFO ---------------------------------------------------------------------------------------------------------------- cisco-ios-cli-3.0 - - X - - - - - - - cisco-nx-cli-3.0 - - X - - - - - - - cisco-iosxr-cli-3.0 - - X - - - - - - - dell-ftos-cli-3.0 - - X - - - - - - - juniper-junos-nc-3.0 X - - - - - - - - - basondole@ncs# exit
From above we see we had errors loading a couple of NEDs with java unitialized
status
The issue here is very likely related to the JavaVM, since all the Java packages are failing,
while the Junos NETCONF NED (which doesn't use any Java) is fine.
Since we have quite a few NEDs, the issue is almost certainly that the JavaVM is out of memory/heap space.
To check the java-vm log on ncs basondole@netbox:~/ncs-run$ less logs/ncs-java-vm.log
To fix the memory problem in my case since my server has 2GB of RAM, I assigned 1GB of memory to java.basondole@netbox:~/ncs-run$ export NCS_JAVA_VM_OPTIONS=-Xmx1G
You can add this to your .bash_profile
so that it is done automatically everytime you log in
We then relaunch the ncs
basondole@netbox:~/ncs-run$ ncs --stop basondole@netbox:~/ncs-run$ ncs basondole@netbox:~/ncs-run$ ncs_cli -C basondole connected from 192.168.56.1 using ssh on netbox basondole@ncs# packages reload >>> System upgrade is starting. >>> Sessions in configure mode must exit to operational mode. >>> No configuration changes can be performed until upgrade has completed. >>> System upgrade has completed successfully. . . reload-result { package cisco-iosxr-cli-3.0 result true } reload-result { package cisco-iosxr-cli-3.5 result true } reload-result { package juniper-junos-nc-3.0 result true } basondole@ncs# basondole@ncs# show packages package oper-status PACKAGE PROGRAM META FILE CODE JAVA BAD NCS PACKAGE PACKAGE CIRCULAR DATA LOAD ERROR NAME UP ERROR UNINITIALIZED VERSION NAME VERSION DEPENDENCY ERROR ERROR INFO ---------------------------------------------------------------------------------------------------------------- cisco-ios-cli-3.0 X - - - - - - - - - cisco-ios-cli-3.8 X - - - - - - - - - cisco-iosxr-cli-3.0 X - - - - - - - - - cisco-iosxr-cli-3.5 X - - - - - - - - - cisco-nx-cli-3.0 X - - - - - - - - - juniper-junos-nc-3.0 X - - - - - - - - - basondole@ncs# exit basondole@netbox:~/ncs-run$
Adding the auth group to the ncs
Before we can add devices in the ncs we have to define an authentication group
admin@ncs# config admin@ncs(config)# devices authgroups group GROUP01 admin@ncs(config-group-GROUP01)# default-map remote-name fisi admin@ncs(config-group-GROUP01)# default-map remote-password fisi123 admin@ncs(config-group-GROUP01)# top admin@ncs(config)# commit check Validation complete admin@ncs(config)# show configuration diff +devices authgroups group GROUP01 + default-map remote-name fisi + default-map remote-password $8$1SgUsPkoEaFvTwK02flfv5Ta5ut9WBf+I1m+OaTo8vQ= +! admin@ncs(config)# commit Commit complete. admin@ncs(config)# do show configuration commit list 2019-12-24 13:53:45 SNo. ID User Client Time Stamp Label Comment ~~~~ ~~ ~~~~ ~~~~~~ ~~~~~~~~~~ ~~~~~ ~~~~~~~ 1000 10002 admin cli 2019-12-24 13:51:41 1000 10001 system system 2019-11-13 13:42:45
Configuring devices for nso
Configuration is pulled from devices I used on my presentation at Pycon Tanzania Dec 2019 excuse the use of pycon for hostnames
Cisco IOS XR
RP/0/0/CPU0:pycon-iosxr(config)#show configuration Tue Dec 24 14:10:36.560 UTC Building configuration... username fisi secret 5 $1$UV0J$uNLTpu2nr6K2ZhY7z2cks/ ssh server v2 ssh server netconf port 830 ssh server logging netconf-yang agent ssh RP/0/0/CPU0:pycon-iosxr(config)#commit RP/0/0/CPU0:pycon-iosxr(config)#exit RP/0/0/CPU0:pycon-iosxr#crypto key generate rsa
JunOS
fisi@pycon-junos> show configuration system services ssh; netconf { ssh; } fisi@pycon-junos> show configuration system login user fisi uid 2000; class super-user; authentication { encrypted-password "$1$ty9HKQjx$n3zBLWY5HgycHOQW2/epX/"; ## SECRET-DATA }
Cisco IOS
Only configure ssh
Adding a cisco ios xr device to nso
admin@ncs(config)# devices device pycon-iosxr admin@ncs(config-device-pycon-iosxr)# address 192.168.56.65 admin@ncs(config-device-pycon-iosxr)# authgroup GROUP01 admin@ncs(config-device-pycon-iosxr)# device-type cli ned-id cisco-iosxr-cli-3.5 admin@ncs(config-device-pycon-iosxr)# device-type cli protocol ssh admin@ncs(config-device-pycon-iosxr)# state admin-state unlocked admin@ncs(config-device-pycon-iosxr)# top admin@ncs(config)# commit check Validation complete admin@ncs(config)# show configuration diff +devices device pycon-iosxr + address 192.168.56.65 ! +devices authgroups group GROUP01 + default-map remote-name fisi + default-map remote-password $8$1SgUsPkoEaFvTwK02flfv5Ta5ut9WBf+I1m+OaTo8vQ= +! devices device pycon-iosxr + authgroup GROUP01 + device-type cli ned-id cisco-iosxr-cli-3.0 + device-type cli protocol ssh + state admin-state unlocked + config + no ios:service pad + no ios:ip domain-lookup + no ios:service password-encryption + no ios:cable admission-control preempt priority-voice + no ios:cable qos permission create + no ios:cable qos permission update + no ios:cable qos permission modems + no ios:ip cef + no ios:ip forward-protocol nd + no ios:ipv6 source-route + no ios:ipv6 cef + no nx:feature ssh + no nx:feature telnet + ! +! admin@ncs(config)# commit Commit complete. admin@ncs(config)# do show running-config | begin pycon devices device pycon-iosxr address 192.168.56.65 authgroup GROUP01 device-type cli ned-id cisco-iosxr-cli-3.0 device-type cli protocol ssh state admin-state unlocked config no ios:service pad no ios:ip domain-lookup no ios:service password-encryption no ios:cable admission-control preempt priority-voice no ios:cable qos permission create no ios:cable qos permission update no ios:cable qos permission modems no ios:ip cef no ios:ip forward-protocol nd no ios:ipv6 source-route no ios:ipv6 cef no nx:feature ssh no nx:feature telnet . . admin@ncs# show devices brief NAME ADDRESS DESCRIPTION NED ID ------------------------------------------------------------ pycon-iosxr 192.168.56.65 - cisco-iosxr-cli-3.0
After adding the device we fetch its ssh keys and then sync-from
so as to sychronise the device config to the ncs database
admin@ncs# devices device pycon-iosxr ssh fetch-host-keys result updated fingerprint { algorithm ssh-rsa value f6:46:c1:32:19:24:ff:21:e6:ac:0f:85:78:94:77:40 } admin@ncs# devices device pycon-iosxr ping result PING 192.168.56.65 (192.168.56.65) 56(84) bytes of data. 64 bytes from 192.168.56.65: icmp_seq=1 ttl=255 time=6.24 ms --- 192.168.56.65 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 6.246/6.246/6.246/0.000 ms admin@ncs# devices device pycon-iosxr sync-from result true admin@ncs# admin@ncs# show devices device pycon-iosxr config config yanglib:modules-state module-set-id 762f393abd3986410711f2cf22587ccd yanglib:modules-state module tailf-ned-cisco-ios-xr 2014-02-18 namespace http://tail-f.com/ned/cisco-ios-xr conformance-type implement admin@ncs#
Now after synching the config from the router to the ncs database, we logon to the router and change configuration
RP/0/0/CPU0:pycon-iosxr(config)#username baggy RP/0/0/CPU0:pycon-iosxr(config-un)#show commi chan diff Tue Dec 24 14:45:24.827 UTC Building configuration... !! IOS XR Configuration 5.3.0 + username baggy ! end RP/0/0/CPU0:pycon-iosxr(config-un)#commit
Then we check with the ncs to see what has changed
admin@ncs# devices device pycon-iosxr compare-config diff devices { device pycon-iosxr { config { + cisco-ios-xr:username baggy { + } } } } admin@ncs#
We see the config that we added on the router is displayed, this is the diff between the actual config on the device and
the config on the ncs database. Here we can either sync-from
this device to update the ncs copy of the config or sync-to
to push the config from ncs to the device and removing the added config.
In this case we synced from the device however this was done via web ui.
Back on the device we revert the change and remove the username
RP/0/0/CPU0:pycon-iosxr(config)#load rollback changes last 1 Building configuration... Loading. 53 bytes parsed in 1 sec (51)bytes/sec RP/0/0/CPU0:pycon-iosxr(config)#show commi chan diff Tue Dec 24 14:50:27.226 UTC Building configuration... !! IOS XR Configuration 5.3.0 - username baggy end RP/0/0/CPU0:pycon-iosxr(config)#commit
Then we check with ncs to see what's changed. The dry-run
option allows us to preview of what would happen if we are to sync the config but with this option the ncs doesnt actually sync the config. After the dry run we then sync the config from device to ncs database
We can use the show run
command to check the synced config
admin@ncs# devices device pycon-iosxr sync-from dry-run cli config { - cisco-ios-xr:username baggy { - } } admin@ncs# devices device pycon-iosxr sync-from result true admin@ncs# admin@ncs# show running-config devices device pycon-iosxr
Adding an ios device
devices device pycon-ios address 192.168.56.63 authgroup GROUP01 device-type cli ned-id cisco-ios-cli-3.0 device-type cli protocol ssh state admin-state unlocked top commit devices device pycon-ios ssh fetch-host-keys devices device pycon-ios sync-from
Adding a Junos device
devices device big address 192.168.56.36 authgroup GROUP01 device-type netconf ned-id juniper-junos-nc-3.0 state admin-state unlocked top commit devices device big ssh fetch-host-keys devices device big sync-from
Configuring junos device
In this snippet we will configure an
apply-group
and apply it in the junos device
admin@ncs(config)# devices device big config admin@ncs(config-config)# junos:configuration groups PYCON admin@ncs(config-groups-PYCON)# system login admin@ncs(config-groups-PYCON)# system login class pycon-su admin@ncs(config-class-pycon-su)# logical-system pycon-junos admin@ncs(config-class-pycon-su)# permissions all admin@ncs(config-class-pycon-su)# exit admin@ncs(config-groups-PYCON)# system login user pycon class pycon-su admin@ncs(config-user-pycon)# uid 2009 admin@ncs(config-user-pycon)# authentication encrypted-password "$1$bq.XK5AI$33.xHE4FRDm30frQY.9gx0" admin@ncs(config-user-pycon)# exit admin@ncs(config-groups-PYCON)# exit admin@ncs(config-config)# junos:configuration apply-groups PYCON admin@ncs(config-config)# exit admin@ncs(config-device-big)# exit admin@ncs(config)# show configuration devices device big devices device big config junos:configuration apply-groups [ PYCON ] junos:configuration groups PYCON system login class pycon-su logical-system pycon-junos permissions [ all ] ! system login user pycon uid 2009 class pycon-su authentication encrypted-password $1$bq.XK5AI$33.xHE4FRDm30frQY.9gx0 authentication ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZGRQBprO0LQeiUDW2hR7Yfj3DIF5MbBiG+3/ZyuedS0shbSwxLOMBNhU7MAuXKVuvtzAsFy/IAKN41LhSvq7ppg0Bq+qMsxKJ8U8BY0svM+Hzpe+fJIfJz6R2dp+R79t+EYRR1UdYQO60I2fUdIgazR1AHV1H/6fO/TNXykI2PsqeXSfrTo8Li/WAyRt+1C+U6LPUO5OnkbP+cJxeqtDPkz1I2I7d4izonbmCrIegIGlGpx1ib2/WmqkpX+r0+iqrCQll7TvM73yduC31qMks/g+ncfeuVQPHdLsTlmNWt3MlLCCo+/lVbsMZJuAs38cn4UfpE78qdGY00r4MHIlJ paul@LWBS-STZ-150YNL;" ! ! ! ! ! admin@ncs(config)# admin@ncs(config)# show configuration diff devices device big config + junos:configuration apply-groups [ PYCON ] + junos:configuration groups PYCON + system login class pycon-su + logical-system pycon-junos + permissions [ all ] + ! + system login user pycon + uid 2009 + class pycon-su + authentication encrypted-password $1$bq.XK5AI$33.xHE4FRDm30frQY.9gx0 + authentication ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZGRQBprO0LQeiUDW2hR7Yfj3DIF5MbBiG+3/ZyuedS0shbSwxLOMBNhU7MAuXKVuvtzAsFy/IAKN41LhSvq7ppg0Bq+qMsxKJ8U8BY0svM+Hzpe+fJIfJz6R2dp+R79t+EYRR1UdYQO60I2fUdIgazR1AHV1H/6fO/TNXykI2PsqeXSfrTo8Li/WAyRt+1C+U6LPUO5OnkbP+cJxeqtDPkz1I2I7d4izonbmCrIegIGlGpx1ib2/WmqkpX+r0+iqrCQll7TvM73yduC31qMks/g+ncfeuVQPHdLsTlmNWt3MlLCCo+/lVbsMZJuAs38cn4UfpE78qdGY00r4MHIlJ paul@LWBS-STZ-150YNL;" + ! + ! + ! ! ! admin@ncs(config)# admin@ncs(config)# devices device big check-sync result out-of-sync info got: 2019-12-24 20:07:03 UTC expected: 2019-12-24 19:24:50 UTC admin@ncs(config)# commit no-out-of-sync-check Commit complete. admin@ncs(config)# devices device big check-sync result unknown admin@ncs(config)# devices device big sync-to result true admin@ncs(config)# devices device big check-sync result in-sync
You will notice the config flow is not exactly what we are used to with a junos device
however it follows the same hierarchy with junos:configuration
being the top level of the config
from which we can go to any other sub stanza and get a corresponding prompt for that
for example the command junos:configuration groups PYCON
gets us to the (config-groups-PYCON)
prompt
same as edit groups PYCON
would take us to [edit groups PYCON]
on the JunoS CLI.
We now logon to the junos device and check the config that's been pushed from the nso
fisi@big> show system commit | match ^0 0 2019-12-24 20:07:57 UTC by fisi via netconf fisi@big> show configuration groups PYCON { system { login { class pycon-su { logical-system pycon-junos; permissions all; } user pycon { uid 2009; class pycon-su; authentication { encrypted-password "$1$bq.XK5AI$33.xHE4FRDm30frQY.9gx0"; ## SECRET-DATA ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZGRQBprO0LQeiUDW2hR7Yfj3DIF5MbBiG+3/ZyuedS0shbSwxLOMBNhU7MAuXKVuvtzAsFy/IAKN41LhSvq7ppg0Bq+qMsxKJ8U8BY0svM+Hzpe+fJIfJz6R2dp+R79t+EYRR1UdYQO60I2fUdIgazR1AHV1H/6fO/TNXykI2PsqeXSfrTo8Li/WAyRt+1C+U6LPUO5OnkbP+cJxeqtDPkz1I2I7d4izonbmCrIegIGlGpx1ib2/WmqkpX+r0+iqrCQll7TvM73yduC31qMks/g+ncfeuVQPHdLsTlmNWt3MlLCCo+/lVbsMZJuAs38cn4UfpE78qdGY00r4MHIlJ paul@LWBS-STZ-150YNL;"; ## SECRET-DATA } } } } } fisi@big> show configuration apply-groups ## Last commit: 2019-12-24 20:07:57 UTC by fisi apply-groups PYCON; fisi@big>
From the above example we see the confiuration has indeed taken effect on the device. This operation was done via the ncs CLI but we can achieve the same effect via the web frontend as well.
Netsim
Netsim offers emulated devices that can be used with the NSO mainly for testing and development.Creating an emulated device
basondole@netbox:~/nso/ncs-run$ ls packages/ cisco-ios-cli-3.8 cisco-iosxr-cli-3.5 juniper-junos-nc-3.0 cisco-ios-cli-3.0 cisco-iosxr-cli-3.0 cisco-nx-cli-3.0 basondole@netbox:~/nso/ncs-run$ cd myownnetsim/ basondole@netbox:~/nso/ncs-run/myownnetsim$ basondole@netbox:~/nso/ncs-run/myownnetsim$ mkdir iosxr basondole@netbox:~/nso/ncs-run/myownnetsim$ cd iosxr basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr$ ncs-netsim create-device cisco-iosxr-cli-3.5 iosxr DEVICE iosxr CREATED basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/$ ls netsim
To start the emulated device
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ cd netsim/ basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ls iosxr README.netsim basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs-netsim start DEVICE iosxr OK STARTED
To check if the device is running
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs-netsim is-alive iosxr DEVICE iosxr OK basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$
To get the port being used by device
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs-netsim list ncs-netsim list for /home/basondole/nso/ncs-run/myownnetsim/iosxr/netsim name=iosxr netconf=12022 snmp=11022 ipc=5010 cli=10022 \ dir=/home/basondole/nso/ncs-run/myownnetsim/iosxr/netsim/iosxr/iosxr basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs-netsim get-port iosxr cli 10022 basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$
Connecting to the device
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs-netsim cli-i iosxr admin connected from 192.168.56.1 using ssh on netbox netbox> enable netbox# show version Cisco IOS XR Software, NETSIM netbox# exit basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$
Adding the device to the nso
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ ncs_cli -C basondole connected from 192.168.56.1 using ssh on netbox basondole@ncs# config Entering configuration mode terminal basondole@ncs(config)# devices device netsim address 127.0.0.1 port 10022 basondole@ncs(config-device-netsim)# device-type cli ned-id cisco-iosxr-cli-3.5 protocol ssh basondole@ncs(config-device-netsim)# authgroup default state admin-state unlocked basondole@ncs(config-device-netsim)# commit basondole@ncs(config-device-netsim)# top basondole@ncs(config)# devices device netsim ssh fetch-host-keys result failed info internal error basondole@ncs(config)# exit basondole# exit
We have run into an error when fetching the host key.
To fix this we have to make sure the right keys are existing in the emulated device ssh directory.
The easy fix is to copy the keys from $NCS_DIR/netsim/confd/etc/confd/ssh/
to our emulated device ssh directory
basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ cp $NCS_DIR/netsim/confd/etc/confd/ssh/ssh_host_rsa_key.pub iosxr/iosxr/ssh/ssh_host_rsa_key.pub basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ diff $NCS_DIR/netsim/confd/etc/confd/ssh/ssh_host_rsa_key.pub iosxr/iosxr/ssh/ssh_host_rsa_key.pub basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ cp $NCS_DIR/netsim/confd/etc/confd/ssh/ssh_host_rsa_key iosxr/iosxr/ssh/ssh_host_rsa_key basondole@netbox:~/nso/ncs-run/myownnetsim/iosxr/netsim$ diff $NCS_DIR/netsim/confd/etc/confd/ssh/ssh_host_rsa_key iosxr/iosxr/ssh/ssh_host_rsa_key
Then we restart the nso and the emulated device then fetch the keys again
basondole@netbox:~/nso/ncs-run$ ncs --stop basondole@netbox:~/nso/ncs-run$ ncs-netsim stop --dir myownnetsim/iosxr/netsim DEVICE iosxr STOPPED basondole@netbox:~/nso/ncs-run$ ncs basondole@netbox:~/nso/ncs-run$ ncs-netsim start --dir myownnetsim/iosxr/netsim DEVICE iosxr OK STARTED basondole@netbox:~/nso/ncs-run$ ncs_cli -C -u admin admin connected from 192.168.56.1 using ssh on netbox admin@ncs# conf Entering configuration mode terminal admin@ncs(config)# devices device netsim ssh fetch-host-keys result updated fingerprint { algorithm ssh-rsa value ff:18:c0:6b:8b:fa:04:d4:8c:ed:91:ce:25:66:e9:df } admin@ncs(config)#
Adding multiple devices
Verifying the NEDs available
basondole@netbox:~/nso/ncs-run$ ls logs ncs-cdb ncs.conf packages README.ncs scripts state storedstate target basondole@netbox:~/nso/ncs-run$ ls packages/ cisco-ios-cli-3.8 cisco-iosxr-cli-3.5 juniper-junos-nc-3.0 cisco-ios-cli-3.0 cisco-iosxr-cli-3.0 cisco-nx-cli-3.0 basondole@netbox:~/nso/ncs-run$
Creating the devices
basondole@netbox:~/nso/ncs-run$ ncs-netsim create-device cisco-ios-cli-3.8 netsim-ios-00 DEVICE netsim-ios-00 CREATED basondole@netbox:~/nso/ncs-run$ ncs-netsim add-device cisco-iosxr-cli-3.5 netsim-xr-00 DEVICE netsim-xr-00 CREATED basondole@netbox:~/nso/ncs-run$ ncs-netsim add-device juniper-junos-nc-3.0 netsim-junos-00 DEVICE netsim-junos-00 CREATED basondole@netbox:~/nso/ncs-run$ ls logs ncs-cdb ncs.conf netsim packages README.ncs scripts state storedstate target basondole@netbox:~/nso/ncs-run$Note:
We initially created one device using
create-device
. The following devices were added using add-device
Starting the devices
basondole@netbox:~/nso/ncs-run$ cd netsim basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim start netsim-ios-00 DEVICE netsim-ios-00 OK STARTED basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim start netsim-xr-00 DEVICE netsim-xr-00 OK STARTED basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim start netsim-junos-00 DEVICE netsim-junos-00 OK STARTED basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim list ncs-netsim list for /home/basondole/nso/ncs-run/netsim name=netsim-ios-00 netconf=12022 snmp=11022 ipc=5010 cli=10022 dir=/home/basondole/nso/ncs-run/netsim/netsim-ios-00/netsim-ios-00 name=netsim-xr-00 netconf=12023 snmp=11023 ipc=5011 cli=10023 dir=/home/basondole/nso/ncs-run/netsim/netsim-xr-00/netsim-xr-00 name=netsim-junos-00 netconf=12024 snmp=11024 ipc=5012 cli=10024 dir=/home/basondole/nso/ncs-run/netsim/netsim-junos-00/netsim-junos-00 basondole@netbox:~/nso/ncs-run/netsim$
Connecting to the devices via ssh
basondole@netbox:~/nso/ncs-run/netsim$ ssh admin@127.0.0.1 -p 10022 admin@127.0.0.1's password: admin connected from 127.0.0.1 using ssh on netbox netsim-ios-00> exit Connection to 127.0.0.1 closed. basondole@netbox:~/nso/ncs-run/netsim$ ssh admin@127.0.0.1 -p 10023 admin@127.0.0.1's password: admin connected from 127.0.0.1 using ssh on netbox netbox# exit Connection to 127.0.0.1 closed. basondole@netbox:~/nso/ncs-run/netsim$ ssh admin@127.0.0.1 -p 10024 admin@127.0.0.1's password: admin connected from 127.0.0.1 using ssh on netbox admin@netsim-junos-00>exit Connection to 127.0.0.1 closed. basondole@netbox:~/nso/ncs-run/netsim$
Bulk export of devices to the nso
When we have multiple emulated devices we can easily export them to the nso easing the urden of addin the devices one by one
basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim ncs-xml-init > devices.xml /home/basondole/nso/nso-5.1.0.1/bin/ncs-netsim: line 895: xsltproc: command not found /home/basondole/nso/nso-5.1.0.1/bin/ncs-netsim: line 895: xsltproc: command not found /home/basondole/nso/nso-5.1.0.1/bin/ncs-netsim: line 895: xsltproc: command not found /home/basondole/nso/nso-5.1.0.1/bin/ncs-netsim: line 895: xsltproc: command not found basondole@netbox:~/nso/ncs-run/netsim$ ls devices.xml netsim-ios-00 netsim-junos-00 netsim-nx-00 netsim-xr-00 README.netsim basondole@netbox:~/nso/ncs-run/netsim$ ncs_load -l -m devices.xml ncs_load: 690: maapi_apply_trans_flags(sock, tid, 0, aflags) failed: Unsatisfied must constraint (41): \ /ncs:devices/device{netsim-ios-00}/device-type : must configure one of: snmp, cli, generic, netconf basondole@netbox:~/nso/ncs-run/netsim$
To uderstand the error we have to check the contents of the created file devices.xml
basondole@netbox:~/nso/ncs-run/netsim$ less devices.xml
<devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>netsim-ios-00</name>
<address>127.0.0.1</address>
<port>10022</port>
<ssh>
<host-key>
<algorithm>ssh-rsa</algorithm>
<key-data>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/23... basondole@netbox</key-data>
</host-key>
</ssh>
<state>
<admin-state>unlocked</admin-state>
</state>
<authgroup>default</authgroup>
<device-type>
<cli>
</cli>
</device-type>
</device>
We see the device-type is not defined. We therefore have to edit it manually. To get the device type you can add the device manully to the nso
basondole@netbox:~/nso/ncs-run/netsim$ ncs_cli -C
basondole@ncs(config)# show configuration | exclude no
devices device test
address test
authgroup default
device-type cli ned-id cisco-nx-cli-3.0
device-type cli protocol ssh
config
!
!
basondole@ncs(config)# commit dry-run outformat xml
result-xml {
local-node {
data <devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>test</name>
<address>test</address>
<authgroup>default</authgroup>
<device-type>
<cli>
<ned-id xmlns:cisco-nx-cli-3.0="http://tail-f.com/ns/ned-id/cisco-nx-cli-3.0">cisco-nx-cli-3.0:cisco-nx-cli-3.0</ned-id>
<protocol>ssh</protocol>
</cli>
</device-type>
</device>
</devices>
}
}
basondole@ncs(config)# end
Uncommitted changes found, commit them? [yes/no/CANCEL] no
basondole@ncs# exit
We then copy the device-type
and paste in the corresponding section in the device.xml file
which then becomes
basondole@netbox:~/nso/ncs-run/netsim$ less devices.xml
devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>netsim-ios-00</name>
<address>127.0.0.1</address>
<port>10022</port>
<ssh>
<host-key>
<algorithm>ssh-rsa</algorithm>
<key-data>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/23... basondole@netbox</key-data>
</host-key>
</ssh>
<state>
<admin-state>unlocked</admin-state>
</state>
<authgroup>default</authgroup>
<device-type>
<cli>
<ned-id xmlns:cisco-ios-cli-3.8="http://tail-f.com/ns/ned-id/cisco-ios-cli-3.8">cisco-ios-cli-3.8:cisco-ios-cli-3.8</ned-id>
<protocol>ssh</protocol>
</cli>
</device-type>
</device>
Loading the devices to the nso
basondole@netbox:~/nso/ncs-run/netsim$ ncs_load -l -m devices.xml basondole@netbox:~/nso/ncs-run/netsim$ ncs_cli -C basondole connected from 192.168.56.1 using ssh on netbox basondole@ncs# show devices brief NAME ADDRESS DESCRIPTION NED ID netsim-ios-00 127.0.0.1 - cisco-ios-cli-3.8 netsim-junos-00 127.0.0.1 - juniper-junos-nc-3.0 netsim-nx-00 127.0.0.1 - cisco-nx-cli-3.0 netsim-xr-00 127.0.0.1 - cisco-iosxr-cli-3.5 basondole@ncs#
Adding devices to a group
basondole@netbox:~/nso/ncs-run/netsim$ ncs_cli -C -u admin admin connected from 192.168.56.1 using ssh on netbox admin@ncs# config admin@ncs(config)# devices device-group netsim admin@ncs(config-device-group-netsim)# device-name netsim-ios-00 admin@ncs(config-device-group-netsim)# device-name netsim-xr-00 admin@ncs(config-device-group-netsim)# device-name netsim-nx-00 admin@ncs(config-device-group-netsim)# device-name netsim-junos-00 admin@ncs(config-device-group-netsim)# top admin@ncs(config)# show configuration devices device-group netsim device-name [ netsim-ios-00 netsim-junos-00 netsim-nx-00 netsim-xr-00 ] ! admin@ncs(config)# commit Commit complete. admin@ncs(config)# do show devices device-group member NAME MEMBER --------------------------------------------------------------------- netsim [ netsim-ios-00 netsim-junos-00 netsim-nx-00 netsim-xr-00 ] admin@ncs(config)# do devices device-group netsim sync-from sync-result { device netsim-ios-00 result true } sync-result { device netsim-junos-00 result true } sync-result { device netsim-nx-00 result true } sync-result { device netsim-xr-00 result true } admin@ncs(config)#
Baseconfig template
This template will configure below parameters- domain name
- ntp server
- syslog server
- username fisi
The template models ios
, iosxr
, nxos
and junos
syntax
admin@ncs(config)# show configuration devices template baseconf ned-id cisco-ios-cli-3.8 config ios:ip domain name basondole.org ios:username fisi privilege 15 secret secret fisi123 ! ios:logging host 10.10.1.100 ! ios:ntp server ip peer-list 10.10.1.100 ! ! ! ned-id cisco-iosxr-cli-3.5 config cisco-ios-xr:logging host 10.10.1.100 ! cisco-ios-xr:domain name basondole.org cisco-ios-xr:ntp server server-list 10.10.1.100 ! cisco-ios-xr:username fisi privilege 15 secret password fisi123 ! ! ! ned-id juniper-junos-nc-3.0 config junos:configuration system domain-name basondole.org junos:configuration system login user fisi class super-user authentication plain-text-password-value fisi123 ! junos:configuration system syslog host 10.10.1.100 ! junos:configuration system ntp server 10.10.1.100 ! ! ! ned-id cisco-nx-cli-3.0 config nx:username fisi password password fisi123 ! nx:ip domain-name basondole.org nx:ntp server 10.10.1.100 ! nx:logging server host 10.10.1.100 ! ! ! admin@ncs(config)# commit
Applying the template to devices
admin@ncs(config)# devices device-group netsim apply-template template-name baseconf
apply-template-result {
device netsim-junos-00
result ok
}
apply-template-result {
device netsim-nx-00
result ok
}
apply-template-result {
device netsim-xr-00
result ok
}
admin@ncs(config)# show configuration
devices device netsim-junos-00
config
junos:configuration system domain-name basondole.org
junos:configuration system login user fisi
class super-user
authentication plain-text-password-value fisi123
!
junos:configuration system syslog host 10.10.1.100
!
junos:configuration system ntp server 10.10.1.100
!
!
!
devices device netsim-nx-00
config
nx:username fisi password fisi123
nx:ip domain-name basondole.org
nx:ntp server 10.10.1.100
nx:logging server 10.10.1.100
!
!
devices device netsim-xr-00
config
cisco-ios-xr:logging 10.10.1.100
cisco-ios-xr:domain name basondole.org
cisco-ios-xr:ntp
server 10.10.1.100
exit
cisco-ios-xr:username fisi
privilege 15
exit
!
!
admin@ncs(config)# commit dry-run outformat native
native {
device {
name netsim-junos-00
data <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
message-id="1">
<edit-config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
<target>
<candidate/>
</target>
<test-option>test-then-set</test-option>
<error-option>rollback-on-error</error-option>
<with-inactive xmlns="http://tail-f.com/ns/netconf/inactive/1.0"/>
<config>
<configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm">
<system>
<syslog>
<host>
<name>10.10.1.100</name>
</host>
</syslog>
<domain-name>basondole.org</domain-name>
<login>
<user>
<name>fisi</name>
<class>super-user</class>
<authentication>
<plain-text-password-value>fisi123</plain-text-password-value>
</authentication>
</user>
</login>
<ntp>
<server>
<name>10.10.1.100</name>
</server>
</ntp>
</system>
</configuration>
</config>
</edit-config>
</rpc>
}
device {
name netsim-nx-00
data username fisi password fisi123
ip domain-name basondole.org
ntp server 10.10.1.100
logging server 10.10.1.100
}
device {
name netsim-xr-00
data logging 10.10.1.100
domain name basondole.org
ntp
server 10.10.1.100
exit
username fisi
privilege 15
exit
}
}
admin@ncs(config)# commit
Commit complete.
Services
Creating service for deploying radius server
Creating an Yang Module using NSO Bash commands
basondole@netbox:~/nso/ncs-run/packages$ ncs-make-package --service-skeleton template simple_radius --augment /ncs:services
basondole@netbox:~/nso/ncs-run/packages$ cd simple_radius/
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ nano src/yang/simple_radius.yang
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ cat src/yang/simple_radius.yang
module simple_radius {
namespace "http://com/example/simple_radius";
prefix simple_radius;
import ietf-inet-types {
prefix inet;
}
import tailf-ncs {
prefix ncs;
}
<b>import tailf-common { prefix tailf; }</b>
augment /ncs:services {
list simple_radius {
tailf:info "deploy radius server config for iosrx nxos junos";
key server-ip;
uses ncs:service-data;
ncs:servicepoint "simple_radius";
leaf server-ip {
tailf:info "ipv4 address of the radius server";
type inet:ipv4-address;
}
leaf-list device {
tailf:info "device to deploy the service to";
type leafref {
path "/ncs:devices/ncs:device/ncs:name";
}
}
leaf-list secret {
tailf:info "secret key";
type string;
}
}
} // augment /ncs:services {
}
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ cd src/
basondole@netbox:~/nso/ncs-run/packages/simple_radius/src$ make
/home/basondole/nso/nso-5.1.0.1/bin/ncsc `ls simple_radius-ann.yang > /dev/null 2>&1 && echo "-a simple_radius-ann.yang"` \
-c -o ../load-dir/simple_radius.fxs yang/simple_radius.yang
basondole@netbox:~/nso/ncs-run/packages/simple_radius/src$
Creating xml template
We will create device template based on the actual CLI configuration. Below are the configuration options- server ip
- authentication port
- accounting port
- key
We configure this on the NSO and then we produce the xml formatted config.
basondole@netbox:~/ncs-run/packages/simple_radius/src$ ncs_cli -C
basondole@ncs# config
Entering configuration mode terminal
basondole@ncs(config)# devices device pycon-iosxr config cisco-ios-xr:radius-server host 10.10.1.100 auth-port 1812 acct-port 1813
basondole@ncs(config-radius-host)# commit dry-run outformat xml
result-xml {
local-node {
data <devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>pycon-iosxr</name>
<config>
<radius-server xmlns="http://tail-f.com/ned/cisco-ios-xr">
<host>
<id>10.10.1.100</id>
<auth-port>1812</auth-port>
<acct-port>1813</acct-port>
</host>
</radius-server>
</config>
</device>
</devices>
}
}
basondole@ncs(config-radius-host)# end
Uncommitted changes found, commit them? [yes/no/CANCEL] no
basondole@ncs#basondole@ncs# exit
We repeat the above procedure to get xml config for all our devices ios
, iosxr
, nxos
and junos
then we edit the xml template by adding the xml config from above process
basondole@netbox:~/nso/ncs-run/packages/simple_radius/src$ cd ../
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ nano templates/simple_radius-template.xml
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ cat templates/simple_radius-template.xml
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="simple_radius">
<devices xmlns="http://tail-f.com/ns/ncs">
<device>
<!--
Select the devices from some data structure in the service
model. In this skeleton the devices are specified in a leaf-list.
Select all devices in that leaf-list:
-->
<name>{/device}</name>
<config>
<!--
Add device-specific parameters here.
-->
<radius-server xmlns="http://tail-f.com/ned/cisco-nx">
<host>
<id>{/server-ip}</id>
<key>
<encryption>0</encryption>
<password>{/secret}</password>
</key>
</host>
</radius-server>
<radius-server xmlns="http://tail-f.com/ned/cisco-ios-xr">
<host>
<id>{/server-ip}</id>
<auth-port>1812</auth-port>
<acct-port>1813</acct-port>
<key>
<key-container>
<key>{/secret}</key>
</key-container>
</key>
</host>
</radius-server>
<configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm">
<system>
<radius-server>
<name>{/server-ip}</name>
<port>1812</port>
<accounting-port>1813</accounting-port>
<secret>{/secret}</secret>
</radius-server>
</system>
</configuration>
</config>
</device>
</devices>
</config-template>
Adding the service to nso
basondole@netbox:~/ncs-run/packages/simple_radius$ ncs_cli -C basondole connected from 192.168.56.1 using ssh on netbox basondole@ncs# packages reload >>> System upgrade is starting. >>> Sessions in configure mode must exit to operational mode. >>> No configuration changes can be performed until upgrade has completed. >>> System upgrade has completed successfully . . reload-result { package simple_radius result true }
Deploying the service on the router
admin@ncs# conf Entering configuration mode terminal admin@ncs(config)# services simple_radius 10.10.1.100 device netsim-junos-00 secret fisi321 admin@ncs(config-simple_radius-10.10.1.100)# exit admin@ncs(config)# services simple_radius 10.10.1.100 device netsim-nx-00 secret fisi321 admin@ncs(config-simple_radius-10.10.1.100)# exit admin@ncs(config)# services simple_radius 10.10.1.100 device netsim-xr-00 secret fisi321 admin@ncs(config-simple_radius-10.10.1.100)# exit admin@ncs(config)# admin@ncs(config)# commit dry-run cli { local-node { data devices { device netsim-junos-00 { config { junos:configuration { system { + # first + radius-server 10.10.1.100 { + port 1812; + accounting-port 1813; + secret fisi321; + } } } } } device netsim-nx-00 { config { nx:radius-server { + host 10.10.1.100 { + key { + encryption 0; + password fisi321; + } + } } } } device netsim-xr-00 { config { cisco-ios-xr:radius-server { + host 10.10.1.100 { + auth-port 1812; + acct-port 1813; + key { + key-container { + key fisi321; + } + } + } } } } } services { + simple_radius 10.10.1.100 { + device [ netsim-junos-00 netsim-nx-00 netsim-xr-00 ]; + } } } } admin@ncs(config)# commit Commit complete. admin@ncs(config)#
Verification
basondole@netbox:~/nso/ncs-run/packages/simple_radius$ ncs_cli -u admin admin connected from 192.168.56.1 using ssh on netbox admin@ncs> configure [edit] admin@ncs% show services simple_radius | tab SERVER IP DEVICE SECRET ------------------------------------------------------------------------- 10.10.1.100 [ netsim-junos-00 netsim-nx-00 netsim-xr-00 ] [ fisi321 ] [ok][2019-12-28 18:31:01] [edit]
Switching to the netsim
directory so as to access the emulated devicesbasondole@netbox:~/nso/ncs-run/packages/simple_radius$ cd ../../netsim/
ios xr
basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim cli-i netsim-xr-00 admin connected from 192.168.56.1 using ssh on netbox netbox> enable netbox# show run logging 10.10.1.100 domain name basondole.org radius-server host 10.10.1.100 auth-port 1812 acct-port 1813 key fisi321 exit ntp server 10.10.1.100 exit username fisi privilege 15 exit netbox# exit
nx os
basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim cli-i netsim-nx-00 admin connected from 192.168.56.1 using ssh on netbox netbox> enable netbox# show run no feature ssh no feature telnet username fisi password fisi123 ip domain-name basondole.org ! ntp server 10.10.1.100 logging server 10.10.1.100 radius-server host 10.10.1.100 key 0 fisi321 netbox# exit
junos
basondole@netbox:~/nso/ncs-run/netsim$ ncs-netsim cli netsim-junos-00 admin connected from 192.168.56.1 using ssh on netbox admin@netsim-junos-00>show configuration configuration system host-name netsim-junos-00; domain-name basondole.org; radius-server 10.10.1.100 { port 1812; accounting-port 1813; secret fisi321; } login { user fisi { class super-user; authentication { plain-text-password-value fisi123; } } } syslog { host 10.10.1.100; } ntp { server 10.10.1.100; } [ok][2019-12-28 17:54:53] admin@netsim-junos-00>exit basondole@netbox:~/nso/ncs-run/netsim$
Using the nso with junos-like cli to deploy service
basondole@netbox:~/nso/ncs-run/netsim$ ncs_cli -u admin admin connected from 192.168.56.1 using ssh on netbox admin@ncs> configure Entering configuration mode private [ok][2019-12-28 18:22:15] [edit] admin@ncs% set services simple_radius 10.1.1.1 device pycon-iosxr secret fisi123 [ok][2019-12-28 18:23:36] [edit] admin@ncs% show | compare services { + simple_radius 10.1.1.1 { + device [ pycon-iosxr ]; + secret [ fisi123 ]; + } } [ok][2019-12-28 18:23:42] [edit] admin@ncs% commit check Validation complete [ok][2019-12-28 18:24:01] [edit] admin@ncs% commit [edit] admin@ncs%
Un-deploying and re-ploying service
Un-deploying the service
basondole@netbox:~/nso/ncs-run/netsim$ ncs_cli -u admin admin connected from 192.168.56.1 using ssh on netbox admin@ncs> configure Entering configuration mode private [ok][2019-12-28 19:09:51] [edit] admin@ncs% edit services simple_radius 10.10.1.100 [ok][2019-12-28 19:09:56] [edit services simple_radius 10.10.1.100] admin@ncs% request un-deploy [ok][2019-12-28 19:10:05] [edit services simple_radius 10.10.1.100] admin@ncs% request check-sync in-sync false [ok][2019-12-28 19:10:11]
Re-deploying the service
[edit services simple_radius 10.10.1.100] admin@ncs% request re-deploy [ok][2019-12-28 19:10:19] [edit services simple_radius 10.10.1.100] admin@ncs% System message at 2019-12-28 19:10:19... Commit performed by admin via ssh using cli. admin@ncs% request check-sync in-sync true [ok][2019-12-28 19:10:22] [edit services simple_radius 10.10.1.100] admin@ncs%
Salt.
Salt
The nice thing about Salt is that you can have it react on events, rather that run only user-initiated jobs like Ansible. Salt functions on a master/minion topology. A master server acts as a central control bus for the clients, which are called minions. The minions connect back to the master.
Install salt and its components
baggy@focalfossa:~$ wget -O - https://repo.saltstack.com/py3/ubuntu/20.04/amd64/3001/SALTSTACK-GPG-KEY.pub | sudo apt-key add -baggy@focalfossa:~$ baggy@focalfossa:~$ sudo nano /etc/apt/sources.list.d/saltstack.list baggy@focalfossa:~$ sudo cat /etc/apt/sources.list.d/saltstack.list deb http://repo.saltstack.com/py3/ubuntu/20.04/amd64/3001 focal main baggy@focalfossa:~$ baggy@focalfossa:~$ sudo apt-get update . . baggy@focalfossa:~$ sudo pip3 install napalm baggy@focalfossa:~$ sudo apt-get install salt-api salt-cloud salt-master salt-minion salt-ssh salt-syndic -y . .
Salt configuration
After the installation completes, edit the proxy configuration file/etc/salt/proxy
and add below lines
master: localhost multiprocessing: false mine_enabled: true pki_dir: /etc/salt/pki/proxyVerify
baggy@focalfossa:~$ sudo cat /etc/salt/proxy | egrep "^[a-z]" master: localhost pki_dir: /etc/salt/pki/proxy multiprocessing: FalseEdit the minion configuration file
/etc/salt/minion
and define the master as local because we are using the local server for the master role in this implementation, otherwise this iswhere you would point to your salt master.
baggy@focalfossa:~$ sudo nano /etc/salt/minion baggy@focalfossa:~$ sudo cat /etc/salt/minion | grep "^[a-z]" master: localhostCreate a pillar directory via command
sudo mkdir -p /srv/pillar
Then add the top.sls and the other sls files according to the devices you have, the sls files contain respective devices parameters while the top.sls
file ties everything together as shown below
baggy@focalfossa:~$ sudo mkdir -p /srv/pillar baggy@focalfossa:~$ sudo nano /srv/pillar/vbox_junos_36.sls proxy: proxytype: napalm driver: junos host: 192.168.56.36 username: fisi passwd: fisi123 baggy@focalfossa:~$ sudo nano /srv/pillar/gns3_ios_63.sls proxy: proxytype: napalm driver: ios host: 192.168.56.63 username: fisi passwd: fisi123 baggy@focalfossa:~$ sudo nano /srv/pillar/top.sls base: gns3.ios.63: - gns3_ios_63 vbox.junos.36: - vbox_junos_36 baggy@focalfossa:~$Restart the minion and master services
baggy@focalfossa:~$ sudo service salt-master start baggy@focalfossa:~$ sudo service salt-minion start baggy@focalfossa:~$ sudo service salt-master status ● salt-master.service - The Salt Master Server Loaded: loaded (/lib/systemd/system/salt-master.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2020-06-21 14:38:49 UTC; 20min ago Docs: man:salt-master(1) file:///usr/share/doc/salt/html/contents.html https://docs.saltstack.com/en/latest/contents.html Main PID: 4880 (salt-master) Tasks: 32 (limit: 1041) Memory: 213.5M . . baggy@focalfossa:~$ sudo service salt-minion status ● salt-minion.service - The Salt Minion Loaded: loaded (/lib/systemd/system/salt-minion.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2020-06-21 14:38:41 UTC; 20min ago Docs: man:salt-minion(1) file:///usr/share/doc/salt/html/contents.html https://docs.saltstack.com/en/latest/contents.html Main PID: 4707 (salt-minion) Tasks: 4 (limit: 1041) Memory: 64.8M . .To configure the minion to run as a service create the file
salt-proxy@.service
sudo nano /etc/systemd/system/salt-proxy@.service
and add the contents as shown in snapshot then reload the system so it picks up the new unit
baggy@focalfossa:~$ sudo cat /etc/systemd/system/salt-proxy@.service [Unit] Description=Salt proxy minion After=network.target [Service] Type=simple ExecStart=/usr/bin/salt-proxy -l debug --proxyid=%i User=root Group=root Restart=always RestartPreventExitStatus=SIGHUP RestartSec=5 [Install] WantedBy=default.target baggy@focalfossa:~$ sudo systemctl daemon-reloadWe then run the the proxy minions
baggy@focalfossa:~$ sudo salt-proxy --proxyid=gns3.ios.63 -l debug . . baggy@focalfossa:~$ sudo salt-proxy --proxyid=vbox.junos.36 -l debug . .You will note the error message on both minions debug output
[ERROR ] The Salt Master has cached the public key for this node, this salt minion will wait for 10 seconds before attempting to re-authenticate [INFO ] Waiting 10 seconds before retry.This is normal and is due to the salt key from the minion not being accepted by the master. Quit the minion with
CTRL+C
We will need to accept the minion key and re-run the proxy.
baggy@focalfossa:~$ sudo salt-key Accepted Keys: Denied Keys: Unaccepted Keys: gns3.ios.63 vbox.junos.36 Rejected Keys: baggy@focalfossa:~$ sudo salt-key -a gns3.ios.63 The following keys are going to be accepted: Unaccepted Keys: gns3.ios.63 Proceed? [n/Y] y Key for minion gns3.ios.63 accepted. baggy@focalfossa:~$ sudo salt-key -a vbox.junos.36 The following keys are going to be accepted: Unaccepted Keys: vbox.junos.36 Proceed? [n/Y] y Key for minion vbox.junos.36 accepted. baggy@focalfossa:~$ sudo salt-proxy --proxyid=gns3.ios.63 -l debug & sudo salt-proxy --proxyid=vbox.junos.36 -l debug On a different terminal window test the configuration by ping as shown below
baggy@focalfossa:~$ sudo salt vbox.junos.36 test.ping vbox.junos.36: True baggy@focalfossa:~$ sudo salt gns3.ios.63 test.ping gns3.ios.63: True baggy@focalfossa:~$It return
True
means everything is well set. On the original terminal window where we ran the minions, we can end the process by CTRL+C
Then we restart the minions as a daemon and sync the packages
baggy@focalfossa:~$ sudo salt-proxy --proxyid=vbox.junos.36 -d baggy@focalfossa:~$ sudo salt-proxy --proxyid=gns3.ios.63 -d baggy@focalfossa:~$ sudo salt gns3.ios.63 saltutil.sync_all . . baggy@focalfossa:~$ sudo salt vbox.junos.36 saltutil.sync_all . . baggy@focalfossa:~$ sudo salt "*" test.ping gns3.ios.63: True vbox.junos.36: True baggy@focalfossa:~$The catch with salt is the fact that each proxy-minion is run as a separate process and consumes memory so in setups with large number of devices resources must be considered.
Salt in action
Using salt to check arp table; in below command the "*" means act on all devices defined in the top.sls
file.
Otherwise salt offers multiple ways of matching devices to act upon. You can match devices by serial numbers, models and software versions among others.
baggy@focalfossa:~$ sudo salt "*" net.arp --out=table vbox.junos.36: ---------- comment: ---------- out: ---------- ---------------------------------------------------------- | Age | Interface | Ip | Mac | ---------------------------------------------------------- | 29.0 | em0.0 | 192.168.56.1 | 0A:00:27:00:00:23 | ---------------------------------------------------------- | 1057.0 | em0.0 | 192.168.56.2 | 08:00:27:88:C9:8E | ---------------------------------------------------------- | 1070.0 | em0.0 | 192.168.56.3 | 00:0C:29:C6:39:09 | ---------------------------------------------------------- | 1409.0 | em0.0 | 192.168.56.63 | CA:01:AD:18:00:08 | ---------------------------------------------------------- result: ---------- gns3.ios.63: ---------- comment: ---------- out: ---------- ------------------------------------------------------------------- | Age | Interface | Ip | Mac | ------------------------------------------------------------------- | 0.0 | FastEthernet0/1.200 | 172.17.13.63 | CA:01:AD:18:00:06 | ------------------------------------------------------------------- | 0.0 | FastEthernet0/1.100 | 172.17.23.63 | CA:01:AD:18:00:06 | ------------------------------------------------------------------- | 0.0 | FastEthernet0/1.400 | 172.17.34.63 | CA:01:AD:18:00:06 | ------------------------------------------------------------------- | 0.0 | FastEthernet0/1.600 | 172.17.35.63 | CA:01:AD:18:00:06 | ------------------------------------------------------------------- | 33.0 | FastEthernet0/0 | 192.168.56.1 | 0A:00:27:00:00:23 | ------------------------------------------------------------------- | 23.0 | FastEthernet0/0 | 192.168.56.2 | 08:00:27:88:C9:8E | ------------------------------------------------------------------- | 225.0 | FastEthernet0/0 | 192.168.56.3 | 00:0C:29:C6:39:09 | ------------------------------------------------------------------- | 0.0 | FastEthernet0/0 | 192.168.56.36 | 08:00:27:C6:60:F8 | ------------------------------------------------------------------- | 0.0 | FastEthernet0/0 | 192.168.56.63 | CA:01:AD:18:00:08 | ------------------------------------------------------------------- result: ----------Salt introduces the concept of grains. Essentially grains store different data collected from the devices. In below example we use the grain switch
G
to match the os
grain; matching devices with ios
operating system and then use the grains.get
function to grab the serial number of the matched device.
baggy@focalfossa:~$ sudo salt -G os:ios grains.get serial gns3.ios.63: 4279256517 baggy@focalfossa:~$Salt also allows to use regular expression when matching on devices. In below example we match devices with names containing the characters "gns" then we use salt function to get lldp neighbors also we specify the output format of the output data to be in "json"
baggy@focalfossa:~$ sudo salt gns\* napalm.call get_lldp_neighbors --out json { "gns3.ios.63": { "out": {}, "result": true, "comment": "" } }In below example we use compound matching, here we say match devices of model OLIVE that's also running version 12
baggy@focalfossa:~$ sudo salt -C 'G@model:OLIVE and G@version:12*' test.ping vbox.junos.36: True baggy@focalfossa:~$In below example we use compound matching, here we say match devices of model OLIVE that's also running version 12; or device of model 7206VX. Resulting to both our devices being matched.
baggy@focalfossa:~$ sudo salt -C 'G@model:OLIVE and G@version:12* or G@model:7206VXR' test.ping vbox.junos.36: True gns3.ios.63: True baggy@focalfossa:~$There are many other functions some are;
bgp.neighbors net.interfaces --out=json net.ipaddrs --out=json net.lldp net.mac vlan=10 net.optics net.ping 8.8.8.8 ttl=3 size=1500 count=5 net.environment net.factsSalt has many other features such as reactors and beacons that takes the automation functionality to a whole new level. For example reactors can be intergrated with syslog engines and have salt reat to certain syslog messages received from the network devices. Learn more about salt.