Ansible gather_facts for Networking Devices

The purpose of this blog is to show some differences in how Ansible handled gathering facts from networking devices prior to version 2.9 and how they can be gathered in version 2.9.2. A main point to show here is how previously the gather_facts key was disabled when working with networking devices and now it can be enabled to gather device facts.

Gathering *_facts the “Old” way

Ansible core comes included with vendor specific *_facts modules. So normally to collect facts from devices the playbook was built using the vendor specific facts module and the needed key was used for any other particular task like asserting device versions, building reports, etc. The example below is a playbook used to gather facts from a Cisco device.


---
 - name: GATHER FACTS FOR IOS
   hosts: csr1
   connection: network_cli
   gather_facts: no    #<---Gather Facts Disabled

   tasks:

     - name: GATHER FACTS FOR IOS IN A TASK
       ios_facts:

     - name: VIEW ALL ANSIBLE FACT KEYS
       debug:
         var: ansible_facts.keys()

     - name: VIEW HOSTNAME
       debug:
         var: ansible_net_hostname
     
     - name: VIEW OS VERSION
       debug:
         var: ansible_net_version

Note: The first task is using the ios_facts module to gather all the data, the other three tasks are used to display the data returned from the facts module and keys inside ansible_facts can be accessed directly.

Notice how in the play definition the key gather_facts is set to no. Originally, Ansible was designed to work with Linux servers. Ansible focuses on collecting as much data as possible from the remote Linux servers and copying Python modules to the servers for automation tasks so they could be executed on the remote machines.

When working with networking devices Ansible does not copy Python modules to the remote devices to run an automation task. The Python module is executed in the local Ansible workstation and things like sending show commands or configuration changes are wrapped in the Python module and sent over through SSH. The gather_facts key was not designed to know the difference between a Linux server and a networking device, so by enabling the feature would end up gathering facts from the local Ansible workstation and not the remote networking device.

Running the Playbook with gather_facts disabled

The output of the current playbook would look like this:

Note: The current Ansible version is 2.7.10

ntc@jump-host:~$ ansible-playbook -i inventory network_facts.yml

PLAY [GATHER FACTS FOR IOS] ********************************************************************************

TASK [GATHER FACTS FOR IOS] ********************************************************************************
ok: [csr1]

TASK [VIEW ALL ANSIBLE FACT KEYS] **************************************************************************
ok: [csr1] => {
    "ansible_facts.keys()": "dict_keys(['net_serialnum', 'net_all_ipv4_addresses', 
'net_model', 'net_hostname', 'net_gather_subset', 'net_filesystems_info', 'net_interfaces', 
'net_version', 'net_neighbors', 'net_all_ipv6_add resses', 'net_memtotal_mb', 'net_filesystems', 
'net_image', 'net_memfree_mb'])"
}

TASK [VIEW HOSTNAME] ***************************************************************************************
ok: [csr1] => {
    "ansible_net_hostname": "csr1"
}

TASK [VIEW OS VERSION] *************************************************************************************
ok: [csr1] => {
    "ansible_net_version": "16.08.01a"
}

PLAY RECAP *************************************************************************************************
csr1                        : ok=4    changed=0    unreachable=0    failed=0

The current output only displays facts that belong to the remote device. The keys seen in the output are the ones available from the ios_facts module.

Running the Playbook with gather_facts enabled

When the gather_facts key is enabled in the play definition (gather_facts: yes) the output will return not only data from the remote device but also from the Ansible workstation as seen below:

ntc@jump-host:~$ ansible-playbook -i inventory network_facts.yml

PLAY [GATHER FACTS FOR IOS] ********************************************************************************

TASK [Gathering Facts] *************************************************************************************
ok: [csr1]

TASK [GATHER FACTS FOR IOS] ********************************************************************************
ok: [csr1]

TASK [VIEW ALL ANSIBLE FACT KEYS] **************************************************************************
ok: [csr1] => {
    "ansible_facts.keys()": "dict_keys(['module_setup', 'distribution_version', 'distribution_file_variety', 'env',
    'userspace_bits', 'architecture', 'default_ipv4', 'swapfree_mb', 'default_ipv6', 'cmdline', 'selinux',
    'userspace_architecture', 'product_uuid', 'pkg_mgr', 'distribution', 'iscsi_iqn', 'all_ipv6_addresses', 
    'uptime_seconds', 'kernel', 'system_capabilities_enforced', 'python', 'is_chroot', 'user_shell', 'product_serial'
    'form_factor', 'distribution_file_parsed', 'fips', 'user_id', 'selinux_python_present', 'ansible_local',
    'processor_vcpus', 'processor', 'ssh_host_key_ecdsa_public', 'mounts', 'system_vendor', 'swaptotal_mb',
    'distribution_major_version', 'real_group_id', 'lsb', 'machine', 'ssh_host_key_rsa_public', 'user_gecos',
    'processor_threads_per_core', 'eth1', 'product_name', 'all_ipv4_addresses', 'python_version',
    'product_version', 'service_mgr', 'memory_mb', 'user_dir', 'gather_subset', 'real_user_id', 
    'virtualization_role', 'tunl0', 'dns', 'effective_group_id', 'lo', 'memtotal_mb', 'device_links', 
    'apparmor', 'memfree_mb', 'processor_count', 'hostname', 'interfaces', 'machine_id', 'fqdn', 'user_gid',
    'nodename', 'domain', 'distribution_file_path', 'virtualization_type', 'ssh_host_key_ed25519_public', 
    'processor_cores', 'bios_version', 'date_time', 'distribution_release', 'os_family', 'effective_user_id',
    'sit0', 'system', 'devices', 'user_uid', 'ssh_host_key_dsa_public', 'bios_date', 'system_capabilities',
    'ens3', 'net_serialnum', 'net_all_ipv4_addresses', 'net_model', 'net_hostname', 'net_gather_subset',
    'net_filesystems_info', 'net_interfaces', 'net_version', 'net_neighbors', 'net_all_ipv6_addresses',
    'net_memtotal_mb', 'net_filesystems', 'net_image', 'net_memfree_mb'])"
}

TASK [VIEW HOSTNAME] ***************************************************************************************
ok: [csr1] => {
    "ansible_net_hostname": "csr1"
}

TASK [VIEW OS VERSION] *************************************************************************************
ok: [csr1] => {
    "ansible_net_version": "16.08.01a"
}

PLAY RECAP *************************************************************************************************
csr1                        : ok=5    changed=0    unreachable=0    failed=0

Gathering Facts from different platforms

Another thing to point out is that when using the core *_facts modules if another vendor or platform needs to be added to the playbook another play would need to be built to gather facts from that vendor or platform. Without this addition, the module will fail because the ios_facts module is vendor specific and it will try to gather facts from any devices targeted in the scope of that play. The playbook would look something like this:

---
 - name: GATHER FACTS FOR IOS
   hosts: csr1
   connection: network_cli
   gather_facts: no

   tasks:

     - name: GATHER FACTS FOR IOS
       ios_facts:

     - name: VIEW ALL ANSIBLE FACT KEYS
       debug:
         var: ansible_facts.keys()

     - name: VIEW HOSTNAME
       debug:
         var: ansible_net_hostname
     
     - name: VIEW OS VERSION
       debug:
         var: ansible_net_version
  
 - name: GATHER FACTS FOR NXOS
   hosts: nxos
   connection: network_cli
   gather_facts: no

   tasks:

     - name: GATHER FACTS FOR NXOS
       nxos_facts:

     - name: VIEW ALL ANSIBLE FACT KEYS
       debug:
         var: ansible_facts.keys()

     - name: VIEW HOSTNAME
       debug:
         var: ansible_net_hostname
    
     - name: VIEW OS VERSION
       debug:
         var: ansible_net_version

Gather Facts the “New” way

There have been a lot of changes with Ansible focused on improving how to manage networking devices. More and more the platform has been used in multi-vendor environments, meaning that the tool needs to be more robust and vendor neutral. Recent features and improvements are being built to be more “vendor neutral” – such as cli_config and cli_command (added in Ansilble version 2.7).

In Ansible version 2.9, gathering facts from devices from the play definition is now possible without having to build it as a task, like shown in the previous playbook. This time the key gather_facts can be enabled and not gather facts from the local workstation but from the devices targeted in the hosts key.

Note: The modules ios_facts and nxos_fact are still being used but are now being executed in the play definition.

---

 - name: GATHER FACTS
   hosts: csr1,nxos
   connection: network_cli
   gather_facts: yes  #<----Gather Facts Enabled 

   tasks:
   
    #NO VENDOR SPECIFIC MODULE LIKE ios_facts OR nxos_facts ANYMORE

    - name: VIEW ALL ANSIBLE FACT KEYS
      debug:
       var: ansible_facts.keys()

    - name: VIEW HOSTNAME
      debug:
        var: ansible_net_hostname
    
    - name: VIEW OS VERSION
      debug:
        var: ansible_net_version

Take a look at the current playbook. The hosts key has two different operating systems which normally would require another play when trying to gather data from different platforms because of the vendor specific modules defined in the tasks. This time gathering facts is being done at the play definition level and at the tasks level only the debug modules are being used to display the available keys and variables to see hostname and version information.

Running the Playbook with gather_facts enabled the “New Way”

ntc@jump-host:~$ ansible-playbook -i inventory network_facts.yml

PLAY [GATHER FACTS] ***************************************************************************************

TASK [Gathering Facts] ************************************************************************************


ok: [csr1]
ok: [nxos]

TASK [VIEW ALL ANSIBLE FACT KEYS] *************************************************************************
ok: [csr1] => {
    "ansible_facts.keys()": "dict_keys(['network_resources', 'net_gather_network_resources', 
    'net_gather_subset', 'net_system', 'net_model', 'net_image', 'net_version', 'net_hostname', 
    'net_api', 'net_python_version', 'net_iostype', 'net_serialnum', 'net_filesystems', 
    'net_filesystems_info', 'net_memtotal_mb', 'net_memfree_mb', 'net_config', 'net_all_ipv4_addresses',
    'net_all_ipv6_addresses', 'net_neighbors', 'net_interfaces', '_facts_gathered'])"
}
ok: [nxos] => {
    "ansible_facts.keys()": "dict_keys(['network_resources', 'net_gather_network_resources', 
    'net_gather_subset', 'net__os', 'net__platform', 'net__hostname', 'net_interfaces_list', 
    'net_vlan_list', 'net_module', 'net_fan_info', 'net_power_supply_info', 'net_filesystems', 
    'net_memtotal_mb', 'net_memfree_mb', 'net_features_enabled', 'net_all_ipv4_addresses', 
    'net_all_ipv6_addresses', 'net_neighbors', 'net_interfaces', 'net_serialnum', 'net_license_hostid',
    'net_system', 'net_model', 'net_image', 'net_version', 'net_platform', 'net_hostname', 'net_api', 
    'net_python_version', 'net_config', '_facts_gathered'])"
}

TASK [VIEW HOSTNAME] ***************************************************************************************
ok: [csr1] => {
    "ansible_net_hostname": "csr1"
}
ok: [nxos] => {
    "ansible_net_hostname": "nxos-spine1"
}

TASK [VIEW OS VERSION] **************************************************************************************
ok: [csr1] => {
    "ansible_net_version": "16.08.01a"
}
ok: [nxos] => {
    "ansible_net_version": "7.0(3)I7(4)"
}

PLAY RECAP *************************************************************************************************
csr1                        : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
nxos                       : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The output above shows the results of the playbook ran in the new version. This time only the device facts are gathered in a single play for two different platforms, rather than running vendor specific modules that have to run in two different plays.

Note: Now that gather_facts can be used with networking devices there are some configurations that can be changed in the ansible.cfg file that will affect how the gathering could behave.

Changing the settings below in the ansible.cfg file will affect how gathering facts from devices will be gathered.

# smart - gather by default, but don't regather if already gathered
# implicit - gather by default, turn off with gather_facts: False
# explicit - do not gather by default, must say gather_facts: True
# gathering = implicit

For more information on what other new changes exist in Ansible 2.9 check out the link below:

Ansible 2.9 Porting Guide


Conclusion

Before this new update in Ansible 2.9.2, when building playbooks for networking devices the gather_facts key was always disabled and it almost seemed like an extra key that needed to be there for no good reason unless facts from the local machine needed to be added. In this new version of Ansible gathering facts from networking devices really makes things easier and more flexible in a structured way. I’m looking forward to test this out with the new rersource modules and showing this feature to more of my students from now on.

-Hector



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author