Contents

  • Python
  • Ansible
  • # Guide To Network Automation

    Network automation refers to the automation of network management tasks, including provisioning, configuration, deployment, and security of both physical and virtual network devices. The goal of network automation is maximizing network efficiency and while increasing reliability. Below are some methods used for network automation.

    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
    These libraries allows for initialization of remote sessions to devices via SSH or telnet and sending of commands to the devices as you would via an ssh or telnet client such as putty or secureCRT.
    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 interpreter
    basondole@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.
    
    1. Import the telnetlib in python interpreter
    2. In [1]: import telnetlib
          
    3. Create a function that will be used to establish connection
    4. 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
          
    5. Create a function that will be used to send commands to the device
    6. In [3]: def send_command (session,command):
                session.write(command.encode())
                session.write(b'\n')
                output = session.read_until(b'#')
                print (output.decode())
        
    7. Define login parameters
    8. In [4]: username = 'fisi'
              password = 'fisi123'
              host = '192.168.56.26'
        
    9. Connect to the device
    10. In [5]: open_session = telnet_connection(host,username,password)
        
    11. Send sample command to the device
    12. 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# 
        
    13. Send another sample command to the device
    14. 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# 
      
    15. End the session and neatly exist.
    16. In [8]: open_session.close() 
      
    Complete script
    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:
    1. ssh is enabled on the remote device
    2. paramiko library is installed on the local working station
    Paramiko demo
    1. Import the paramiko module
      In [1]: import paramiko
        
    2. Create a paramiko ssh client object
      In [2]: session = paramiko.SSHClient()
        
    3. Specify the policy for missing host keys from the ssh server
      In [3]: session.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
    4. Define login parameters
      In [4]: username = 'fisi'
              password = 'fisi123'
              host = '192.168.56.26'
        
    5. Connect to the device
      In [5]: session.connect(host,username=username,password=password)
        
    6. Invoke the device shell to interact with
      In [6]: shell = session.invoke_shell()
        
    7. 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
        
    8. 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#
        
    9. Send another sample command
      In [9]: shell.send('show ver | i up\n')
      Out[9]: 16
        
    10. 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#
        
    11. Close the session gracefully
      In [11] session.close()
        
    Complete script
    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

    Sample genie testbed
    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: ssh
    
    Important: 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
    1. Import the genie module
      In [1]: from genie.conf import Genie
        
    2. Load the testbed file
      In [2]: testbed = Genie.init('testbed.yaml')
        
    3. 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>})
        
    4. Create an object for the device
      In [4]: router = testbed.devices['KH16']
        
    5. 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}
        
    6. Verify the connection
      In [6]: router.connected
      Out[6]: True
        
    7. 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}
        
    8. Import the csv module for creating a csv file
      In [8]: import csv
        
    9. Define the field of the csv file
      In [9]: fields = ['interfaces','MAC']
        
    10. 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
        
    11. Close the session gracefully
      In [11]: router.disconnect()
        
    12. Exit and verify the contents of the file created on step 10 above
      In [12]: exit
        
    We can verify the new file is created as interfaces.csv and we can verify the contents
    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. 12ff
    
    Complete 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 here
    Pyntc
    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 here
    Summary
    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
    Learn more about YANG here

    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 command netconf-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 ipython
    basondole@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.
    
    1. 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
        
    2. Define the router and its login parameters
      In [2]: router = {
                'address': '192.168.56.26',
                'netconf_port': 830,
                'username': 'fisi',
                'password': 'fisi123} 
        
    3. 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)
        
    4. Verify the device is connected
      In [4]: router_manager.connected 
      Out[4]: True 
        
    5. 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> 
      '''
        
    6. 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) 
        
    7. Verify the operation was successful
      In [7]: router_GigEth3_config_xml.ok 
      Out[7] True 
        
    8. 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 
        
    9. 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)
        
    10. 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 the data section which is a leaf of the rpc-reply
      In [10]: router_GigEth3_config_data = router_GigEth3_config_dict['rpc-reply']['data'] 
        
    11. 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 is ietf containing #text data which is the name of the interface
      In [11]: router_GigEth3_config_data['interfaces']['interface']['name'] 
      Out[11]: 
      OrderedDict([('@xmlns:nc', 'urn:ietf:params:xml:ns:netconf:base:1.01),
                   ('#text', 'GigabitEthernet3']) 
        
    12. Get the ipv4 address configured on the interface, we follow same paradigm as above except we want the key ipv4 instead of name
      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')]))])
        
    13. 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> 
      '''
        
    14. 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) 
        
    15. Verify the operation was successful
      In [15]: send_loopback_0_config.ok 
      Out[15]: True 
        
    Complete script
    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 ipython
    basondole@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. 
    
    1. 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
        
    2. 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 
        
    3. Define the router and its login parameters
      In [3]: junos = {'address':'192.168.56.36'
                       'netconf_port':830,
                       'username':'fisi',
                       'password':'fisi123' }
        
    4. 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) 
        
    5. 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'] 
        
    6. 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 
        
    7. 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>
        
    Complete script
    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 device
    Junos PyEZ API demo
    In this demo we use PyEZ API to send commands to a junos device as well as equivalent CLI command
    1. Import the Device module from junos library
      In [1]: from jnpr.junos import Device
              from lxml import etree 
        
    2. Define login parameters
      In [2]: username = 'fisi'
              password = 'fisi123'
              host = '192.168.56.36'
        
    3. 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)
        
    4. 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'})
        
    5. 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()
        
    6. 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>
        
    7. 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 debugging
      In [7]: bgp_info_cli = router.cli('show bgp summary', warning=False) 
        
    8. 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
        
    Complete script
    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 interpreter
    Procedures:
    1. Import the Device module from junos library
      In [1]: from jnpr.junos import Device
        
    2. Import the xml module for converting xml data
      In [2]: from lxml import etree 
        
    3. Import the os module that will be used to access the environment variables for username and password
      In [3]: import os 
        
    4. Create a device object for the router
      In [4]: router = Device(host='192.168.56.36',user=os.getenv('JUSER'),passwd=os.getenv('JPASS')) 
        
    5. Open a session with the JunOS device
      In [5]: router.open()
      Out[5]. Device(192.168.56.36) 
        
    6. Get the rpc command equivalent for the cli command show route
      In [6]: rpc_command = router.display_xml_rpc('show route',format='text') 
        
    7. Display the rpc command which is get-route-information
      In [7]: print(rpc_command) 
      <get-route-information>
      </get-route-information> 
        
    8. 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') 
        
    9. Display the rpc command which is get-system-alarm-information
      In [9]: print(rpc_command) 
      <get-system-alarm-information> 
      </get-system-alarm-information> 
        
    10. 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') 
        
    11. Print the results from the device
      In [11]: print((etree.tostring(alarms)).decode()) 
      <output> 
      No alarms currently active 
      </output> 
        
    12. Close the connection
      In [12]: router.close() 
        
    Complete script
    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 operations
    NAPALM with Cisco ios
    To enable NAPALM interaction with IOS devices configure ip 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.
    1. Import napalm driver
      In [1]: from napalm import get_network_driver 
        
    2. Define login parameters
      In [2]: username = 'fisi'
              password = 'fisi123'
              iosxe_host = '192.168.56.26'
        
    3. Create a napalm driver for the ios. By the time of this writing NAPALM did not have a dedicated iosxe driver
      In [3]: ios_driver = get_network_driver('ios') 
        
    4. 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() 
        
    5. 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']}
        
    6. Create configuration for loopback1 interface following the the regular CLI syntax
      In [6]: iosxe_loopback1_cfg = '''
      int loopback 1 
      ip add 10.2.2.2 255.255.255.255 
      desc CONFIGURED BY NAPALM 
      '''
        
    7. Load the configuration to the device (without saving the config)
      In [7]: iosxe_dev.load_merge_candidate(config=iosxe_loopback1_cfg) 
        
    8. 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 IOS
      In [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 
        
    9. Commit the configuration on the device
      In [9]: iosxe_dev.commit_config()
        
    10. 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 9
      In [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']}
        
    11. Issue a rollback operation to delete interface loopback1
      In [11]: iosxe_dev.rollback()
        
    12. Close the session
      In [12]: iosxe_dev.close()
        
    Complete script
    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
  • First we see there was no interface loopback1
  • Then log messages show there was a config change by user fisi via napalm and the interface loopback1 comes up
  • We can now see the configuration on interface loopback1 that was sent by napalm in the running config
  • Then we see the rollback operation taking place as we issued the rollback command on NAPALM
  • When we redo a 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
    1. Import napalm driver
      In [1]: from napalm import get_network_driver 
        
    2. Define login parameters
      In [2]: username = 'fisi' 
              password = 'fisi123'
              junos_host = '192.168.56.36'
        
    3. 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() 
        
    4. 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']}
        
    5. 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";
      }
      }
      } 
      '''
        
    6. 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; 
      +  } 
      
        
    7. Commit the config
      In [7]: junos_dev.commit_config() 
        
    8. Use the CLI method to get configuration of interface em2.555
      In [8]: em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555'])
        
    9. 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'}
        
    10. Rollback the config
      In [10] junos dev.rollback() 
        
    11. Use the CLI method to get configuration of interface em2.555
      In [11] em2_555_cf = junos_dev.cli(['show configuration interfaces em2.555']) 
        
    12. 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': ''} 
        
    13. Close the session.
      In [13]: junos_dev.close()
        
    Complete script
    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 files
  • Hosts.yaml
  • Groups.yaml (optional)

    See 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
    1. 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 
      
        
    2. Create a nornir object
      In [2] op = InitNornir() 
        
    3. 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')
        
    4. 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 device
      
      In [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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        
    5. 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') 
        
    6. 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
        
    7. Import a netmiko send config function for sending config to the device
      In [7]: from nornir.plugins.tasks.networking import netmiko_send_config 
        
    8. Filter the hosts to only hosts running cisco_ios
      In [8]: ios_host = op.filter(platform='cisco_ios') 
        
    9. Create config for interface loopback10
      In [9]: loopback_10 = '''interface lo10
      description CONPTGURED BY NORNIR'''
        
    10. Send config to the device
      In [10]: result = ios_host.run(task = netmiko_send_config, config_commands=loopback_10)
        
    11. 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
        
    12. Close the session.
      In [12]: op.close_connections() 
        
      Complete script
      from 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 the backup folder in the playbook directory or role root directory, if playbook is part of an ansible role
      pbasondole 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 the hosts file and junos_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 the tree command there is a new folder with the backup config for the router with ip 192.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 here
      Playbook 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 the configuring 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 below
      pbasondole $ 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 command pyats 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. The routing_iosxe_KH16_console.txt contains all the commands sent to the device cli and their respective outputs and finally routing_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 files
      pbasondole: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 devices
    basondole@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 the show 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 device
    basondole@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: 
    
    TASK [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
    
    After a playbook completes you get a recap of the tasks done

    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.
      ERROR
      
    To 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 devices
    basondole@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/proxy
    
    Verify
    baggy@focalfossa:~$ sudo cat /etc/salt/proxy | egrep "^[a-z]"
    master: localhost
    pki_dir: /etc/salt/pki/proxy
    multiprocessing: False
    
    Edit 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: localhost
    
    Create 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-reload
    
    We 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.facts
    
    Salt 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.
    IT & Network Expert / Lead
    Network & Automation designer;
    2x JNCIP (RS/SP) 1x CCNP (RS)
    BSc Telecommunications Eng.

    Sliding Sidebar