Site icon Karneliuk

Telemetry. Part 3. OpenConfig YANG modules for Arista EOS, Nokia SR OS, Cisco IOS XR with Ansible – Nokia

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.


1
2
3
4
5
 No part of this blogpost could be reproduced, stored in a
 retrieval system, or transmitted in any form or by any
 means, electronic, mechanical or photocopying, recording,
 or otherwise, for commercial purposes without the
 prior permission of the author.

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:

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
                          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
         x-------------BGP:IPV4_UNI/IPV6_UNI------------x-------------BGP:IPV4_UNI/IPV6_UNI-----------x

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:

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:

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:

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:

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
$ cat roles/openconfig/140_lab/templates/netconf_request.j2
{% if item.profile == 'interfaces' %}
{% elif item.profile == 'routing_bgp' and ansible_network_os == 'iosxr' %}
{% elif item.profile == 'routing_bgp' and ansible_network_os != 'iosxr' %}
{% 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
$ 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.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.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
    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
For the BGP processing we have even more complicated template, though taking into account highlighted points it’s much easier to compose it:

$ 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
$ ansible-playbook 140_lab.yml --limit=XR3,SR1,vEOS2<br>
[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.”
}

1
2
3
4
PLAY RECAP *************************************************************************************************************************************************************<br>
SR1                        : ok=22   changed=12   unreachable=0    failed=0<br>
XR3                        : ok=22   changed=12   unreachable=0    failed=0<br>
vEOS2                      : ok=22   changed=12   unreachable=0    failed=0<br>

 

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": &#91;
 {
 "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": &#91;
 {
 "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": &#91;
 {
 "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
$ cat /tmp/140/vEOS2_test_report.txt<br>
============================================================<br>
Checking of the status for openconfig interfaces<br>
============================================================

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

1
2
#-----------------------------------------------------------<br>
! FURTHER OUTPUT IS OMITTED<br>

 

Cisco IOS XR:

1
2
3
4
$ cat /tmp/140/XR3_test_report.txt<br>
============================================================<br>
Checking of the status for openconfig interfaces<br>
============================================================

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

1
2
#-----------------------------------------------------------<br>
! FURTHER OUTPUT IS OMITTED<br>

 

Nokia SR OS:

1
2
3
4
$ cat /tmp/140/SR1_test_report.txt<br>
============================================================<br>
Checking of the status for openconfig interfaces<br>
============================================================

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

1
2
#-----------------------------------------------------------<br>
! FURTHER OUTPUT IS OMITTED<br>

 

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 (https://karneliuk.com/contact/). Also don’t forget to share the article on your social media, if you like it.

BR,

Anton Karneliuk

Exit mobile version