top of page
Writer's pictureBalasubramanian Vellore Sridhar

Automate a Multi-Vendor switch/router using Python(Netmiko)

Updated: Feb 17

There's no one-size-fits-all network vendor, so companies mix and match switches and routers from different vendors to suit their needs. This guide helps automate multi-vendor network switches/routers using the netmiko library in Python.


Pre-requisites:

  1. The switches/routers must be accessible via SSH, preferably over out-of-band.

  2. Username and Password configured for all the devices in the subnet(s).

  3. Correct spelling and spacing of commands.

  4. Installed Python ( Preferably python3 +, but it works with python2.7 as well )

  5. Installed nano or vi on the terminal or an IDE like Visual Studio with Python.

  6. Knowledge of basic python programming and Linux.

Optional: Linux/Windows VM for cronjob tasks.


Disclaimer: This program does not consider Telnet.


STEP 1: The Inventory.


Store the IPs in the first column in an excel sheet and save the file in a ".csv" format.

192.168.1.20
192.168.1.21

STEP 2: The Setup - Importing Necessary Modules.


Before writing the code, install the netmiko module in the CLI ( cmd for windows and terminal if it's Linux). Install the modules using installer pip. All the other modules are pre-installed when python3 is installed. Run the following command on the CLI ( Terminal for Linux and command prompt for Windows).

pip install netmiko

Create a python file using nano or vi. "nano" is used in this example.

nano pythonexample.py

The following modules must be imported at the beginning of the code. Ensure that the script's shebang is written on top to make this program executable with bash.

#!/usr/bin/env python3
import netmiko         # send commands to switch.
import csv             # extract IP addresses from csv file.
import time            # introduce delays in code.
import socket          # check if device is pinging.
import paramiko        # paramiko is used for exception handling.
from netmiko.ssh_autodetect import SSHDetect    # autodetects vendor.
from netmiko.ssh_dispatcher import ConnectHandler # SSH into Device.
import concurrent.futures   # Multithreading.

STEP 3: The Initiation - Data Collection and extracting IPs.


Create the main( ) function and input the credential combinations for the IP list in the CSV file. Next, create the inventory( ) function to extract IPs from the CSV file.


NOTE 1: Usernames and Passwords should ideally be encrypted.

NOTE 2: If there is only one user/password combo, create two variables, one for each, and fill the username and password fields accordingly.

# The following code is written right after import statements.
# Create the main function with a simple banner using print statements

def main():
	print("---------------------------------------------------\n")
	print(" 		This is a network automation script		   \n")
	print("---------------------------------------------------\n")
	
	# Credential list. Replace creds with yours.
	credentials = [['admin','password'],['admin','access']]
	
	# If only one cred combo, then comment above and uncomment below.
				
	# username = 'admin'		
	# password = 'password'	
	
	# inventory() function to use csv module and get IP data.
	# Copy the location of csv file and paste in the open function.
	# Example location : /Users/Documents/pythonexample.py	
	
	def inventory():
		try:
			with open('paste the location here') as csvfile:
				csvreader = csv.reader(csvfile, delimiter = ',')
				ipaddresses = []				# Create empty list.

				for row in csvreader:
					ipaddress = row[0]			
					ipaddresses.append (ipaddress) # Add the IPs.

		except Exception as e:
			print(e)			# Print exception for troubleshooting.
		
		return ipaddresses # Will reference argument in automation().

STEP 4: The Connection - Logging into the devices.


Create the automation( ) function and establish connectivity with the network devices using the in-built functions of netmiko and socket. Use the ipaddresses value from inventory( ) as the argument. In this example, the argument is address. The netmiko statements are from the netmiko docs ( link attached ) https://pyneng.readthedocs.io/en/latest/book/18_ssh_telnet/netmiko.html#


NOTE: If there are no multiple user/password combos as written above, then in the following code, remove the for loop, replace the element[0] and element[] with variables username and password, and remove all the "break" statements and then indent the try block to the left ( Select the whole try block and use Ctrl + [ to indent left ).


# The following code is written after inventory() and within main()
# Create automation(address), ping and automate configs using netmiko
# Indent to ensure function stays within main()

    def automation(address):  # address is ipaddresses from inventory()
        print("Attempting to connect to - " + str(address))
        fileinfo = (address + '_config.txt') #Backup file name to store
        # Initiate connection using socket module
        sock =  socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # Crucial to set socket timeout to reduce wait times
        sock.settimeout(2)
        # socket attempts to ping to network device in csv list
        # 22 in the argument for connect_ex is port 22 for SSH
        result = sock.connect_ex((address,22))
        if result == 0:                #IP pings and true connection
            print("Ping successful for " + str(address))
            print("Looping through the creds")   
            #looping through the credential list created initially
            for element in credentials:  
               try:
                  netmikologin = 
                  {
                  'device_type':'auto_detect',  # autodetect vendor
                  'host': address,              # IP address from csv
                  'username': element[0],       # username
                  'password': element[1],       # password
                  'conn_timeout': 20,           # timeout 
                  'global_delay_factor': 4      # 8 second timeout
                  }
                  print("Credential worked for : " + address )
                  # Using in-built netmiko ConnectHandler
                  connection = ConnectHandler(**netmikologin)  
                  time.sleep(0.5)                # small delay   
                  print(" Detecting the vendor of device : " + address)
                  # Using in-built netmiko SSHDetect and autodetect() 
                  detect = SSHDetect(**netmikologin) 
                  vendor = detect.autodetect
                  time.sleep(1)                  # small delay

                
                        

STEP 5: The logic - Configuring commands.


Use the if/elif/else conditional statements to apply the appropriate commands based on what vendor has been detected. Each vendor has a value in the netmiko docs. For cisco ios, it's cisco_ios; for Dell OS10, it's dell_os10. The complete list of keywords is given in


Using the if/elif/else conditions, use the vendor variable derived from the previous SSHDetect and autodetect parameters, and there can be as many vendors as needed.


connection.send_command_timing is split into two. First, connection signifies the ConnectHandler function, and send_command_timing is the function of netmiko where the command waits for network devices to print the result. Waiting depends on the global delay factor in the netmikologin details.


Enter the command needed to be sent to the network devices and introduce the time.sleep delays if needed.


NOTE: The command should have the whole spelling and appropriate spaces.

# The following code is continued under the try block ( after time.sleep )
# Use indent to put the following under try block.
                  if vendor ==  "dell_os10":
                     print(address + " is a DellOS10 device")
                     print(connection.send_command_timing('command\n'))
                     print(connection.send_command_timing('command\n'))
                     print(connection.send_command_timing('command\n'))                      
                     print("-----------------------------------------")
                     connection.disconnect()
                     break
                  elif vendor == "cisco_ios":
                     print(address + " is a CiscoIOS device")
                     print(connection.send_command_timing('command\n'))
                     print(connection.send_command_timing('command\n'))
                     print(connection.send_command_timing('command\n'))
                     print("-----------------------------------------")
                     connection.disconnect()
                     break
                  elif vendor == "juniper_junos":
                     # same lines as other vendors
                  elif vendor == "arista_eos":
                     # same lines as other vendors
                  elif vendor == "extreme_nos":
                     # same lines as other vendors
                  else:
                     print("The vendor is not available in netmiko\n")
                     break
            # ensure that try/except is under for elements loop    
            except (netmiko.ssh_exception.NetmikoTimeoutException):
                print("Timed out. Moving to next")
                break       
            except (netmiko.ssh_exception.NetmikoAuthenticationException):
                print("Credentials are incorrect. Trying others")
                break
        # This else is for the first if result == 0 block
        else:
            # socket fails to ping
            print(address + " is not reachable. Skipping IP")
            

STEP 6: Multithreading - Efficient programming.


For the final step, introduce multithreading which enables the code to access multiple devices using multiple threads. This is achieved through the CPU cores. Python has the concurrent.futures module to thank for this.


Use the with statement to introduce multithreading to both the inventory( ) and automation( ). This statement should be under the main ( ) function, so indent once.



# The with block is under main() so indent once.
# concurrent.futures.ThreadPoolExecutor is built-in multithreading.
    with concurrent.futures.ThreadPoolExecutor( ) as multithread:
        ipinfo = inventory() # ipinfo extracts IP list in inventory.
        multithread.map(automation,ipinfo) # call info in automation.

# The following is a failsafe global variable called __name__.
# Do not indent the if statement. It should be outside main()

if __name__ == '__main__':
    main()

The program is complete. Save the file using Ctrl + X and answer "yes" or "Y" to the save question [ If using nano ] ((or)) save the file using <esc>:wq! [ If using vi ] and run the code using "python3 pythonexample.py"


Sample Output.


Consider there are two switches. One is Dell OS10, the second is CiscoIOS. The IPs are 192.168.1.20, and 192.168.1.21. The user/pass combo is admin/password.


The command of choice is show version



---------------------------------------------------
        This is a network automation script		   
---------------------------------------------------
Attempting to connect to 192.168.1.20
Attempting to connect to 192.168.1.21
Ping successful for 192.168.1.20
Ping successful for 192.168.1.21
Looping through creds
Looping through creds
Credential worked for 192.168.1.20
Credential worked for 192.168.1.21
Detecting the vendor of device : 192.168.1.20
Detecting the vendor of device : 192.168.1.21
192.168.1.20 is a DellOS10 device
192.168.1.20 is a CiscoIOS device
OS10# show version
Dell EMC Networking OS10 Enterprise
Copyright (c) 1999-2018 by Dell Inc. All Rights Reserved.
OS Version: 10.4.0E(X2)
Build Version: 10.4.0E(X2.22)
Build Time: 2018-01-26T17:46:11-0800
System Type: S4148F-ON
Architecture: x86_64
Up Time: 02:50:18
-----------------------------------------
switch>show version
Cisco Internetwork Operating System Software 
IOS (tm) C3560 Software (C3560-IPSERVICES-M), Version 12.2(25)SEB, RELEASE SOFTWARE (fc1)
Copyright (c) 1986-2005 by cisco Systems, Inc.
Compiled Tues 15-Feb-05 21:54 by yenanh
Image text-base: 0x00003000, data-base: 0x009197B8

ROM: Bootstrap program is C3560 boot loader
BOOTLDR: C3560 Boot Loader (C3560-HBOOT-M), Version 12.1 [rneal-vegas-0806 101]

tree uptime is 1 minute
System returned to ROM by power-on
System image file is "flash:c3560-i5-mz"
cisco WS-C3560-24PS (PowerPC405) processor (revision 01) with 118776K/12288K bytes of 
memory.
Processor board ID CSJ0737U00J
Last reset from power-on
Bridging software.
1 Virtual Ethernet/IEEE 802.3  interface(s)
24 FastEthernet/IEEE 802.3 interface(s)
2 Gigabit Ethernet/IEEE 802.3 interface(s)
The password-recovery mechanism is enabled.

512K bytes of flash-simulated non-volatile configuration memory.
Base ethernet MAC Address       : 00:0B:46:30:6B:80
Motherboard assembly number     : 73-9299-01
Power supply part number        : 341-0029-02
Motherboard serial number       : CSJ0736990B
Power supply serial number      : LIT0717000Y
Model revision number           : 01
Motherboard revision number     : 03
Model number                    : WS-C3560-24PS-S
System serial number            : CSJ0737U00J
Top Assembly Part Number        : 800-24791-01
Top Assembly Revision Number    : 02

Switch   Ports  Model              SW Version              SW Image            
------   -----  -----              ----------              ----------          
*    1   26     WS-C3560-24PS      12.2(25)SEB             C3560-IPSERVICES-M    
Configuration register is 0xF
-----------------------------------------

9.2

[Optional] Troubleshooting.


If the switch output is not registering correctly, introduce time.sleep(x) delays within the if/elif/else block of the vendor or increase the Global Delay Factor in the netmikologin block.

[Optional] Improvements.

  1. Ansible is the more compact and robust version of the code. Easier to understand.

  2. Proper Queueing and thread lock will make the output neater.


References.
































396 views0 comments

Recent Posts

See All

Comments


bottom of page