Hello everyone. Once more, trying to inset automation in maintenance tasks, after we performed a DC upgrade at the company network, separating DC/Campus networks, we needed to clean up a lot of vlans from the campus networks. I came up with this script to play around, make the task more interesting and also avoid repetitive mundane tasks that are prone to human error.
The concept behind this script is simple. What would a human engineer do? We would start at the campus core, check where the vlans to be removed are listed as active (show vlan id command), follow through those ports to the respective cdp neighbor access switch (show cdp neighbor detail), find out where that vlan is active (show interfaces status) and change the config for those ports so that the access vlan is a dummy vlan, rending the port inactive (but detectable and responsive).
It would take us a little more than a day perhaps to do that task without automation. It took me 2-3 evenings (at home sadly) to put the code together and workout stuff I didn’t know, but my head is doing fine (no headaches from the repetitive tasks and fine reading in the terminal window), I made no mistakes and had tremendous fun. And I can reuse this, partly or as a whole.
So what was missing? I used Netmiko with NTC-Templates again but there was no template for the ‘show vlan id’ command. There was one for ‘show vlan’ but with that command the campus core would not respond with the physical ports that those vlans were active. So, although I was tempted to create my own template (and even tried to learn how for a bit), I cheated a little by copying everything from the show vlan template to a new show vlan id template and got a list of dictionaries as a result, which only had one item in the list.. I bet it can be done better, but I am still learning, so you are welcome to let me know if you come up with something better.
We start by reading the excel file and storing the vlan data in a list of dictionaries (I got the vlan name as well but never used it, so you could use just a plain list there). After that, we connect to the first core switch.
In the core switch there were vlans to be removed that didn’t stretch at all to access switches and only went through to the connection with the other campus core, and some vlans to be removed that did stretch to access switches. So we create two separate lists to accommodate that data.
For each vlan in the list we send the command show vlan id ## where the ## represents the vlan number. What we get back with NTC-Templates (use_textfsm=true) is a list of dictionaries with only one list item. That item contains field values for vlan id, vlan name and a port list (yes it’s actually a list of values). Those vlans that have more than one port in the list are put in the list of vlans to be processed further. The others are vlans with only the core to core connection assigned. So we can delete them immediately. The information for those vlans is printed out, and then we run the ‘show cdp neighbor detail’ command as we are ready to disconnect from the first core and start processing our data. The result from the command is again a list of dictionaries with every bit of information we need to start creating our list of neighbors (access switches).
Two lists are created. The first will accommodate the neighboring switches. The second one is a port list which is relevant only to each one of the campus core switches. It’s the list of ports through which we expect to discover the neighbors. The port list is populated going through the vlan to be processed list and adding port names to the port list. We take care not to add duplicate ports.
We could obviously choose to delete the vlans in code. We chose not to, because we wanted to be able to interact with the equipment in a case where a spanning tree loop might occur. It’s up to the engineer really and what he is prepared to do with code on a production network.
For each port in the port list a match is made with ports we found out from our show cdp neighbor detail command. The matching loop is a little complicated as it needs to be split in half, first to add the neighbors themselves but then going through different data to add which vlans should be added to every neighbor for checking. Everything is stored as lists of dictionaries, except the port list. Now that we are done processing our data from the first campus core switch, we move to connect to the second.
Same actions are performed for the second campus core. However when we get to the point of transforming our data to enrich our neighbors/vlans list, we need to make sure we are not creating duplicate data, adding only info when a neighbor is present and adding only the vlans that are missing. When that is done, we have our list of neighbors (ip, platform, list of vlans).
For every neighbor we check first if it’s a cat2950. If it is, then telnet is not supported. Netmiko does have a platform detection capability but ssh is needed for it. So no other way to connect there but know before hand that you need telnet instead of ssh and change the connection parameters. These relics will soon be off our network thank God.
After connecting we get the list of interfaces with the show interfaces status command that exposes vlan value (and also helps to avoid trunk interfaces). We then run a loop through the interfaces and where the interface access vlan is included in the list of vlans we are processing for this neighbor, we change the access vlan to a dummy value rending the port inactive to users but available for monitoring.
The script follows below. IP addresses and the dummy vlan value has been changed for security reasons.
If you like network automation and you need more info about what was used here and where to go from there, you can either browse through my earlier posts or contact me through twitter @mythryll handle. In the earlier posts you will find introductory text about what is out there, links to websites and cisco devnet resources. If you are not a cisco user, it’s fine, you have plenty of stuff to learn and apply. But probably not through the Cisco Devnet channels (with some exceptions).
from netmiko import ConnectHandler, ssh_exception
from paramiko.ssh_exception import SSHException
import os
import subprocess
import sys
import openpyxl
from datetime import datetime
from getpass import getpass
#we read the vlan list from the excel file
wbvlans = openpyxl.load_workbook("vlansdel.xlsx")
ws = wbvlans.active
max_row = ws.max_row
#we are creating a list for our vlans, every list item will be a dictionary
vlans = []
#we add vlans from the excel to the list and close the excel file
for row in range (1, max_row+1):
vlan = dict()
vlan ['id'] = ws.cell(row= row, column = 1).value.strip()
vlan ['name'] = ws.cell(row= row, column = 2).value
vlans.append(vlan)
wbvlans.close()
user = input('username:')
passwd = getpass()
#we define the connection for the 1rst core switch and connect to it
kkcore1 = {
'device_type': "cisco_ios_ssh",
'ip': "x.y.z.w",
'username': user,
'password': passwd,
}
try:
net_connect = ConnectHandler(**kkcore1)
except SSHException:
print ("can't connect to last device")
sys.exit(1)
except ssh_exception.NetMikoTimeoutException:
print(" SSH Timed out")
sys.exit(1)
except ssh_exception.NetMikoAuthenticationException:
print("Invalid Credentials: ", ipaddress)
sys.exit(1)
#we check where we are connected (mainly for debug)
output = net_connect.find_prompt()
print (output)
#we create two separate lists for the vlans that
#have no interfaces assinged and one for the vlans to check
#on other switches
vlreadyfordel = []
vlcheckcdp = []
for vlan in vlans:
#we use the show vlan id command for each vlan and capture
#active interfaces through textfsm (ntc-template)
commandtext = "show vlan id" + " " + vlan['id']
kka1vlanlist = net_connect.send_command(commandtext, use_textfsm=True)
#if there is only the Po254 interface then the vlan doesn't
#spread to other switches. if there are 2 or more, it does
if (len(kka1vlanlist[0]['interfaces'])) >= 2:
print(vlan['id'],kka1vlanlist[0]['interfaces'])
vl2proc = dict()
vl2proc['id'] = vlan['id']
vl2proc['name'] = vlan['name']
vl2proc['portlist'] = []
for port in kka1vlanlist[0]['interfaces']:
vl2proc['portlist'].append(port)
vlcheckcdp.append(vl2proc)
else:
vl2del = dict()
vl2del['id'] = vlan['id']
vl2del['name'] = vlan['name']
vlreadyfordel.append(vl2del)
print ("vlans to delete now:")
for vlan in vlreadyfordel:
print (vlan['id'],vlan['name'])
#we get all the details we need from our neighbors and we disconnect
cdplist = net_connect.send_command("show cdp neighbors detail", use_textfsm=True)
net_connect.disconnect()
#we create two different lists one global for the neighbors to check
#and one local to keep track of the ports we need to check
neighbors = []
ports = []
for vlan in vlcheckcdp:
for port in vlan['portlist']:
if (port not in ports) and ("Po254" not in port):
ports.append(port)
#for each neighbor we check whether each port is included in the ports we need to check
#if it is, then we take notes on the neighbor details and add the neighbor to the global lists
#not adding the vlans to check for that neighor at that point.
for cdpneighbor in cdplist:
cdpneighbor['local_port'] = cdpneighbor['local_port'].replace('GigabitEthernet','Gi')
for port in ports:
if (cdpneighbor['local_port'] == port) and ("6509" not in cdpneighbor['platform']):
neighbor = dict()
neighbor['port'] = port
neighbor['ip'] = cdpneighbor['management_ip']
neighbor['platform'] = cdpneighbor['platform']
neighbor['vlans'] = []
neighbors.append(neighbor)
#now for the neighbors we added we check which vlans they need to bec checked for
for neighbor in neighbors:
for vlan in vlcheckcdp:
for port in vlan['portlist']:
if neighbor['port'] == port:
neighbor['vlans'].append(vlan['id'])
#we are done with the 1rst core, so we move to the next to do the same.
kkcore2 = {
'device_type': "cisco_ios_ssh",
'ip': "a.b.c.d",
'username': user,
'password': passwd,
}
try:
net_connect = ConnectHandler(**kkcore2)
except SSHException:
print ("can't connect to last device")
sys.exit(1)
except ssh_exception.NetMikoTimeoutException:
print(" SSH Timed out")
sys.exit(1)
except ssh_exception.NetMikoAuthenticationException:
print("Invalid Credentials: ", ipaddress)
sys.exit(1)
#we check where we are connected (mainly for debug)
output = net_connect.find_prompt()
print (output)
#we create two separate lists for the vlans that
#have no interfaces assinged and one for the vlans to check
#on other switches
vlreadyfordel = []
vlcheckcdp = []
for vlan in vlans:
#we use the show vlan id command for each vlan and capture
#active interfaces through textfsm (ntc-template)
commandtext = "show vlan id" + " " + vlan['id']
kka1vlanlist = net_connect.send_command(commandtext, use_textfsm=True)
#if there is only the Po254 interface then the vlan doesn't
#spread to other switches. if there are 2 or more, it does
if (len(kka1vlanlist[0]['interfaces'])) >= 2:
print(vlan['id'],kka1vlanlist[0]['interfaces'])
vl2proc = dict()
vl2proc['id'] = vlan['id']
vl2proc['name'] = vlan['name']
vl2proc['portlist'] = []
for port in kka1vlanlist[0]['interfaces']:
vl2proc['portlist'].append(port)
vlcheckcdp.append(vl2proc)
else:
vl2del = dict()
vl2del['id'] = vlan['id']
vl2del['name'] = vlan['name']
vlreadyfordel.append(vl2del)
print ("vlans to delete now:")
for vlan in vlreadyfordel:
print (vlan['id'],vlan['name'])
#we get all the details we need from our neighbors and we disconnect
cdplist = net_connect.send_command("show cdp neighbors detail", use_textfsm=True)
net_connect.disconnect()
#only the local port list is created from scratch
ports = []
for vlan in vlcheckcdp:
for port in vlan['portlist']:
if (port not in ports) and ("Po254" not in port):
ports.append(port)
#for every neighbor in the switch we check whether his address
# is already in the global list. If it is, then we need to add the respective vlans,
#but only if they are not there already. We don't want to bother with the other core switch of course
for cdpneighbor in cdplist:
cdpneighbor['local_port'] = cdpneighbor['local_port'].replace('GigabitEthernet','Gi')
#print(cdpneighbor['local_port'])
for port in ports:
if (cdpneighbor['local_port'] == port) and ("6509" not in cdpneighbor['platform']):
neigh_exists = False
for exneigh in neighbors:
if exneigh['ip'] == cdpneighbor['management_ip']:
neigh_exists = True
for vlan in vlcheckcdp:
for vlport in vlan['portlist']:
if (port == vlport) and (vlan['id'] not in exneigh['vlans']):
exneigh['vlans'].append(vlan['id'])
if neigh_exists == False:
neighbor = dict()
neighbor['port'] = port
neighbor['ip'] = cdpneighbor['management_ip']
neighbor['platform'] = cdpneighbor['platform']
neighbor['vlans'] = []
for vlan in vlcheckcdp:
for vlport in vlan['portlist']:
if neighbor['port'] == vlport:
neighbor['vlans'].append(vlan['id'])
neighbors.append(neighbor)
print("Neighbors to check for access vlans:")
#From this point on we can initiate connections to all those neihgbors,
#and change the access vlan for those interfaces where the access vlan is to be changed.
#We can use the show interfaces status command that includes type and access vlan.
for neighbor in neighbors:
print(neighbor['ip'], neighbor['platform'])
if "2950" not in neighbor['platform']:
contype = "cisco_ios_ssh"
else:
contype = "cisco_ios_telnet"
swcon = {
'device_type': contype,
'ip': neighbor['ip'],
'username': user,
'password': passwd,
}
try:
net_connect = ConnectHandler(**swcon)
except SSHException:
print ("can't connect to ", neighbor['ip'])
sys.exit(1)
except ssh_exception.NetMikoTimeoutException:
print(" SSH Timed out")
sys.exit(1)
except ssh_exception.NetMikoAuthenticationException:
print("Invalid Credentials: ", neighbor['ip'])
sys.exit(1)
interlist = net_connect.send_command("show interfaces status", use_textfsm=True)
output = net_connect.config_mode()
confcheck = net_connect.check_config_mode()
print(confcheck)
for item in interlist:
if item['vlan'] in neighbor['vlans']:
print(item['port'], item['vlan'])
#the use of send_command_timing is necessary to get over the obstacle of some 2950s
#not providing suitable prompt patterns to be matched by the base_connection function
#of netmiko, resulting to failure to execute configuration commands. Instead a timeout
#is used to send the command. If there are a lot of commands to run, this can slow down
#things significantly
output=net_connect.send_command_timing("interface "+ item['port'])
output=net_connect.send_command_timing("switchport access vlan 299")
print (output)
output = net_connect.exit_config_mode()
output = net_connect.send_command("wr")
print (output)
net_connect.disconnect()
print("Done")
That’s it! I hope you enjoyed it. One probable next post may be about PyATS but maybe I will find something else to write about in between.