Ansible and IOSXE - NAPALM
Introduction
In this blog post, we used Ansible to interact with our IOS XE devices through a module called ios-modules
. We followed up with using the netconf
module for Ansible, check this post. In this post, we will use Ansible with NAPALM. Have a look at this Github project.
For all the examples, we will use a Cisco sandbox environment delivered by Cisco Devnet. To get a list of all sandboxes, check out this link. For this blog post, we will use the IOS XE sandbox.
Installation
Installation is pretty simple.
wauterw@WAUTERW-M-65P7 napalm % pip3 install napalm-ansible
wauterw@WAUTERW-M-65P7 napalm % pip3 install napalm
I got some issues after installing the napalm-ansible, as my Python installation could not find the proper library in the site-packages. This was mainly because I run multiple versions of Python on my laptop and it kind of messed up a little bit. Therefore, it’s import you know the sites-packages directory that Napalm and Napalm-Ansible got installed.
It’s important to follow the Configuring Ansible
instructions on the napalm-ansible Github repo. It mentions there that you need to create an ansible.cfg file and specify the library
and action_plugins
variables. Here’s what my file looks like.
[defaults]
library = /usr/local/lib/python3.7/site-packages/napalm_ansible/modules
action_plugins = /usr/local/lib/python3.7/site-packages/napalm_ansible/plugins/action
hostfile = hosts
host_key_checking = False
deprecation_warnings=False
stdout_callback = skippy
[persistent_connection]
connect_timeout = 100
command_timeout = 80
NAPALM-Ansible functions
NAPALM-Ansible module primarily works with 3 functions:
- napalm_get_facts: wrapper around the NAPALM getter methods, it users a filter to select only the items that you are interested in
- napalm_install_config: wrapper around all the NAPLM configuration options such as merging and replacing a configuration, getting the diff of a change and commit/roll back of a change
- napalm_validate: uses a YAML or JSON file (with intended configuration) to compare against the actual configuration
Retrieve information from IOSXE device
Have a look at the information for napalm_get_facts
. It allows you to gather facts from a network device through NAPALM. Here’s an example:
---
- name: Retrieve interfaces
hosts: iosxe
gather_facts: no
tasks:
- name: get facts from device
napalm_get_facts:
username: "{{ ansible_user }}"
password: "{{ ansible_ssh_pass }}"
filter: facts
optional_args:
port: 8181
register: result
- name: print data
debug: var=result
When we run the above script, we see the following output.
wauterw@WAUTERW-M-65P7 napalm % ansible-playbook -i hosts get_facts.yml
PLAY [Retrieve interfaces] *********************************************************************************************
TASK [get facts from device] *******************************************************************************************
ok: [ios-xe-mgmt-latest.cisco.com]
TASK [print data] ******************************************************************************************************
ok: [ios-xe-mgmt-latest.cisco.com] => {
"result": {
"ansible_facts": {
"napalm_facts": {
"fqdn": "csr1000v-1.abc.inc",
"hostname": "csr1000v-1",
"interface_list": [
"GigabitEthernet1",
"GigabitEthernet2",
"GigabitEthernet3",
"Loopback21",
"Loopback1001"
],
"model": "CSR1000V",
"os_version": "Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.11.1a, RELEASE SOFTWARE (fc1)",
"serial_number": "9EFJDZV5LZF",
"uptime": 59520,
"vendor": "Cisco"
},
"napalm_fqdn": "csr1000v-1.abc.inc",
"napalm_hostname": "csr1000v-1",
"napalm_interface_list": [
"GigabitEthernet1",
"GigabitEthernet2",
"GigabitEthernet3",
"Loopback21",
"Loopback1001"
],
"napalm_model": "CSR1000V",
"napalm_os_version": "Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.11.1a, RELEASE SOFTWARE (fc1)",
"napalm_serial_number": "9EFJDZV5LZF",
"napalm_uptime": 59520,
"napalm_vendor": "Cisco"
},
"changed": false,
"failed": false
}
}
PLAY RECAP *************************************************************************************************************
ios-xe-mgmt-latest.cisco.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
We get a nice overview of the basic information of our IOS device but this time through the NAPALM module. In case you want to retrieve a list of interfaces, change the following line:
filter: facts
to
filter: facts, interfaces
As such, you will receive a list of the configured interfaces as well.
Other paramets you could use for filter are: mac_address_table, arp_table, bgp_config, bgp_neighbors, bgp_neighbors_detail, interfaces_counters, interfaces_ip, lldp_neighbors, lldp_neighbors_detail, network_instances, ntp_servers, ntp_stats, users…and some more.
Execute commands
Have a look at the information for napalm_cli
module. It allows you to execute commands on your IOS XE device. Have a look at the below script which executes two simple commands.
---
- name: Execute commands
hosts: iosxe
gather_facts: no
tasks:
- name: Execute commands
napalm_cli:
hostname: "{{ inventory_hostname }}"
username: "{{ ansible_user }}"
password: "{{ ansible_ssh_pass }}"
dev_os: "ios"
optional_args:
port: 8181
args:
commands:
- show version
- show ip interface brief
register: result
- name: print data
debug: var=result
And you will get the following output. Note that I truncated the output quite a bit.
wauterw@WAUTERW-M-65P7 napalm % ansible-playbook -i hosts show_command.yml
PLAY [Configure loopback using ios_interface] **************************************************************************
TASK [get facts from device] *******************************************************************************************
ok: [ios-xe-mgmt-latest.cisco.com]
TASK [print data] ******************************************************************************************************
ok: [ios-xe-mgmt-latest.cisco.com] => {
"result": {
"changed": false,
"cli_results": {
"show ip interface brief": "Interface IP-Address OK? Method Status Protocol\nGigabitEthernet1 10.10.20.48 YES NVRAM up up \nGigabitEthernet2 172.16.31.202 YES other administratively down down \nGigabitEthernet3 unassigned YES NVRAM administratively down down \nLoopback1001 10.111.100.3 YES other up up",
"show version": "Cisco IOS XE Software,
***Truncated***"
},
"failed": false
}
}
PLAY RECAP *************************************************************************************************************
ios-xe-mgmt-latest.cisco.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Create loopback interfaces
In this section, we will focus on adding some Loopback interfaces. We will use NAPALM’s merge function for that. Again, a similar example was discussed in an earlier post although this was done through Python. Here we will use the ansbible-napalm module.
Let’s create a loopbacks.txt file. This file contains the configuration for the interfaces:
interface Loopback100
description LOOPBACK 100
ip address 4.4.4.101 255.255.255.255
!
interface Loopback200
description LOOPBACK 100
ip address 1.1.1.20 255.255.255.255
end
Next, let’s have a look at the Ansible playbook. Again, Napalm-Ansible module makes it fairly straightforward for us.
---
- name: Configure loopback using ios_interface
hosts: iosxe
gather_facts: no
tasks:
- name: create loopback interface
napalm_install_config:
hostname: "{{ inventory_hostname }}"
username: "{{ ansible_user }}"
password: "{{ ansible_ssh_pass }}"
dev_os: "ios"
optional_args:
port: 8181
config_file: 'loopbacks.txt'
commit_changes: true
replace_config: false
The important part is that we point to our loopbacks.txt file we created earlier. The commit_changes
will allow us to merge/replace the configuration. If set to true, we will merge the loopbacks information with the existing configuration. The replace_config
is set to false, which means we will not replace the entire configuration. The get_diffs
will indicate that we want a diff to be generated between the existing and the new configuration. The diff_file
will let you select the path where the diff file will be stored.
Before we continue, let’s see the current overview of configured interfaces on our device.
csr1000v-1#show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 10.10.20.48 YES NVRAM up up
GigabitEthernet2 unassigned YES NVRAM administratively down down
GigabitEthernet3 unassigned YES NVRAM administratively down down
When we execute the ansible playbook, we get the following:
wauterw@WAUTERW-M-65P7 napalm % ansible-playbook -i hosts create_loopback.yml
PLAY [Configure loopback using ios_interface] **************************************************************************
TASK [create loopback interface] ***************************************************************************************
changed: [ios-xe-mgmt-latest.cisco.com]
PLAY RECAP *************************************************************************************************************
ios-xe-mgmt-latest.cisco.com : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
When we look in our device, we will see the following:
csr1000v-1#show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 10.10.20.48 YES NVRAM up up
GigabitEthernet2 unassigned YES NVRAM administratively down down
GigabitEthernet3 unassigned YES NVRAM administratively down down
Loopback100 4.4.4.101 YES TFTP up up
Loopback200 1.1.1.20 YES TFTP up up
And also, you’ll notice a diff file has been created (in the current directory) with the following contents:
+interface Loopback100
+description LOOPBACK 100
+ ip address 4.4.4.101 255.255.255.255
+interface Loopback200
+description LOOPBACK 100
+ ip address 1.1.1.20 255.255.255.255
NAPALM Validation
We discussed NAPALM validation already in this post. The idea is to check or validate the configuration on our device. We learned that we need to create a validation file which contains the proper values. In our case, we would want to validate whether the device is running a particular software version and we would like to validate the IP address of the loopback interface. Let’s do this first, the validation file looks as follows:
---
- get_facts:
os_version: 16.11.1a
vendor: Cisco
- get_interfaces_ip:
Loopback100:
ipv4:
4.4.4.101:
prefix_length: 32
In this post, we would obviously like to use Napalm-ansible module. The playbook is in fact very straightforward:
---
- name: Validate command
hosts: iosxe
gather_facts: no
tasks:
- name: NaPalm Validation
napalm_validate:
hostname: "{{ inventory_hostname }}"
username: "{{ ansible_user }}"
password: "{{ ansible_ssh_pass }}"
dev_os: "ios"
optional_args:
port: 8181
validation_file: "{{ validate_file }}"
You’ll notice that we reference the {{ validate_file }}
variable so do not forget to add this variable in the group_vars/iosxe.yaml file.
host_key_checking : False
ansible_user: ***
ansible_ssh_pass: ***
ansible_port: 8181
ansible_python_interpreter: /usr/local/bin/python3
ansible_network_os: ios
ansible_connection: network_cli
validate_file: "validation.yml"
Let’s execute this playbook next.
wauterw@WAUTERW-M-65P7 napalm % ansible-playbook -i hosts validate_command.yml
PLAY [Validate command] ************************************************************************************************
TASK [NaPalm Validation] ***********************************************************************************************
ok: [ios-xe-mgmt-latest.cisco.com]
PLAY RECAP *************************************************************************************************************
ios-xe-mgmt-latest.cisco.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You will notice the validation was successful. Let’s change the value of the IP address inside the validation.yml file. We will change it from 4.4.4.101 to 4.4.4.102 so we expect the validation to fail. Let’s have a look:
wauterw@WAUTERW-M-65P7 napalm % ansible-playbook -i hosts validate_command.yml
PLAY [Validate command] ************************************************************************************************
TASK [NaPalm Validation] ***********************************************************************************************
fatal: [ios-xe-mgmt-latest.cisco.com]: FAILED! => {"changed": false, "compliance_report": {"complies": false, "get_facts": {"complies": true, "extra": [], "missing": [], "present": {"os_version": {"complies": true, "nested": false}, "vendor": {"complies": true, "nested": false}}}, "get_interfaces_ip": {"complies": false, "extra": [], "missing": [], "present": {"Loopback100": {"complies": false, "diff": {"complies": false, "extra": [], "missing": [], "present": {"ipv4": {"complies": false, "diff": {"complies": false, "extra": [], "missing": ["4.4.4.102"], "present": {}}, "nested": true}}}, "nested": true}}}, "skipped": []}, "msg": "Device does not comply with policy"}
PLAY RECAP *************************************************************************************************************
ios-xe-mgmt-latest.cisco.com : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
In the above output, you will notice that the compliance status is false as expected.
Check out my Github repo for these examples. Hope to see you back soon.