Have you ever found yourself in a situation where you needed to standardize the configuration of VLAN names across multiple switches? Recently, a friend of mine faced a similar challenge and reached out to me for help. I decided to use my Python skills to come up with a solution.
In this blog post, I will share with you how I used Python to come up with a script that would standardize the VLAN names across 100s of switches. Whether you're a Network Engineer, a Python enthusiast, or just someone interested in learning about Network Automation, this blog post will provide you with practical tips and tricks to help you Automate Network configuration tasks. So, let's dive in!
This blog post assumes basic knowledge of Python and Netmiko. If you are unfamiliar with Netmiko, I highly recommend checking out my previous posts below.
Part 1 - https://www.packetswitch.co.uk/netmiko-intro/
Part 2 - https://www.packetswitch.co.uk/netmiko-par2/
Part 3 - https://www.packetswitch.co.uk/netmiko-and-textfsm-example/
Assumptions
All the switches are running Cisco-IOS and have the same login credentials.
Requirements
- I have a list of VLANs to work with that have the VLAN ID and the correct Name.
- If a particular VLAN doesn't exist on a switch, don't create it.
- If a particular VLAN does exist on a switch but has a wrong name, the script should change the name to the correct one.
- The script should never delete or create new VLANs.
The Script
Let's take a look at the script in detail, starting from the very beginning. The following is the entire script, let's break it down and go bit by bit.
from netmiko import ConnectHandler
switch_list = ['10.10.20.18', '10.10.20.19', '10.10.20.20']
device_inventroy = []
vlans = {
'10': 'MGMT',
'20': 'DATA',
'30': 'active_directory',
'31': 'web_servers',
'32': 'admin',
'33': 'network'
}
for ip in switch_list:
device = {
"device_type": "cisco_ios",
"host": ip,
"username": "cisco",
"password": 'cisco123',
"secret": 'cisco123' # Enable password
}
device_inventroy.append(device)
for switch in device_inventroy:
connection = ConnectHandler(**switch)
output = connection.send_command('show vlan', use_textfsm=True)
connection.enable() # Enable method
connection.config_mode() # Global config mode
current_vlans_dict = {}
for vlan in output:
current_vlans_dict[vlan['vlan_id']] = vlan['name']
for k,v in vlans.items():
if k in current_vlans_dict and v != current_vlans_dict[k]:
commands = [f"vlan {k}", f"name {v}"]
config_output = connection.send_config_set(commands)
print(config_output)
connection.disconnect()
Here is a quick summary of what the code does
- Import the
ConnectHandler
class from the Netmiko library. - Define a list of IP addresses for the switches that we want to connect to and check the VLAN configuration.
- Define an empty list called
device_inventroy
which will be used to store the devices that we want to connect to. - Define a dictionary called
vlans
which contains the VLAN IDs and names that we want to check on the switches. - Loop through the
switch_list
and for each IP address, create a dictionary containing the necessary device information (device type, IP address, username, password, and enable secret), and append it to thedevice_inventroy
list. - Loop through the
device_inventroy
list and for each device, use theConnectHandler
class to establish a connection to the device. - Use the
send_command()
method to execute theshow vlan
command on the switch, parse the output using TextFSM and capture the output in a variable called output. - Create an empty dictionary called
current_vlans_dict
- Loop through the
output
and populate thecurrent_vlans_dict
with VLAN IDs and names from the output. - Loop through the
vlans
dictionary and for each key-value pair:- Check if the VLAN ID is already in the
current_vlans_dict
and if the VLAN name is not the same as the one in the vlans dictionary. - If both conditions are true, create a list of configuration commands to configure the VLAN ID and name, and then use the
send_config_set()
method to send the commands to the switch. - Print the output of the
send_config_set()
method.
- Check if the VLAN ID is already in the
- Use the disconnect() method to close the connection to the switch.
Import Required Libraries
We only need to import netmiko ConnectHandler
from netmiko import ConnectHandler
Switch Inventory and VLAN List
For this example, I'm only working with three switches and six VLANs as shown below. The variable vlans
is a dictionary that holds the VLAN IDs and Names. Our ultimate goal here is to ensure that all switches use the correct VLAN names that are defined here.
The empty list called device_inventory
will be used in the subsequent steps.
switch_list = ['10.10.20.18', '10.10.20.19', '10.10.20.20']
device_inventroy = []
vlans = {
'10': 'MGMT',
'20': 'DATA',
'30': 'active_directory',
'31': 'web_servers',
'32': 'admin',
'33': 'network'
}
Switch Inventory
If you previously worked with Netmiko, you might know that we need to provide Netmiko with some info about the devices such as IP, device type and credentials. Running the following block of code will provide just that.
Once you run the command, the variable device_inventory
will hold the details of all three switches that we can pass it to Netmiko.
for ip in switch_list:
device = {
"device_type": "cisco_ios",
"host": ip,
"username": "cisco",
"password": 'cisco123',
"secret": 'cisco123' # Enable password
}
device_inventroy.append(device)
#device_inventory
[
{
"device_type": "cisco_ios",
"host": "10.10.20.18",
"username": "cisco",
"password": "cisco123",
"secret": "cisco123"
},
{
"device_type": "cisco_ios",
"host": "10.10.20.19",
"username": "cisco",
"password": "cisco123",
"secret": "cisco123"
},
{
"device_type": "cisco_ios",
"host": "10.10.20.20",
"username": "cisco",
"password": "cisco123",
"secret": "cisco123"
}
]
Log in to the Switch and Parse the Ouput
The next step is to use Netmiko to SSH to the device, issue show vlan
command and use ntc_templates
to parse the output using TextFSM.
for switch in device_inventroy:
connection = ConnectHandler(**switch)
output = connection.send_command('show vlan', use_textfsm=True)
connection.enable() # Enable method
connection.config_mode() # Global config mode
The above block of code would provide the following output. As you can see below, there are three VLANs configured on one of the test switches.
[
{
"vlan_id": "31",
"name": "web_servers",
"status": "active",
"interfaces": []
},
{
"vlan_id": "32",
"name": "admin",
"status": "active",
"interfaces": []
},
{
"vlan_id": "33",
"name": "network",
"status": "active",
"interfaces": []
}
]
Python will save this output into a variable called output
. The variable is a Python type list
which we can iterate over and access each element. For example, I can access the first element where the vlan_id
is 31
and the name
is web_servers
.
Comparison
The final step is to compare the show vlan
output with the VLAN inventory and make the configuration changes (aka change the VLAN name if required).
current_vlans_dict = {}
for vlan in output:
current_vlans_dict[vlan['vlan_id']] = vlan['name']
for k,v in vlans.items():
if k in current_vlans_dict and v != current_vlans_dict[k]:
commands = [f"vlan {k}", f"name {v}"]
config_output = connection.send_config_set(commands)
connection.send_command('wr')
print(config_output)
connection.disconnect()
- Line #1-3 - Add the current VLANs into a new dictionary called
current_vlans_dict
(see below screenshot) - Line #6 - The
if
condition says that for Netmiko to change the name of the VLAN,- The VLAN must exist on the switch AND
- The Name of the VLAN must be different to what we have in the inventory
Line #1-3
Just to give you an idea and visualisation, the current_vlans_dict
dictionary would look like the following when you run it.
Verification
For the purpose of this example, I've pre-configured the three switches with the following VLANs.
Before
Switch-01 - VLAN 31 name is wrong so, the script should change it. Everything else should stay the same. Please note that this switch doesn’t have VLAN 30.
switch-01#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/3, Gi1/0, Gi1/3
10 MGMT active Gi0/0
20 DATA active Gi0/1, Gi0/2, Gi1/1, Gi1/2
31 web active
32 admin active
33 network active
Switch-02 - VLAN names for 10 and 20 are wrong so, the script should change them. Everything else stays the same. Please note that this switch doesn’t have any other VLANs (apart from the default one)
switch-02#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/2, Gi0/3, Gi1/0, Gi1/3
10 VLAN0010 active Gi0/0
20 VLAN0020 active Gi0/1, Gi1/1, Gi1/2
Switch-03 - VLAN names for 10 and 20 are wrong so, the script should change them. Everything else stays the same. Please note that this switch has all the VLANs.
switch-03#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/3, Gi1/0, Gi1/3
10 VLAN0010 active Gi0/0
20 VLAN0020 active Gi0/1, Gi0/2, Gi1/1, Gi1/2
30 active_directory active
31 web_servers active
32 admin active
33 network active
100 random active
Let's run the script
suresh@mac:~/Documents/vlan_standard|⇒ python vlan_script.py
vlan 31
switch-01(config-vlan)#name web_servers
switch-01(config-vlan)#end
switch-01#
vlan 10
switch-02(config-vlan)#name MGMT
switch-02(config-vlan)#end
switch-02#
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
switch-02(config)#vlan 20
switch-02(config-vlan)#name DATA
switch-02(config-vlan)#end
switch-02#
vlan 10
switch-03(config-vlan)#name MGMT
switch-03(config-vlan)#end
switch-03#
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
switch-03(config)#vlan 20
switch-03(config-vlan)#name DATA
switch-03(config-vlan)#end
switch-03#
After
As you can see below, we did get the intended results. For example, switch-01 VLAN 31 name got changed from web
to web_servers
switch-01#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/3, Gi1/0, Gi1/3
10 MGMT active Gi0/0
20 DATA active Gi0/1, Gi0/2, Gi1/1, Gi1/2
31 web_servers active
32 admin active
33 network active
switch-02#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/2, Gi0/3, Gi1/0, Gi1/3
10 MGMT active Gi0/0
20 DATA active Gi0/1, Gi1/1, Gi1/2
switch-03#show vlan
VLAN Name Status Ports
---- -------------------------------- --------- -------------------------------
1 default active Gi0/3, Gi1/0, Gi1/3
10 MGMT active Gi0/0
20 DATA active Gi0/1, Gi0/2, Gi1/1, Gi1/2
30 active_directory active
31 web_servers active
32 admin active
33 network active
100 random active
Do you want to run the task concurrently so, the script can connect to multiple switches and execute the script much faster? If so, please check out the second part of the post below.
Read device information from a text file
If you already have a text file with the IP address of all the devices, it wouldn't make sense to create a new list. Here's an example Python script that reads from a text file and appends each item in a line to a list which you can use with the above script.
#example.txt
192.168.10.10
192.168.15.66
192.168.12.12
with open('example.txt', 'r') as file:
# Use list comprehension to create a list of items from each line
items = [item for line in file for item in line.split()]
# Print the resulting list
print(items)
#output
['192.168.10.10', '192.168.15.66', '192.168.12.12']
In this example, the script opens the file example.txt
and reads each line. It then removes newline characters and splits each line into items using the split()
method, which splits a string into a list of substrings based on a delimiter (in this case, whitespace). The strip()
method is used to remove any leading or trailing whitespace from each line.
Finally, the script uses the extend()
method to add each item to the end of the list, instead of adding the entire line as a single element. This ensures that each item is a separate element in the list.
Note: You should replace 'example.txt' with the filename of your own text file. Also, make sure the text file is in the same directory as your Python script, or provide the full path to the file if it's located elsewhere.
Closing Thoughts
Please let me know in the comments if you think there is a better way to do this.