Hello my friend,

This is the third article about telemetry in network functions and this time we’ll speak about OpenConfig again. To be more precise, we’ll review how to extract telemetry data from Arista EOS, Cisco IOS XR and Nokia SR OS in OpenConfig YANG modules via NETCONF.

Brief description

Some time ago we have reviewed OpenConfig for basic configuration, collecting some information and advanced automated configuration of data center fabric. Though there were some deviations caused by different versions of OpenConfig modules implemented in across our VNFs, there were much more similarities, making OpeConfig suitable for rollout of infrastructure.

Assuming rollout has been done successfully, it’s necessary to verify the infrastructure afterwards. The best way, for sure, is to use telemetry, how we have already explained for Cisco IOS XR and Nokia SR OS. And OpenConfig YANG modules foresee this possibility by exporting telemetry information in the format aligned with what is configured.

What are we going to test?

We are going to collect information from the network functions in OpenConfig YANG modules. We hope that the output will be the same or at least very similar.

We’ll use “netconf_get” Ansible module for that.

Software version

The following software components are used in this lab:

  • CentOS 7.5.1804 with python 2.7.5
  • Ansible 2.7.0
  • Nokia SR OS 16.0.R3 [guest VNF]
  • Cisco IOS XR 6.1.2 [guest VNF]
  • Arista EOS 4.21.1.F [guest VNF]

See the previous article to get details how to build the lab

Topology

The physical topology, I hope, is very well known to you:

The logical topology is simplified from the data center fabric comparing to what we used earlier. Following the same approach, we introduced recently, topology is provided in ASCII format:

1
2
3
4
5
6
7
8
9
10
                          fc00::10:11:33:0/112                          fc00::10:22:33:0/112
    +----------------+       10.11.33.0/24        +----------------+       10.22.33.0/24        +----------------+
    |      SR1       +----------------------------+       XR3      +----------------------------+      vEOS2     |
    +-------+--------+ 1/1/c1/1.13    g0/0/0/0.13 +--------+-------+ g0/0/0/1.23        Eth1.23 +--------+-------+
            |          .11/:11            .33/:33          |         .33/:33            .22/:22          |
            |                                              |                                             |
            | system: 10.0.0.11/32                         | loopback0: 10.0.0.33/32                     | loopback0: 10.0.0.22/32
                      fc00::10:0:0:11/128                               fc00::10:0:0:33/128                           fc00::10:0:0:22/128

            <-------------BGP:IPV4_UNI/IPV6_UNI------------x-------------BGP:IPV4_UNI/IPV6_UNI----------->

 

Actually the configuration looks like data center fabric, or Internet peering. All the interfaces in the topology has dual-stack IPv4/IPv6 interfaces and eBGP peering both for IPv4/IPv6 address families and announcement of IPv4/IPv6 addresses of the loopback/system interfaces.

The initial configuration you can see in the attached files: 140_config_initial_vEOS2 140_config_initial_XR3 140_config_initial_SR1

Brief topology check

Based on the performed configuration, the following checks might have sense:

  • Check the status of the interfaces
  • Check the status of BGP peering
  • Perform ping from all loopbacks/system interfaces to all other loopbacks/system interfaces.

Previously we have shown how to perform those checks manually. To reduce the space of the article, we won’t show them again with “show” commands, as we are going to perform those verifications using telemetry data with OpenConfig and NETCONF.

But to check that connectivity between all the network functions exists, we’ll perform verification using ICMP:

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
A:admin@vSIM# //ping 10.0.0.33 so 10.0.0.11
INFO: CLI #2051: Switching to the classic CLI engine
A:vSIM# /ping 10.0.0.33 so 10.0.0.11
PING 10.0.0.33 56 data bytes
64 bytes from 10.0.0.33: icmp_seq=1 ttl=255 time=11.0ms.
64 bytes from 10.0.0.33: icmp_seq=2 ttl=255 time=6.19ms.
64 bytes from 10.0.0.33: icmp_seq=3 ttl=255 time=2.75ms.
64 bytes from 10.0.0.33: icmp_seq=4 ttl=255 time=7.98ms.
64 bytes from 10.0.0.33: icmp_seq=5 ttl=255 time=2.73ms.

---- 10.0.0.33 PING Statistics ----
5 packets transmitted, 5 packets received, 0.00% packet loss
round-trip min = 2.73ms, avg = 6.13ms, max = 11.0ms, stddev = 3.17ms
INFO: CLI #2052: Switching to the MD-CLI engine


A:admin@vSIM# //ping 10.0.0.22 so 10.0.0.11
INFO: CLI #2051: Switching to the classic CLI engine
A:vSIM# /ping 10.0.0.22 so 10.0.0.11
PING 10.0.0.33 56 data bytes
64 bytes from 10.0.0.22: icmp_seq=1 ttl=255 time=8.13ms.
64 bytes from 10.0.0.22: icmp_seq=2 ttl=255 time=7.85ms.
64 bytes from 10.0.0.22: icmp_seq=3 ttl=255 time=4.17ms.
64 bytes from 10.0.0.22: icmp_seq=4 ttl=255 time=4.89ms.
64 bytes from 10.0.0.22: icmp_seq=5 ttl=255 time=3.74ms.

---- 10.0.0.22 PING Statistics ----
5 packets transmitted, 5 packets received, 0.00% packet loss
round-trip min = 3.74ms, avg = 5,75ms, max = 8.13ms, stddev = 2.47ms
INFO: CLI #2052: Switching to the MD-CLI engine

 

As Nokia SR OS based VNF SR1 is connected only to Cisco IOS XR based VNF XR3, and Arista EOS based VNF vEOS2 is connected only to XR3, it’s enough to perform single check from SR1 or vEOS2 to all other relevant prefixes.

OpenConfig // Operational YANG modules

Where is my telemetry, man?

The good thing about OpenConfig is that it doesn’t have separated YANG modules for configuration and operation like Nokia SR OS or Cisco IOS XR does. You remember, what makes module operational or interesting for telemetry is “ro” or “read-only” nodes in YANG module. Let’s take a look on OpenConfig module for interface, which we have already used previously:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
$ pyang -f tree -p ~/Yang/openconfig/release/models/ ~/Yang/openconfig/release/models/interfaces/openconfig-interfaces.yang
module: openconfig-interfaces
  +--rw interfaces
     +--rw interface* [name]
        +--rw name             -> ../config/name
        +--rw config
        |  +--rw name?            string
        |  +--rw type             identityref
        |  +--rw mtu?             uint16
        |  +--rw loopback-mode?   boolean
        |  +--rw description?     string
        |  +--rw enabled?         boolean
        +--ro state
        |  +--ro name?            string
        |  +--ro type             identityref
        |  +--ro mtu?             uint16
        |  +--ro loopback-mode?   boolean
        |  +--ro description?     string
        |  +--ro enabled?         boolean
        |  +--ro ifindex?         uint32
        |  +--ro admin-status     enumeration
        |  +--ro oper-status      enumeration
        |  +--ro last-change?     oc-types:timeticks64
        |  +--ro logical?         boolean
        |  +--ro counters
        |     +--ro in-octets?             oc-yang:counter64
        |     +--ro in-pkts?               oc-yang:counter64
        |     +--ro in-unicast-pkts?       oc-yang:counter64
        |     +--ro in-broadcast-pkts?     oc-yang:counter64
        |     +--ro in-multicast-pkts?     oc-yang:counter64
        |     +--ro in-discards?           oc-yang:counter64
        |     +--ro in-errors?             oc-yang:counter64
        |     +--ro in-unknown-protos?     oc-yang:counter64
        |     +--ro in-fcs-errors?         oc-yang:counter64
        |     +--ro out-octets?            oc-yang:counter64
        |     +--ro out-pkts?              oc-yang:counter64
        |     +--ro out-unicast-pkts?      oc-yang:counter64
        |     +--ro out-broadcast-pkts?    oc-yang:counter64
        |     +--ro out-multicast-pkts?    oc-yang:counter64
        |     +--ro out-discards?          oc-yang:counter64
        |     +--ro out-errors?            oc-yang:counter64
        |     +--ro carrier-transitions?   oc-yang:counter64
        |     +--ro last-clear?            oc-types:timeticks64
        +--rw hold-time
        |  +--rw config
        |  |  +--rw up?     uint32
        |  |  +--rw down?   uint32
        |  +--ro state
        |     +--ro up?     uint32
        |     +--ro down?   uint32
        +--rw subinterfaces
           +--rw subinterface* [index]
              +--rw index     -> ../config/index
              +--rw config
              |  +--rw index?         uint32
              |  +--rw description?   string
              |  +--rw enabled?       boolean
              +--ro state
                 +--ro index?          uint32
                 +--ro description?    string
                 +--ro enabled?        boolean
                 +--ro name?           string
                 +--ro ifindex?        uint32
                 +--ro admin-status    enumeration
                 +--ro oper-status     enumeration
                 +--ro last-change?    oc-types:timeticks64
                 +--ro logical?        boolean
                 +--ro counters
                    +--ro in-octets?             oc-yang:counter64
                    +--ro in-pkts?               oc-yang:counter64
                    +--ro in-unicast-pkts?       oc-yang:counter64
                    +--ro in-broadcast-pkts?     oc-yang:counter64
                    +--ro in-multicast-pkts?     oc-yang:counter64
                    +--ro in-discards?           oc-yang:counter64
                    +--ro in-errors?             oc-yang:counter64
                    +--ro in-unknown-protos?     oc-yang:counter64
                    +--ro in-fcs-errors?         oc-yang:counter64
                    +--ro out-octets?            oc-yang:counter64
                    +--ro out-pkts?              oc-yang:counter64
                    +--ro out-unicast-pkts?      oc-yang:counter64
                    +--ro out-broadcast-pkts?    oc-yang:counter64
                    +--ro out-multicast-pkts?    oc-yang:counter64
                    +--ro out-discards?          oc-yang:counter64
                    +--ro out-errors?            oc-yang:counter64
                    +--ro carrier-transitions?   oc-yang:counter64
                    +--ro last-clear?            oc-types:timeticks64

 

I have intentionally haven’t included any augmenting YANG modules to reduce the length of the output. What is important here is the “state” containers, which has “read-only” type and contains plenty of operational data, like operational status of the interface, actual MTU and, for sure, counters of the transmitted and received packets/octets.

Actually it’s quite convenient, that the same module is used both for configuration and verification as it simplifies the logic for automation: you need less YANG modules to managed the network functions

OpenConfig // Model-driven telemetry algorithm

Following the same path we’ve used in Cisco IOS XR telemetry and Nokia SR OS telemetry, we rely on “netconf_get” Ansible module to collect via NETCONF telemetry information from Arista vEOS, Cisco XRv and Nokia VSR. The only difference, obviously, we will use OpenConfig YANG modules. The rest of the procedure is the same:

  • We list information, we’d like to collect, in “configuration profiles”. Today it is telemetry data about BGP routing and network interfaces.
  • For each entry in “configuration profiles” we construct proper NETCONF get RPC message using namespaces and nodes from corresponding OpenConfig YANG module.
  • Collect telemetry data using NETCONF
  • Build necessary reports on top of collected information

So algorithm isn’t new for you, if you already read the first or the second article about telemetry.

OpenConfig // Ansible playbook for telemetry collection

After it’s clear what we are going to do, it’s time to develop the tool, which will do it for us. Yes, we speak about Ansible playbooks, which will include roles and responsibilities 🙂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+--ansible
   +--140_lab.yml
   +--group_vars
   |  +--arista
   |  |  +--arista_host.yml
   |  +--cisco
   |  |  +--cisco_host.yml
   |  +--nokia
   |     +--nokia_host.yml
   +--roles
      +--openconfig
         +--140_lab
            +--tasks
            |  +--comparing_loop.yml
            |  +--main.yml
            +--templates
            |  +--netconf_request.j2
            |  +--openconfig_interfaces.j2
            |  +--openconfig_routing_bgp.j2
            +--vars
               +--infra_profile.yml

 

If you have read any previous articles about OpenConfig, you can see that I’ve changed the logic. There is no more dedicated per vendor roles, but rather there is single “openconfig” role. Why haven’t I thought about it previously? Well, in perfect world all the vendors will support the same OpenConfig YANG modules, but in my lab it isn’t the case as I’m using quite old Cisco IOS XRv 6.1.2. Nevertheless, I found a way how to overcome this problem on a cost of templates complexity. There is no free coffee, so everything has its own price. But I believe that having single playbook is worth to have slightly more complicated jinja2 templates.

Before going to details of the playbooks itself, let’s review the authentication and node data in “group_vars”:

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
$ cat group_vars/arista/arista_host.yml
---
# ansible_network_os: eos
ansible_network_os
: nexus
ansible_port
: 22
ansible_user
: aaa
ansible_ssh_pass
: aaa
ansible_pass
: aaa
ansible_become
: yes
ansible_become_method
: enable
...


$ cat group_vars/cisco/cisco_host.yml
---
ansible_network_os
: iosxr
ansible_user
: cisco
ansible_pass
: cisco
ansible_ssh_pass
: cisco
...


$ cat group_vars/nokia/nokia_host.yml
---
ansible_network_os
: sros
ansible_user
: admin
ansible_pass
: admin
ansible_ssh_pass
: admin
...

 

If you want to learn, why “ansible_network_os” for Arista EOS is “nexus”, check this article.

Now, the background preparations are done, and we can take a look at the master Ansible playbook:

1
2
3
4
5
6
7
$ cat 140_lab.yml
---
- hosts
: all
  connection
: netconf
  roles
:
   - openconfig/140_lab
...

 

It’s marvellous, isn’t it? Having just one role it’s much easier to control playbooks, if we need to update them. It’s also easier in terms that we even don’t need to have tags here, comparing to previous OpenConfig playbooks.

As there are not too much interesting things to see on the master playbook, let’s verify the main playbook with tasks for “openconfig/140_lab” role:

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
$ cat roles/openconfig/140_lab/tasks/main.yml
---
- name
: VERIFICATION // IMPORTING INFRASTRUCTURE PROFILE
  include_vars
:
      file
: infra_profile.yml
      name
: PROFILE

- name
: VERIFICATION // CREATING FOLDER FOR RESULTS
  file
:
      dest
: /tmp/140/{{ inventory_hostname }}
      state
: directory

- name
: VERIFICATION // DELETE PREVIOUS TEST REPORT
  file
:
      dest
: /tmp/140/{{ inventory_hostname }}_test_report.txt
      state
: absent
  ignore_errors
: yes

- name
: VERIFICATION // CREATING TEST REPORT
  file
:
      dest
: /tmp/140/{{ inventory_hostname }}_test_report.txt
      state
: touch

- name
: VERIFICATION // COLLECTING TELEMETRY AND SEARCHING DATA
  include_tasks
: comparing_loop.yml
  loop
: "{{ PROFILE.node.configuration_profiles }}"

- name
: VERIFICATION // REPORTING READINESS
  debug
:
      msg
: "Collection of telemetry data from {{ inventory_hostname }} is done."
...

 

It’s pretty much similar to one we had previously in Cisco IOS XR telemetry. In Cisco IOS XR we also have collected several outputs, so the “comparing_loop.yml” was used a lot. In Nokia SR OS there was single output for telemetry with all information, so “comparing_loop.yml” was used only for parsing data.

The playbook above performs step by step the following activities:

  • Importing requests for telemetry to be collected and reports to be generated
  • Folder to store per-device telemetry output is created. If it exists, nothing is done
  • The results of the previous reports are deleted, if they exist. If they don’t exist, nothing happens.
  • The blank file for new report is created.
  • Nested playbook “comparing_loop.yml” is activated for each telemetry data, we requested in the first step. These tasks extract telemetry data, parse them and create some reports based on templates. In a minute we’ll review the details of this playbook.
  • After all the requested telemetry data is collected and reports are generated, the notification is sent to the user that all activities are done.

As you can see, the crucial part happens in the child playbook, but before to review it, we need to see on how the requests for telemetry are composed in step 1. To do that, we need to screen file with variables:

1
2
3
4
5
6
7
$ cat roles/openconfig/140_lab/vars/infra_profile.yml
---
node
:
    configuration_profiles
:
        - profile
: interfaces
        - profile
: routing_bgp
...

 

More about configuration profiles you can read in one of the previous article

It’s more clear now on what basis nested playbook “comparing_loop.yml” is looped. That’s why it’s a good moment to review that playbook itself:

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
$ cat roles/openconfig/140_lab/tasks/comparing_loop.yml
---
- name
: VERIFICATION // LOOP // FETCHING TELEMETRY DATA
  netconf_get
:
      filter
: "{{ lookup ('template', 'netconf_request.j2') }}"
      display
: json
  register
: output_json

- name
: VERIFICATION // LOOP // SAVING TELEMETRY DATA
  copy
:
      content
: "{{ output_json.output | to_nice_json }}"
      dest
: /tmp/140/{{ inventory_hostname }}/{{ inventory_hostname }}_openconfig_{{ item.profile }}_telemetry.json

- name
: VERIFICATION // LOOP // MODIFIICATION OF COLLECTED TELEMETRY FOR PYTHON PROCESSING
  replace
:
      path
: /tmp/140/{{ inventory_hostname }}/{{ inventory_hostname }}_openconfig_{{ item.profile }}_telemetry.json
      regexp
: '-'
      replace
: '_'

- name
: VERIFICATION // LOOP // IMPORTING COLLECTED TELEMETRY DATA
  include_vars
:
      file
: /tmp/140/{{ inventory_hostname }}/{{ inventory_hostname }}_openconfig_{{ item.profile }}_telemetry.json
      name
: COLLECTED

- name
: VERIFICATION // LOOP // CREARING TEMPORARY REPORT FOR {{ item.profile }}
  template
:
      src
: openconfig_{{ item.profile }}.j2
      dest
: /tmp/140/temp_report_{{ inventory_hostname }}_openconfig_{{ item.profile }}.txt
      mode
: 0755

- name
: VERIFICATION // LOOP // COMPILING SUMMARY REPORT
  shell
: "cat /tmp/140/temp_report_{{ inventory_hostname }}_openconfig_{{ item.profile }}.txt >> /tmp/140/{{ inventory_hostname }}_test_report.txt"

- name
: VERIFICATION // LOOP // DELETING TEMPORARY FILES
  file
:
      dest
: /tmp/140/temp_report_{{ inventory_hostname }}_openconfig_{{ item.profile }}.txt
      state
: absent
  ignore_errors
: yes
...

 

A very similar playbook was used once already.

This playbook is a heart of the whole automation by Ansible, because it’s here we extract the data and create reports. We do it in the following way:

  • Using Ansible module “netconf_get” (link) we extract specific telemetry data. Depending on configuration profile above, appropriate request (actually object and XML namespace) comes from Jinja2 template “netconf_request.j2”.
  • Extracted telemetry (and also config) information is stored into per-device folder.
  • Data is modified in a way that “-” symbols are replaced by “_” as Python variable can’t have symbol “-“ in the name.
  • Extracted telemetry information is imported back into automation script as variables.
  • Based on the Jinja2 templates, which also depends on configuration profile, temporary report is created.
  • Information from the temporary report is added to the master report
  • The temporary report is deleted.

To finalize the picture, we need to verify a couple of Jinja2 templates and that’s it. Let’s start with the template, used to create proper NETCONF requests:

1
2
3
4
5
6
7
8
$ cat roles/openconfig/140_lab/templates/netconf_request.j2
{% if item.profile == 'interfaces' %}
<interfaces xmlns="http://openconfig.net/yang/interfaces"/>
{% elif item.profile == 'routing_bgp' and ansible_network_os == 'iosxr' %}
<bgp xmlns="http://openconfig.net/yang/bgp"/>
{% elif item.profile == 'routing_bgp' and ansible_network_os != 'iosxr' %}
<network-instances xmlns="http://openconfig.net/yang/network-instance"/>
{% endif %}

 

Here the proper string for filter in NETCONF “get” request is chosen depending on the configuration profile. Also here we starts paying for the simplification of the playbook in terms we have single file for all vendors. Cisco IOS XR 6.1.2 doesn’t have “network-instance” module comparing to Nokia SR OS 16.0.R3 and Arista EOS 4.21.1.F, that’s why we need to have another filter for Cisco. The deviation is quite small, just one string, but it shows that in future it might be not easy.

It isn’t but manageable. The following template is used for OpenConfig interfaces:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
$ cat roles/openconfig/140_lab/templates/openconfig_interfaces.j2
============================================================
    Checking of the status for openconfig {{ item.profile }}
============================================================
{% if ansible_network_os == 'iosxr' %}
{% for if_current in COLLECTED. rpc_reply.data.interfaces.interface %}

    Interface:          {% if if_current.state is defined and if_current.state.name is defined %}{{ if_current.state.name }}{% else %}{{ if_current.name }}{% endif %}

    Status:
        Administrative: {% if if_current.state is defined and if_current.state.admin_status is defined %}{{ if_current.state.admin_status }}{% else %} no information available {% endif %}
       
        Operational:    {% if if_current.state is defined and if_current.state.oper_status is defined %}{{ if_current.state.oper_status }}{% else %} no information available {% endif %}

    MTU:                {% if if_current.state is defined and if_current.state.mtu is defined %}{{ if_current.state.mtu }}{% else %} no information available {% endif %}

    Packets:
        Sent:
           Unicast:     {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_unicast_pkts is defined %}{{ if_current.state.counters.out_unicast_pkts }}{% else %} no information available {% endif %}

           Multicast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_multicast_pkts is defined %}{{ if_current.state.counters.out_multicast_pkts }}{% else %} no information available {% endif %}

           Broadcast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_broadcast_pkts is defined %}{{ if_current.state.counters.out_broadcast_pkts }}{% else %} no information available {% endif %}

        Received:
           Unicast:     {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_unicast_pkts is defined %}{{ if_current.state.counters.in_unicast_pkts }}{% else %} no information available {% endif %}

           Multicast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_multicast_pkts is defined %}{{ if_current.state.counters.in_multicast_pkts }}{% else %} no information available {% endif %}

           Broadcast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_broadcast_pkts is defined %}{{ if_current.state.counters.in_broadcast_pkts }}{% else %} no information available {% endif %}


#-----------------------------------------------------------
{% endfor %}
{% elif ansible_network_os != 'iosxr' %}
{% for if_current in COLLECTED.data.interfaces.interface %}

    Interface:          {% if if_current.state is defined and if_current.state.name is defined %}{{ if_current.state.name }}{% else %}{{ if_current.name }}{% endif %}

    Status:
        Administrative: {% if if_current.state is defined and if_current.state.admin_status is defined %}{{ if_current.state.admin_status }}{% else %} no information available {% endif %}

        Operational:    {% if if_current.state is defined and if_current.state.oper_status is defined %}{{ if_current.state.oper_status }}{% else %} no information available {% endif %}

    MTU:                {% if if_current.state is defined and if_current.state.mtu is defined and if_current.state.mtu != '0' %}{{ if_current.state.mtu }}{% elif if_current.state is defined and ansible_network_os == 'nexus' %}{% if if_current.subinterfaces.subinterface.ipv4 is defined %}{{ if_current.subinterfaces.subinterface.ipv4.state.mtu }}{% else %}{% for sif_current in if_current.subinterfaces.subinterface %}{% if sif_current.index == '0' %}{{ sif_current.ipv4.state.mtu }}{% endif %}{% endfor %}{% endif %}{% else %} no information available {% endif %}

    Packets:
        Sent:
           Unicast:     {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_unicast_pkts is defined %}{{ if_current.state.counters.out_unicast_pkts }}{% else %} no information available {% endif %}

           Multicast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_multicast_pkts is defined %}{{ if_current.state.counters.out_multicast_pkts }}{% else %} no information available {% endif %}

           Broadcast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.out_broadcast_pkts is defined %}{{ if_current.state.counters.out_broadcast_pkts }}{% else %} no information available {% endif %}

        Received:
           Unicast:     {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_unicast_pkts is defined %}{{ if_current.state.counters.in_unicast_pkts }}{% else %} no information available {% endif %}

           Multicast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_multicast_pkts is defined %}{{ if_current.state.counters.in_multicast_pkts }}{% else %} no information available {% endif %}

           Broadcast:   {% if if_current.state is defined and if_current.state.counters is defined and if_current.state.counters.in_broadcast_pkts is defined %}{{ if_current.state.counters.in_broadcast_pkts }}{% else %} no information available {% endif %}


#-----------------------------------------------------------
{% endfor %}
{% endif %}

============================================================
    Verification is done for openconfig {{ item.profile }}
============================================================

 

There are a lot of things happening there, but I want to highlight just four moments, what have paramount importance in my eyes.

The first one is that Cisco IOS XR has a bit different path within JSON file with telemetry comparing to not Cisco IOS XR (meaning Arista EOS and Nokia SR OS). The difference is that Cisco has path “rpc_reply.data” and the rest have “data”. It seems very small, but it pushes us to have a two copy of the same report, just because we need call for an appropriate JSON object to start parsing proper data. For sure, there might be a way to overcome this, but I didn’t have too much time to play with it, so if you can suggest something, you are very welcome.

The second one is that different vendors pushes different values to same keys. For instance, Cisco IOS XRv contains MTU values in “interface” object, whereas Arista stores them in “subinterface”. The same is true for the counters of the sent/received packets. That’s why you can see for non-Cisco part, that we are performing complex lookup for MTU.

The third key point is that depending on the vendor implementation, it’s not guaranteed that each container in telemetry output has consistent amount of keys. It might be that all are presented, it might that they are presented partially. Therefore, we need to verify each and every time that variable exist, before we call for it. Otherwise it’s very easy to get failure in the playbook in terms that you are calling for not existing object.

The last, but for sure not least, topic I want to point out is that conversion of XML output, which is collected through NETCONF “get” request to JSON isn’t straightforward. Ansible by default doesn’t understand, should be some objects saved as container or as list. The very simple example here is following. If we have only one “subinterface”, it will be just container, but if there are more than one (like two and more), it will be list, so we need to treat it respectively. Let’s have a more detailed look on MTU lookup for Arista EOS and Nokia SR OS:

1
2
3
4
5
    MTU:                {% if if_current.state is defined and if_current.state.mtu is defined and if_current.state.mtu != '0' %}{{ if_current.state.mtu }}{% elif if_current.state is defined and ansible_network_os == 'nexus' %}

{% if if_current.subinterfaces.subinterface.ipv4 is defined %}{{ if_current.subinterfaces.subinterface.ipv4.state.mtu }}{% else %}{% for sif_current in if_current.subinterfaces.subinterface %}{% if sif_current.index == '0' %}{{ sif_current.ipv4.state.mtu }}{% endif %}{% endfor %}{% endif %}

{% else %} no information available {% endif %}

 

It’s copy from the previous output with subtracted part to highlight the fourth point. It’s checked, if subinterface is container or list and then corresponding processing is chosen.

For the BGP processing we have even more complicated template, though taking into account highlighted points it’s much easier to compose it:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
$ cat roles/openconfig/140_lab/templates/openconfig_routing_bgp.j2
============================================================
    Checking of the status for openconfig {{ item.profile }}    
============================================================
{% if ansible_network_os == 'iosxr' %}
{% if COLLECTED.rpc_reply.data.bgp is defined %}

    Process BGP is configured within routing context
   
    Local ASN:          {% if COLLECTED.rpc_reply.data.bgp.global.state is defined and COLLECTED.rpc_reply.data.bgp.global.state.as is defined %}{{ COLLECTED.rpc_reply.data.bgp.global.state.as }}{% else %}no information available{% endif %}

    Local RID:          {% if COLLECTED.rpc_reply.data.bgp.global.state is defined and COLLECTED.rpc_reply.data.bgp.global.state.router_id is defined %}{{ COLLECTED.rpc_reply.data.bgp.global.state.router_id }}{% else %}no information available{% endif %}

    Active AFI/SAFI:    {% if COLLECTED.rpc_reply.data.bgp.global.afi_safis is defined %}{% for afi_safi_current in COLLECTED.rpc_reply.data.bgp.global.afi_safis.afi_safi %}{% if afi_safi_current.state is defined and afi_safi_current.state.afi_safi_name is defined and afi_safi_current.state.enabled == 'true' %}{{ afi_safi_current.state.afi_safi_name }} {% endif %}{% endfor %}{% else %}no information available{% endif %}

    Neighbors:
{% if COLLECTED.rpc_reply.data.bgp.neighbors is defined %}
{% for neighbor_current in COLLECTED.rpc_reply.data.bgp.neighbors.neighbor %}

        Neighbor:       {% if neighbor_current.state is defined and neighbor_current.state.neighbor_address is defined %}{{ neighbor_current.state.neighbor_address }}{% else %}no information available{% endif %}

        Remote ASN:     {% if neighbor_current.state is defined and neighbor_current.state.peer_as is defined %}{{ neighbor_current.state.peer_as }}{% else %}no information available{% endif %}

        State:          {% if neighbor_current.state is defined and neighbor_current.state.session_state is defined %}{{ neighbor_current.state.session_state }}{% else %}no information available{% endif %}

        AFI/SAFI:       {% if neighbor_current.afi_safis is defined %}{% if neighbor_current.afi_safis.afi_safi.state is defined and neighbor_current.afi_safis.afi_safi.state.enabled == 'true' %}{{ neighbor_current.afi_safis.afi_safi.state.afi_safi_name }}{% else %}{% for afi_safi_neighbor in neighbor_current.afi_safis.afi_safi %}{% if afi_safi_neighbor.state is defined and afi_safi_neighbor.state.afi_safi_name is defined and afi_safi_neighbor.state.enabled == 'true' %}{{ afi_safi_neighbor.state.afi_safi_name }} {% endif %}{% endfor %}{% endif %}{% else %}no information available{% endif %}

{% endfor %}
{% else %}

        There are no active neighbors
{% endif %}

#-----------------------------------------------------------
{% else %}

    There is no BGP process configured within routing context

#-----------------------------------------------------------
{% endif %}
{% elif ansible_network_os != 'iosxr' %}
{% if COLLECTED.data.network_instances.network_instance is defined and COLLECTED.data.network_instances.network_instance.name is defined %}

    Process BGP is configured within routing context '{{ COLLECTED.data.network_instances.network_instance.name }}'

    Local ASN:          {% if COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state is defined and COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state.as is defined %}{{ COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state.as }}{% else %}no information available{% endif %}
   
    Local RID:          {% if COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state is defined and COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state.router_id is defined %}{{ COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.state.router_id }}{% else %}no information available{% endif %}

    Active AFI/SAFI:    {% if COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.afi_safis is defined %}{% for afi_safi_current in COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.global.afi_safis.afi_safi %}{% if afi_safi_current.state is defined and afi_safi_current.state.afi_safi_name is defined %}{{ afi_safi_current.state.afi_safi_name }} {% endif %}{% endfor %}{% else %}no information available{% endif %}

    Neighbors:
{% if COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.neighbors is defined %}
{% for neighbor_current in COLLECTED.data.network_instances.network_instance.protocols.protocol.bgp.neighbors.neighbor %}

        Neighbor:       {% if neighbor_current.state is defined and neighbor_current.state.neighbor_address is defined %}{{ neighbor_current.state.neighbor_address }}{% else %}no information available{% endif %}

        Remote ASN:     {% if neighbor_current.state is defined and neighbor_current.state.peer_as is defined %}{{ neighbor_current.state.peer_as }}{% else %}no information available{% endif %}

        State:          {% if neighbor_current.state is defined and neighbor_current.state.session_state is defined %}{{ neighbor_current.state.session_state }}{% else %}no information available{% endif %}

        AFI/SAFI:       {% if neighbor_current.afi_safis is defined %}{% if neighbor_current.afi_safis.afi_safi.state is defined and neighbor_current.afi_safis.afi_safi.state.enabled == 'true' %}{{ neighbor_current.afi_safis.afi_safi.state.afi_safi_name }}{% else %}{% for afi_safi_neighbor in neighbor_current.afi_safis.afi_safi %}{% if afi_safi_neighbor.state is defined and afi_safi_neighbor.state.afi_safi_name is defined and afi_safi_neighbor.state.enabled == 'true' %}{{ afi_safi_neighbor.state.afi_safi_name }} {% endif %}{% endfor %}{% endif %}{% else %}no information available{% endif %}

{% endfor %}
{% else %}

        There are no active neighbors
{% endif %}

#-----------------------------------------------------------
{% else %}
{% for router_current in COLLECTED.data.network_instances.network_instance %}
{% if router_current.protocols is defined and router_current.protocols.protocol.bgp is defined %}

    Process BGP is configured within routing context '{{ router_current.name }}'
   
    Local ASN:          {% if router_current.protocols.protocol.bgp.global.state is defined and router_current.protocols.protocol.bgp.global.state.as is defined %}{{ router_current.protocols.protocol.bgp.global.state.as }}{% else %}no information available{% endif %}

    Local RID:          {% if router_current.protocols.protocol.bgp.global.state is defined and router_current.protocols.protocol.bgp.global.state.router_id is defined %}{{ router_current.protocols.protocol.bgp.global.state.router_id }}{% else %}no information available{% endif %}

    Active AFI/SAFI:    {% if router_current.protocols.protocol.bgp.global.afi_safis is defined %}{% for afi_safi_current in router_current.protocols.protocol.bgp.global.afi_safis.afi_safi %}{% if afi_safi_current.state is defined and afi_safi_current.state.afi_safi_name is defined %}{{ afi_safi_current.state.afi_safi_name }} {% endif %}{% endfor %}{% else %}no information available{% endif %}

    Neighbors:
{% if router_current.protocols.protocol.bgp.neighbors is defined %}
{% for neighbor_current in router_current.protocols.protocol.bgp.neighbors.neighbor %}

        Neighbor:       {% if neighbor_current.state is defined and neighbor_current.state.neighbor_address is defined %}{{ neighbor_current.state.neighbor_address }}{% else %}no information available{% endif %}

        Remote ASN:     {% if neighbor_current.state is defined and neighbor_current.state.peer_as is defined %}{{ neighbor_current.state.peer_as }}{% else %}no information available{% endif %}

        State:          {% if neighbor_current.state is defined and neighbor_current.state.session_state is defined %}{{ neighbor_current.state.session_state }}{% else %}no information available{% endif %}

        AFI/SAFI:       {% if neighbor_current.afi_safis is defined %}{% for afi_safi_neighbor in neighbor_current.afi_safis.afi_safi %}{% if afi_safi_neighbor.state is defined and afi_safi_neighbor.state.afi_safi_name is defined and afi_safi_neighbor.state.enabled == 'true' %}{{ afi_safi_neighbor.state.afi_safi_name }} {% endif %}{% endfor %}{% else %}no information available{% endif %}

{% endfor %}
{% else %}

        There are no active neighbors
{% endif %}

#-----------------------------------------------------------
{% else %}

    There is no BGP process configured within routing context '{{ router_current.name }}'

#-----------------------------------------------------------
{% endif %}
{% endfor %}
{% endif %}
{% endif %}

============================================================
    Verification is done for openconfig {{ item.profile }}
============================================================

 

Here situation is even more difficult in terms that Nokia SR OS and Arista EOS has different number of routing contexts, that’s why the template is copied three times, with just different entry points, but same logic. But apart from the length, the four points explained above are relevant here, so it was much easier to construct that point.

All the pieces for automation is verified and we can go to verification

OpenConfig // Collecting telemetry via NETCONF with Ansible

As we as hosts put “all” in the master playbook, we need to limit the scope upon execution:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
$ ansible-playbook 140_lab.yml --limit=XR3,SR1,vEOS2
 [WARNING] Ansible is being run in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source. For more information see https://docs.ansible.com/ansible/devel/reference_appendices/config.html#cfg-in-world-writable-dir

PLAY [all] *************************************************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [SR1]
ok: [vEOS2]
ok: [XR3]

TASK [openconfig/140_lab : VERIFICATION // IMPORTING INFRASTRUCTURE PROFILE] *******************************************************************************************
ok: [vEOS2]
ok: [SR1]
ok: [XR3]

TASK [openconfig/140_lab : VERIFICATION // CREATING FOLDER FOR RESULTS] ************************************************************************************************
ok: [XR3]
ok: [SR1]
ok: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // DELETE PREVIOUS TEST REPORT] ************************************************************************************************
changed: [vEOS2]
changed: [XR3]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // CREATING TEST REPORT] *******************************************************************************************************
changed: [XR3]
changed: [vEOS2]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // COLLECTING TELEMETRY AND SEARCHING DATA] ************************************************************************************
included: /home/aaa/ansible/roles/openconfig/140_lab/tasks/comparing_loop.yml for XR3, SR1, vEOS1 => (item={u'profile': u'interfaces'})
included: /home/aaa/ansible/roles/openconfig/140_lab/tasks/comparing_loop.yml for XR3, SR1, vEOS1 => (item={u'profile': u'routing_bgp'})
included: /home/aaa/ansible/roles/openconfig/140_lab/tasks/comparing_loop.yml for XR3, SR1, vEOS1 => (item={u'profile': u'routing_bgp'})

TASK [openconfig/140_lab : VERIFICATION // LOOP // FETCHING TELEMETRY DATA] ********************************************************************************************
ok: [SR1]
ok: [vEOS2]
ok: [XR3]

TASK [openconfig/140_lab : VERIFICATION // LOOP // SAVING TELEMETRY DATA] **********************************************************************************************
changed: [XR3]
changed: [vEOS2]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // LOOP // MODIFIICATION OF COLLECTED TELEMETRY FOR PYTHON PROCESSING] *********************************************************
changed: [XR3]
changed: [SR1]
changed: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // LOOP // IMPORTING COLLECTED TELEMETRY DATA] *********************************************************************************
ok: [XR3]
ok: [SR1]
ok: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // LOOP // CREARING TEMPORARY REPORT FOR interfaces] ***************************************************************************
changed: [XR3]
changed: [vEOS2]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // LOOP // COMPILING SUMMARY REPORT] *******************************************************************************************
changed: [vEOS2]
changed: [XR3]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // LOOP // DELETING TEMPORARY FILES] *******************************************************************************************
changed: [SR1]
changed: [vEOS2]
changed: [XR3]

TASK [openconfig/140_lab : VERIFICATION // LOOP // FETCHING TELEMETRY DATA] ********************************************************************************************
ok: [SR1]
ok: [XR3]
ok: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // LOOP // SAVING TELEMETRY DATA] **********************************************************************************************
changed: [vEOS2]
changed: [SR1]
changed: [XR3]

TASK [openconfig/140_lab : VERIFICATION // LOOP // MODIFIICATION OF COLLECTED TELEMETRY FOR PYTHON PROCESSING] *********************************************************
changed: [XR3]
changed: [SR1]
changed: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // LOOP // IMPORTING COLLECTED TELEMETRY DATA] *********************************************************************************
ok: [SR1]
ok: [vEOS2]
ok: [XR3]

TASK [openconfig/140_lab : VERIFICATION // LOOP // CREARING TEMPORARY REPORT FOR routing_bgp] **************************************************************************
changed: [vEOS2]
changed: [XR3]
changed: [SR1]

TASK [openconfig/140_lab : VERIFICATION // LOOP // COMPILING SUMMARY REPORT] *******************************************************************************************
changed: [XR3]
changed: [SR1]
changed: [vEOS2]

TASK [openconfig/140_lab : VERIFICATION // LOOP // DELETING TEMPORARY FILES] *******************************************************************************************
changed: [SR1]
changed: [vEOS2]
changed: [XR3]

TASK [openconfig/140_lab : VERIFICATION // REPORTING READINESS] ********************************************************************************************************
ok: [XR3] => {
    "msg": "Collection of telemetry data from XR3 is done."
}
ok: [vEOS2] => {
    "msg": "Collection of telemetry data from SR1 is done."
}
ok: [SR1] => {
    "msg": "Collection of telemetry data from SR1 is done."
}

PLAY RECAP *************************************************************************************************************************************************************
SR1                        : ok=22   changed=12   unreachable=0    failed=0  
XR3                        : ok=22   changed=12   unreachable=0    failed=0    
vEOS2                      : ok=22   changed=12   unreachable=0    failed=0

 

The playbook is successfully executed.

To show you some similarities, which provides OpenConfig YANG model, let’s take a look at interfaces. Arista EOS has the following:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
$ cat /tmp/140/vEOS2/vEOS2_openconfig_interfaces_telemetry.json
{
    "data": {
        "interfaces": {
            "interface": [
                {
                    "aggregation": {
                        "config": {
                            "fallback_timeout": "90",
                            "min_links": "0"
                        }
                    },
                    "config": {
                        "description": "",
                        "enabled": "true",
                        "load_interval": "300",
                        "loopback_mode": "false",
                        "mtu": "0",
                        "name": "Ethernet3",
                        "tpid": "TPID_0X8100",
                        "type": "ethernetCsmacd"
                    },
                    "ethernet": {
                        "config": {
                            "fec_encoding": {
                                "disabled": "false",
                                "fire_code": "false",
                                "reed_solomon": "false",
                                "reed_solomon544": "false"
                            },
                            "mac_address": "00:00:00:00:00:00",
                            "port_speed": "SPEED_UNKNOWN",
                            "sfp_1000base_t": "false"
                        },
                        "state": {
                            "auto_negotiate": "false",
                            "counters": {
                                "in_crc_errors": "0",
                                "in_fragment_frames": "0",
                                "in_jabber_frames": "0",
                                "in_mac_control_frames": "0",
                                "in_mac_pause_frames": "0",
                                "in_oversize_frames": "0",
                                "out_mac_control_frames": "0",
                                "out_mac_pause_frames": "0"
                            },
                            "duplex_mode": "FULL",
                            "enable_flow_control": "false",
                            "hw_mac_address": "00:50:56:39:8a:ce",
                            "mac_address": "00:50:56:39:8a:ce",
                            "negotiated_port_speed": "SPEED_UNKNOWN",
                            "port_speed": "SPEED_UNKNOWN"
                        }
                    },
! FURTHER OUTPUT IS OMITTED

 

Cisco IOS XR:

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
$ cat /tmp/140/XR3/XR3_openconfig_interfaces_telemetry.json
{
    "rpc_reply": {
        "data": {
            "interfaces": {
                "interface": [
                    {
                        "config": {
                            "enabled": "true",
                            "name": "Loopback0",
                            "type": "idx:softwareLoopback"
                        },
                        "name": "Loopback0",
                        "state": {
                            "admin_status": "UP",
                            "description": "",
                            "enabled": "true",
                            "ifindex": "8",
                            "last_change": "15337",
                            "mtu": "1500",
                            "name": "Loopback0",
                            "oper_status": "UP",
                            "type": "idx:softwareLoopback"
                        },
                        "subinterfaces": {
                            "subinterface": {
                                "index": "0",
                                "ipv4": {
                                    "address": {
                                        "config": {
                                            "ip": "10.0.0.33",
                                            "prefix_length": "32
! FURTHER OUTPT IS OMITTED

 

Nokia SR OS:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
$ cat /tmp/140SR1/SR13_openconfig_interfaces_telemetry.json
{
    "data": {
        "interfaces": {
            "interface": [
                {
                    "config": {
                        "enabled": "true",
                        "name": "1/1/c1/1",
                        "type": "ethernetCsmacd"
                    },
                    "ethernet": {
                        "state": {
                            "auto_negotiate": "false",
                            "counters": {
                                "in_crc_errors": "0",
                                "in_fragment_frames": "0",
                                "in_jabber_frames": "0",
                                "in_mac_pause_frames": "0",
                                "in_oversize_frames": "0",
                                "out_mac_pause_frames": "0"
                            },
                            "hw_mac_address": "52:54:00:02:01:01",
                            "mac_address": "52:54:00:02:01:01",
                            "negotiated_duplex_mode": "FULL",
                            "negotiated_port_speed": "SPEED_10GB"
                        }
                    },
                    "hold_time": {
                        "state": {
                            "down": "0",
                            "up": "0"
                        }
                    },
                    "name": "1/1/c1/1",
                    "state": {
                        "admin_status": "UP",
                        "counters": {
                            "carrier_transitions": "0",
                            "in_broadcast_pkts": "4",
                            "in_discards": "0",
                            "in_errors": "0",
                            "in_fcs_errors": "0",
                            "in_multicast_pkts": "114",
                            "in_octets": "271863",
                            "in_unicast_pkts": "3041",
                            "in_unknown_protos": "0",
                            "out_broadcast_pkts": "38",
                            "out_discards": "0",
                            "out_errors": "0",
                            "out_multicast_pkts": "65",
                            "out_octets": "271204",
                            "out_unicast_pkts": "3081"
                        },
                        "description": "10_Gig Ethernet",
                        "enabled": "true",
                        "ifindex": "1610899521",
                        "last_change": "36370000",
                        "mtu": "9212",
                        "name": "1/1/c1/1",
                        "oper_status": "UP",
                        "type": "ethernetCsmacd"
                    },
! FURTHER OUTPT IS OMITTED

 

If you try to compare these outputs, there are very similar to each other. Nevertheless, you might see that Cisco IOS XR has less counters. By the way, they are splitted across several objects, what is super strange. All the telemetry information is stored in “state” containers.

After the telemetry is collected, it’s processed in order to create the reports, and here are some examples.

Arista EOS:

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
$ cat /tmp/140/vEOS2_test_report.txt
============================================================
    Checking of the status for openconfig interfaces
============================================================

    Interface:          Ethernet1
    Status:
        Administrative: UP
        Operational:    UP
    MTU:                1514
    Packets:
        Sent:
           Unicast:     5672
           Multicast:   2726
           Broadcast:   2
        Received:
           Unicast:     5322
           Multicast:   382
           Broadcast:   7

#-----------------------------------------------------------

    Interface:          Loopback0
    Status:
        Administrative:  no information available
        Operational:     no information available
    MTU:                1500
    Packets:
        Sent:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available
        Received:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available

#-----------------------------------------------------------
! FURTHER OUTPUT IS OMITTED

 

Cisco IOS XR:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
$ cat /tmp/140/XR3_test_report.txt
============================================================
    Checking of the status for openconfig interfaces
============================================================

    Interface:          Loopback0
    Status:
        Administrative: UP        
        Operational:    UP
    MTU:                1500
    Packets:
        Sent:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available
        Received:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available

#-----------------------------------------------------------

    Interface:          MgmtEth0/0/CPU0/0
    Status:
        Administrative: UP        
        Operational:    UP
    MTU:                1514
    Packets:
        Sent:
           Unicast:      no information available
           Multicast:   0
           Broadcast:   3
        Received:
           Unicast:      no information available
           Multicast:   0
           Broadcast:   2

#-----------------------------------------------------------

    Interface:          GigabitEthernet0/0/0/0
    Status:
        Administrative: UP        
        Operational:    UP
    MTU:                1514
    Packets:
        Sent:
           Unicast:      no information available
           Multicast:   96
           Broadcast:   2
        Received:
           Unicast:      no information available
           Multicast:   0
           Broadcast:   0

#-----------------------------------------------------------
! FURTHER OUTPUT IS OMITTED

 

Nokia SR OS:

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
$ cat /tmp/140/SR1_test_report.txt
============================================================
    Checking of the status for openconfig interfaces
============================================================

    Interface:          1/1/c1/1
    Status:
        Administrative: UP
        Operational:    UP
    MTU:                9212
    Packets:
        Sent:
           Unicast:     3081
           Multicast:   65
           Broadcast:   38
        Received:
           Unicast:     3041
           Multicast:   114
           Broadcast:   4

#-----------------------------------------------------------

    Interface:          system
    Status:
        Administrative:  no information available
        Operational:     no information available
    MTU:                 no information available
    Packets:
        Sent:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available
        Received:
           Unicast:      no information available
           Multicast:    no information available
           Broadcast:    no information available

#-----------------------------------------------------------
! FURTHER OUTPUT IS OMITTED

 

As your, information for Arista EOS and Nokia SR OS is more or less consistent in terms of data plane interfaces. Cisco IOS XR has not consistent structure for data plane interfaces. All the vendors have problems with reporting loopbacks information.

The full output of the telemetry information and related data plane outputs you can find in the attachment.

Here you can find the playbooks and full reports and telemetry files from this lab: 140_lab.tar

Lessons learned

The most important lessons learned were already explained earlier, when we have talked about Jinja2 templates for interfaces. Here I want to show some weird output, which exists in OpenConfig YANG telemetry in Cisco IOS XR 6.1.2. I hope, it’s fixed in the upcoming versions, but I haven’t tested that in any newer version yet. So, take a look on the output below (you will found them if you download attachment above as well):

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
$ cat /tmp/140/XR3/XR3_openconfig_interfaces_telemetry.json
! OUTPUT IS OMITTED
                        },
                        "name": "GigabitEthernet0/0/0/0",
                        "state": {
                            "admin_status": "UP",
                            "counters": {
                                "in_broadcast_pkts": "0",
                                "in_discards": "0",
                                "in_errors": "0",
                                "in_multicast_pkts": "0",
                                "in_unknown_protos": "0",
                                "last_clear": "Never",
                                "out_broadcast_pkts": "2",
                                "out_discards": "0",
                                "out_errors": "0",
                                "out_multicast_pkts": "96"
                            }
                        },
                        "subinterfaces": {
                            "subinterface": {
                                "config": {
                                    "enabled": "true",
                                    "index": "13",
                                    "name": "GigabitEthernet0/0/0/0.13"
                                },
                                "state": {
                                    "admin_status": "UP",
                                    "counters": {
                                        "in_broadcast_pkts": "0",
                                        "in_discards": "0",
                                        "in_errors": "0",
                                        "in_multicast_pkts": "0",
                                        "in_unknown_protos": "0",
                                        "last_clear": "Never",
                                        "out_broadcast_pkts": "2",
                                        "out_discards": "0",
                                        "out_errors": "0",
                                        "out_multicast_pkts": "96",
                                        "out_octets": "225118"
                                    },
! OUTPUT IS OMITTED
                    {
                        "name": "GigabitEthernet0/0/0/0",
                        "state": {
                            "counters": {
                                "in_octets": "0",
                                "in_unicast_pkts": "0",
                                "out_octets": "0",
                                "out_unicast_pkts": "0"
                            }
                        }
                    },
! OUTPUT IS OMITTED
                    {
                        "name": "GigabitEthernet0/0/0/0.13",
                        "state": {
                            "counters": {
                                "in_octets": "49860",
                                "in_unicast_pkts": "554",
                                "out_octets": "49528",
                                "out_unicast_pkts": "649"
                            }
                        }
                    },
! FURTHER OUTPUT IS OMITTED

 

First of all, there are two different containers, which are related to the same interface “GigabitEthernet0/0/0/0”. This is very misleading, cause we expect to get all the relevant information from single source… But we don’t get it. It means that logic for collecting the counters should be much more complicated in terms that all containers are reviewed to collect all the counters. Another strange point is that for subinterface “GigabitEthernet0/0/0/0.13” there is dedicated object created at interface level to export some counters, whereas the rest of the counters are exported, where they are expected: at subinterface level of parent’s interface.

To sum up, you need to be very carefully by reviewing the telemetry outputs to make sure you understand potential caveats so that you can adapt your reports or the further automation pipeline properly.

Conclusion

Telemetry in OpenConfig YANG modules provides you possibility to manage your multi-vendor network in a consistent way. At least it should do this and we strongly believe that newer version of network operation systems implements the latest OpenConfig version, meaning that they are more or less aligned to each other. Even now, with proper tuning, we can have the same set of the information collected in the same way across different vendors, as it was shown with Arista EOS, Cisco IOS XR and Nokia SR OS. On the other hand, this proper tuning requires from you clear understanding of what you are going to achieve and what are the data you have. Take care and good bye!

Support us





P.S.

If you have further questions or you need help with your networks, I’m happy to assist you, just send me message (http://karneliuk.com/contact/). Also don’t forget to share the article on your social media, if you like it.

BR,

Anton Karneliuk