[python]Creating vlans on multiple switches

Introduction

The entire script demo can be found here.

The script reads from an excel sheet named as “vlans.xlsx” and extracts the information, the information is then converted into vlan commands with a jinja2 template, the script is able to send to multiple switches by using threading, on each thread a new Switch instance is created, below is the demo.

Script demonstration.

There are two variables for creating vlans, one is the ip address of each switch and another is whether the vlan has name. Naming created vlan in Cisco IOS is optional, and is based on the vlans.xlsx, if there is nothing written in the name column then there is no naming, the vlan column is compulsory and cannot left blank.

This is the excel sheet of vlan creation information.

Handling NaN in excel file

Pandas’ ExcelFile parses empty cell as “nan” object, this is a float type and jinja2 will raise exception if I do not process the nan object properly. One straight forward method I think of is to convert the nan object to “None” object, hence to achieve this I am borrowing numpy to use its nan object.

Below is a code snippet on what I want to achieve.

from numpy import nan
def check_nan(test_obj):
    """
    if there is nan object change to None object, otherwise
    return the original value.
    :param test_obj:
    :return:
    """
    return test_obj if test_obj is not nan else None

This is the dictionary after pandas.ExcelFile parses the excel sheet.

{'vlan': {0: 10, 1: 11, 2: 12}, 'name': {0: 'office', 1: 'front office', 2: nan}}

Notice in column – name – row 3 it is a nan object because it is empty in this cell in the excel sheet.

Jinja2 template

I love to use jinja2 template as it can dynamically changes the configuration based on different supplied data, another template engine is mako which has similar logics and syntax.

You can view the template here.

In order to use it I need to define where to find the template files, by supplying the directory name with FileSystemLoader, then create an environment object, this environment object is then used to create a template object by supplying the template file.

Here is the code snippet.

from jinja2 import Environment, FileSystemLoader
def config_template(template_path="templates", template_file=None):
    env = Environment(loader=FileSystemLoader(template_path))
    return env.get_template(template_file)

Decorator for instance method

A decorator is a wrapper function this is convenient if there is something I need to do before and/or after the execution of the function. There are a lot of examples of decorator but I find the decorator for logging function execution time to be the most practical and easier to understand, another example to understand decorator in a practical way is to observe this netmiko code.

This is the code snippet of decorator, it ensures enable mode is executed before the configuration is sent, and save the configuration after the configuration set is sent.

def config(fn):
    @wraps(fn)
    def wrapper(self, *args, **kwargs):
        self.session.enable()
        result = fn(self, *args, **kwargs)
        self.session.save_config()
        return result

    return wrapper

So to apply the decorator on an instance method I only need to do “@config”.

This is a code snippet of Switch class method to show why config decorator is used.

@config
    def add_vlan(self, xls_filename):
        vlans_config = config_template(template_file="vlans.j2")
        config_dict = xls_to_dict(xls_filename)
        for row in range(len(config_dict["vlan"])):
            if check_nan(config_dict["vlan"][row]) is None:
                raise ValueError("vlan column is compulsory and cannot be left blank.")
            vlan = config_dict["vlan"][row]
            name = check_nan(config_dict["name"][row])
            config = vlans_config.render(vlan=vlan, name=name)
            output = self.session.send_config_set(cmd for cmd in config.splitlines())
            print(output)

Any methods that require enable mode and save configuration will just need to decorate the method with “@config”.

Evaluate each row and convert information to commands

I am finding out the number of rows by getting the length of vlan column, the script extracts the information from each row of vlan and name columns, then use the jinja2 template to render the information extracted.

Here is the code snippet on how this is done.

        for row in range(len(config_dict["vlan"])):
            if check_nan(config_dict["vlan"][row]) is None:
                raise ValueError("vlan column is compulsory and cannot be left blank.")
            vlan = config_dict["vlan"][row]
            name = check_nan(config_dict["name"][row])
            config = vlans_config.render(vlan=vlan, name=name)
            output = self.session.send_config_set(cmd for cmd in config.splitlines())
            print(output)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s