Tinkering with Python and Netmiko on basic stuff

Day to day in all that involves networking/telecommunications processes we always face with tasks that becomes repetitive like from a list of equipments in our network we want to verify the IOS version, verify if the NBAR protocol-pack was already uploaded, configure an equipment in the simple way, or upgrade the all IOS in a massive way.

There are a lot of documentation and examples on the internet related with the lib Netmiko, by this reason, I’ll share two of them that I use on my day-to-day just to try and 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)#!

On an enterprise environment you can use Cisco Prime to prepare a firmware upgrade for a set of equipments, but with my experience it only support a little number of concurrent equipments.

So, the second script that I want to share is related to prepare ISR4321 for a firmware upgrade using multi-threaded, so it connects to all the 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 just a simple example of how you can use Python to automate different network processes. There are many others libs out there that you can use in conjunction with Netmiko that can allow you to do just about anything you can think of 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