Introduction

Within this article, we will look at two tools used in the world of network automation - Netmiko and TextFSM.

Our examples will be based on a small topology consisting of 3 devices an Arista, a Cisco NXOS9K and a Cisco IOS router. Below shows the topology,

netmiko2
Figure 1: Topology

Netmiko

netmiko is a multi-vendor SSH Python library that simplifies the process of connecting to network devices via SSH. This library adds vendor specific logic to paramiko, which is the de-facto SSH library in Python.[1]

Installation

Netmiko can be installed via the Python package manager - pip. Like so,

pip3 install netmiko

Connect

To connect to a device we use the ConnectHandler which we pass our connection details to. Like so,

from netmiko import ConnectHandler

arista = {
   'device_type': 'arista_eos', 
   'host': '172.29.133.1', 
   'username': 'rick', 
   'password': 'abc123',  
}

cisco = {
   'device_type': 'cisco_ios', 
   'host': '172.29.133.2', 
   'username': 'rick', 
   'password': 'abc123',  
}

nxos = {
   'device_type': 'cisco_nxos', 
   'host': '172.29.133.3', 
   'username': 'rick', 
   'password': 'abc123',  
}

net_connect_arista = ConnectHandler(**arista)
net_connect_cisco = ConnectHandler(**cisco)
net_connect_nxos = ConnectHandler(**nxos)

Display Prompt

Now that we have a connection built to our devices we can start performing some tasks. A small task we can start with is displaying the prompt for each device. Like so,

>>> for device in [net_connect_arista, net_connect_cisco, net_connect_nxos]:
...   device.find_prompt()
...
'veos>'
'viosl3#'
'nxos#'

Send Command

Let’s send a command to each of the devices. As each device is running OSPF, let's check the OSPF neighbor table.

>>> for device in [net_connect_arista, net_connect_cisco, net_connect_nxos]:
...     output = device.send_command('show ip ospf neighbor')
...     print("----{}----".format(device))
...     print(output)
...     print("\n")
...
----<netmiko.arista.arista.AristaSSH object at 0x7f9713239278>----
Neighbor ID     VRF      Pri State                  Dead Time   Address         Interface
2.2.2.2         default  1   FULL/DR                00:00:35    10.1.1.2        Ethernet3


----<netmiko.cisco.cisco_ios.CiscoIosSSH object at 0x7f9713233978>----

Neighbor ID     Pri   State           Dead Time   Address         Interface
3.3.3.3           1   FULL/BDR        00:00:36    10.2.1.1        GigabitEthernet0/2
1.1.1.1           1   FULL/BDR        00:00:37    10.1.1.1        GigabitEthernet0/1


----<netmiko.cisco.cisco_nxos_ssh.CiscoNxosSSH object at 0x7f97132334a8>----
 OSPF Process ID 1 VRF default
 Total number of neighbors: 1
 Neighbor ID     Pri State            Up Time  Address         Interface
 2.2.2.2           1 FULL/DR          04:25:36 10.2.1.2        Eth1/3

Looking good!

Update Config

Let’s say we want to edit/push some configuration. This is achieved via the send_config_set method. This method takes a list() of commands, that will be pushed to the device.

# pre config
>>> print(net_connect_cisco.send_command('show run | sec ospf'))
router ospf 1
 router-id 2.2.2.2
 redistribute connected subnets
 network 10.1.1.0 0.0.0.255 area 0
 network 10.2.1.0 0.0.0.255 area 0

# update config
>>> output = net_connect_cisco.send_config_set(['router ospf 1', 'no redistribute connected subnets'])
>>> print(output)
config term
Enter configuration commands, one per line.  End with CNTL/Z.
viosl3(config)#router ospf 1
viosl3(config-router)#no redistribute connected subnets
viosl3(config-router)#end
viosl3#

# post config
>>> print(net_connect_cisco.send_command('show run | sec ospf'))
router ospf 1
 router-id 2.2.2.2
 network 10.1.1.0 0.0.0.255 area 0
 network 10.2.1.0 0.0.0.255 area 0

TextFSM

TextFSM is a Python module that allows you to take unstructured data and convert it to structured data via sets of regex-based templates.

The great thing with Netmiko is that it provides direct integration with TextFSM, which we will shortly show.

TextFSM Templates

To get us up and running we will pull some pre-created templates from https://github.com/networktocode/ntc-templates.

root@dekstop:~# git clone https://github.com/networktocode/ntc-templates
Cloning into 'ntc-templates'...
remote: Enumerating objects: 14, done.
remote: Counting objects: 100% (14/14), done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 4990 (delta 2), reused 6 (delta 1), pack-reused 4976
Receiving objects: 100% (4990/4990), 1.19 MiB | 2.29 MiB/s, done.
Resolving deltas: 100% (2749/2749), done.
Checking connectivity... done.

Applying TextFSM to Netmiko

To use TextFSM and the templates previously downloaded we simply use the send_command as before, whilst also setting the use_textfsm attribute to True like so,

>>> from netmiko import ConnectHandler
>>> arista = {
...    'device_type': 'arista_eos',
...    'host': '172.29.133.1',
...    'username': 'rick',
...    'password': 'abc123',
... }

>>> net_connect_arista = ConnectHandler(**arista)

>>> output = net_connect_arista.send_command('sh ver', use_textfsm=True)

If we now look at our output we will see that the output is now structured.

>>> import pprint
>>> pprint.pprint(output)
[{'free_memory': '121936',
  'hw_version': '',
  'image': '4.15.10M',
  'model': 'vEOS',
  'serial_number': '',
  'sys_mac': '5000.00d7.ee0b',
  'total_memory': '1897596'}]

>>> output[0]['model']
'vEOS'

The Index File

The example is all well and good, but how does Netmiko know which template to use?

This is achieved via an index file, which contains a mapping of commands to templates (snippet shown below). By default Netmiko has been configured to automatically look in ~/ntc-template/templates/index for an index file. However, this can be changed to a different path by using the environment variable NET_TEXTFSM.

    ...
    arista_eos_show_ip_route.template, .*, arista_eos, sh[[ow]] i[[p]] rou[[te]]
    arista_eos_show_version.template, .*, arista_eos, sh[[ow]] ver[[sion]]
    arista_eos_show_ip_arp.template, .*, arista_eos, sh[[ow]] i[[p]] ar[[p]]
    arista_eos_show_ip_bgp.template, .*, arista_eos, sh[[ow]] i[[p]] bg[[p]] 
    arista_eos_show_module.template, .*, arista_eos, sh[[ow]] modu[[le]]
    ...

Building Custom Templates

To build your own template 2 steps are required - template creation and index update. To demonstrate these steps we will create a template for the EOS command show uptime.

Template Creation

First of all, we create a new template file. This must be named based on the following schema: {{ vendor_name }}_{{show_command}}.template. For example, in our case this will be arista_eos_show_uptime.template. This file will be placed within ntc-templates/templates.

Below shows you the content of the template. The various options around writing TextFSM template is way outside of the scope of this article. However, in a nutshell, we split our text down via regex shown at the bottom, using Values to define the text that we will extract.

Value TIME (\S+)
Value UPTIME (\S+)
Value USERS (\d+)
Value LOAD_AVERAGE (\S+)

Start
  ^\s${TIME}\s+up\s+${UPTIME}.*${USERS}\s+.*load average.\s+${LOAD_AVERAGE} -> Record

Index Update

The index file is then updated to map our template to the command (shown below). Note: This will also allow for abbreviations of the command such as sh uptime or sh up.

...
arista_eos_show_uptime.template, .*, arista_eos, sh[[ow]] up[[time]]

Test

A quick test shows us the command output in its raw state, and structured via the use of our newly created template.

>>> net_connect_arista.send_command('show uptime')
' 19:54:17 up 23 min,  1 user,  load average: 0.15, 0.15, 0.21'

>>> net_connect_arista.send_command('show uptime', use_textfsm=True)
[{'load_average': '0.15,', 'time': '19:54:20', 'users': '1', 'uptime': '23'}]

References


  1. "Using Python Context Managers for SSH connections - Packet Pushers." 1 Mar. 2015, https://packetpushers.net/using-python-context-managers/. Accessed 17 May. 2019. ↩︎