Skip to content
Mar 2 / Greg

Set Cisco Nexus Interface Description From CDP Using Ansible AAP


A customer recently asked about setting CDP neighbors as interface descriptions on their switches, which is actually a pretty cool idea. I thought, though, that I don’t want just anything set…ya know, like I don’t care if the neighbor is an IP phone, what I really care about are other switches. So I created a playbook that will find the CDP neighbors and set the interface description to that neighbor IF it is a switch.

Video Overview

Playbooks

First, you can find all of my playbooks in my public git repo here.

So the playbook is actually broken into two parts. One is the main playbook, and one is a task file used for looping.

Main playbook(cisco-nxos-cdp-interface.yml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
---
- name: set interface desc based on cdp neighbors
  hosts: nexus9k1
  gather_facts: false
  vars:
  tasks:
  - name: Gather all legacy facts
    cisco.nxos.nxos_facts:
      gather_subset: all
    register: gather_info
 
#  - name: debug gather_info
#    ansible.builtin.debug:
#      var: gather_info
 
#  - name: debug gather_info filtered
#    ansible.builtin.debug:
#      msg: "{{ item.key }} - {{ item.value.mode | default('nothing') }}"
#    loop: "{{ gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('value.mode','defined') | selectattr('value.mode','search','trunk') }}"
 
  - name: gather cdp neighbors
    nxos_command:
      commands: "show cdp neighbors | json"
    register: cdp_neighbors
 
 
#  - name: debug cdp_neighbors crazy filter stuff
##    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('key','search',item.intf_id) | selectattr('value.mode','defined') | map(attribute='value.mode') | default('access') == 'trunk'
##    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('key','search',item.intf_id) | selectattr('value.mode','defined') | map(attribute='value.mode') | default('access') == "trunk"
#    when: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('key','equalto',item.intf_id) | selectattr('value.mode','defined') | map(attribute='value.mode') | default('access') == "trunk"
#    ansible.builtin.debug:
##      var: cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info
##      var: gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('key','search',item.intf_id) | selectattr('value.mode','defined') | map(attribute='value.mode') | default('access')
#      msg: "{{ device_id }} on {{ item.intf_id }}"
#    loop: "{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}"
 
  - name: loop through cdp neighbors and call the task file to further search for trunks
    ansible.builtin.include_tasks:
      file: cisco-nxos-cdp-interface-taskfile.yml
    loop: "{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}"
    loop_control:
      loop_var: cdp_loop

I’ll break it down into its pieces:

1
2
3
4
5
6
7
8
9
  - name: Gather all legacy facts
    cisco.nxos.nxos_facts:
      gather_subset: all
    register: gather_info
 
  - name: gather cdp neighbors
    nxos_command:
      commands: "show cdp neighbors | json"
    register: cdp_neighbors

These first two tasks are all about information gathering. On the very first task I’m using the nxos_facts module to gather information about the switch. While it pulls a lot of info, what I’m really interested in here is how the interfaces are configured, and in particular if the interface mode is set to access or trunk.
The second task issues a simple command via the nxos_command module “show cdp neighbors | json”. Here I’m piping the output to json, which means it will be saved in my “cdp_neighbors” variable directly as key/value pairs(which means it’s directly ready for me to reference without having to do any parsing).

Here’s the last task in the main playbook:

1
2
3
4
5
6
  - name: loop through cdp neighbors and call the task file to further search for trunks
    ansible.builtin.include_tasks:
      file: cisco-nxos-cdp-interface-taskfile.yml
    loop: "{{ cdp_neighbors.stdout[0].TABLE_cdp_neighbor_brief_info.ROW_cdp_neighbor_brief_info }}"
    loop_control:
      loop_var: cdp_loop

This task loops over the collected CDP neighbors, and for each one it will call the “cisco-nxos-cdp-interface-taskfile.yml” task file for further processing. Just remember that each time we hit the taskfile I’ll be processing a single CDP neighbor.

Now for the task file(cisco-nxos-cdp-interface-taskfile.yml):

1
2
3
4
5
6
7
8
- name: Update interface descpription when it's a trunk port using Merge
  when: item == "trunk"
  check_mode: true
  cisco.nxos.nxos_interfaces:
    config:
    - name: "{{ cdp_loop.intf_id }}"
      description: "{{ cdp_loop.device_id }} on {{ cdp_loop.port_id }}"
  loop: "{{ gather_info.ansible_facts.ansible_net_interfaces | dict2items | selectattr('key','equalto',cdp_loop.intf_id) | selectattr('value.mode','defined') | map(attribute='value.mode') | default('access') }}"

There is only a single task here, but I am doing some kungfu down in the loop section. Also note that I have “check_mode: true” set; I’m doing this because it is a demo I want to run multiple times and show interesting results, so I have it set to check mode so it won’t actually make the change, rather just show what it would do(in production this wouldn’t be there).
The loop section looks pretty wacky, and if you don’t fully understand it, have a look at this article where I explain how to do complex parsing. In short I’m: taking the gathered facts interfaces, converting them to a list, finding the key field that has the same interface name(meaning while it’s a loop, it will return only a single object), then I ensure that the mode is defined(either access or trunk), I then map that value(which means I return just that one piece of info), and last if the value is null I set it to “access”. So really what I’m doing is just finding out if it’s a “trunk” port, and if it’s not I make sure it’s set to “access”.
At the top of the task you can see there’s a conditional that only processes the task if the interface that the CDP neighbor lives in is actually a “trunk” port.
If it is a trunk port, then the interface description is set to the ID of the remote device as well as the port it is connecting via.

Conclusion

This is a pretty simple example of using ansible for a task, but I tried to jazz it up a bit by adding the extra filtering to give you an idea of how you can extend simple tasks with more specificity. I’d be curious to hear how you’d tweak or tune this to fit your environment.

Thanks and happy automating!

Leave a Comment

 

*