Network automation is not just about configuration management. Equally, if not more, important is validation of the state and models. After all, what is automating configuration changes good for if you don't know whether the end state of the system is correct?

In the first blog post in the series I'll briefly describe Napalm, and it's validation feature, followed by an example Ansible playbook that we will use to automatically validate LLDP neighbours. This can be used to makes sure newly deployed devices are correctly patched, or to confirm that the existing environment is connected the way we think it is.

Contents


Introduction

NAPALM, in case you haven't heard of it, is a Python library that attempts to provide a unified API for interacting with Operating Systems running on network devices from many different vendors. The idea is to let NAPALM work out how to connect, configure, and retrieve data from a particular OS. You use the same API calls for all devices, no matter the vendor or OS, and get output in a standardised format. If you want to learn more, and I highly recommend that you do, head over to https://napalm-automation.net/ .

In my posts I will be using NAPALM in Ansible Playbooks by utilising NAPALM's Ansible modules. Let's now move on to the main topic.

Validating LLDP neighbours

Napalm validation leverages its built in getters, and it uses YAML files containing description of the desired state. NAPALM expects our YAML files to conform to the data structure these getters use, so if you're unsure of how your YAML should look like, just run the 'get_facts' module and inspect the output.

For example, the below shows result of retrieving LLDP neighbours from a test device:

"ansible_facts": {
            "napalm_lldp_neighbors": {
                "Ethernet1": [
                    {
                        "hostname": "vEOS-02.lab",
                        "port": "Ethernet1"
                    }
                ],
                "Ethernet2": [
                    {
                        "hostname": "vEOS-03.lab",
                        "port": "Ethernet1"
                    }
                ],
                "Ethernet3": [
                    {
                        "hostname": "CSR-01.lab",
                        "port": "Gi5"
                    }
                ]
            }
        },

Using the above output we can create a YAML file we will use to validate LLDP neighbours. NAPALM lets us decide which attributes we want to validate, so we can include only a subset of the device's state. Let's say that we want to verify both, hostname and port, for the neighbours seen on Ethernet1 and Ethernet3, but only hostname for the neighbour connected to Ethernet2.

Our YAML file will look like so:

---
- get_lldp_neighbors:
    Ethernet1:
      - hostname: vEOS-02.lab
        port: Ethernet1
    Ethernet2:
      - hostname: vEOS-03.lab
    Ethernet3:
      - hostname: CSR-01.lab
        port: Gi5

Armed with the validation YAML, we can write our playbook.

[przemek@quasar napalm_validate]$ cat validate_lldp.yml
---
- name: Validate LLDP neighbours
  hosts: arista
  connection: local

  vars:
    val_dir: "{{ playbook_dir }}/validate"

  tasks:
  - name: Use Napalm to automatically validate LLDP neighbours
    napalm_validate:
      provider: "{{ napalm_provider }}"
      validation_file: "{{ val_dir }}/{{ inventory_hostname }}_lldp.yml"
      optional_args:
        eos_transport: http
    register: val_lldp
    ignore_errors: yes

  - name: Display full compliance report
    debug:
      var: val_lldp.compliance_report

  - name: Compliance check failed
    fail:
      msg: "Non-compliant state encountered. Refer to the full report."
    when: not val_lldp.compliance_report.complies

The most important task here is the one using 'napalm_validate' module. We feed it YAML files describing the state and register the output. Once the output is registered we display the full compliance report and check if the actual state matches with our desired state. If it doesn't, we stop execution of the playbook using 'fail' module. Otherwise we let the playbook run to completion.

Let's run this playbook, against vEOS-01, and see what happens.

[przemek@quasar napalm_validate]$ ansible-playbook validate_lldp.yml

PLAY [Validate LLDP neighbours] **************************************************************************************************

TASK [Use Napalm to automatically validate LLDP neighbours] **********************************************************************
ok: [vEOS-01]

TASK [Display full compliance report] ********************************************************************************************
ok: [vEOS-01] => {
    "val_lldp.compliance_report": {
        "complies": true,
        "get_lldp_neighbors": {
            "complies": true,
            "extra": [],
            "missing": [],
            "present": {
                "Ethernet1": {
                    "complies": true,
                    "nested": false
                },
                "Ethernet2": {
                    "complies": true,
                    "nested": false
                },
                "Ethernet3": {
                    "complies": true,
                    "nested": false
                }
            }
        },
        "skipped": []
    }
}

TASK [Compliance check failed] ***************************************************************************************************
skipping: [vEOS-01]

PLAY RECAP ***********************************************************************************************************************
vEOS-01                    : ok=2    changed=0    unreachable=0    failed=0

Success! NAPALM is happily telling us that the actual state matches our expectations.

Now, we will run the same playbook against a different device, vEOS-02, whose validation YAML file doesn't match the actual state.

[przemek@quasar napalm_validate]$ ansible-playbook validate_lldp.yml

PLAY [Validate LLDP neighbours] **************************************************************************************************

TASK [Use Napalm to automatically validate LLDP neighbours] **********************************************************************
fatal: [vEOS-02]: FAILED! => {"changed": false, "compliance_report": {"complies": false, "get_lldp_neighbors": {"complies": false, "extra": [], "missing": [], "present": {"Ethernet1": {"actual_value": [{"hostname": "vEOS-01.lab", "port": "Ethernet1"}], "complies": false, "expected_value": [{"hostname": "vEOS-03.lab", "port": "Ethernet1"}], "nested": false}, "Ethernet2": {"actual_value": [{"hostname": "vEOS-03.lab", "port": "Ethernet2"}], "complies": false, "expected_value": [{"hostname": "vEOS-01.lab", "port": "Ethernet2"}], "nested": false}}}, "skipped": []}, "failed": true, "msg": "Device does not comply with policy"}
...ignoring

TASK [Display full compliance report] ********************************************************************************************
ok: [vEOS-02] => {
    "val_lldp.compliance_report": {
        "complies": false,
        "get_lldp_neighbors": {
            "complies": false,
            "extra": [],
            "missing": [],
            "present": {
                "Ethernet1": {
                    "actual_value": [
                        {
                            "hostname": "vEOS-01.lab",
                            "port": "Ethernet1"
                        }
                    ],
                    "complies": false,
                    "expected_value": [
                        {
                            "hostname": "vEOS-03.lab",
                            "port": "Ethernet1"
                        }
                    ],
                    "nested": false
                },
                "Ethernet2": {
                    "actual_value": [
                        {
                            "hostname": "vEOS-03.lab",
                            "port": "Ethernet2"
                        }
                    ],
                    "complies": false,
                    "expected_value": [
                        {
                            "hostname": "vEOS-01.lab",
                            "port": "Ethernet2"
                        }
                    ],
                    "nested": false
                }
            }
        },
        "skipped": []
    }
}

TASK [Compliance check failed] ***************************************************************************************************
fatal: [vEOS-02]: FAILED! => {"changed": false, "failed": true, "msg": "Non-compliant state encountered. Refer to the full report."}

PLAY RECAP ***********************************************************************************************************************
vEOS-02                    : ok=2    changed=0    unreachable=0    failed=1

As expected, playbook failed as NAPALM detected discrepancies between the desired state and the state found on the device. We see that patches for interfaces Ethernet1 and Ethernet2 have been swapped around, all too common occurrence in many DCs. NAPALM dutifully reported the actual values and the values it found in the YAML file.

What's next

This concludes the first post in the series on using NAPALM's validation feature. I hope that even these tiny examples showed you how powerful this tool can be, and how well it complements configuration management side of automation.

In the next posts we will talk about using more advanced validation options offered by NAPALM, that will allow us to be more precise when expressing our desired state. We'll also have some more examples of the state validation.

We will finish the series with the talk on using service/infrastructure models to automatically generate validation files. I don't recommend creating YAML files by hand if you have more than a handful of devices, so we will call upon powers of automation to make our lives easier.

Playbook listings


get_facts_lldp.yml

---
- name: Get lldp neighbours
  hosts: arista
  connection: local

  tasks:
  - name: Use napalm_get_facts to retrieve lldp neighbours
    napalm_get_facts:
      provider: "{{ napalm_provider }}"
      optional_args:
        eos_transport: http
      filter: 'lldp_neighbors'
    register: lldp_facts

  - name: Debug lldp neighbours
    debug:
      var: lldp_facts 


validate_lldp.yml

---
- name: Validate LLDP neighbours
  hosts: arista
  connection: local

  vars:
    val_dir: "{{ playbook_dir }}/validate"

  tasks:
  - name: Use Napalm to automatically validate LLDP neighbours
    napalm_validate:
      provider: "{{ napalm_provider }}"
      validation_file: "{{ val_dir }}/{{ inventory_hostname }}_lldp.yml"
      optional_args:
        eos_transport: http
    register: val_lldp
    ignore_errors: yes

  - name: Display full compliance report
    debug:
      var: val_lldp.compliance_report

  - name: Compliance check failed
    fail:
      msg: "Non-compliant state encountered. Refer to the full report."
    when: not val_lldp.compliance_report.complies 

You can also get full listings of the playbooks from my GitHub repository:
https://github.com/progala/ttl255.com/tree/master/ansible/napalm-validate-p1