Post Image

Network Automation Find Ports in Error Disable

In my experience the most common ways teams find ports that are in error disable status has been when someone reports an end device not working, and investigating the port on the switch you find it is in err-disable state or you are working on something different and you notice it when listing the port status of the switch. In either case this is far from efficient or proactive so to come up with a better way, in this post I build a Python script using the CiscoAutomationFramework library that will be able to audit hundreds of network devices within seconds, and print out any ports that are in error disabled status so you can investigate them, and even better build upon this script and create an audit that runs on a schedule sending email alerts. Sending email alerts will be beyond the scope of this article but it will give you the foundation to quickly build that logic on top of the script built here.

 

Requirements

It is required that this script be able to accomplish the following.

  • Complete its run in under 30 seconds regardless of the number of devices
  • List out only ports in error disable status with the following information
    • Switch Hostname
    • Switch IP Address
    • Switch Port

 

Prerequisites

I assume that you have the following done

  • Python 3.7+ installed 
  • CiscoAutomationFramework installed in your Python environment
    • Install via pip install CiscoAutomationFramework
  • Have a basic understanding of Python

 

Building the Script

In this we will leverage much of the built in functionality of the CiscoAutomationFramework library so we don't need to worry about things like concurrency, printing in nice columns, or scraping the CLI.

 

We will first import the libraries we will use in the script

from CiscoAutomationFramework.util import column_print
from CiscoAutomationFramework.ThreadLib import SSH, start_threads
from CiscoAutomationFramework.Parsers.InterfaceStatusParser import InterfaceStatusOutputParser

Then we need to build a switch object that inherits from CiscoAutomationFramework.ThreadLib.SSH to allow us to run it concurrently against multiple switches.

This object will need to get the output of the command show interface status and contain the code for grabbing all of the interfaces and putting them in a list.

 

class Switch(SSH):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.int_status_table = []

    @property
    def ports_in_error_disable(self):
        interfaces = []
        for interface in InterfaceStatusOutputParser(self.int_status_table).interfaces:
            if 'err' in interface.status.lower():
                interfaces.append(interface.name)
        return interfaces

    @property
    def has_ports_in_error_disable(self):
        if self.ports_in_error_disable:
            return True
        return False

    def during_login(self, ssh):
        self.int_status_table = ssh.send_command_get_output('show int status')

Stepping through the code we will start with the dunder init method.

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.int_status_table = ''

We extend the init method becuse we need our switch object to be able to store the raw output of the show interface status command. You can see we are storing it in the instance variable self.int_status_table which by default is an empty list.

 

Then to actually get the output of the show interface status command when logged in we override the during_login method

def during_login(self, ssh):
    self.int_status_table = ssh.send_command_get_output('show int status')

And you can see that we set the instance variable self.int_status_table to the output of the command show int status.

 

We now need to be able to extract what ports are in error disable from the raw output. so I define the method ports_in_error_disable

@property
def ports_in_error_disable(self):
    interfaces = []
    for interface in InterfaceStatusOutputParser(self.int_status_table).interfaces:
        if 'err' in interface.status.lower():
            interfaces.append(interface.name)
    return interfaces

Because I want to reference this method like it is an attribute versus a function later in the code to make it appear as if it is not running a function I use the @property decorator. This method is also designed to be run after the during_login method has executed, so we know that we will have data the raw output from the switch in the self.int_status_table variable.

Stepping through the function:

  • I define an empty list in the variable interfaces
  • I load the raw output from the switch into the InterfaceStatusOutputParser and loop over the interfaces attribute.
    • This will handle each interface in the output.
  • Within the loop we check if the interface status, interpreted as lower case, contains the string 'err'
    • If it does we append the interface name (ex. Gi1/0/4) to our interfaces list we defined.
  • Lastly once we have looped through all of the interfaces in the output, we return the interfaces list which at this point might be empty, or will contain strings of interfaces that are in err-disable

 

Now to make life easier in other parts of the script I also define another property which will tell us True/False if the switch has any ports in error disable so we can build more readable code later on.

@property
def has_ports_in_error_disable(self):
    if self.ports_in_error_disable:
        return True
    return False

This method simply returns True if the ports_in_error_disable method returns a non empty list, and false if it returns an empty list.

 

At this point we have an object that can run concurrently, that gets the data we need, and parses it in a way that we can build a script off of. Now we need to build the logic to use this Switch object to run against the switches and list the output.

 

if __name__ == '__main__':

    from getpass import getpass
    
    username = ''
    password = getpass(f'Enter password for {username}: ')

    # replace with the IP addresses of your switches or get them programmatically here
    ips = ['192.168.1.2', '192.168.1.3']  

    print('Reaching out to network devices!')
    threads = start_threads(Switch, ips, username, password, wait_for_threads=False)

    print('Waiting for threads to complete.')
    for t in threads:
        t.join()

    # List out the data
    data = [['Hostname', 'Device IP', 'Port in Err Disable']]
    for t in threads:
        for port in t.ports_in_error_disable:
            data.append([t.hostname, t.ip, port])
    column_print(data)

 

This is the script for tying it all together, which we will step through.

 

if __name__ == '__main__':

Only run this code if the file is called directly, this allows our module to contain the standalone script, and in addition be dropped into a larger project so we can import the Switch object defined above and not run this script.

 

from getpass import getpass

username = input('Enter Username: ')
password = getpass(f'Enter password for {username}: ')

Get the credentials that the script will use to login.

 

# replace with the IP addresses of your switches or get them programmatically here
ips = ['192.168.1.2', '192.168.1.3']

Define the IP addresses (hostnames can be used too) of all the switches we want to login. I recommend loading these programmatically from a file or other system via an API versus hard coding them.

 

print('Reaching out to network devices!')
threads = start_threads(Switch, ips, username, password, wait_for_threads=False)

Run the Switch object against all of our IP addresses.

 

print('Waiting for threads to complete.')
for t in threads:
    t.join()

Wait for all of the threads to complete.

 

# List out the data
data = [['Hostname', 'Device IP', 'Port in Err Disable']]
for t in threads:
    for port in t.ports_in_error_disable:
        data.append([t.hostname, t.ip, port])
column_print(data)

Construct a data list which contains a list of the column names we want to print out

Loop through all of the threads, and then all of the interfaces that are in error_disable. Construct a list of the switch hostname, IP, and port, and append that list to our larger data list.

Pass the data list to the helper function column_print to print everything out in nice even columns.

 

Running the Script

So now we have our script created, when running it we will get output similar to below

$ python myscript.py
Enter Username: kylekowalczyk
Enter password for kylekowalczyk:
Reaching out to network devices!
Waiting for threads to complete.
Hostname   Device IP    Port in Err Disable
switch-1   192.168.1.2  Gi1/0/2
switch-2   192.168.1.3  Gi1/0/20
switch-2   192.168.1.3  Gi1/0/24
$

And you can see there are 3 ports in error disable on 2 switches, I have the IP address of the switch so I can quickly reference to SSH in and figure out what is going on.

 

Another note about this script is while I only run it against 2 devices, we could scale this to 200 devices by just adding the IP addresses and it will not take notably longer. Obviously if you go from 2 to 800 devices you may see the execution time go from somewhere from around 6 seconds to maybe 10-15, when considering the scale, that is nearly no extra time at all.

 

Complete Script

Below I have the complete script so you can easily copy and paste and run it in your environment.

from CiscoAutomationFramework.util import column_print
from CiscoAutomationFramework.ThreadLib import SSH, start_threads
from CiscoAutomationFramework.Parsers.InterfaceStatusParser import InterfaceStatusOutputParser


class Switch(SSH):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.int_status_table = []

    @property
    def ports_in_error_disable(self):
        interfaces = []
        for interface in InterfaceStatusOutputParser(self.int_status_table).interfaces:
            if 'err' in interface.status.lower():
                interfaces.append(interface.name)
        return interfaces

    @property
    def has_ports_in_error_disable(self):
        if self.ports_in_error_disable:
            return True
        return False

    def during_login(self, ssh):
        self.int_status_table = ssh.send_command_get_output('show int status')


if __name__ == '__main__':

    from getpass import getpass

    username = input('Enter Username: ')
    password = getpass(f'Enter password for {username}: ')

    # replace with the IP addresses of your switches or get them programmatically here
    ips = ['192.168.1.2', '192.168.1.3']

    print('Reaching out to network devices!')
    threads = start_threads(Switch, ips, username, password, wait_for_threads=False)

    print('Waiting for threads to complete.')
    for t in threads:
        t.join()

    # List out the data
    data = [['Hostname', 'Device IP', 'Port in Err Disable']]
    for t in threads:
        for port in t.ports_in_error_disable:
            data.append([t.hostname, t.ip, port])
    column_print(data)

 

 

So as you can see, within 53 lines of code you can perform an audit over hundreds of your switches in a matter of seconds. A great idea to build upon this script is instead of, or in addition to printing out the data you could add the logic to send an email to you or your team if there are any ports in error disable and run this script on a cron job every hour so you get alerts proactively!

 

I hope this script helps you in troubleshooting and proactively finding issues in your network, and if you have any issues or questions please feel free to leave a comment so I can clear up any issues.

 



Comments (0)
Leave a Comment