
This topic came up via Twitter recently and I heard this use case before but wasn’t aware how easy it could be solved with Ansible, until I started thinking about it. The little playbook in this blogpost fetches all discovered neighbors per device and sets the interface description according to the remote host and port. It supports the two platforms Cisco IOS XE and NX-OS to demonstrate the path to a multivendor solution for the common brownfield networks out there.
Prerequisits
You need a working Ansible Installation and most of the time a dedicated Control Node is very useful. I personally use a CentOS VM, so in this case a simple
$ sudo yum install ansible
gets the job done, including all dependencies. Of cause, there should be ssh access to the control node and from the control node to all network devices.

The required Ansible Inventory of my little VirtualBox powered homelab is located at the control node in the /etc/ansible/hosts path and looks like this.
[ios]
CSR-1 ansible_host=192.168.10.211 ansible_network_os=ios
CSR-2 ansible_host=192.168.10.212 ansible_network_os=ios
[nxos]
N9K-1 ansible_host=192.168.10.221 ansible_network_os=nxos
There is no need to provide credentials when the playbook is executed via the -k option, it then uses the local user and prompts for the password.
Ansible Facts
The Ansible facts function is automatically called by playbooks to gather useful variables about remote hosts, it just has to be turned on in the playbook header.
---
- name: Set Interface description based on CDP/LLDP discovery
hosts: all
gather_facts: yes
connection: network_cli
All the data is stored in an ansible_facts object per remote host and can be referenced as structured JSON by every task during the playbook runtime. All kinds of useful device facts are collected, like OS version, S/N, all Interfaces including configuration, and so on. The interesting part with regards to the neighbor discovery can be found in the dictionary called net_neighbors. It shows only Interfaces with discovered neighbors via CDP or LLDP.
{
"net_neighbors": {
"GigabitEthernet2": [
{
"host": "CSR-2.example.com",
"port": "GigabitEthernet2"
}
],
"GigabitEthernet3": [
{
"host": "CSR-2.example.com",
"port": "GigabitEthernet3"
}
]
}
}
Cisco IOS-XE and NX-OS Tasks
To set the right interface description the task has to loop over the net_neighbors dictionary and use the ios_config or nxos_config module to execute the configuration change.
tasks:
- name: Set IOS Interface Description
ios_config:
lines:
- description Connected to {{ item.value[0].host }} via its {{ item.value[0].port }}
parents: interface {{ item.key }}
save_when: changed
with_dict: "{{ansible_facts.net_neighbors }}"
when: ansible_network_os == 'ios'
- name: Set NXOS Interface Description
nxos_config:
lines:
- description Connected to {{ item.value[0].host }} via its {{ item.value[0].port }}
parents: interface {{ item.key }}
save_when: changed
with_dict: "{{ansible_facts.net_neighbors }}"
when: ansible_network_os == 'nxos'
The Interface name can be referenced via the dynamic variable {{item.key}} which translates to ‘GigabitEthernet2‘ for the first item of the loop. The second level of the dictionary contains a list of key-value pairs, namely the remote host and port name, and can be referenced by {{item.value[0].host}}, ‘CSR-2.example.com‘ for instance.
The save_when: changed parameter stores the running-config, but only when changes to the interface configuration happend in this task.
Playbook Run
Given that CSR-1 has the following ‘default’ interface descriptions.

The Playbook is available for download via Github:
wget https://raw.githubusercontent.com/NWMichl/network-automation/master/fix_ifdesc.yml
$ ansible-playbook -k fix_ifdesc.yml

Et voila, CSR-1 has the desired Interface description as expected.

Caveat with the Multiprotocol Use Case
Despite the Ansible documentation the destinction between CDP and LLDP doesn’t happen on a port level, but seemingly on a device level with CDP superseeding LLDP. Meaning, that you might miss the local interface description of an LLDP only remote device. As most networks I know run
- CDP only
- CDP and LLDP (for server connectivity or non Cisco VoIP phones)
- LLDP only
the playbook should at least do the job Pareto style (80%/20%). But I think this should be fixed in future releases and I’ll raise an issue with the Ansible maintainers.
Closing
So it’s 2020 and really nobody has to fix broken interface namings by hand, there are plenty of viable solutions out there, this Ansible playbook is only one of them. John (@ipvZero on the Twitters) did a similar thing via Python / Nornir in a more flexible way and at his usual scale.
Is there a way to just specify this to trunk port?
Hi Richard,
I just found time to have a look at your question and yes, this is actually quite simple!
The ansible_facts.net_interfaces include the interface mode (access / trunk):
net_interfaces:
Ethernet1/10:
bandwidth: ‘10000000’
duplex: auto
macaddress: 0800.2728.cb45
mode: access
mtu: ‘1500’
speed: auto-speed
state: down
type: 100/1000/10000 Ethernet
So the task conditional has to look like
when: ansible_network_os == ‘nxos’ and ansible_facts.net_interfaces[item.key].mode is defined and ansible_facts.net_interfaces[item.key].mode == ‘trunk’
Where item.key references the interface name (e.g. Ethernet1/1) of all available neighbor dicts when looping via with_dict.
Michael
Hi Michael,
thank you for responding, I just looked in my spam folder and saw that you posted a new blog. I will fix that. I would like to try this on a catalyst switch but I am not seeing what mode it is configured for. This is a trunk port
“ansible_facts”: {
“ansible_network_resources”: {},
“ansible_net_gather_network_resources”: [],
“ansible_net_gather_subset”: [
“interfaces”,
“hardware”,
“default”
],
“ansible_net_all_ipv4_addresses”: [
“172.28.5.2”,
“172.28.20.2”
],
“ansible_net_all_ipv6_addresses”: [],
“ansible_net_neighbors”: {
“GigabitEthernet1/0/24”: [
{
“host”: “MikeThunderdome-1.ne.gov”,
“port”: “GigabitEthernet1/0/24”
}
]
},
“ansible_net_interfaces”: {
“GigabitEthernet1/0/24”: {
“description”: “Connected to MikeThunderdome-1.ne.gov via its GigabitEthernet1/0/24”,
“macaddress”: “500f.80bc.2398”,
“mtu”: 1500,
“bandwidth”: 1000000,
“mediatype”: “10/100/1000BaseTX”,
“duplex”: null,
“lineprotocol”: null,
“operstatus”: “up”,
“type”: “Gigabit Ethernet”,
“ipv4”: []
What I do not want are the lldp neighbor info from VOIP phones or access ports
Another thing I would like to get to is to select interfaces based on if they are trunk or access and what vlan and I hope going down this rabbit hole will help me with that.
Thank you much Richard
Hi Richard,
right, the port type is not reported using standard ansible_facts and Cisco IOS devices, and combined with the vlan definition a little out of the scope of this blog 😉
But, I can at least point you in the right direction: https://github.com/ansible-collections/cisco.ios/blob/main/docs/cisco.ios.ios_l2_interfaces_module.rst
This network resource module provides the information you need.
Regards,
Michael
Thank you Michael, I will check it out.
Thank you for sharing your knowledge, I am an absolute beginner and found your article very helpful