My friend Netmiko

Ansible with crutch can automate the network and non-CloudEngine of Huawei switches, as recently proven in our Enterprise Forum. However, on a network that has different switch models, Ansible does not seem to be an effective tool at the moment. And despite undeniable improvement the quality of the Python code for Telnet, this script also did not work for a number of reasons.

I wanted to find a simple solution that could work in a hybrid network with different Huawei switch models: connect via SSH and solve real-world configuration tasks. In this case, the code could be operated by a person who is not versed in programming like me: change and adapt the script for his tasks, using open sources. Netmiko came to the rescue.

As a test bench, I put together the topology in eNSP of four Huawei loopback switches simulating the aggregation level with STP… I set the task – to forward several additional VLANs through this ring, adding them to the trunk on the ports, while issuing the verification command before and after the configuration roll-over to make sure that nothing has broken. The topology is as follows:

Topology in eNSP for testing Python script
Topology in eNSP for testing Python script

As a platform for automation, I used Ubuntu 20.04.2 for Windows, Python 3.8.5 and the main player – Netmiko 3.4.0.

Netmiko is a multi-vendor library that is based on the Paramiko SSH library and makes it easy to connect to network devices. Paramiko also allows you to establish secure connections with devices, but its use is considered more difficult, but I tried to complete the task in the shortest way.

Netmiko is an open source library, all details about it (including a manual and example scripts) are available at Github

Since Netmiko is a multi-vendor library, it needs to know to connect to which device it will use and select the appropriate class for it. The function that will do this is called ConnectHandler. First I imported it:

from netmiko import ConnectHandler

The ConnectHandler function looks at the value of the ‘device_type’ variable. Device types supported by Netmiko can be found in ssh_dispatcher.py For more information, see CLASS_MAPPER_BASE.

The type of device I was interested in was called huawei.

All other variables were known, namely the ip of the network device, username, password. Therefore, it was possible to create a Python dictionary, naming it according to the network name CE_1_BORDER – the main switch of the ring, as indicated in the topology, and define the values ​​of all variables in it:

from netmiko import ConnectHandler
 
CE_1_BORDER = {
    'device_type': 'huawei',
    'ip':   '7.7.7.1',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

At this point, the script should have been able to establish an SSH connection to the device. It remains to call the ConnectHandler function and transfer to it the CE_1_BORDER dictionary with the parameters of the device to which it will connect:

ssh_connect = ConnectHandler(**CE_1_BORDER)

Or (alternative way) to do without the dictionary:

ssh_connect = ConnectHandler(device_type="huawei", ip='7.7.7.1', username="vasyo1", password='@ghjcnjnF358986')

For me with a dictionary, the script looks more structured and understandable.

Now you could send a command over the SSH connection and get the output back. Here I used the method .send_command () to send one command ‘display stp brief’ and function print () to display the received data. To send multiple commands, you need to use a different method.

output = ssh_connect.send_command('display stp brief')
print(output)

The easiest way to add additional devices is to define them as dictionaries, and then list them as a Python list:

from netmiko import ConnectHandler

CE_1_BORDER = {
    'device_type': 'huawei',
    'ip':   '7.7.7.1',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_2 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.2',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_3 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.3',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_4 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.4',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

all_devices = [CE_1_BORDER, CE_2, CE_3, CE_4]

After I applied the loop forwhich will repeat the same operation for all these devices: SSH connecting to the device, executing the ‘display stp brief’ command, and displaying the output.

for device in all_devices:
    ssh_connect = ConnectHandler(**device)
    output = ssh_connect.send_command('display stp brief')

If you run the script in this form, then the output will be displayed in solid form and it will be difficult to understand where the end of the output of the first switch and the beginning of the second are. The best solution would be to indicate the name of the switch at the beginning of each output, but I did not figure out how to do this yet, and instead indicated the ip of the switch using the function print (f).

for device in all_devices:
    ssh_connect = ConnectHandler(**device)
    output = ssh_connect.send_command('display stp brief')
    print(f"nn-------------- Device {device['ip']} --------------")
    print(output)
    print("-------------------- End -------------------")

In curly braces, I indicated the variable, the value of which I wanted to substitute. In this case, the variable ‘device’ is equal to the variable ‘all_devices’, which, in turn, contains a list of dictionaries, and since the print (f) function is in the for loop (offset by 4 spaces), then for each output will be substituted by the order of the value of the ‘ip’ variable.

n n – will create two blank lines between Device and End. If you make one n, then there will be one blank line.

Running the ‘display stp brief’ command is only needed to capture the state of the network prior to making any changes. Instead, of course, you can use any other command.

After that, I defined a list of operations that I needed to perform. It:

  1. Create VLANs 300 and 301 and assign them names (description).

  2. Register the created VLANs on the ports (add to the trunk).

  3. Apply configuration (commit).

Before adding commands to the script, I made sure that they work “in manual mode”:

#
vlan 300
description NETMIKO_VLAN 300
#
vlan 301
description NETMIKO_VLAN 301
#
int range GE 1/0/9 GE 1/0/10
port trunk allow-pass vlan 300 301
#
commit
#

To create multiple VLANs, I used a loop for, and to apply a list of commands (rather than a single command), the method .send_config_set ():

for device in all_devices:
    ssh_connect = ConnectHandler(**device)
    output = ssh_connect.send_command('display stp brief')
    print(f"nn-------------- Device {device['ip']} --------------")
    print(output)

    for n in range (300,302):
        print ("Creating VLAN " + str(n))
        config_commands = [
                          'vlan ' + str(n),
                          'desc NETMIKO_VLAN ' + str(n),
                          'Commit'
        ]
        output = ssh_connect.send_config_set(config_commands)

    output = ssh_connect.send_config_set(
        [
        'interface range GE 1/0/9 GE 1/0/10',
        'port trunk allow-pass vlan 300 301',
        'commit'
        ]
    )
    print(output)
    print("-------------------- End -------------------")

In this form, the script should first show the output of the ‘display stp brief’ command, then apply the configuration commands and complete the process. However, I would like to see the output of the ‘display stp brief’ command again after configuring to make sure the network is not broken. The most beautiful thing would be to write such a short rule, which at the end would run the same section of the script again, but I haven’t figured out how to do this yet, so I primitively inserted a piece of code with ‘display stp brief’ at the end of the script:

for device in all_devices:                                     
ssh_connect = ConnectHandler(**device)
output = ssh_connect.send_command('display stp brief')
    print(f"nn-------------- Device {device['ip']} --------------")
    print(output)
    print("-------------------- End -------------------")

The resulting code looks like this:

from netmiko import ConnectHandler

CE_1_BORDER = {
    'device_type': 'huawei',
    'ip':   '7.7.7.1',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_2 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.2',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

CE_3 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.3',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'        
}

CE_4 = {
    'device_type': 'huawei',
    'ip':   '7.7.7.4',
    'username': 'vasyo1',
    'password': '@ghjcnjnF358986'
}

all_devices = [CE_1_BORDER, CE_2, CE_3, CE_4]

for device in all_devices:
    ssh_connect = ConnectHandler(**device)
    output = ssh_connect.send_command('display stp brief')
    print(f"nn-------------- Device {device['ip']} --------------")
    print(output)

    for n in range (300,302):
        print ("Creating VLAN " + str(n))
        config_commands = [
                          'vlan ' + str(n),
                          'desc NETMIKO_VLAN ' + str(n),
                          'Commit'
        ]
        output = ssh_connect.send_config_set(config_commands)

    output = ssh_connect.send_config_set(
        [
        'interface range GE 1/0/9 GE 1/0/10',
        'port trunk allow-pass vlan 300 301',
        'commit'
        ]
    )
    print(output)
    print("-------------------- End -------------------")

for device in all_devices:
    ssh_connect = ConnectHandler(**device)
    output = ssh_connect.send_command('display stp brief')
    print(f"nn-------------- Device {device['ip']} --------------")
    print(output)
    print("-------------------- End -------------------")

To run the script, it was required to install the netmiko module.

I applied the command:

pip3 install -U netmiko

Created a file called netmiko10.py using nano editor:

nano netmiko10.py

And ran the script:

python3 netmiko10.py

The following output followed (I give an example of outputting only CE_4, otherwise it will be too long):

Output of the script execution result on the CE_4 switch
Output of the script execution result on the CE_4 switch

As you can see, the script behaved exactly as it was set: at first it showed a dividing line indicating the ip-address of the switch, then the output of the ‘display stp brief’ command, which made it possible to make sure that the GE1 / 0/10 port was in a blocked (discarding ) state, then two VLANs are created and added to the trunk on the specified ports and, finally, the configuration is applied with the ‘commit’ command. The ‘return’ command is automatically applied by the netmiko library according to the given device type (huawei): it returns the custom view from the configuration mode. A dividing line with the word End indicates that the cycle for the specified device has ended.

However, it was impossible not to notice that only two VLANs were created: 300 and 301, although the range (300,302) function indicated from 300 to 302. The fact is that the function range () consists of two configurable parameters:

  1. range (stop)where stop is the number of integers to generate, starting at zero, e.g. range (3) == [0, 1, 2]…

  2. range ([start], stop[, step]), where start is the first number in the sequence, stop is the number up to which the value is generated without including it, and step is the difference between each number in the sequence.

So the function range (300,302) meant to start generating a sequence of numbers at 300 and end 302 without including it.

When the cycle for all devices ended, then only the ‘display stp brief’ command was launched for all devices and output in this form:

Display stp brief output for all devices
Display stp brief output for all devices

According to the output of Device 7.7.7.4, it was clear that the GE1 / 0/10 port remained in the same discarding state as before the configuration roll-over, which means that everything went well.

What improvements would I like to make:

  1. Apply an inventory file in which to list the ip-addresses of all network devices, rather than create a dictionary for each of them.

  2. A short command to rerun the ‘display stp brief’ code instead of writing the whole code again.

  3. Apply some commands for some network devices, and different commands for other network devices. In the real world, the port numbers on switches are likely to be different, so you won’t be able to apply the same config to all devices.

But these improvements are already for the next post!

Resources:

https://pynet.twb-tech.com/blog/automation/netmiko.html

https://pyneng.readthedocs.io/en/latest/book/18_ssh_telnet/netmiko.html

https://github.com/ktbyers/netmiko

https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py

Udemy.com – Python Network Programming for Network Engineers (Python 3) (David Bombal)

https://www.pythoncentral.io/pythons-range-function-explained/

Similar Posts

Leave a Reply