Netmiko

Using Python (Netmiko) to Standardize VLANs across 100+ Switches

Using Python (Netmiko) to Standardize VLANs across 100+ Switches
In: Netmiko, Python, NetDevOps, Cisco

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 the device_inventroy list.
  • Loop through the device_inventroy list and for each device, use the ConnectHandler class to establish a connection to the device.
  • Use the send_command() method to execute the show 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 the current_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.
  • 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.

Concurrent Futures with Netmiko
concurrent.futures is used to handle the parallel execution of tasks. It provides a high-level interface for asynchronously executing callables

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.

Written by
Suresh Vina
Tech enthusiast sharing Networking, Cloud & Automation insights. Join me in a welcoming space to learn & grow with simplicity and practicality.
Comments
More from Packetswitch
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Packetswitch.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.