Tinkering with Python and Netmiko on basic stuff

In day-to-day networking and telecommunications work, we often face repetitive tasks — like checking the IOS version across a list of devices in our network, verifying if an NBAR protocol-pack was already uploaded, configuring a device, or upgrading the IOS on multiple devices at once.

There is a lot of documentation and examples on the internet about the Netmiko library. For this reason, I’ll share two scripts that I use in my day-to-day work to simplify some tasks.

The first script could be used as a configurator or to obtain certain outputs of show commands. Using a text file with pre-written commands can make it easier on you by allowing you to have a generic template script always ready to go. All you have to do is edit the text file and then execute.

But, if you need to execute the same commands on a set of equipments, it is possible to modify the script and read a text file that contain the list of equipments that will receive the configuration.

Script 1:

from netmiko import ConnectHandler
from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException
import time, sys, paramiko
import optparse

parser = optparse.OptionParser()

parser.add_option('-i', '--ip',
    dest="IPAddress",
    help="IP Addresses")

parser.add_option('-u', '--username',
    dest="Username",
    help="Username")

parser.add_option('-p', '--password',
    dest="Password",
    help="Password")

parser.add_option('-c', '--config',
    dest="Config",
    help="Configuration File")

options, args = parser.parse_args()

device = {'device_type': 'cisco_ios',     
		  'ip': '1.1.1.1',
		  'username': 'username',
		  'password': 'password',
		  'secret': 'password'
		}

device['username']=options.Username
device['password']=options.Password
device['secret']=options.Password
device['ip']=options.IPAddress

print("Connecting Device... ",options.IPAddress)
try:
    net_connect = ConnectHandler(**device)
    net_connect.enable()
    time.sleep(2)
    print ("Passing command set ")
    output = net_connect.send_config_from_file(options.Config, delay_factor=10)
    print(output)
    print ("#" * 60)
except (NetMikoTimeoutException, NetMikoAuthenticationException) as e:
    print("could not connect due to {}".format(e))
    print ("#" * 60)

Output:

~]$ cat verify-ios-nbar-pack.txt
!
do show version | i 16.
!
do dir flash: | i .pack
!

~]$ python script1.py -i 10.5.100.1 -u cisco -p c15c0 -c verify-ios-nbar-pack.txt

Connecting Device... 10.5.100.1
Passing command set
config term
Enter configuration commands, one per line.  End with CNTL/Z.
rt-cr-1000(config)#!
rt-cr-1000(config)#do show version | i 16.
Cisco IOS XE Software, Version 16.09.04
Cisco IOS Software [Fuji], ISR Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.4, RELEASE SOFTWARE (fc2)
System image file is "bootflash:isr4300-universalk9.16.09.04.SPA.bin"
rt-cr-1000(config)#!
rt-cr-1000(config)#do dir flash: | i .pack
   22  -rw-          3787863   Oct 8 2019 15:36:42 -03:00  pp-adv-isr4000-169.1-34-44.0.0.pack
rt-cr-1000(config)#!

In an enterprise environment, you can use Cisco Prime to prepare a firmware upgrade for a set of devices, but in my experience, it only supports a limited number of concurrent devices.

So, the second script I want to share is used to prepare ISR4321 routers for a firmware upgrade using multi-threading, connecting to all routers simultaneously. I have tested it with up to 40 routers at the same time.

Script 2:

import threading
from Queue import Queue
from netmiko import ConnectHandler
from netmiko.ssh_exception import NetMikoTimeoutException, NetMikoAuthenticationException
from datetime import datetime
from prettytable import PrettyTable
import time, sys, paramiko
import optparse

parser = optparse.OptionParser()

parser.add_option('-i', '--ip',
    dest="IPFile",
    help="IP Addresses File")

parser.add_option('-u', '--username',
    dest="Username",
    help="Username")

parser.add_option('-p', '--password',
    dest="Password",
    help="Password")

options, args = parser.parse_args()

def ssh_session(_r, _q):
    no_space = "Good"
    output_dict = {}
    hostname = _r
    ios_verified = "N/A"
    already_downloaded = "No"
    device = {
        'device_type': 'cisco_ios',
        'ip': hostname,
        'username': options.Username,
        'password': options.Password,
        'secret': options.Password,
        'verbose': False
    }

    net_connect = ConnectHandler(**device)
    net_connect.enable()
	# Verify the platform
    output = net_connect.send_command_timing('show inv', delay_factor=4, max_loops=1000)
    output_list = []
    output_list.append(output)
    model = "None"

    if "ISR4321" in output:
        model = "4321"

    # Verify the current IOS version
	output = net_connect.send_command_timing('show ver | i 16.', delay_factor=4, max_loops=1000)
    output_list.append(output)
    already_upgraded = "No"

    if model == "4321":
        if "16.09.04" in output:
            already_upgraded = "Yes"

    ftp_success = "Failed"

    if already_upgraded == "Yes":
        ftp_success = "N/A"

    if already_upgraded == "No":

        if model == "4321":
            # Verify if the current if the IOS 16.9.4 was already downloaded by an end-user
            output = net_connect.send_command_timing('dir flash: | i 585126403', delay_factor=4, max_loops=1000)
            output_list.append(output)
            if "isr4300-universalk9.16.09.04.SPA.bin" in output:
                already_downloaded = "Yes"
                ftp_success = "N/A"

            if already_downloaded == "No":
                # Download the IOS from the FTP  server
                output = net_connect.send_command_timing('copy ftp://192.168.254.1/isr4300-universalk9.16.09.04.SPA.bin flash:', delay_factor=30, max_loops=1000)
                output_list.append(output)
				# If the CLI ask us about the destination file, will be used the new IOS filename.
                if 'Destination filename' in output:
                    output += net_connect.send_command_timing('isr4300-universalk9.16.09.04.SPA.bin', delay_factor=30, max_loops=1000)
                    output_list.append(output)
                if "[OK " in output:
                    ftp_success = "Success"
					# If the IOS was downloaded with success, verify the MD5 checksum
                    output = net_connect.send_command_timing('verify /md5 bootflash:isr4300-universalk9.16.09.04.SPA.bin', delay_factor=30, max_loops=1000)
                    output_list.append(output)
                    if "cf977970ace2973ed8a45ab0610b7bf7" in output:
                        ios_verified = "Success"
                    else:
                        ios_verified = "Failed"
                if "No space" in output:
                    outfile.write(hostname + ': No space left on device\n')
                    no_space = "Bad"


    t.add_row([hostname, str(model), already_upgraded, already_downloaded, ftp_success, ios_verified, no_space])
    output_list.append(t)
    print t

    final_output = ''
    for item in output_list:
        final_output += '\n' + str(item)

    output_dict['###########' + hostname + '###########'] = final_output
    _q.put(output_dict)
    print output_dict['###########' + hostname + '###########']


if __name__ == "__main__":

    _q = Queue()
    t = PrettyTable(
        ['Hostname', 'Model', 'Already upgraded', 'Already Downloaded', 'FTP', 'IOS verified', 'Enough space'])

    with open(options.IPFile) as f:
        _ddump = f.readlines()

    _dlist = [x.strip() for x in _ddump]

    for _r in _dlist:
        print _r
        _thread = threading.Thread(target=ssh_session, args=(_r, _q))
        _thread.start()

    _mthread = threading.currentThread()
    for _sthread in threading.enumerate():
        if _sthread != _mthread:
            _sthread.join()

    while not _q.empty():
        _mdict = _q.get()
        for i, j in _mdict.iteritems():
            print i
            print j

Output:

~]$ cat branch.txt
10.5.100.1
10.5.101.1
10.5.102.1
10.5.103.1

~]$ python script2.py -i branch.txt -u cisco -p c15c0

+-------------+-------+------------------+--------------------+---------+--------------+--------------+
|   Hostname  | Model | Already upgraded | Already Downloaded |   FTP   | IOS verified | Enough space |
+-------------+-------+------------------+--------------------+---------+--------------+--------------+
|  10.5.100.1 |  4321 |        No        |         No         | Success |   Success    |     Good     |
|  10.5.101.1 |  4321 |        No        |         No         | Success |   Success    |     Good     |
|  10.5.102.1 |  4321 |        No        |         No         | Success |   Success    |     Good     |
|  10.5.103.1 |  4321 |        No        |         No         | Success |   Success    |     Good     |
+-------------+-------+------------------+--------------------+---------+--------------+--------------+

This is a simple example of how you can use Python to automate network tasks. There are many other libraries that you can use together with Netmiko to automate almost anything at even faster speeds.

References

  1. Netmiko Documentation
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora