NAPALM Multivendor Config
Tutorials

NAPALM Network Automation Python: Making Configurations in a Multivendor Network

Introduction

In this article we will learn about the NAPALM functions and methods to configure routers and switches in a multi-vendor network. We will also make use of two important tools used in automation, Jinja and YAML. This is a continuation of the article in which Collecting data in a multi-vendor network and is part of the adventure in which I want to show you how you can implement Network Programmability and Automation with Python and the NAPALM library.

The main content is in the following video. So start here so that you can understand some sections of the article.

NAPALM Python

It is in this section where we will begin to see and enjoy the benefits of using NAPALM for programmability and network automation in Cisco and Huawei devices. And it is where I basically share the code of what was seen in the video.

Jinja Template y YAML

Jinja is basically an engine used to create templates in Python. With Jinja we plan to create the configuration templates for our routers.

YAML on the other hand is a standard for serializing data in a human-readable form. With YAML we plan to define the parameters or values that we will use in the Jinja templates.

Let’s see it to make it easier to understand.

Cisco_template.yml

interfaces:
    GigabitEthernet0/1:
       description: "to_VRP-R2 Eth1/0/1 External"
       ipv4_addr: 172.17.3.1
       ipv4_mask: 255.255.255.252
bgp:
    as: 300
    neighbors:
        vrp_router:
            as: 200
            ipv4_addr: 172.17.3.2

Huawei_template.yml

interfaces:
    Ethernet1/0/1:
       description: "to_IOS-R3 Gi0/1 External"
       ipv4_addr: 172.17.3.2
       ipv4_mask: 255.255.255.252
bgp:
    as: 200
    neighbors:
        ios_router:
            as: 300
            ipv4_addr: 172.17.3.1

Cisco_template.j2

{% for if_name,if_data in interfaces.items() %}
interface {{if_name}}
 description {{if_data.description}}
 ip address {{if_data.ipv4_addr}} {{if_data.ipv4_mask}}
 no shutdown
{% endfor %}
!
router bgp {{bgp.as}}
 neighbor {{bgp.neighbors.vrp_router.ipv4_addr}} remote-as {{bgp.neighbors.vrp_router.as}}
 !
 address-family ipv4
  neighbor {{bgp.neighbors.vrp_router.ipv4_addr}} activate
 exit-address-family

Huawei_template.j2

{% for if_name,if_data in interfaces.items() %}
interface {{if_name}}
 description {{if_data.description}}
 ip address {{if_data.ipv4_addr}} {{if_data.ipv4_mask}}
 undo shutdown
 undo dcn
{% endfor %}
#
bgp {{bgp.as}}
 peer {{bgp.neighbors.ios_router.ipv4_addr}} as-number {{bgp.neighbors.ios_router.as}}
 #
 ipv4-family unicast
  peer {{bgp.neighbors.ios_router.ipv4_addr}} enable
#

In the video you will find the explanation of this code.

NAPALM Python: Config Code 

What we will do now is that we will edit the script that we wrote in the previous video for the iOS and IOS-XR and Huawei VRP routers.

import napalm
from tabulate import tabulate
from jinja2 import Environment, FileSystemLoader
import yaml
def main():
    driver_ios = napalm.get_network_driver("ios")
    driver_iosxr = napalm.get_network_driver("iosxr")
    driver_vrp = napalm.get_network_driver("ce")
    device_list = [["vrp-r2", "vrp", "router"],["ios-r3", "ios", "router"]]
   # device_list = [["ios-sw2","ios", "switch"],["iosxr-r1", "iosxr", "router"],
   # ["ios-r3", "ios", "router"],["vrp-r2", "vrp", "router"],["vrp-sw1", "vrp", "switch"]]
    network_devices = []
    for device in device_list:
        if device[1] == "ios":
            network_devices.append(
                            driver_ios(
                            hostname = device[0],
                            username = "codingnetworks",
                            password = "Coding.Networks1"
                            )
                              )
        elif device[1] == "iosxr":
            network_devices.append(
                            driver_iosxr(
                            hostname = device[0],
                            username = "codingnetworks",
                            password = "Coding.Networks1"
                            )
                              )
        elif device[1] == "vrp":
            network_devices.append(
                            driver_vrp(
                            hostname = device[0],
                            username = "codingnetworks",
                            password = "Coding.Networks1"
                            )
                              )
    devices_table = [["hostname", "vendor", "model", "uptime", "serial_number"]]
    
    for device in network_devices:
        print("Connecting to {} ...".format(device.hostname))
        device.open()
        print("Getting device facts")
        device_facts = device.get_facts()
        devices_table.append([device_facts["hostname"],
                              device_facts["vendor"],
                              device_facts["model"],
                              device_facts["uptime"],
                              device_facts["serial_number"]
                              ])
        #Testing Getters
        device.load_merge_candidate(filename=None, config=get_template_config(device_facts["vendor"]))
        print("\nDiff:")
        print(device.compare_config())
        # You can commit or discard the candidate changes.
        try:
            choice = raw_input("\nWould you like to commit these changes? [yN]: ")
        except NameError:
            choice = input("\nWould you like to commit these changes? [yN]: ")
        if choice == "y":
            print("Committing ...")
            device.commit_config()
        else:
            print("Discarding ...")
            device.discard_config()
        device.close()
        print("Done.")
    print(tabulate(devices_table, headers="firstrow"))
def get_template_config(vendor):
    #This loads data from YAML into Python dictionary
    config_data = yaml.load(open('{}_template.yml'.format(vendor)), Loader=yaml.FullLoader)
    #This line uses the current directory and loads the jinja2 template
    env = Environment(loader = FileSystemLoader('.'), trim_blocks=True, lstrip_blocks=True)
    template = env.get_template('{}_template.j2'.format(vendor))
    #Return the template with data
    print(template.render(config_data))
    return(template.render(config_data))
if __name__ == '__main__':
    main()

In the video you will find the explanation of this code.

Let’s see the output of this code:

Connecting to vrp-r2 ...
Getting device facts
interface Ethernet1/0/1
 description to_IOS-R3 Gi0/1 External
 ip address 172.17.3.2 255.255.255.252
 undo shutdown
 undo dcn
#
bgp 200
 peer 172.17.3.1 as-number 300
 #
 ipv4-family unicast
  peer 172.17.3.1 enable
#

Diff:
 description to_IOS-R3 Gi0/1 External
 ip address 172.17.3.2 255.255.255.252
 peer 172.17.3.1 as-number 300
  peer 172.17.3.1 enable

Would you like to commit these changes? [yN]: y
Committing ...
Done.
Connecting to ios-r3 ...
Getting device facts
interface GigabitEthernet0/1
 description to_VRP-R2 Eth1/0/1 External
 ip address 172.17.3.1 255.255.255.252
 no shutdown
!
router bgp 300
 neighbor 172.17.3.2 remote-as 200
 !
 address-family ipv4
  neighbor 172.17.3.2 activate
 exit-address-family

Diff:
+interface GigabitEthernet0/1
+ description to_VRP-R2 Eth1/0/1 External
+ ip address 172.17.3.1 255.255.255.252
- no shutdown
+router bgp 300
+ neighbor 172.17.3.2 remote-as 200
+ address-family ipv4
+  neighbor 172.17.3.2 activate

Would you like to commit these changes? [yN]: y
Committing ...
Done.
hostname    vendor    model      uptime  serial_number
----------  --------  -------  --------  ---------------------
VRP-R2      Huawei    NE40E        1020  []
IOS-R3      Cisco     IOSv         1140  95ZAVRE4Q305F6ET85347

Conclusion

Let’s appreciate how with Network Programmability we configure interfaces and a BGP peering. For a single Peering it is probably faster to do this configuration via the command line.

The truth is that this example makes sense when we have to make massive configurations. In addition to being faster, the use of Jinja and YAML templates reduces the risk of human errors and maintains consistency in configurations.

By Michael Alvarez