Lab BONUS 01.3 - Using NAPALM with Juniper JUNOS¶
This lab uses multi-vendor NAPALM with Juniper JUNOS in particular.
- Lab BONUS 01.3 - Using NAPALM with Juniper JUNOS
- Task 1 - Basic Configuration Merge with NAPALM
- Task 2 - Declarative Network Configuration with NAPALM for a Configuration Section
- Task 3 - NAPALM Getters
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 JUNOS vMX device.
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.
set snmp community networktocode authorization read-only
set snmp community public authorization read-only
set snmp community private authorization read-write
set snmp community supersecret authorization read-write
set snmp location SYDNEY
set snmp 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 Juniper router in this lab, load the JUNOS driver.
Step 1-5¶
Create a junos device object for vmx1 using the previously loaded driver. Use the variable name device.
Step 1-6¶
Use help on device. You will be able to see all of supported properties and methods of this object.
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.
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.
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 JUNOS, NAPALM depends on the candidate configuration feature, prior to committing the configuration.
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 JUNOS, the candidate configuration database is being used.
In a separate terminal, open an SSH session to vmx1 to verify:
ntc@ntc-training:~$ ssh vmx1
Password:
--- JUNOS 18.2R1.9 Kernel 64-bit JNPR-11.0-20180614.6c3f819_buil
ntc@vmx1> configure
Entering configuration mode
Users currently editing the configuration:
ntc (pid 8530) on since 2021-12-17 16:36:25 UTC, idle 00:00:37
exclusive
The configuration has been changed but not committed
[edit]
ntc@vmx1#
You can then generate a diff on the JUNOS CLI, if desired, using the following command:
ntc@vmx1# show | compare
[edit]
+ snmp {
+ location SYDNEY;
+ contact JOHN_SMITH;
+ community networktocode {
+ authorization read-only;
+ }
+ community public {
+ authorization read-only;
+ }
+ community private {
+ authorization read-write;
+ }
+ community supersecret {
+ authorization read-write;
+ }
+ }
[edit]
Step 1-9¶
Back at the Python shell, commit the config to the device.
This is when the configuration will be activated and committed to the running configuration.
If you wanted to discard this change rather than commit, alternatively you could have run the following:
Step 1-10¶
To rollback to the previous state from Python, 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.
Step 1-11¶
We now want to see how to view diffs directly from Python and not using the JUNOS CLI.
Reload the configuration on the device.
After it's loaded, view the diffs with the compare_config method.
>>> diffs = device.compare_config()
>>> print(diffs)
[edit]
+ snmp {
+ location SYDNEY;
+ contact JOHN_SMITH;
+ community networktocode {
+ authorization read-only;
+ }
+ community public {
+ authorization read-only;
+ }
+ community private {
+ authorization read-write;
+ }
+ community supersecret {
+ authorization read-write;
+ }
+ }
>>>
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 two other JUNOS vMX routers (vmx2 and vmx3).
Use a for loop to build the proper NAPALM device object as well as load and commit the configuration for each JUNOS 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 a Juniper vMX router. 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 vmx2 router.
Load the following BGP configuration onto the device by first going into configuration mode with the configure command, then inputting the commands below, and finally executing the commit command to save the changes:
set routing-options autonomous-system 65512
set protocols bgp group NTC_INTERNAL type internal
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.1
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.4
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.5
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.6
set protocols bgp group NTC_EXTERNAL type external
set protocols bgp group NTC_EXTERNAL peer-as 65515
set protocols bgp group NTC_EXTERNAL neighbor 10.0.0.10
set protocols bgp group NTC_EXTERNAL neighbor 10.0.0.12
You may verify with the following "show" commands:
ntc@vmx2# show protocols bgp
group NTC_INTERNAL {
type internal;
neighbor 10.0.0.1;
neighbor 10.0.0.4;
neighbor 10.0.0.5;
neighbor 10.0.0.6;
}
group NTC_EXTERNAL {
type external;
peer-as 65515;
neighbor 10.0.0.10;
neighbor 10.0.0.12;
}
[edit]
ntc@vmx2# show routing-options autonomous-system
65512;
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.
delete protocols bgp
set protocols bgp group NTC_INTERNAL type internal
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.4
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.5
set protocols bgp group NTC_INTERNAL neighbor 10.0.0.99
set protocols bgp group NTC_EXTERNAL type external
set protocols bgp group NTC_EXTERNAL peer-as 65515
set protocols bgp group NTC_EXTERNAL neighbor 10.0.0.101
set protocols bgp group NTC_EXTERNAL neighbor 10.0.0.102
Take note of the first line
delete protocols bgp. Watch what's going to happen next.
Step 2-4¶
Enter a new Python shell from your working directory, import the junos napalm driver and create an junos device object for vmx2 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('junos')
>>> device = driver('vmx2', 'ntc', 'ntc123')
>>>
Step 2-5¶
Open a connection to the device.
Step 2-6¶
Load the configuration you created in Step 3 onto the device.
All of these steps are no different than Task 1.
Step 2-7¶
Use the compare_config method to show the configuration diffs.
This is where you get to see the real power of JUNOS and NAPALM working together.
>>> print(device.compare_config())
[edit protocols bgp group NTC_INTERNAL]
neighbor 10.0.0.5 { ... }
+ neighbor 10.0.0.99;
- neighbor 10.0.0.1;
- neighbor 10.0.0.6;
[edit protocols bgp group NTC_EXTERNAL]
+ neighbor 10.0.0.101;
+ neighbor 10.0.0.102;
- neighbor 10.0.0.10;
- neighbor 10.0.0.12;
>>>
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 state are the only commands actually being applied.
Step 2-8¶
Finally, commit the config to the device.
Feel free to log back to the device and verify the configuration has been applied correctly.
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.
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:
What object type is it?
Does it store more than the running configuration? Try to print only the "running" configuration now.