Skip to content

Lab BONUS 01.1 - Using NAPALM with Arista

This lab uses multi-vendor NAPALM with Arista in particular.

Task 1 - Basic Configuration Merge with NAPALM

In this task, you will explore working with the NAPALM Python library to perform a configuration merge on an Arista switch.

Step 1-1

Create a new file called snmp.conf in your /home/ntc/labs/python/ directory and open the file in the editor of your choice.

Step 1-2

Take the config snippet below and save it in the file just created (snmp.conf). These commands will be used to directly configure the switches.

snmp-server community networktocode ro
snmp-server community public ro
snmp-server community private rw
snmp-server community supersecret rw
snmp-server location SYDNEY
snmp-server contact JOHN_SMITH

Step 1-3

Enter the Python shell from the same directory.

ntc@ntc-training:python$ python
Python 3.8.12 (default, Oct 13 2021, 09:22:51)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Step 1-4

Load the correct NAPALM driver. Since we're using the Arista switch in this lab, load the eos driver.

>>> from napalm import get_network_driver
>>>
>>> driver = get_network_driver('eos')
>>>

Step 1-5

Create an eos device object for eos-spine1 using the previously loaded driver. Use the variable name device.

>>> device = driver('eos-spine1', 'ntc', 'ntc123')
>>>

Step 1-6

Use help on device. You will be able to see all of supported properties and methods of this object.

>>> help(device)

Just like you've seen with built-in data types, you can use help() and dir() on 3rd party object types too.

There are two primary methods of the device class you'd work with to apply configurations. They are load_merge_candidate and load_replace_candidate.

These are used to load a partial configuration (and merge with the existing configuration) and load a full configuration that will replace the full running configuration, respectively. You'll see these if you look at the full help documentation (the above is just a snippet from the help output).

For this lab, we are using load_merge_candidate.

Step 1-7

Open a connection to the device. This is done using the open() method.

>>> device.open()
>>>

Note: technically this is not required for Arista devices as there is not a persistent connection used to connect to the device as opposed to protocols such as SSH and NETCONF.

Step 1-8

Load the configuration you created in Step 2 onto the device.

This is done by using the load_merge_candiate method of the device object.

>>> device.load_merge_candidate(filename='snmp.conf')
>>>

As soon as you hit enter in this step, NAPALM is loading this configuration onto the device, but NOT committing it to the running configuration. How this happens is different for every OS. For Arista EOS, the feature called configuration sessions is being used.

At this step you can verify the changes manually by going to the device to view the preview of configs to be pushed too. As already stated, in the NAPALM implementation for Arista, the session feature is being used, so first you can use view the pending session using the command show configuration sessions:

ntc@ntc-training:~$ ssh eos-spine1
Password:
eos-spine1#show configuration sessions
Maximum number of completed sessions: 1
Maximum number of pending sessions: 5

  Name             State         User    Terminal
  ------------- ------------- ---------- --------
  napalm_585693    pending

eos-spine1#

You can then generate a diff on the EOS CLI, if desired, using the command:

show session-config named <name> diffs

These commands are exactly what NAPALM does for you.

eos-spine1#show session-config named napalm_585693 diffs
--- system:/running-config
+++ session:/napalm_585693-session-config
@@ -7,7 +7,12 @@
 hostname eos-spine1
 ip domain-name ntc.com
 !
+snmp-server contact JOHN_SMITH
+snmp-server location SYDNEY
 snmp-server community networktocode ro
+snmp-server community private rw
+snmp-server community public ro
+snmp-server community supersecret rw
 !
 spanning-tree mode mstp
 !

eos-spine1#

Step 1-9

Commit the config to the device while back at the Python shell.

This is when the configuration will be activated and committed to the running configuration.

>>> device.commit_config()
>>>

If you wanted to discard this change rather than commit, alternatively you could have run the following:

>>> device.discard_config()
>>>

Step 1-10

To rollback, you use the rollback method.

Feel free to view the config on the CLI of the device before and after you issue the next command.

>>> device.rollback()
>>>

After the rollback, the configuration applied from the local file should be gone.

eos-spine1#sh running-config | i snmp
snmp-server community networktocode ro

Step 1-11

We now want to see how to view diffs directly from Python and not using the Arista CLI.

Reload the configuration on the device.

>>> device.load_merge_candidate(filename='snmp.conf')
>>>

After it's loaded, view the diffs with the compare_config method.

>>> diffs = device.compare_config()
>>>
>>> print(diffs)
@@ -7,7 +7,12 @@
 hostname eos-spine1
 ip domain-name ntc.com
 !
+snmp-server contact JOHN_SMITH
+snmp-server location SYDNEY
 snmp-server community networktocode ro
+snmp-server community private rw
+snmp-server community public ro
+snmp-server community supersecret rw
 !
 spanning-tree mode mstp
 !
>>>

Now NAPALM is generating the diffs using the same CLI command you just tried in Step 8.

Step 1-12

Load the new SNMP configuration on the three other Arista switches.

Use a for loop to build the proper NAPALM device object as well as load and commit the configuration for each EOS switch.

Task 2 - Declarative Network Configuration with NAPALM for a Configuration Section

In this task, you will use NAPALM to declaratively configure BGP on an Arista switch. Normally, NAPALM is known for declaratively managing full configuration files, but we'll show you can still use NAPALM to declaratively manage a single section.

Note: this is 100% dependent on the OS being used.

Step 2-1

Use SSH to manully log to eos-spine1 switch.

Load the following BGP configuration onto the device (open an SSH session, then at the CLI type configure before entering the commands below):

router bgp 65512
   neighbor 10.0.0.0 remote-as 65500
   neighbor 10.0.0.0 maximum-routes 12000
   neighbor 10.0.0.1 remote-as 65512
   neighbor 10.0.0.1 maximum-routes 12000
   network 20.20.20.0/24

Step 2-2

Create a new file called bgp.conf in your working directory and open the file in the text editor of your choice.

Step 2-3

We now want to declaratively manage just BGP. This means we DO NOT CARE what's there. What's in our new bgp.conf should be the only BGP config that ends up on the device.

Take the config below and save it as bgp.conf.

no router bgp 65512
router bgp 65512
   neighbor 10.0.0.2 remote-as 65500
   neighbor 10.0.0.2 maximum-routes 12000
   neighbor 10.0.0.1 remote-as 65512
   neighbor 10.0.0.1 maximum-routes 12000
   neighbor 10.0.0.10 remote-as 65512
   network 100.0.100.0/24

Take note of the first line no router bgp 65512. Watch what's going to happen next.

Step 2-4

Enter a new Python shell from your working directory, import the eos napalm driver and create an eos device object for eos-spine1 just like you already did on Task 1.

ntc@ntc-training:python$ python
Python 3.8.12 (default, Oct 13 2021, 09:22:51)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from napalm import get_network_driver
>>> driver = get_network_driver('eos')
>>> device = driver('eos-spine1', 'ntc', 'ntc123')
>>>

Step 2-5

Open a connection to the device.

>>> device.open()
>>>

Step 2-6

Load the configuration you created in Step 3 onto the device.

All of these steps are no different than Task 1.

>>> device.load_merge_candidate(filename='bgp.conf')
>>>

Step 2-7

Use the compare_config method to show the configuration diffs.

This is where you get to see the real power of EOS and NAPALM working together.

>>> print(device.compare_config())
@@ -62,11 +62,13 @@
 ip routing
 !
 router bgp 65512
-   neighbor 10.0.0.0 remote-as 65500
-   neighbor 10.0.0.0 maximum-routes 12000
    neighbor 10.0.0.1 remote-as 65512
    neighbor 10.0.0.1 maximum-routes 12000
-   network 20.20.20.0/24
+   neighbor 10.0.0.2 remote-as 65500
+   neighbor 10.0.0.2 maximum-routes 12000
+   neighbor 10.0.0.10 remote-as 65512
+   neighbor 10.0.0.10 maximum-routes 12000
+   network 100.0.100.0/24
 !
 management api http-commands
    protocol http
>>>

Notice how the commands aren't just getting pushed. BGP is NOT getting un-configured and re-configured. Simply, the commands required to get BGP into its final are the only commands actually being used EOS.

Step 2-8

Finally, commit the config to the device.

>>> device.commit_config()
>>>

Feel free to log back to the device and verify the configuration has been applied correctly (hint: use the show running-config | section bgp command).

Task 3 - NAPALM Getters

In this task you will now practice using NAPALM getters on several platforms at the same time. Since NAPALM abstracts and normalizes data, you will see how easy it is to work with devices from other vendors using the same language constructs.

Step 3-1

Enter into a new Python shell from your working directory.

ntc@ntc-training:python$ python
Python 3.8.12 (default, Oct 13 2021, 09:22:51)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Step 3-2

Import get_network_driver from napalm and load the drivers for IOS, NXOS, Junos and EOS.

>>> from napalm import get_network_driver
>>>
>>> ios_driver = get_network_driver('ios')
>>> nxos_driver = get_network_driver('nxos')
>>> eos_driver = get_network_driver('eos')
>>>

Step 3-3

Create 3 devices, one for each NAPALM driver you have loaded.

>>> ios_device = ios_driver('csr1', 'ntc', 'ntc123')
>>> nxos_device = nxos_driver('nxos-spine1', 'ntc', 'ntc123')
>>> eos_device = eos_driver('eos-spine1', 'ntc', 'ntc123')
>>>

Step 3-4

Open a NAPALM session for all the devices you have just created.

>>> ios_device.open()
>>> nxos_device.open()
>>> eos_device.open()
>>>

Note: if you get an SSL exception for NXOS here, please connect to the device via ssh and ensure the following command is present: nxapi ssl protocols TLSv1 TLSv1.1 TLSv1.2

Step 3-5

Import json library and pretty print facts for all devices.

>>> import json
>>>
>>> print(json.dumps(ios_device.get_facts(), indent=4))
{
    "uptime": 11280,
    "vendor": "Cisco",
    "os_version": "Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.1.1, RELEASE SOFTWARE (fc3)",
    "serial_number": "9SAGBHTUEE9",
    "model": "CSR1000V",
    "hostname": "csr1",
    "fqdn": "csr1.ntc.com",
    "interface_list": [
        "GigabitEthernet1",
        "GigabitEthernet2",
        "GigabitEthernet3",
        "GigabitEthernet4",
        "GigabitEthernet5",
        "GigabitEthernet6",
        "GigabitEthernet7",
        "GigabitEthernet8",
        "GigabitEthernet9"
    ]
}
>>>
>>> print(json.dumps(nxos_device.get_facts(), indent=4))
{
    "vendor": "Cisco",
    "serial_number": "9X487AEJUEE",
    "model": "Nexus9000 C9300v Chassis",
    "hostname": "nxos-spine1",
    "os_version": "9.3(3)",
    "uptime": 11187,
    "interface_list": [
        "mgmt0",
        "Ethernet1/1",
        "Ethernet1/2",
        "Ethernet1/3",
        "Ethernet1/4",
        "Ethernet1/5",
        "Ethernet1/6",
        "Ethernet1/7",
        "Ethernet1/8",
        "Ethernet1/9",
        "Ethernet1/10",
        "Ethernet1/11",
        "Ethernet1/12",
        "Ethernet1/13",
        "Ethernet1/14",
        "Ethernet1/15",
        "Ethernet1/16",
        "Ethernet1/17",
        "Ethernet1/18",
        "Ethernet1/19",
        "Ethernet1/20",
        "Ethernet1/21",
        "Ethernet1/22",
        "Ethernet1/23",
        "Ethernet1/24",
        "Ethernet1/25",
        "Ethernet1/26",
        "Ethernet1/27",
        "Ethernet1/28",
        "Ethernet1/29",
        "Ethernet1/30",
        "Ethernet1/31",
        "Ethernet1/32",
        "Ethernet1/33",
        "Ethernet1/34",
        "Ethernet1/35",
        "Ethernet1/36",
        "Ethernet1/37",
        "Ethernet1/38",
        "Ethernet1/39",
        "Ethernet1/40",
        "Ethernet1/41",
        "Ethernet1/42",
        "Ethernet1/43",
        "Ethernet1/44",
        "Ethernet1/45",
        "Ethernet1/46",
        "Ethernet1/47",
        "Ethernet1/48",
        "Ethernet1/49",
        "Ethernet1/50",
        "Ethernet1/51",
        "Ethernet1/52",
        "Ethernet1/53",
        "Ethernet1/54",
        "Ethernet1/55",
        "Ethernet1/56",
        "Ethernet1/57",
        "Ethernet1/58",
        "Ethernet1/59",
        "Ethernet1/60",
        "Ethernet1/61",
        "Ethernet1/62",
        "Ethernet1/63",
        "Ethernet1/64",
        "Vlan1"
    ],
    "fqdn": "nxos-spine1.ntc.com"
}
>>>
>>> print(json.dumps(eos_device.get_facts(), indent=4))
{
    "hostname": "eos-spine1",
    "fqdn": "eos-spine1.ntc.com",
    "vendor": "Arista",
    "model": "vEOS",
    "serial_number": "",
    "os_version": "4.22.4M-15583082.4224M",
    "uptime": 10761,
    "interface_list": [
        "Ethernet1",
        "Ethernet2",
        "Ethernet3",
        "Ethernet4",
        "Ethernet5",
        "Ethernet6",
        "Ethernet7",
        "Ethernet8",
        "Ethernet9",
        "Ethernet10",
        "Ethernet11",
        "Ethernet12",
        "Ethernet13",
        "Ethernet14",
        "Ethernet15",
        "Ethernet16",
        "Ethernet17",
        "Ethernet18",
        "Ethernet19",
        "Management1"
    ]
}
>>>

Step 3-6

Print all interfaces for all devices. Output is not shown below due to its length.

>>> print(json.dumps(ios_device.get_interfaces(), indent=4))
>>> print(json.dumps(nxos_device.get_interfaces(), indent=4))
>>> print(json.dumps(eos_device.get_interfaces(), indent=4))
>>>

Pay special attention to the output structure, noting it is the same regardless of the device platform.

Step 3-7

To further drive home the normalization aspect, print out the up/down state of each interface across all three devices using a function.

First, copy/paste the function definition below into the Python interpreter, then use it for each device on their facts dictionary.

def print_up_down_state(device_interfaces):
    for interface in device_interfaces:
        state = "up" if device_interfaces[interface]['is_up'] else "down"
        print(f"Interface {interface} is {state}")
>>> def print_up_down_state(device_interfaces):
...     for interface in device_interfaces:
...         state = "up" if device_interfaces[interface]['is_up'] else "down"
...         print(f"Interface {interface} is {state}")
...
>>> print_up_down_state(ios_device.get_interfaces())
Interface GigabitEthernet1 is up
Interface GigabitEthernet2 is up
Interface GigabitEthernet3 is up
Interface GigabitEthernet4 is up
Interface GigabitEthernet5 is up
Interface GigabitEthernet6 is down
Interface GigabitEthernet7 is down
Interface GigabitEthernet8 is down
Interface GigabitEthernet9 is down

>>> print_up_down_state(nxos_device.get_interfaces())
Interface Ethernet1/1 is up
Interface Ethernet1/2 is up
Interface Ethernet1/3 is up
Interface Ethernet1/4 is up
Interface Ethernet1/5 is up
... OUTPUT SNIPPED ...

>>> print_up_down_state(eos_device.get_interfaces())
Interface Management1 is up
Interface Ethernet8 is up
Interface Ethernet9 is up
Interface Ethernet2 is up
... OUTPUT SNIPPED ...

Step 3-8

Finally, try printing just the "running" configuration of each device using the get_config() method:

>>> ios_config = ios_device.get_config()
>>> print(ios_config)

What object type is it?

Does it store more than the running configuration? Try to print only the "running" configuration now.