Shutdown thousands access ports with Python and Netmiko

Shutdown thousands of network access ports with Python and Netmiko

When people ask me “what a network engineer should do to start in network automation?”, my first answer is: start with small things. Try to automate basic and repetitive tasks you do every day. Go after the low-hanging fruits first. Then, step by step, you can do more and more complex things. This is how I learn the best.
In the same vein, I want to share with you my recent experience of automating a very basic and repetitive task: shutdown thousands of unused network access ports.

The environment

This has been done in a large data center, who recently made a huge migration of compute nodes. During the migration, it can happen that some sys-admins disconnect old machines without going through the usual procedures of change-management or similar. The result is, after a couple of weeks, that many network ports are in operational state “down” and administrative state “up”. And I don’t like this! Because this could be a risk: someone can connect something on that port. Even you use 802.x, this may create confusion, problems, support tickets, etc… In short: wasted time! So, what is the solution? Admin-shutdown every unused port!

 

The goal

The goal is to shutdown all “oper-down” and “admin-up” ports of the entire Data-Center access layer, composed of Cisco Nexus 5k and 9k and many Nexus 2K / FEX behind them. And to avoid problems, I choose to do this only on ports in this state for more than 60 days.

 

The tools

To make the network engineers life easier in this D-C, there is already a lot of open-source tools that make assessment and backups of the network devices. Among other things, there is Switchmap. Switchmap is a Perl program that creates HTML pages that show information about a set of Ethernet switches. It uses SNMP to gather data from the switches. This is a very nice and useful tool. And an importantly point for my small project: it updates a CVS file for each switch, with the state of each port, plus a lot of other information per port. This will be the “source of truth” from where I will start my script.

So, knowing that I have a reliable source of information in CSV. What tool should I use to “grind” this? Ansible? Python? An expect script maybe? Here, the choices may be different for each person. Personally, I feel more comfortable with Python to extract and play with the variables. Then, Netmiko came as an obvious choice to do the configuration part.

 

The structure

Here is the structure of the script:

  1. Get the CSV files from Switchmap. The good point here is Switchmap creates one file per switch.
  2. Extract the data: the “admin-up” / “oper-down” interfaces in this state since more than 60 days. Make a list of interfaces, for each switch.
  3. Print a list of interfaces / switches we will shutdown – this script will be started manually.
  4. Check on the switch if the interface is currently really down – in case this interface has been allocated between the last Switchmap check and now.
  5. Shutdown the interfaces, one by one, switch by switch.
  6. Keep a log of this

This sounds very simple, right?  Well, exactly, it is very simple! And this is what I wrote at the beginning of this article: start with simple things.

 

The script

I past the script here, it’s on a private git because it includes the CVS files of the switches and this is not public. I also anonymized it a little bit:

#!/usr/bin/python

import csv
import os
import sys
import pprint
import datetime
import time
from netmiko import Netmiko
from getpass import getpass

# --- yes/no function
def confirm(prompt=None, resp=False):
    """prompts for yes or no response from the user. Returns True for yes and
    False for no.

    'resp' should be set to the default value assumed by the caller when
    user simply types ENTER.

    >>> confirm(prompt='Create Directory?', resp=True)
    Create Directory? [y]|n: 
    True
    >>> confirm(prompt='Create Directory?', resp=False)
    Create Directory? [n]|y: 
    False
    >>> confirm(prompt='Create Directory?', resp=False)
    Create Directory? [n]|y: y
    True

    """
    
    if prompt is None:
        prompt = 'Confirm'

    if resp:
        prompt = '%s [%s]|%s: ' % (prompt, 'y', 'n')
    else:
        prompt = '%s [%s]|%s: ' % (prompt, 'n', 'y')
        
    while True:
        ans = input(prompt)
        if not ans:
            return resp
        if ans not in ['y', 'Y', 'n', 'N']:
            print ('please enter y or n.')
            continue
        if ans == 'y' or ans == 'Y':
            return True
        if ans == 'n' or ans == 'N':
            return False



# --- Import the CSV files from switchmap - here I use SCP but you can use anything else.
os.system("scp user@server.hidden.com:/var/www/switchmap/csv/*.csv ./switchmap-csv-files/")

# --- For each file (1 csv file is 1 switch), create a list of the Inactive interfaces since more than 60 days
interfaces_dict={}
list_files = []
switch_name = ()
switch_interfaces = ()

list_files = sorted(os.listdir('./switchmap-csv-files/'))
for filename in list_files:
    switch_name = str(filename[:-4])
    switch_interfaces = list()
    with open("./switchmap-csv-files/"+filename, 'r') as csvfile:
        csv_reader = csv.reader(csvfile, delimiter=',')
        line_count = 0
        file_size = 0
        for row in csv_reader:
            days_shut = 0
            if str(row[3])=="Inactive" and "Ethernet" in str(row[1]):
                # Small trick to test the inactive day, because if less that 24h, the value is empty ('')
                try:
                    days_shut = int(row[4])
                except ValueError:
                    days_shut = 0
                    #print(f'--- Switch: {row[0]} interface {row[1]} have empty INACTIVE days value {row[4]}')
                if days_shut >60:
                    print(f'Switch: {row[0]} interface {row[1]} is INACTIVE since {row[4]} days.')
                    switch_interfaces.append(str(row[1]))
                    line_count += 1
                    # --- Increment this counter to see later if the file is empty or not
                    file_size +=1
            else:
                line_count += 1
        # Save results only if there is at least one interface to shutdown for this switch (file_size > 0)
        if file_size >0:
            # --- Let's create the lists
            interfaces_dict[switch_name] = switch_interfaces

# --- Print the summary of interfaces per switch
tot_infs = 0
print(60*"-")
print("Summary:")
for key, value in sorted(interfaces_dict.items()):
    print("On switch:", key, "we will shutdown:", len(value), "interfaces.")
    tot_infs = tot_infs + len(value)
print(60*"=")
print("Total intefaces:", tot_infs)
print(60*"-")

# --- If total interfaces = 0, then we stop here with a nice message
if tot_infs == 0:
    print("There is no interface to shutdown, goodbye.")
    print(60*"=")
    sys.exit()

# --- Otherwise, let's start to connect
username = input('username:')
print("please insert your password:")
password = getpass()

# --- Now, we work on each device
for device in dict.keys(interfaces_dict):

    # --- Prepare the log file
    now = datetime.datetime.now()
    logfile = str(now.strftime("%Y%m%d-%H-%M_"))+str(device)+".txt"

    # --- Connect to the switch, check and print the prompt
    print("Connecting to :", device)
    net_connect = Netmiko(device_type='cisco_nxos', host=device, username=username, password=password) 
    print(net_connect.find_prompt())

    # --- Get the interfaces status with show int xxx brief | xml | inc , and check if it is down
    print("Parsing interfaces... please wait...")
    for interface in interfaces_dict[device]:
        command = "show int " + str(interface) + " brief | xml | inc "
        show_output = net_connect.send_command(command)

        # --- If an interface is up and should be down, we stop everything here.
        if "down" not in show_output:
            print("WARNING --- interface: ",interface, "of switch: ", device, "is not down")
            sys.exit()
    print("All intefaces to shutdown are currently down for this switch")

    # --- All interface marked to shutdown are currently down, we can proceed to the shutdown
    prompt = str("Do you really want to SHUTDOWN theses interfaces?")
    # --- If yes, then lets shutdown thousands of network access ports ... otherwise we skip this switch
    if confirm(prompt=prompt, resp=False) == True:
        for interface in interfaces_dict[device]:
            config_command = ["interface " + str(interface), "shutdown"]
            shutdown_output = net_connect.send_config_set(config_command)
            print(shutdown_output)
            # --- Log this into the file
            with open("./logs/"+logfile, 'a') as log_file:
                log_file.write("%s \n" % shutdown_output)
        wrmem_output = net_connect.send_command("copy run start")
        print(wrmem_output)
        # --- Log this into the file
        with open("./logs/"+logfile, 'a') as log_file:
            log_file.write("%s \n" % wrmem_output)

    else:
        print("Okay, we skip this device...")
    print(40*"=")
    net_connect.disconnect()        

print("End.")

 

And that’s it!

I hope you liked it and it will give you ideas to start (small) in network automation.

If you would like to read more on this subject, please see my older post about my journey to Network Automation.

Related posts

5 Thoughts to “Shutdown thousands access ports with Python and Netmiko”

  1. John Tanner

    A good script indeed. I made a similar one but without using switchmap. My script would login to the switch and create a list of interfaces and start inspecting each port looking for input packets and output packets. if both = Zero then shutdown the port. the idea is that if a switch port never passed traffic then it is unused port hence shut it down.

    Thanks again for sharing your script.

    1. Thank you for your comment John. This is a pretty good idea to check the in/out packets counters and make the same. Can you please share your script somewhere?
      All the best,
      Jerome

      1. John Tanner

        Hi Jerome

        Here is the script
        =========================================================
        from getpass import getpass, getuser
        from netmiko import ConnectHandler

        #get the Username and Password to login to the devices
        usrnm=getuser()
        pswd=getpass(‘Enter Your Password: ‘)

        #open the files ‘hostnames.txt’ to read the hosts and ErrorLog.txt to report any error
        r=open(‘hostnames.txt’, ‘r’)
        x=open(‘ErrorLog.txt’, ‘w’)

        names=r.read().split()

        for name in names:
        print (“Checking ” + name +”\n”)
        try:
        rtrconnect=ConnectHandler(device_type=’cisco_ios’,ip=name, username=usrnm, password=pswd, secret=pswd)
        except Exception as error:
        print(“Host Unreachable\n”)
        x.writelines(name + ” Host Unreachable” + ‘\n’)

        else:
        rtrconnect.find_prompt()
        prompt= rtrconnect.enable()
        w=open(name + ‘.txt’, ‘w’)

        infotemp=rtrconnect.send_command(‘term len 0’)
        All_INT=rtrconnect.send_command(‘sh int status | ex trunk’)
        INT_LIST=All_INT.split(‘\n’)
        counter=0
        for interface in INT_LIST:

        if interface==” or ‘Name’ in interface:
        continue
        else:
        print(interface.split()[0])
        INT_STAT=rtrconnect.send_command(‘sh int ‘ + interface.split()[0])
        if ‘ 0 packets input,’ in INT_STAT and ‘ 0 packets output,’ in INT_STAT and ‘Received 0 broadcasts’ in INT_STAT:
        w.writelines(interface.split()[0] + ‘\n’)
        counter+=1
        continue
        else:
        continue
        rtrconnect.disconnect()
        w.writelines(‘\n’ + “Number of Available Ports: ” + str(counter))
        w.close()
        r.close()
        x.close()
        =============================================================
        I added an additional check “Received 0 broadcasts” Just to be sure.

        Have a great day!

      2. John Tanner

        Sorry Jerome, the indentations are messed up when paste the script in the form

        1. Not a problem. And thank you very much for the script, interesting.

Leave a Comment