Hello my friend,

Today we’ll continue (and probably finalize) our conceptual discussion about OpenConfig for the automation of the data center network based on BGP-EVPN/VXLAN. We’ll speak about BGP and associated routing policies and again about interfaces, but we’ll do it from different angle comparing to the first OpenConfig article. So we won’t create the jinja2 templates ourselves, but we’ll rely on built in functionality in “pyang “tool instead.

Disclaimer

There are two points you need to know, before going further. First, this article is inspired by OpenConfig demo by Peter Sprygada, which my friend Nicola Arnoldi has shared with me recently and which I mentioned in the previous article as well. So, I’m not inventing the wheel, just bringing missing pieces as I see them as the video lacks details a lot. Basically, there are no details

Second, all the configuration examples and my statements should be treated in the context of particular SW versions. As I known from Victor Osipchuk, there were significant changes in OpenConfig model in Cisco IOS XR, where routing protocols are put to single “network-instance” context now. You will learn later on in this article what is it.

Brief description

In a nutshell we have explained OpenConfig in one of the previous articles, so we could focus solely on following the same approach creating our jinja2 templates and that’s it. And to be honest, it’s still valid approach, because there are a lot of caveats with built-in tools as you will learn shortly. Nevertheless, usage of two tools “pyang” and json2xml” provides huge advantage as a lot of work could be automated.

Probably, in a future there will be dedicated OpenConfig module for Ansible, which will make usage of OpenConfig even easier. On the other hand, as we have learned earlier there are two critical point:

  • Each vendor has some deviations, meaning that some particular features aren’t supported or augments, meaning that some additional not standard features are supported.
  • Even within one vendor and NOS (network operation system) family the implementation of OpenConfig may change between different releases so there should be some inelegance to deal with those differences.

Using some ideas from the previous article we will solve both of those points. Are you thrilled? Let’s go!

What we are going to test?

We’ll automate the rollout data centre fabric with the following things in mind:

  • We will create Ansible playbook to configure interfaces, routing policies and BGP
  • We will use OpenConfig
  • We adapt the configuration file based on the particular OpenConfig support of the particular NOS
  • We rely on “payng” and “json2xml” to avoid creating jinja2 templates unless it’s absolutely necessary

Software version

The following infrastructure is used in my lab:

  • CentOS 7 with python 2.7.
  • Ansible 2.6.0
  • Arista EOS 4.20.7M
  • Nokia SR OS 16.0.R1
  • Cisco IOS XR 6.1.2

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

Topology

From the phsycial topology prospective we are conservative and we are using the standard our topology:

Logical topology is also quite stable as we have used it a lot previously for all our data center articles:

There are only initial configurations per device provided, which allows us to reach network elements on management interface: 132_config_initial_vEOS2 132_config_initial_XR3 132_config_initial_SR1 128_config_initial_linux

General algorithm for YANG-based automation w/ focus on OpenConfig

Based on my tests there are following tests, there are two major sets of activities we have.

The first one is collecting of the information. Here we do the following steps:

  • Fetch names of old supported modules per network function using “netconf_get”
  • Collect all the YANG modules themselves using “netconf_rpc” module with “get-schema” RPC request.
  • Create appropriate “jtox” drivers of supported OpenConfig YANG model using “pyang”

More information about “netconf_get” and “netconf_rpc” Ansible modules you can find in the previous article.

“Pyang” is a Python tool, which can be installed in the Linux. We have used it previously to building YANG trees, but it has some other output format. In this article we’ll just “jtox” as on output format and we’ll save output to temporary file. It works as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ pyang -f jtox -o openconfig-interfaces.jtox -p YANG/ YANG/openconfig-interfaces.yang YANG/openconfig-if-* YANG/openconfig-vlan.yang --lax-quote-checks
$
$ more openconfig-interfaces.jtox
{"tree": {"openconfig-interfaces:interfaces": ["container", {"interface": ["list", {"openconfig-vlan:routed-vlan": ["container", {"openconfig-if-ip:ipv6": ["c
ontainer", {"state": ["container", {"enabled": ["leaf", "boolean"], "dup-addr-detect-transmits": ["leaf", "uint32"], "mtu": ["leaf", "uint32"]}], "autoconf":
["container", {"state": ["container", {"temporary-valid-lifetime": ["leaf", "uint32"], "create-temporary-addresses": ["leaf", "boolean"], "create-global-addre
sses": ["leaf", "boolean"], "temporary-preferred-lifetime": ["leaf", "uint32"]}], "config": ["container", {"temporary-valid-lifetime": ["leaf", "uint32"], "cr
eate-temporary-addresses": ["leaf", "boolean"], "create-global-addresses": ["leaf", "boolean"], "temporary-preferred-lifetime": ["leaf", "uint32"]}]}], "confi
g": ["container", {"enabled": ["leaf", "boolean"], "dup-addr-detect-transmits": ["leaf", "uint32"], "mtu": ["leaf", "uint32"]}], "neighbor": ["list", {"ip": [
"leaf", "string"], "state": ["container", {"origin":...
!
! FURTHER OTPUT IS OMITTED
!

 

Pay attention that we create single ”jtox” driver from several source YANG modules “YANG/openconfig-interfaces.yang YANG/openconfig-if-* YANG/openconfig-vlan.yang”.

Now you might start thinking, what “jtox” driver is. Don’t panic, it’s just a construct that translates JSON input (files with network function parameters) into proper XML output used by NETCONF. The fact that XML is “proper” is guaranteed by creating “jtox” driver from particular OpenConfig YANG model extracted from the network function.

The second set of activities is related to configuration of the network functions using OpenConfig, where we undertake these steps:

  • Create the file in JSON format with parameter we want to configure according to “jtox” driver
  • Render the JSON variables into XML file using “json2xml” tool and “jtox”
  • Send configuration to network function using “netconf_config” module

As you see, in this article we are using all available NETCONF modules from Ansible arsenal. Hypothetically we could limit ourselves only to “netconf_rpc”, but this would make our Ansible playbooks much more complicated.

I’d like also to say some words about “json2xml” tool. As it’s said, it translates JSON to XML using “jtox” driver created by “pyang”. To do this, initial JSON file should be structured in the proper format matching “jtox” driver. Let’s assume we have it (details will be shared into vendor-specific chapter), then we use “json2xml” as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ json2xml -t config -o oc_if.xml openconfig-interfaces.jtox XR3_oc_interface.json
$
$ more oc_if.xml
<?xml version='1.0' encoding='utf-8'?>
<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ocif="http://openconfig.net/yang/interfaces" xmlns:ocip="http://openconfig.net/yang/interf
aces/ip" xmlns:eth="http://openconfig.net/yang/interfaces/ethernet" xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type" xmlns:if="urn:ietf:params:xml:ns:
yang:ietf-interfaces" xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types" xmlns:lag="http://openconfig.net/yang/interface/aggregate" xmlns:ocext="http://
openconfig.net/yang/openconfig-ext" xmlns:vlan="http://openconfig.net/yang/vlan" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types"><ocif:interfaces><oc
if:interface><ocif:name>Loopback10</ocif:name><ocif:config><ocif:type>ocif:softwareLoopback</ocif:type><ocif:enabled>true</ocif:enabled><ocif:name>Loopback10<
/ocif:name></ocif:config><ocif:subinterfaces><ocif:subinterface><ocif:index>0</ocif:index><ocip:ipv6><ocip:address><ocip:ip>fc00::10:0:0:33</ocip:ip><ocip:con
fig><ocip:ip>fc00::10:0:0:33</ocip:ip><ocip:prefix-length>128</ocip:prefix-length></ocip:config></ocip:address></ocip:ipv6><ocip:ipv4><ocip:address><ocip:ip>1
0.0.0.33</ocip:ip><ocip:config><ocip:ip>10.0.0.33</ocip:ip><ocip:prefix-length>32</ocip:prefix-length></ocip:config></ocip:address></ocip:ipv4></ocif:subinter
face></ocif:subinterfaces></ocif:interface></ocif:interfaces></nc:config>

 

There will be needed some manual modification of such XML file, depending on particular vendor implementation of OpenConfig; you will see the details later.

Before we go to particular per vendor (Cisco, Nokia, Arista) example, make sure you have installed “pyang” and “json2xml”.

#1. Example of automated OpenConfig-driven configuration for Cisco IOS XR.

I’m starting with Cisco IOS XR this time, because it’s possible to find some open information in the Internet about OpenConfig comparing Arista and Nokia (Alcatel-Lucent), what will be covered later in this chapter.

The structure of Ansible playbook based on roles is following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+--ansible
   +--132_lab.yml
   +--group_vars
   |  +--cisco
   |     +--cisco_host.yml
   +--roles
      +--cisco
         +--132_lab
            +--files
            |  +--XR3_openconfig-bgp.json
            |  +--XR3_openconfig-interfaces.json
            |  +--XR3_openconfig-routing-policy.json
            +--tasks
            |  +--collect.yml
            |  +--configure.yml
            |  +--jtox_builder.yml
            |  +--main.yml
            |  +--yang_collector.yml
            |  +--yang_configurator.yml
            +--templates
            |  +--pyang_request_jtox.j2
            +--vars
               +--desired_opencofnig_modules.yml

 

The parent Ansible playbook, which contains roles has the following content:

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
$ cat 132_lab.yml
---
- hosts
: cisco
  connection
: netconf
  tags
: cisco_collect
  roles
:
    - { role
: cisco/132_lab, activity: collect }

- hosts
: cisco
  connection
: netconf
  tags
: cisco_configure
  roles
:
    - { role
: cisco/132_lab, activity: configure }

- hosts
: arista
  connection
: netconf
  tags
: arista_collect
  roles
:
    - { role
: arista/132_lab, activity: collect }

- hosts
: arista
  connection
: netconf
  tags
: arista_configure
  roles
:
    - { role
: arista/132_lab, activity: configure }

- hosts
: nokia
  connection
: netconf
  tags
: nokia_collect
  roles
:
    - { role
: nokia/132_lab, activity: collect }

- hosts
: nokia
  connection
: netconf
  tags
: nokia_configure
  roles
:
    - { role
: nokia/132_lab, activity: configure }
...

 

So, for each vendor we have two set of actions, which are called through specific tag. We’ll split our explanation logically in the same set of actions: collect and configure.

But before jumping into details of each task, take a look at Cisco variables “group_vars” below:

1
2
3
4
5
6
7
8
$ cat group_vars/cisco/cisco_host.yml
---
ansible_network_os
: iosxr
ansible_user
: cisco
ansible_pass
: cisco
ansible_ssh_pass
: cisco
ansible_port
: 830
...

 

This data is used for authentication and for proper functioning of ncclient.

Now you have full picture and we can analyze details.

#1.1. Cisco IOS XR / Fetching YANG models and constructing JTOX drivers

As you have seen in the beginning of this part, we have a lot files with different tasks. So here is the “main.yml” file, which is called by parent Ansible playbook:

1
2
3
4
5
6
7
8
9
10
$ cat roles/cisco/132_lab/tasks/main.yml
---
- name
: COLLECTOR
  include_tasks
: collect.yml
  when
: activity == 'collect'

- name
: CONFIGURATOR
  include_tasks
: configure.yml
  when
: activity == 'configure'
...

 

As you see, we have two children playbook:

  • One is used to collect YANG models from the network function and build “jtox” drivers
  • Another is used to configure network function using variable created “jtox” drivers

The activation of the nested Ansible playbook is controlled by tag we use in the parent.

We have used this connect in Ansible as VNF-M approach.

Inside nested “collect.yml” Ansible playbook we have the following tasks:

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
$ cat roles/cisco/132_lab/tasks/collect.yml
---
- name
: CREATING DIRECTORY FOR YANG MODULES
  file
:
    dest
: /tmp/{{ inventory_hostname }}/YANG
    state
: directory

- name
: GETTING LIST OF ALL SUPPORTED YANG MODULES FROM {{ inventory_hostname }}
  netconf_get
:
    display
: json
    filter
: <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"><schemas><schema/></schemas></netconf-state>
    lock
: never
  register
: list_of_schemas

- name
: SAVING LIST OF ALL SUPPORTED YANG MODULES FROM {{ inventory_hostname }}
  copy
:
    content
: "{{ list_of_schemas.output | to_nice_json }}"
    dest
: /tmp/{{ inventory_hostname }}/list_of_schemas.json

- name
: PERFORMING SOME WORKAROUNDS ON VARIABLES
  replace
:
    path
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    regexp
: 'rpc-reply'
    replace
: 'rpc_reply'

- name
: PERFORMING SOME WORKAROUNDS ON VARIABLES
  replace
:
    path
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    regexp
: 'netconf-state'
    replace
: 'netconf_state'

- name
: IMPORTING NAMES OF SUPPORTED YANG MODELS
  include_vars
:
    file
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    name
: YANG

- name
: FETCHING YANG MODELS FROM {{ inventory_hostname }} / {{ ansible_network_os }}
  include_tasks
: yang_collector.yml
  loop
: "{{ YANG.rpc_reply.data.netconf_state.schemas.schema }}"

- name
: IMPORTING VARS
  include_vars
:
    file
: desired_openconfig_modules.yml

- name
: CREATING JTOX DRIVERS
  include_tasks
: jtox_builder.yml
  loop
: "{{ desired_openconfig_modules }}"
  loop_control
:
    loop_var
: outer_item
...

 

I’ll streamline explanation to save the time, as it took me a while before I got working automation.

The first task creates the folder, where we store all our temporary (and not only) files.

The second task gets list of all supported YANG modules from the network function. We have shown in the previous article how it works.

The third task saves list of supported modules to a file into “/tmp/” folder.

The fourth and fifth tasks modify the saved file to remove symbol “_” from variable name, as it doesn’t work in Ansible. I know that it’s ugly (hello @Nicola), but it’s working. Probably there is a better way to do it, so I appreciate your advices, my friends. Unmodified file looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ more /tmp/XR3/list_of_schemas.json
{
    "rpc_reply"
: {
        "data"
: {
            "netconf_state"
: {
                "schemas"
: {
                    "schema"
: [
                        {
                            "format"
: "yang",
                            "identifier"
: "Cisco-IOS-XR-aaa-lib-cfg",
                            "location"
: "NETCONF",
                            "namespace"
: "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-lib-cfg",
                            "version"
: "2015-11-09"
                        },
!
! FURTHER OUTPUT IS OMITED
!

 

So, if we make those two text modifications, we can easily import it and use as variables in task six.

Then in task seven, which is by the way one of the most time consuming but important tasks, we are looping another nested Ansible playbook, which collects the YANG models from the network function. The sub-playbook looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat roles/cisco/132_lab/tasks/yang_collector.yml
---
    - name
: FETCHING {{ item.identifier }} FROM {{ inventory_hostname }}
      netconf_rpc
:
        rpc
: get-schema
        xmlns
: "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
        content
:
          identifier
: "{{ item.identifier }}"
        display
: json
      register
: supported_yang_modules

    - name
: SAVING {{ item.identifier }} FROM {{ inventory_hostname }}
      copy
:
        content
: "{{ supported_yang_modules.stdout }}"
        dest
: /tmp/{{ inventory_hostname }}/YANG/{{ item.identifier }}.yang

    - name
: MAKING {{ item.identifier }} USABLE
      replace
:
        path
: /tmp/{{ inventory_hostname }}/YANG/{{ item.identifier }}.yang
        regexp
: '<.+>'
        replace
: ''
...

 

In this nested playbook we use new “netconf_rpc” module to collect YANG module, save it into files on our management host and remove XML framing, which comes as part of NETCONF response. In such a way we fetch all available YANG modules from this particular network function.

Back to original playbook “collect.yml”, and we reached the eight task. Here we import information about OpenConfig modules, which we want to use during configuration. The variables look like 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
$ cat roles/cisco/132_lab/vars/desired_openconfig_modules.yml
---
desired_openconfig_modules
:
    - name
: OC INTERFACES
      jtox_output
: openconfig-interfaces.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-interfaces.json"
      src_yang
:
          - module
: openconfig-interfaces.yang
          - module
: openconfig-if-ip.yang
          - module
: openconfig-if-ethernet.yang
          - module
: openconfig-if-aggregate.yang
          - module
: openconfig-vlan.yang
    - name
: OC ROUTING POLICY
      jtox_output
: openconfig-routing-policy.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-routing-policy.json"
      src_yang
:
          - module
: openconfig-routing-policy.yang
    - name
: OC BGP
      jtox_output
: openconfig-bgp.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-bgp.json"
      src_yang
:
          - module
: openconfig-bgp.yang
...

 

As you might see, some “jtox” drivers will consist from more than one source YANG models! It’s important.

The structure of this file is arbitrary, as usual with variables, so I defined something that works good for me. Not all variables are used in present playbook, as “related_config” will be used in configuration part.”

The last task, the ninth one, calls another nested Ansible playbook, exactly the same as the seventh did, to loop the creation of appropriate “jtox” drivers. This sub-nested playbook has the following structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat roles/cisco/132_lab/tasks/jtox_builder.yml
---
- name
: FIXING YANG MODULES // {{ item }}
  replace
:
    path
: "{{ outer_item.path }}/YANG/{{ item.module }}"
    regexp
: 'require-instance true'
    replace
: '// require-instance true'
  loop
: "{{ outer_item.src_yang }}"

- name
: PREPARING PYANG REQUEST // {{ outer_item.name }}
  template
:
    src
: pyang_request_jtox.j2
    dest
: /tmp/imba.jtox

- name
: CONSTRUCTING DESIRED JTOX DRIVER // {{ outer_item.name }}
  command
: "{{ lookup ('file', '/tmp/imba.jtox') }}"

- name
: CLEARING PYANG REQUEST // {{ outer_item.name }}
  file
:
    dest
: /tmp/imba.jtox
    state
: absent
...

 

The “jtox” drivers are created using “pyang”, it’s its standard functionality. So in the second task we use jinja2 template to prepare correct command, which is used in task number three:

1
2
$ cat roles/cisco/132_lab/templates/pyang_request_jtox.j2
pyang --lax-quote-checks -f jtox -o {{ outer_item.path }}/{{ outer_item.jtox_output}} -p {{ outer_item.path }}/YANG {% for src_module in outer_item.src_yang %} {{ outer_item.path }}/YANG/{{ src_module.module}} {% endfor %}

 

If the jinja2 template looks complicated, take a look at the command shared previously

Then in task number four we remove temporary file with proper request.

But beforehand, in the task number one we fix the problem that some YANG modules, which is used within the parent (like “openconfig-if-ip” uses “openconfig-interfaces”), requires existing of the instance. I think that this check is correct in some context, but it prevents creation of “jtox” drivers. So the task number one fixes that by commenting this requirement and drivers are constructed properly.

The explanation of the operation of this Ansible playbook is quite long. To make your better feeling about, what it makes, let’s test it

#1.2. Verification of collection and preparation

I must warn you that at my laptop the execution of this playbook took a while. 443 YANG models were fetched from Cisco IOS XRv 6.1.2, what took 35 minutes including processing (saving and text modification).

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
$ ansible-playbook 132_lab.yml --limit=XR3 --tag=cisco_collect
 [WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.

PLAY [cisco] *************************************************************************************************************************************************

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

TASK [cisco/132_lab : COLLECTOR] *****************************************************************************************************************************
included: /home/aaa/ansible/roles/cisco/132_lab/tasks/collect.yml for XR3

TASK [cisco/132_lab : CREATING DIRECTORY FOR YANG MODULES] ***************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : GETTING LIST OF ALL SUPPORTED YANG MODULES FROM XR3] ***********************************************************************************
ok: [XR3]

TASK [cisco/132_lab : SAVING LIST OF ALL SUPPORTED YANG MODULES FROM XR3] ************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : PERFORMING SOME WORKAROUNDS ON VARIABLES] **********************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : PERFORMING SOME WORKAROUNDS ON VARIABLES] **********************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : IMPORTING NAMES OF SUPPORTED YANG MODELS] **********************************************************************************************
ok: [XR3]

TASK [cisco/132_lab : FETCHING YANG MODELS FROM XR3 / iosxr] *************************************************************************************************
!
! OUTPUT IS OMITTED
!
PLAY RECAP ***************************************************************************************************************************************************
XR3                        : ok=1793 changed=900  unreachable=0    failed=0

 

As you see, we have significantly reduced the output of the playbook, as it’s impossible to show hundreds of lines.

After the execution of this Ansible playbook we have the following structure created:

1
2
3
4
5
6
7
8
9
10
11
+--/tmp/XR3/
   +-- list_of_schemas.json
   +-- openconfig-bgp.jtox
   +-- openconfig-interfaces.jtox
   +-- openconfig-routing-policy.jtox
   +--YANG/
      +--Cisco-IOS-XR-aaa-lib-cfg.yang
      +--Cisco-IOS-XR-aaa-locald-admin-cfg.yang
      +--... OUTPUT IS OMMITED
      +--openconfig-types.yang
      +--openconfig-vlan.yang

 

The number of collected YANG modules from Cisco IOS XRv is 443, so I show you just some of the collected. We have reviewed their structure (earlier), so I won’t explain them again. The key new component here is “jtox” drivers, I will show you the smallest one:

1
2
$ cat /tmp/XR3/openconfig-routing-policy.jtox
{"tree": {"openconfig-routing-policy:routing-policy": ["container", {"policy-definitions": ["container", {"policy-definition": ["list", {"statements": ["container", {"statement": ["list", {"conditions": ["container", {"match-tag-set": ["container", {"tag-set": ["leaf", "string"], "match-set-options": ["leaf", "enumeration"]}], "match-neighbor-set": ["container", {"match-set-options": ["leaf", "enumeration"], "neighbor-set": ["leaf", "string"]}], "install-protocol-eq": ["leaf", "identityref"], "call-policy": ["leaf", "string"], "igp-conditions": ["container", {}], "match-prefix-set": ["container", {"prefix-set": ["leaf", "string"], "match-set-options": ["leaf", "enumeration"]}]}], "name": ["leaf", "string"], "actions": ["container", {"accept-route": ["leaf", "empty"], "igp-actions": ["container", {"set-tag": ["leaf", ["union", ["uint32", "string"]]]}], "reject-route": ["leaf", "empty"]}]}, [["openconfig-routing-policy", "name"]]]}], "name": ["leaf", "string"]}, [["openconfig-routing-policy", "name"]]]}], "defined-sets": ["container", {"prefix-sets": ["container", {"prefix-set": ["list", {"prefix-set-name": ["leaf", "string"], "prefix": ["list", {"masklength-range": ["leaf", "string"], "ip-prefix": ["leaf", ["union", ["string", "string"]]]}, [["openconfig-routing-policy", "ip-prefix"], ["openconfig-routing-policy", "masklength-range"]]]}, [["openconfig-routing-policy", "prefix-set-name"]]]}], "neighbor-sets": ["container", {"neighbor-set": ["list", {"neighbor-set-name": ["leaf", "string"], "neighbor": ["list", {"address": ["leaf", ["union", ["string", "string"]]]}, [["openconfig-routing-policy", "address"]]]}, [["openconfig-routing-policy", "neighbor-set-name"]]]}], "tag-sets": ["container", {"tag-set": ["list", {"tag": ["list", {"value": ["leaf", ["union", ["uint32", "string"]]]}, [["openconfig-routing-policy", "value"]]], "tag-set-name": ["leaf", "string"]}, [["openconfig-routing-policy", "tag-set-name"]]]}]}]}]}, "modules": {"ietf-inet-types": ["inet", "urn:ietf:params:xml:ns:yang:ietf-inet-types"], "openconfig-extensions": ["ocext", "http://openconfig.net/yang/openconfig-ext"], "ietf-yang-types": ["yang", "urn:ietf:params:xml:ns:yang:ietf-yang-types"], "openconfig-policy-types": ["ptypes", "http://openconfig.net/yang/policy-types"], "openconfig-routing-policy": ["rpol", "http://openconfig.net/yang/routing-policy"]}, "annotations": {}}

 

You know, this is the holy grail of YANG automation (remember, OpenConfig is just one the data models). This driver shows exactly how the file with network function parameters should be structured and which format each variable must have. It’s provided as a single string, what is probably not easy to read, but you can convert it manually or automatically in something more convenient for you The same structure have both “openconfig-interfaces.jtox” and “openconfig-bgp.jtox”.

So to recap what we have achieved so far:

  • We have get list of all supported YANG modules from particular network function
  • We have collected all these YANG modules
  • We have created “jtox” drivers to the OpenConfig YANG modules we are going to use

So it’s time to proceed with configuration part. That’s why we are here, aren’t we?

#1.3. Cisco IOS XR / Configuring network function using OpenConfig through NETCONF

There are no changes to the top level Ansible playbook “132_lab.yml” and first-level nested “main.yml” for Cisco role, so I won’t show them again. But we’ll review in details the playbooks for configuration and associated variables.

The configurator playbook looks as follows:

1
2
3
4
5
6
7
8
9
10
$ cat roles/cisco/132_lab/tasks/configure.yml
---
- name
: LOADING LIST OF YANG MODELS TO CONFIGURE
  include_vars
:
    file
: desired_openconfig_modules.yml

- name
: LAUNCHING CONFIGURATION ENGINE
  include_tasks
: yang_configurator.yml
  loop
: "{{ desired_openconfig_modules }}"
...

 

As you see, it’s very easy. We import the same “desired_openconfig_modules.yml” to loop configuration activities according to what we want to get. You might think about “desired_openconfig_modules.yml” file as particular set of instructions “what to do”, as it glues together YANG modules and associated config files based on that YANG modules. The rest, the other playbooks I mean, is just “how to do”.

The configuration is done in a cycle per entry in imported instructions:

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
---
- name
: MAKING TEMPORARY COPY OF CONFIG FILE {{ item.related_config }}
  copy
:
    src
: "{{ item.related_config }}"
    dest
: "{{ item.path }}/{{ item.related_config }}"

- name
: CREATING XML FOR NETCONF FROM {{ item.related_config }} using {{ item.jtox_output }}
  command
: json2xml -t config -o {{ item.path }}/oc_conf.xml {{ item.path }}/{{ item.jtox_output }} {{ item.path }}/{{ item.related_config }}

- name
: REMOVING UNNECESSARY FRAMING
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '<\?.+\?>\n'
    replace
: ''

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'type>ocif'
    replace
: 'type xmlns:idx="urn:ietf:params:xml:ns:yang:iana-if-type">idx'
  when
: item.jtox_output == "openconfig-interfaces.jtox"

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type"'
    replace
: ''
  when
: item.jtox_output == "openconfig-interfaces.jtox"

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'afi-safi-name>bgp'
    replace
: 'afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx'
  when
: item.jtox_output == "openconfig-bgp.jtox"

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'xmlns:bgp-types="http://openconfig.net/yang/bgp-types"'
    replace
: ''
  when
: item.jtox_output == "openconfig-bgp.jtox"

- name
: PUSHING PARAMETERS FROM {{ item.related_config }} to {{ inventory_hostname }}
  netconf_config
:
    host
: "{{ inventory_hostname }}"
    username
: "{{ ansible_user }}"
    password
: "{{ ansible_pass }}"
    port
: "{{ ansible_port }}"
    hostkey_verify
: false
    look_for_keys
: false
    xml
: "{{ lookup ('file', '{{ item.path }}/oc_conf.xml') }}"

- name
: DELETING TEMPORARY XML FOR NETCONF
  file
:
    dest
: "{{ item.path }}/oc_conf.xml"
    state
: absent

- name
: DELETING TEMPORARY COPY OF CONFIG FILE {{ item.related_config }}
  file
:
    dest
: "{{ item.path }}/{{ item.related_config }}"
    state
: absent
...

 

In this Ansible playbook we can split the tasks in two major categories:

  • Applicable for all vendors regardless of OpenConfig version and implementation. These are the first three and the last three tasks
  • Applicable for this particular vendor for this particular version of OpenConfig modules. Those are rest of the tasks.

To illustrate the execution of one iteration of this configuration cycle, we’ll take configuration example of the BGP.

In the first two tasks of the playbook, we make temporary copy of JSON file with variables (its name is defined in “desired_openconfig_modules.yml”) and convert it into XML. So, here is the file with BGP variables:

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
$ cat roles/cisco/132_lab/files/XR3_openconfig-bgp.json
{
    "openconfig-bgp:bgp": {
        "global": {
            "afi-safis": {
                "afi-safi": [
                    {
                        "afi-safi-name": "ipv4-unicast",
                        "config": {
                            "afi-safi-name": "ipv4-unicast",
                            "enabled": true
                        }
                    }
                ]
            },
            "config": {
                "as": 65001,
                "router-id": "10.0.0.33"
            }
        },
        "neighbors": {
            "neighbor": [
                {
                    "afi-safis": {
                        "afi-safi": [
                            {
                                "afi-safi-name": "ipv4-unicast",
                                "apply-policy": {
                                   "config": {
                                        "export-policy": [
                                            "RP_OC_TEST"
                                        ],
                                        "import-policy": [
                                            "RP_OC_TEST"
                                        ]
                                    }
                                },
                                "config": {
                                    "afi-safi-name": "ipv4-unicast",
                                    "enabled": true
                                }
                            }
                        ]
                    },
                    "config": {
                        "neighbor-address": "10.11.33.11",
                        "peer-as": 65011
                    },
                    "neighbor-address": "10.11.33.11"
                },
                {
                    "afi-safis": {
                        "afi-safi": [
                            {
                                "afi-safi-name": "ipv4-unicast",
                                "apply-policy": {
                                    "config": {
                                        "export-policy": [
                                            "RP_OC_TEST"
                                        ],
                                        "import-policy": [
                                            "RP_OC_TEST"
                                        ]
                                    }
                                },
                                "config": {
                                    "afi-safi-name": "ipv4-unicast",
                                    "enabled": true
                                }
                            }
                        ]
                    },
                    "config": {
                        "neighbor-address": "10.22.33.22",
                        "peer-as": 65022
                    },
                    "neighbor-address": "10.22.33.22"
                }
            ]
        }
    }
}

 

As I mentioned earlier, the idea how this file should be structured comes from used YANG model, so conversion it to “jtox” driver helps a lot as there all relevant variables of the names, their format and sequence are shown. In the very beginning of the file we write “openconfig-bgp:bgp”, what instructs which YANG module (which part of tree out of “jtox” driver to use). To better understand the topic, let’s take a look on “jtox” again:

1
2
3
4
5
6
$ more /tmp/XR3/openconfig-bgp.jtox
{"tree": {"openconfig-bgp:bgp": ["container", {"neighbors": ["container", {"neighbor": ["list", {"neighbor-address": ["leaf", ["union", ["string", "string"]]]
, ...
!
! FURTHER OUTPUT IS OMITTED
!

 

Container means we need to frame data with “{ }” and list requires “[ ]”.

Important! If you fetch the config from the network function in OpenConfig format using “netconf_get”, it’s quite often that list framing “[ ]” is lost. If you use example data fetched from device, make sure you check it against “jtox” driver, otherwise there will be error.

So, after the first two tasks in this playbook is done, we become the following XML file:

1
2
3
$ cat /tmp/XR3/oc_conf.xml
<?xml version='1.0' encoding='utf-8'?>
<nc:config xmlns:bgp="http://openconfig.net/yang/bgp" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:bgp-mp="http://openconfig.net/yang/bgp-multiprotocol" xmlns:bgp-op="http://openconfig.net/yang/bgp-operational" xmlns:bgp-types="http://openconfig.net/yang/bgp-types" xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types" xmlns:ocext="http://openconfig.net/yang/openconfig-ext" xmlns:openconfig-types="http://openconfig.net/yang/openconfig-types" xmlns:ptypes="http://openconfig.net/yang/policy-types" xmlns:rpol="http://openconfig.net/yang/routing-policy" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types"><bgp:bgp><bgp:neighbors><bgp:neighbor><bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:config><bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:peer-as>65011</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config></bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:neighbor><bgp:neighbor><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:config><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:peer-as>65022</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config></bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:neighbor></bgp:neighbors><bgp:global><bgp:config><bgp:as>65001</bgp:as><bgp:router-id>10.0.0.33</bgp:router-id></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:global></bgp:bgp></nc:config>

 

We don’t need the first line. Or even more important, if we don’t delete the first line and import the whole file to “netconf_config” task in the end, it will fail. So we need to delete, what is done by the third task:

1
2
$ cat /tmp/XR3/oc_conf.xml
<nc:config xmlns:bgp="http://openconfig.net/yang/bgp" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:bgp-mp="http://openconfig.net/yang/bgp-multiprotocol" xmlns:bgp-op="http://openconfig.net/yang/bgp-operational" xmlns:bgp-types="http://openconfig.net/yang/bgp-types" xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types" xmlns:ocext="http://openconfig.net/yang/openconfig-ext" xmlns:openconfig-types="http://openconfig.net/yang/openconfig-types" xmlns:ptypes="http://openconfig.net/yang/policy-types" xmlns:rpol="http://openconfig.net/yang/routing-policy" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types"><bgp:bgp><bgp:neighbors><bgp:neighbor><bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:config><bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:peer-as>65011</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config></bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:neighbor><bgp:neighbor><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:config><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:peer-as>65022</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config></bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:neighbor></bgp:neighbors><bgp:global><bgp:config><bgp:as>65001</bgp:as><bgp:router-id>10.0.0.33</bgp:router-id></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:global></bgp:bgp></nc:config>

 

Then, even though we have fetched the YANG modules directly from Cisco IOS XRv and have constructed “jtox” drivers from them, we aren’t able to send this XML back. In the first article about OpenConfig we created our own jinja2 templates (link) based on exact XML structure we saw in NETCONF packets. That information was useful, so in XML “edit-config” rpc request for Cisco IOS XR 6.1.2 we need to convert string:

1
<bgp:afi-safi-name>bgp:ipv4-unicast</bgp:afi-safi-name>

 

Into:

1
<bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name>

 

And sequentially we need to remove from header the following string

1
xmlns:bgp-types="http://openconfig.net/yang/bgp-types"

 

These actions are achieved by tasks five and six, which are applied only in case we configure BGP with OpenConfig in Cisco IOS XR 6.1.2. The tasks three and four have the same meaning for configuration of interfaces using OpenConfig model for Cisco IOS XR 6.1.2.

After modification is done, we got proper XML request to configure BGP with OpenConfig YANG model:

1
2
$ cat /tmp/XR3/oc_conf.xml
<nc:config xmlns:bgp="http://openconfig.net/yang/bgp" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:bgp-mp="http://openconfig.net/yang/bgp-multiprotocol" xmlns:bgp-op="http://openconfig.net/yang/bgp-operational" xmlns:bgp-pol="http://openconfig.net/yang/bgp-policy" xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types" xmlns:ocext="http://openconfig.net/yang/openconfig-ext" xmlns:openconfig-types="http://openconfig.net/yang/openconfig-types" xmlns:ptypes="http://openconfig.net/yang/policy-types" xmlns:rpol="http://openconfig.net/yang/routing-policy" xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types"><bgp:bgp><bgp:neighbors><bgp:neighbor><bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:config> <bgp:neighbor-address>10.11.33.11</bgp:neighbor-address><bgp:peer-as>65011</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config> </bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled> <bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi> </bgp:afi-safis></bgp:neighbor><bgp:neighbor><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:config><bgp:neighbor-address>10.22.33.22</bgp:neighbor-address><bgp:peer-as>65022</bgp:peer-as></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name><bgp:apply-policy><bgp:config><bgp:import-policy>RP_OC_TEST</bgp:import-policy><bgp:export-policy>RP_OC_TEST</bgp:export-policy></bgp:config></bgp:apply-policy><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:neighbor></bgp:neighbors><bgp:global><bgp:config><bgp:as>65001</bgp:as><bgp:router-id>10.0.0.33</bgp:router-id></bgp:config><bgp:afi-safis><bgp:afi-safi><bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name><bgp:config><bgp:enabled>true</bgp:enabled><bgp:afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx:ipv4-unicast</bgp:afi-safi-name></bgp:config></bgp:afi-safi></bgp:afi-safis></bgp:global></bgp:bgp></nc:config>

 

This file is used in task seven to construct “edit-config” RPC request message using “netconf_config” module. All the authentication parameters as well as port for NETCONF are coming from the “group_vars”.

The last two tasks in the looped Ansible playbook, after the configuration is placed onto the router via NETCONF, are to delete temporary XML configuration file and JSON file with parameters.

That’s how configuration cycle is working for each OpenConfig module, we’d like to use. Before I will show you the execution of this Ansible playbook called “configure.yml”, take a look onto two another set of variables:

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
$ cat roles/cisco/132_lab/vars/XR3_openconfig-routing-policy.json
{
    "openconfig-routing-policy:routing-policy": {
        "defined-sets": {
            "prefix-sets": {
                "prefix-set": [
                    {
                        "prefix": [
                            {
                               "ip-prefix": "10.0.0.0/24",
                                "masklength-range": "32..32"
                            }
                        ],
                        "prefix-set-name": "PS_OC_TEST"
                    }
                ]
            }
        },
        "policy-definitions": {
            "policy-definition": [
                {
                    "name": "RP_OC_TEST",
                    "statements": {
                        "statement": [
                            {
                                "actions": {
                                    "accept-route": [null]
                                },
                                "conditions": {
                                    "match-prefix-set": {
                                        "match-set-options": "ANY",
                                        "prefix-set": "PS_OC_TEST"
                                    }
                                },
                                "name": "statement-10"
                            }
                        ]
                    }
                }
            ]
        }
    }
}

 

This file contains parameters associated with route policies, we are going to use in network function controller by Cisco IOS XR 6.1.2. As you see, it consists from prefix-sets and policy-statements, what generally reflects structure of CLI.

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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
$ cat roles/cisco/132_lab/vars/XR3_openconfig-interfaces.json
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "config": {
                    "enabled": true,
                    "name": "Loopback0",
                    "type": "softwareLoopback"
                },
                "name": "Loopback0",
                "subinterfaces": {
                    "subinterface": [
                        {
                            "index": 0,
                            "openconfig-if-ip:ipv4": {
                                "address": [
                                    {
                                        "config": {
                                            "ip": "10.0.0.33",
                                            "prefix-length": 32
                                        },
                                        "ip": "10.0.0.33"
                                    }
                                ]  
                            },
                            "openconfig-if-ip:ipv6": {
                                "address": [
                                        {
                                        "config": {
                                            "ip": "fc00::10:0:0:33",
                                            "prefix-length": 128
                                        },
                                        "ip": "fc00::10:0:0:33"
                                    }
                                ]
                            }
                        }
                    ]
                }
            },
            {
                "config": {
                    "enabled": true,
                    "name": "GigabitEthernet0/0/0/0",
                    "type": "ethernetCsmacd"
                },
                "openconfig-if-ethernet:ethernet": {
                    "config": {
                        "auto-negotiate": false
                    }
                },
                "name": "GigabitEthernet0/0/0/0",
                "subinterfaces": {
                    "subinterface": [
                        {
                            "config": {
                                "enabled": true,
                                "index": 13,
                                "name": "GigabitEthernet0/0/0/0.13"
                            },
                            "index": 13,
                            "openconfig-if-ip:ipv4": {
                                "address": [
                                    {
                                        "config": {
                                            "ip": "10.11.33.33",
                                            "prefix-length": 24
                                        },
                                        "ip": "10.11.33.33"
                                    }
                                ]
                            },
                            "openconfig-if-ip:ipv6": {
                                "address": [
                                    {
                                        "config": {
                                            "ip": "fc00::10:11:33:33",
                                            "prefix-length": 112
                                        },
                                        "ip": "fc00::10:11:33:33"
                                    }
                                ],
                                "config": {
                                    "enabled": false
                                }
                            },
                            "openconfig-vlan:vlan": {
                                "config": {
                                    "vlan-id": 13
                                }
                            }
                        },
                        {
                            "config": {
                                "enabled": true,
                                "index": 23,
                                "name": "GigabitEthernet0/0/0/0.23"
                            },
                            "index": 23,
                            "openconfig-if-ip:ipv4": {
                                "address": [
                                    {
                                        "config": {
                                            "ip": "10.22.33.33",
                                            "prefix-length": 24
                                        },
                                        "ip": "10.22.33.33"
                                    }
                                ]
                            },
                            "openconfig-if-ip:ipv6": {
                                "address": [
                                    {
                                        "config": {
                                            "ip": "fc00::10:22:33:33",
                                            "prefix-length": 112
                                        },
                                        "ip": "fc00::10:22:33:33"
                                    }
                                ],
                                "config": {
                                    "enabled": false
                                }
                            },
                            "openconfig-vlan:vlan": {
                                "config": {
                                    "vlan-id": 23
                                 }
                            }
                        }
                    ]
                }
            }
        ]
    }
}

 

The next file contains information about configuration of interfaces. Earlier we have discovered that we need to call submodules upon IP or VLAN configuration. Here we instruct the to call for particular nested YANG modules by adding module name:

  • “openconfig-if-ip:ipv4” or “openconfig-if-ip:ipv6” for IP addresses
  • “openconfig-vlan:vlan” for VLAN

They must be included into single “jtox” driver, as we have created it from all these modules together (explained in the beginning of this article):

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat /tmp/XR3/openconfig-interfaces.jtox
{"tree": {"openconfig-interfaces:interfaces": ["container", {"interface": ["list",
!
! OUTPUT IS OMITTED
!
"openconfig-if-ip:ipv4": ["container", {"state": ["container", {"enabled": ["leaf", "boolean"], "mtu": ["leaf", "uint16"]}], "config": ["container",
!
! OUTPUT IS OMITTED
!
"openconfig-vlan:vlan": ["container", {"state": ["container", {"vlan-id": ["leaf", ["union", ["uint16", "string"]]], "global-vlan-id": ["leaf", ["union", ["uint16", "string"]]]}]
!
! FURTHER OUTPUT IS OMITTED
!

 

Now we are ready to perform the configuration set of activities for our Cisco IOS XR 6.1.2 based network functions. But the last step, before we do this, let’s verify configuration of XR3 (the file is provided in the beginning of the article):

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
RP/0/0/CPU0:XR3#show run
Sun Aug 26 09:33:05.373 UTC
Building configuration...
!! IOS XR Configuration 6.1.2
!! Last configuration change at Sun Aug 26 09:08:03 2018 by cisco
!
hostname XR3
logging console debugging
logging buffered 9999999
logging buffered debugging
service timestamps log datetime msec
service timestamps debug datetime msec
vrf MGMT
 address-family ipv4 unicast
 !
 address-family ipv6 unicast
 !
!
line console
 exec-timeout 0 0
!
control-plane
 management-plane
  inband
   interface GigabitEthernet0/0/0/0.999
    allow SSH peer
     address ipv4 192.168.0.0/16
    !
    allow NETCONF peer
     address ipv4 192.168.0.0/16
    !
   !
  !
 !
!
interface MgmtEth0/0/CPU0/0
 shutdown
!
interface GigabitEthernet0/0/0/0
 carrier-delay up 0 down 0
!
interface GigabitEthernet0/0/0/0.999
 vrf MGMT
 ipv4 address 192.168.1.111 255.255.255.0
 encapsulation dot1q 999
 logging events link-status
!
netconf agent tty
!
netconf-yang agent
 ssh
!
ssh server v2
ssh server vrf MGMT
ssh server netconf vrf MGMT
end

 

The Configuration process using this Ansible playbook looks like:

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
121
122
123
124
125
126
127
128
129
130
131
$ ansible-playbook 132_lab.yml --tag=cisco_configure --limit=XR3
 [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 [cisco] *************************************************************************************************************************************************

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

PLAY [cisco] *************************************************************************************************************************************************

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

TASK [cisco/132_lab : COLLECTOR] *****************************************************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CONFIGURATOR] **************************************************************************************************************************
included: /home/aaa/ansible/roles/cisco/132_lab/tasks/configure.yml for XR3

TASK [cisco/132_lab : LOADING LIST OF YANG MODELS TO CONFIGURE] **********************************************************************************************
ok: [XR3]

TASK [cisco/132_lab : LAUNCHING CONFIGURATION ENGINE] ********************************************************************************************************
included: /home/aaa/ansible/roles/cisco/132_lab/tasks/yang_configurator.yml for XR3
included: /home/aaa/ansible/roles/cisco/132_lab/tasks/yang_configurator.yml for XR3
included: /home/aaa/ansible/roles/cisco/132_lab/tasks/yang_configurator.yml for XR3

TASK [cisco/132_lab : MAKING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-interfaces.json] *******************************************************************
changed: [XR3]

TASK [cisco/132_lab : CREATING XML FOR NETCONF FROM XR3_openconfig-interfaces.json using openconfig-interfaces.jtox] *****************************************
changed: [XR3]

TASK [cisco/132_lab : REMOVING UNNECESSARY FRAMING] **********************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : PUSHING PARAMETERS FROM XR3_openconfig-interfaces.json to XR3] *************************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY XML FOR NETCONF] ****************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-interfaces.json] *****************************************************************
changed: [XR3]

TASK [cisco/132_lab : MAKING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-routing-policy.json] ***************************************************************
changed: [XR3]

TASK [cisco/132_lab : CREATING XML FOR NETCONF FROM XR3_openconfig-routing-policy.json using openconfig-routing-policy.jtox] *********************************
changed: [XR3]

TASK [cisco/132_lab : REMOVING UNNECESSARY FRAMING] **********************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : PUSHING PARAMETERS FROM XR3_openconfig-routing-policy.json to XR3] *********************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY XML FOR NETCONF] ****************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-routing-policy.json] *************************************************************
changed: [XR3]

TASK [cisco/132_lab : MAKING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-bgp.json] **************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CREATING XML FOR NETCONF FROM XR3_openconfig-bgp.json using openconfig-bgp.jtox] *******************************************************
changed: [XR3]

TASK [cisco/132_lab : REMOVING UNNECESSARY FRAMING] **********************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
skipping: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML] ******************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : PUSHING PARAMETERS FROM XR3_openconfig-bgp.json to XR3] ********************************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY XML FOR NETCONF] ****************************************************************************************************
changed: [XR3]

TASK [cisco/132_lab : DELETING TEMPORARY COPY OF CONFIG FILE XR3_openconfig-bgp.json] ************************************************************************
changed: [XR3]

PLAY [arista] ************************************************************************************************************************************************
skipping: no hosts matched

PLAY [arista] ************************************************************************************************************************************************
skipping: no hosts matched

PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched

PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched

PLAY RECAP ***************************************************************************************************************************************************
XR3                        : ok=29   changed=22   unreachable=0    failed=0

 

After that we check our network function XR3:

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
RP/0/0/CPU0:XR3#show run
Sun Aug 26 09:58:20.379 UTC
Building configuration...
!! IOS XR Configuration 6.1.2
!! Last configuration change at Sun Aug 26 09:54:19 2018 by cisco
!
hostname XR3
logging console debugging
logging buffered 9999999
logging buffered debugging
service timestamps log datetime msec
service timestamps debug datetime msec
vrf MGMT
 address-family ipv4 unicast
 !
 address-family ipv6 unicast
 !
!
line console
 exec-timeout 0 0
!
control-plane
 management-plane
  inband
   interface GigabitEthernet0/0/0/0.999
    allow SSH peer
     address ipv4 192.168.0.0/16
    !
    allow NETCONF peer
     address ipv4 192.168.0.0/16
    !
   !
  !
 !
!
interface Loopback0
 ipv4 address 10.0.0.33 255.255.255.255
 ipv6 address fc00::10:0:0:33/128
!
interface MgmtEth0/0/CPU0/0
 shutdown
!
interface GigabitEthernet0/0/0/0
 carrier-delay up 0 down 0
!
interface GigabitEthernet0/0/0/0.13
 ipv4 address 10.11.33.33 255.255.255.0
 ipv6 address fc00::10:11:33:33/112
 encapsulation dot1q 13
!
interface GigabitEthernet0/0/0/0.23
 ipv4 address 10.22.33.33 255.255.255.0
 ipv6 address fc00::10:22:33:33/112
 encapsulation dot1q 23
!
interface GigabitEthernet0/0/0/0.999
 vrf MGMT
 ipv4 address 192.168.1.111 255.255.255.0
 encapsulation dot1q 999
 logging events link-status
!
prefix-set PS_OC_TEST
  10.0.0.0/24 ge 32 le 32
end-set
!
route-policy RP_OC_TEST
  #statement-name statement-10
  if destination in PS_OC_TEST then
    done
  endif
end-policy
!
router bgp 65001
 bgp router-id 10.0.0.33
 address-family ipv4 unicast
 !
 neighbor 10.11.33.11
  remote-as 65011
  address-family ipv4 unicast
   route-policy RP_OC_TEST in
   route-policy RP_OC_TEST out
  !
 !
 neighbor 10.22.33.22
  remote-as 65022
  address-family ipv4 unicast
   route-policy RP_OC_TEST in
   route-policy RP_OC_TEST out
  !
 !
!
netconf agent tty
!
netconf-yang agent
 ssh
!
ssh server v2
ssh server vrf MGMT
ssh server netconf vrf MGMT
end

 

We are done with Cisco IOS XR.

#2. Example of automated OpenConfig-driven configuration for Arista EOS.

To be honest, with Arista EOS the situation looks not optimistic as of today. I have reached moderate results with configuration of Arista EOS in conjunction with OpenConfig YANG modules via NETCONF, as there is absolute lack of documentation. So I will show the situation how it looks from my prospective, without fully working scenario. Probably, in case you have access to the documentation from Arista and you can solve those issues. I also hope that colleagues from Arista could help and will share some info afterwards.

Just to recap, my friends, the structure of Ansible automation with OpenConfig looks like as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+--ansible
   +--132_lab.yml
   +--group_vars
   |  +--arista
   |     +--arista_host.yml
   +--roles
      +--arista
         +--132_lab
            +--files
            |  +--vEOS2_openconfig-bgp.json
            |  +--vEOS2_openconfig-interfaces.json
            |  +--vEOS2_openconfig-routing-policy.json
            +--tasks
            |  +--collect.yml
            |  +--configure.yml
            |  +--jtox_builder.yml
            |  +--main.yml
            |  +--yang_collector.yml
            |  +--yang_configurator.yml
            +--templates
            |  +--pyang_request_jtox.j2
            +--vars
               +--desired_opencofnig_modules.yml

 

That’s why we just copy initial Cisco playbooks and just adjust the configuration files:

1
$ cp –R roles/cisco/132_lab/ roles/arista/132_lab

 

#2.1. Arista EOS / Fetching YANG models and constructing JTOX drivers

This part hasn’t changed too much comparing to Cisco IOS XR part. So I will show you the necessary adjustments:

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
$ cat roles/arista/132_lab/tasks/collect.yml
---
- name
: CREATING DIRECTORY FOR YANG MODULES
  file
:
    dest
: /tmp/{{ inventory_hostname }}/YANG
    state
: directory

- name
: GETTING LIST OF ALL SUPPORTED YANG MODULES FROM {{ inventory_hostname }}
  netconf_get
:
    display
: json
    filter
: <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"><schemas><schema/></schemas></netconf-state>
    lock
: never
  register
: list_of_schemas

- name
: SAVING LIST OF ALL SUPPORTED YANG MODULES FROM {{ inventory_hostname }}
  copy
:
    content
: "{{ list_of_schemas.output | to_nice_json }}"
    dest
: /tmp/{{ inventory_hostname }}/list_of_schemas.json

- name
: PERFORMING SOME WORKAROUNDS ON VARIABLES
  replace
:
    path
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    regexp
: 'rpc-reply'
    replace
: 'rpc_reply'
  when
: ansible_network_os == "iosxr"

- name
: PERFORMING SOME WORKAROUNDS ON VARIABLES
  replace
:
    path
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    regexp
: 'netconf-state'
    replace
: 'netconf_state'

- name
: IMPORTING NAMES OF SUPPORTED YANG MODELS
  include_vars
:
    file
: /tmp/{{ inventory_hostname }}/list_of_schemas.json
    name
: YANG

- name
: FETCHING YANG MODELS FROM {{ inventory_hostname }} / {{ ansible_network_os }}
  include_tasks
: yang_collector.yml
  loop
: "{{ YANG.rpc_reply.data.netconf_state.schemas.schema }}"
  when
: ansible_network_os == "iosxr"

- name
: FETCHING YANG MODELS FROM {{ inventory_hostname }} / {{ ansible_network_os }}
  include_tasks
: yang_collector.yml
  loop
: "{{ YANG.data.netconf_state.schemas.schema }}"
  when
: ansible_network_os != "iosxr"

- name
: IMPORTING VARS
  include_vars
:
    file
: desired_openconfig_modules.yml

- name
: CREATING JTOX DRIVERS
  include_tasks
: jtox_builder.yml
  loop
: "{{ desired_openconfig_modules }}"
  loop_control
:
    loop_var
: outer_item
...

 

As you can see, I’ve just add condition making lots of playbooks to be executed in case we have “iosxr” as “ansible_network_os”, what is not the case for Arista EOS, where we temporary use “nexus”. Also you might have spotted that we have two tasks, where we loop the collecting of the particular YANG modules, as RPC reply in Arista EOS is different to one in Cisco IOS XR, what requires to have different variable structure.

The rest of the content in the playbooks remains the same, so we just launch the playbooks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ansible-playbook 132_lab.yml --limit=vEOS2 --tag=arista_collect
 [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 [cisco] *************************************************************************************************************************************************
skipping
: no hosts matched

PLAY [cisco] *************************************************************************************************************************************************
skipping
: no hosts matched

PLAY [arista] ************************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************    file
: desired_openconfig_modules.yml
!
! OUTPUT IS OMITTED
!
LAY [nokia] *************************************************************************************************************************************************
skipping
: no hosts matched

PLAY [nokia] *************************************************************************************************************************************************
skipping
: no hosts matched

PLAY RECAP ***************************************************************************************************************************************************
vEOS2                      
: ok=464  changed=231  unreachable=0    failed=0

 

Comparing to Cisco it took only 11 minutes to execute this playbook of Ansible EOS, as there are only 110 YANG modules copied from the Arista EOS switch.

Afterwards we do have quite similar result in terms of created files:

1
2
3
4
5
6
7
8
9
10
11
+--/tmp/vEOS2/
   +-- list_of_schemas.json
   +-- openconfig-interfaces.jtox
   +-- openconfig-network-instance.jtox
   +-- openconfig-routing-policy.jtox
   +--YANG/
      +--arista-acl-deviations.yang
      +--arista-bgp-deviations.yang
      +--... OUTPUT IS OMMITED
      +--openconfig-yang-types.yang
      +--vlan-translation.yang

 

From the “jtox” prospective, the difference is caused by a bit different set of YANG modules chosen for further configuration (BGP is replaced by network-instance, as it incorporates BGP in Arista EOS (and Nokia (Alcatel-Lucent) SR OS 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
$ cat roles/arista/132_lab/vars/desired_openconfig_modules.yml
---
desired_openconfig_modules
:
    - name
: OC INTERFACES
      jtox_output
: openconfig-interfaces.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-interfaces.json"
      src_yang
:
          - module
: openconfig-interfaces.yang
          - module
: openconfig-if-ip.yang
          - module
: openconfig-if-ethernet.yang
          - module
: openconfig-if-aggregate.yang
          - module
: openconfig-vlan.yang
    - name
: OC ROUTING POLICY
      jtox_output
: openconfig-routing-policy.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-routing-policy.json"
      src_yang
:
          - module
: openconfig-routing-policy.yang
    - name
: OC NETWORK INSTANCE
      jtox_output
: openconfig-network-instance.jtox
      path
: /tmp/{{ inventory_hostname }}
      related_config
: "{{ inventory_hostname }}_openconfig-network-instance.json"
      src_yang
:
          - module
: openconfig-network-instance.yang
... [arista]
vEOS1
vEOS2

 

#2.2. Arista EOS / Configuring network function using OpenConfig through NETCONF

If the first part, where we collect the information about supported YANG modules and the modules themselves, doesn’t show any problems and work exactly the same as we have it with Cisco IOS XR. But in this part dedicated to the configuration of the Arista EOS network functions, the problems arise. The key point is that Arista vEOS-lab 4.20.7M somewhat partially accepts the configuration, I have sent to it via NETCONF, but it doesn’t install it in the “running-config”. On the one hand, such approach provides the advantage so that the faulty config doesn’t cause the errors. On the other hand, I’m not able to find the proper working set of JSON parameters for OpenConfig, so I hope with the access to Arista guidelines it’s possible to solve the issue.

There is no single change in the „yang_configurator.yml“ and “collect.yml”, that’s I won’t provide its content again to save the space. If you have question, look into Cisco IOS XR chapter. What I will do, I will provide the files with variables. Let’s start with the 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
$ cat roles/arista/132_lab/files/vEOS2_openconfig-interfaces.json
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "name": "Loopback0",
                "config": {
                    "description": "test loopback",
                    "enabled": true,
                    "name": "Loopback0",
                    "type": "softwareLoopback"
                },
                "subinterfaces": {
                    "subinterface": [
                        {
                            "index": 0,
                            "openconfig-if-ip:ipv4": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "10.0.0.22",
                                                "prefix-length": 32
                                            },
                                            "ip": "10.0.0.22"
                                        }
                                    ]
                                },
                                "config": {
                                    "enabled": true,
                                    "mtu": 1500
                                }

                            },
                            "openconfig-if-ip:ipv6": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "fc00::10:0:0:22",
                                                "prefix-length": 128
                                            },
                                            "ip": "fc00::10:0:0:22"
                                        }
                                    ]
                                },
                                "config": {
                                    "enabled": true,
                                    "mtu": 1500
                                }
                            }
                        }
                    ]
                }
            }
        ]
    }
}

 

Here is only loopback is provided, as I stuck with the configuration with Ethernet interfaces. Besides IPv4/IPv6 addresses, the key point in Ethernet is how to make interface “no switchport” over OpenConfig YANG model, which I haven’t solved yet.

The routing policies are also quite similar, with some little exception:

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
$ cat roles/arista/132_lab/files/vEOS2_openconfig-routing-policy.json
{
    "openconfig-routing-policy:routing-policy": {
        "defined-sets": {
            "prefix-sets": {
                "prefix-set": [
                    {
                        "config": {
                            "name": "PS_OC_TEST"
                        },
                        "name": "PS_OC_TEST",
                        "prefixes": {
                            "prefix": [
                                {
                                   "config": {
                                        "ip-prefix": "10.0.0.0/24",
                                        "masklength-range": "32..32"
                                    },
                                    "ip-prefix": "10.0.0.0/24",
                                    "masklength-range": "32..32"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "policy-definitions": {
            "policy-definition": [
                    {
                    "config": {
                        "name": "RP_OC_TEST"
                    },
                    "name": "RP_OC_TEST",
                    "statements": {
                        "statement": [
                            {
                                "actions": {
                                    "config": {
                                        "policy-result": "ACCEPT_ROUTE"
                                    }
                                },
                                "conditions": {
                                    "match-prefix-set": {
                                        "config": {
                                            "match-set-options": "ANY",
                                            "prefix-set": "PS_OC_TEST"
                                        }
                                    }
                                },
                                "config": {
                                    "name": "statement-10"
                                },
                                "name": "statement-10"
                            }
                        ]
                    }
                }
            ]
        }
    }
}

 

The routing policies looks quite similar to Cisco IOS XR 6.1.2 OpenConfig structure, with the exception of the “actions” container. The difference is caused by newer version of OpenConfig supported in Arista EOS 4.20.7M.

The final variables are related to BGP config and are included in “network-instance” context:

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
cat roles/arista/132_lab/files/vEOS2_openconfig-network-instance.json
{
    "openconfig-network-instance:network-instances": {
        "network-instance": [
            {
                "config": {
                    "name": "default",
                    "type": "DEFAULT_INSTANCE"
                },
                "name": "default",
                "protocols": {
                    "protocol": [
                        {
                            "bgp": {
                                "global": {
                                    "afi-safis": {
                                        "afi-safi": [
                                            {
                                                "afi-safi-name": "IPV4_UNICAST",
                                                "config": {
                                                    "afi-safi-name": "IPV4_UNICAST",
                                                    "enabled": true
                                                }
                                            }
                                        ]
                                    },
                                    "config": {
                                        "as": 65011,
                                        "router-id": "10.0.0.11"
                                    }
                                },
                                "neighbors": {
                                    "neighbor": [
                                        {
                                            "afi-safis": {
                                                "afi-safi": [
                                                    {
                                                        "afi-safi-name": "IPV4_UNICAST",
                                                        "config": {
                                                            "afi-safi-name": "IPV4_UNICAST",
                                                            "enabled": true
                                                        }
                                                    }

                                                ]
                                            },
                                            "config": {
                                                "neighbor-address": "10.11.33.33",
                                                "peer-group": "FABRIC"
                                            },
                                            "neighbor-address": "10.11.33.33"
                                        }
                                    ]
                                },
                                "peer-groups": {
                                    "peer-group": [
                                        {
                                            "afi-safis": {
                                                "afi-safi": [
                                                    {
                                                        "afi-safi-name": "IPV4_UNICAST",
                                                        "config": {
                                                            "afi-safi-name": "IPV4_UNICAST",
                                                            "enabled": true
                                                        }
                                                    }
                                                ]
                                            },
                                            "config": {
                                                "peer-group-name": "FABRIC",
                                                "peer-as": 65001,
                                                "auth-password": "OLOLO",
                                                "send-community": "EXTENDED"
                                            },
                                            "peer-group-name": "FABRIC"
                                        }
                                    ]
                                }
                            },
                            "config": {
                                "enabled": true,
                                "identifier": "BGP",
                                "name": "BGP"
                            },
                            "identifier": "BGP",
                            "name": "BGP"
                        }
                    ]
                }
            }
        ]
    }
}

 

The key point here is that we enabled only IPv4 unicast address family. I have tried to configure another address family “evpn” as well, but for whatever reason it hasn’t been accepted. Also, as you have seen, I have removed associated address policies, as I haven’t managed to attach them neither to “peer-group” nor to “neighbor” context. Before launch the Ansible playbook, let’s take a look onto Arista EOS configuration:

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
vEOS2#show run
! Command: show running-config
! device: vEOS2 (vEOS, EOS-4.20.7M)
!
! boot system flash:/vEOS-lab.swi
!
daemon OpenConfig
   exec /usr/bin/OpenConfig
   no shutdown
!
transceiver qsfp default-mode 4x10G
!
service routing protocols model multi-agent
!
logging console debugging
!
hostname vEOS2
!
spanning-tree mode mstp
!
no aaa root
!
username aaa privilege 15 secret sha512 $6$zswywWMDghnHsfCC$8F6wg3.62cfaVPDzOxRmMQlcz9eOK8A5Vo.5ex9MVZ3KInD5UetwNjagkmomSrov106/n2J/YrpTs6WRwcUtm.
!
vrf definition mgmt
!
interface Ethernet1
   no switchport
!
interface Ethernet2
!
interface Ethernet3
!
interface Management1
   vrf forwarding mgmt
   ip address 192.168.44.82/24
!
ip routing
no ip routing vrf mgmt
!
ipv6 unicast-routing
!
management api netconf
   transport ssh default
      vrf mgmt
!
management ssh
   vrf mgmt
      no shutdown
!
end

 

Now, let’s rock with Arista EOS and OpenConfig using Ansible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ansible-playbook 132_lab.yml --limit=vEOS2 --tag=arista_configure
 [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 [cisco] *************************************************************************************************************************************************
skipping: no hosts matched

PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched

PLAY [arista] ************************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [vEOS2]
!
! OUTPUT IS OMITTED
!
PLAY RECAP ***************************************************************************************************************************************************
vEOS2                      : ok=26   changed=19   unreachable=0    failed=0

 

Well, rock hasn’t been very good, because not everything has been implemented:

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
vEOS2#show run
! Command: show running-config
! device: vEOS2 (vEOS, EOS-4.20.7M)
!
! boot system flash:/vEOS-lab.swi
!
daemon OpenConfig
   exec /usr/bin/OpenConfig
   no shutdown
!
transceiver qsfp default-mode 4x10G
!
service routing protocols model multi-agent
!
logging console debugging
!
hostname vEOS2
!
spanning-tree mode mstp
!
no aaa root
!
username aaa privilege 15 secret sha512 $6$zswywWMDghnHsfCC$8F6wg3.62cfaVPDzOxRmMQlcz9eOK8A5Vo.5ex9MVZ3KInD5UetwNjagkmomSrov106/n2J/YrpTs6WRwcUtm.
!
vrf definition mgmt
!
interface Ethernet1
   no switchport
!
interface Ethernet2
!
interface Ethernet3
!
interface Loopback0
   description test loopback
!
interface Management1
   vrf forwarding mgmt
   ip address 192.168.44.82/24
!
ip routing
no ip routing vrf mgmt
!
ip prefix-list PS_OC_TEST
   seq 10 permit 10.0.0.0/24 eq 32
!
ipv6 unicast-routing
!
router bgp 65011
   router-id 10.0.0.11
   neighbor FABRIC peer-group
   neighbor FABRIC remote-as 65001
   neighbor FABRIC password 7 $1$8qyzMA24QKky5P7s2e3ynA==
   neighbor FABRIC send-community extended
   neighbor FABRIC maximum-routes 12000
   neighbor 10.11.33.33 peer-group FABRIC
   !
   address-family ipv4
      neighbor FABRIC activate
      neighbor 10.11.33.33 activate
!
management api netconf
   transport ssh default
      vrf mgmt
!
management ssh
   vrf mgmt
      no shutdown
!
end

 

So, in the same way as in the first article about OpenConfig, I haven’t managed to configure IP address in the interface, though NETCONF packets are fully accepted. In terms of routing policy, we have prefix-list configured, but not the route-map. BGP looks more or less OK, but I wasn’t able to configure “evpn” address family.

To sum up, Arista supports OpenConfig, but there are a plenty of things to be modified in my data automation scripts or in their implementation. Both variants are possible.

#3. Example of automated OpenConfig-driven configuration for Nokia (Alcatel-Lucent) SR OS.

The last but not least is Nokia. It stays the third in the row in this article, because they don’t support “get-schema” RPC request. As we’ve already explained in the previous article, it will be supported in Nokia (Alcatel-Lucent) SR OS 16.0.R4, what will be released later in the November this year. For now, we need to deal with modules Nokia are redistributing offline, thanks a lot to Nokia colleagues :-).

To refresh, below you can find the structure of the Automation scripts based on Ansible roles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+--ansible
   +--132_lab.yml
   +--group_vars
   |  +--nokia
   |     +--nokia_host.yml
   +--roles
      +--nokia
         +--132_lab
            +--files
            |  +--SR1_openconfig-bgp.json
            |  +--SR1_openconfig-interfaces.json
            |  +--SR1_openconfig-routing-policy.json
            +--tasks
            |  +--collect.yml
            |  +--configure.yml
            |  +--jtox_builder.yml
            |  +--main.yml
            |  +--yang_collector.yml
            |  +--yang_configurator.yml
            +--templates
            |  +--pyang_request_jtox.j2
            +--vars
               +--desired_opencofnig_modules.yml

 

So we just copy the playbooks from folder with Arista to the folder with Nokia

1
$ cp -R roles/arista/132_lab/ roles/nokia/132_lab

 

#3.1.Nokia SR OS / Fetching YANG models and constructing JTOX drivers

As mentioned above, the “get-schema” isn’t working, meaning that the key part of the Ansible playbook “collect.yml”, which is used for all collecting activities, isn’t working. Nevertheless, the info about supported YANG modules is collected, when we execute the playbook. So, we just execute the playbook:

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
$ ansible-playbook 132_lab.yml --limit=SR6 --tag=nokia_collect
 [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
!
! OUTPUT IS OMITTED
!
PLAY [nokia] *************************************************************************************************************************************************

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

TASK [nokia/132_lab : COLLECTOR] *****************************************************************************************************************************
included: /home/aaa/ansible/roles/nokia/132_lab/tasks/collect.yml for SR6

TASK [nokia/132_lab : CREATING DIRECTORY FOR YANG MODULES] ***************************************************************************************************
ok: [SR6]

TASK [nokia/132_lab : GETTING LIST OF ALL SUPPORTED YANG MODULES FROM SR6] ***********************************************************************************
ok: [SR6]

TASK [nokia/132_lab : SAVING LIST OF ALL SUPPORTED YANG MODULES FROM SR6] ************************************************************************************
changed: [SR6]

TASK [nokia/132_lab : PERFORMING SOME WORKAROUNDS ON VARIABLES] **********************************************************************************************
skipping: [SR6]

TASK [nokia/132_lab : PERFORMING SOME WORKAROUNDS ON VARIABLES] **********************************************************************************************
changed: [SR6]

TASK [nokia/132_lab : IMPORTING NAMES OF SUPPORTED YANG MODELS] **********************************************************************************************
ok: [SR6]

TASK [nokia/132_lab : FETCHING YANG MODELS FROM SR6 / sros] **************************************************************************************************
skipping: [SR6]

TASK [nokia/132_lab : FETCHING YANG MODELS FROM SR6 / sros] **************************************************************************************************
!
! OUTPUT IS OMITTED
!
TASK [nokia/132_lab : FETCHING nokia-conf FROM SR6] **********************************************************************************************************
fatal: [SR6]: FAILED! => {"changed": false, "msg": "Element is not valid in the specified context."}
    to retry, use: --limit @/home/aaa/ansible/132_lab.retry

PLAY RECAP ***************************************************************************************************************************************************
SR6                        : ok=304  changed=2    unreachable=0    failed=1

 

Though we got error in the playbook execution, the list of YANG models is there:

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
$ cat /tmp/SR6/list_of_schemas.json | grep 'identifier.*openconfig'
                        "identifier": "openconfig-acl",
                        "identifier": "openconfig-bgp",
                        "identifier": "openconfig-bgp-common",
                        "identifier": "openconfig-bgp-common-multiprotocol",
                        "identifier": "openconfig-bgp-common-structure",
                        "identifier": "openconfig-bgp-global",
                        "identifier": "openconfig-bgp-neighbor",
                        "identifier": "openconfig-bgp-peer-group",
                        "identifier": "openconfig-bgp-policy",
                        "identifier": "openconfig-bgp-types",
                        "identifier": "openconfig-extensions",
                        "identifier": "openconfig-if-aggregate",
                        "identifier": "openconfig-if-ethernet",
                        "identifier": "openconfig-if-ip",
                        "identifier": "openconfig-if-ip-ext",
                        "identifier": "openconfig-inet-types",
                        "identifier": "openconfig-interfaces",
                        "identifier": "openconfig-isis",
                        "identifier": "openconfig-isis-lsdb-types",
                        "identifier": "openconfig-isis-lsp",
                        "identifier": "openconfig-isis-policy",
                        "identifier": "openconfig-isis-routing",
                        "identifier": "openconfig-isis-types",
                        "identifier": "openconfig-lacp",
                        "identifier": "openconfig-lldp",
                        "identifier": "openconfig-lldp-types",
                        "identifier": "openconfig-local-routing",
                        "identifier": "openconfig-mpls",
                        "identifier": "openconfig-mpls-rsvp",
                        "identifier": "openconfig-mpls-sr",
                        "identifier": "openconfig-mpls-te",
                        "identifier": "openconfig-mpls-types",
                        "identifier": "openconfig-network-instance",
                        "identifier": "openconfig-network-instance-types",
                        "identifier": "openconfig-packet-match",
                        "identifier": "openconfig-packet-match-types",
                        "identifier": "openconfig-policy-types",
                        "identifier": "openconfig-relay-agent",
                        "identifier": "openconfig-routing-policy",
                        "identifier": "openconfig-rsvp-sr-ext",
                        "identifier": "openconfig-types",
                        "identifier": "openconfig-vlan",
                        "identifier": "openconfig-vlan-types",
                        "identifier": "openconfig-yang-types",

 

Now I just copy the requested modules according the logic of the playbook to “/tmp/SR6/YANG/” and manually construct “jtox” drivers. Here is the requested modules:

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
$ ls /tmp/SR6/YANG/
nokia-sr-openconfig-acl-deviations.yang               openconfig-bgp.yang                  openconfig-network-instance-l3.yang
nokia-sr-openconfig-bgp-deviations.yang               openconfig-extensions.yang           openconfig-network-instance-types.yang
nokia-sr-openconfig-bgp-policy-deviations.yang        openconfig-if-aggregate.yang         openconfig-network-instance.yang
nokia-sr-openconfig-if-aggregate-deviations.yang      openconfig-if-ethernet.yang          openconfig-ospf-types.yang
nokia-sr-openconfig-if-ethernet-deviations.yang       openconfig-if-ip-ext.yang            openconfig-ospfv2-area-interface.yang
nokia-sr-openconfig-if-ip-deviations.yang             openconfig-if-ip.yang                openconfig-ospfv2-area.yang
nokia-sr-openconfig-if-ip-ext-deviations.yang         openconfig-inet-types.yang           openconfig-ospfv2-common.yang
nokia-sr-openconfig-interfaces-deviations.yang        openconfig-interfaces.yang           openconfig-ospfv2-global.yang
nokia-sr-openconfig-isis-policy-deviations.yang       openconfig-isis-lsdb-types.yang      openconfig-ospfv2-lsdb.yang
nokia-sr-openconfig-lldp-deviations.yang              openconfig-isis-lsp.yang             openconfig-ospfv2.yang
nokia-sr-openconfig-mpls-deviations.yang              openconfig-isis-policy.yang          openconfig-packet-match-types.yang
nokia-sr-openconfig-network-instance-deviations.yang  openconfig-isis-routing.yang         openconfig-packet-match.yang
nokia-sr-openconfig-relay-agent-deviations.yang       openconfig-isis-types.yang           openconfig-pf-forwarding-policies.yang
nokia-sr-openconfig-routing-policy-deviations.yang    openconfig-isis.yang                 openconfig-pf-interfaces.yang
nokia-sr-openconfig-vlan-deviations.yang              openconfig-lacp.yang                 openconfig-pf-path-groups.yang
openconfig-acl.yang                                   openconfig-lldp-types.yang           openconfig-policy-forwarding.yang
openconfig-aft-network-instance.yang                  openconfig-lldp.yang                 openconfig-policy-types.yang
openconfig-aft-types.yang                             openconfig-local-routing.yang        openconfig-relay-agent.yang
openconfig-aft.yang                                   openconfig-mpls-igp.yang             openconfig-routing-policy.yang
openconfig-bgp-common-multiprotocol.yang              openconfig-mpls-ldp.yang             openconfig-rsvp-sr-ext.yang
openconfig-bgp-common-structure.yang                  openconfig-mpls-rsvp.yang            openconfig-segment-routing.yang
openconfig-bgp-common.yang                            openconfig-mpls-sr.yang              openconfig-types.yang
openconfig-bgp-global.yang                            openconfig-mpls-static.yang          openconfig-vlan-types.yang
openconfig-bgp-neighbor.yang                          openconfig-mpls-te.yang              openconfig-vlan.yang
openconfig-bgp-peer-group.yang                        openconfig-mpls-types.yang           openconfig-yang-types.yang
openconfig-bgp-policy.yang                            openconfig-mpls.yang
openconfig-bgp-types.yang                             openconfig-network-instance-l2.yang

 

And here we create “jtox” drivers:

1
2
3
4
5
6
7
$ pwd
/tmp/SR6
$ pyang -f jtox -o openconfig-interfaces.jtox -p YANG/ YANG/openconfig-interfaces.yang YANG/openconfig-if-* YANG/openconfig-vlan \
> --lax-quote-checks
error YANG/openconfig-vlan: [Errno 2] No such file or directory: 'YANG/openconfig-vlan'
$ pyang -f jtox -o openconfig-interfaces.jtox -p YANG/ YANG/openconfig-interfaces.yang YANG/openconfig-if-* YANG/openconfig-vlan.yang --lax-quote-checks
$ pyang -f jtox -o openconfig-routing-policy.jtox -p YANG/ YANG/openconfig-routing-policy.yang --lax-quote-checks[aaa@sandbox SR6]$ pyang -f jtox -o openconfig-network-instance.jtox -p YANG/ YANG/openconfig-network-instance.yang --lax-quote-checks

 

The main deviation of this case with Nokia (Alcatel-Lucent) SR OS is that we don’t use particular modules from the device, but as I’m using one distributed by Nokia, it shouldn’t be a problem. In future, when it’s fixed, this part of the automation dedicated to collection will work exactly the same as for Arista EOS and Cisco IOS XR.

As a result, we have the same final structure, but manually:

1
2
3
4
5
6
7
8
9
10
11
+--/tmp/SR6/
   +-- list_of_schemas.json
   +-- openconfig-interfaces.jtox
   +-- openconfig-network-instance.jtox
   +-- openconfig-routing-policy.jtox
   +--YANG/
      +--nokia-sr-openconfig-acl-deviations.yang
      +--nokia-sr-openconfig-bgp-deviations.yang
      +--... OUTPUT IS OMMITED
      +--openconfig-vlan.yang
      +--openconfig-yang-types.yang

 

#3.2. Nokia SR OS / Configuring network function using OpenConfig through NETCONF

This part works pretty good for Nokia, but as usual, there are some vendor dependencies in OpenConfig realisation, which we need to address in our Ansible playbooks. Here is the list:

  • As we already shown in the first OpenConfig article, Nokia (Alcatel-Lucent) SR OS 16.0.R1 doesn’t call additional submodule for IP address or VLAN configuration (link), what deviates from standard and from Cisco IOS XR and Arista EOS implementations. So we need modify XML accordingly, though in JSON parameters we need to call for this submodule to construct XML properly
  • Currently Nokia (Alcatel-Lucent) SR OS doesn’t support any “urn:ietf:params:xml:ns:yang” YANG modules, this what we see in the list of supported modules via “netconf_get”. It means we need to delete all the capabilities are deleted from XML otherwise we’ll get an error.
  • We also need to make sure any other OpenConfig YANG modules, which aren’t currently supported by Nokia SR OS, aren’t included in XML header.

So all this three points are reflected into “yang_configurator.yml” used for Nokia (Alcatel-Lucent) SR OS 16.0.R1 with the tasks starting with “NOKIA SR OS 16.0.R1 SPECIFIC”:

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
$ cat roles/nokia/132_lab/tasks/yang_configurator.yml
---
- name
: MAKING TEMPORARY COPY OF CONFIG FILE {{ item.related_config }}
  copy
:
    src
: "{{ item.related_config }}"
    dest
: "{{ item.path }}/{{ item.related_config }}"

- name
: CREATING XML FOR NETCONF FROM {{ item.related_config }} using {{ item.jtox_output }}
  command
: json2xml -t config -o {{ item.path }}/oc_conf.xml {{ item.path }}/{{ item.jtox_output }} {{ item.path }}/{{ item.related_config }}

- name
: REMOVING UNNECESSARY FRAMING
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '<\?.+\?>\n'
    replace
: ''

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'type>ocif'
    replace
: 'type xmlns:idx="urn:ietf:params:xml:ns:yang:iana-if-type">idx'
  when
: (item.jtox_output == "openconfig-interfaces.jtox" and ansible_network_os == "iosxr")

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML HEADER
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type"'
    replace
: ''
  when
: (item.jtox_output == "openconfig-interfaces.jtox" and ansible_network_os == "iosxr")

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'afi-safi-name>bgp'
    replace
: 'afi-safi-name xmlns:idx="http://openconfig.net/yang/bgp-types">idx'
  when
: (item.jtox_output == "openconfig-bgp.jtox" and ansible_network_os == "iosxr")

- name
: CISCO IOS XR 6.1.2 SPECIFIC // MODIFYING XML HEADER
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'xmlns:bgp-types="http://openconfig.net/yang/bgp-types"'
    replace
: ''
  when
: (item.jtox_output == "openconfig-bgp.jtox" and ansible_network_os == "iosxr")

- name
: ARISTA EOS 4.20.7M AND NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '>oc-netinst:'
    replace
: '>'
  when
: (item.jtox_output == "openconfig-network-instance.jtox" and ansible_network_os != "iosxr")

- name
: NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'oc-ip:'
    replace
: 'oc-if:'
  when
: (item.jtox_output == "openconfig-interfaces.jtox" and ansible_network_os == "sros")

- name
: ARISTA EOS 4.20.7M AND NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '>oc-if:'
    replace
: '>'
  when
: (item.jtox_output == "openconfig-interfaces.jtox" and ansible_network_os != "iosxr")

- name
: NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML BODY
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: 'oc-vlan:'
    replace
: 'oc-if:'
  when
: (item.jtox_output == "openconfig-interfaces.jtox" and ansible_network_os == "sros")

- name
: NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML HEADER
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '<nc:.+">'
    replace: '<nc:config xmlns:nc="
urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:oc-if="http://openconfig.net/yang/interfaces" xmlns:oc-ip="http://openconfig.net/yang/interfaces/ip" xmlns:oc-vlan="http://openconfig.net/yang/vlan" xmlns:oc-eth="http://openconfig.net/yang/interfaces/ethernet" xmlns:oc-inet="http://openconfig.net/yang/types/inet" xmlns:oc-ip-ext="http://openconfig.net/yang/interfaces/ip-ext" xmlns:oc-lag="http://openconfig.net/yang/interfaces/aggregate" xmlns:oc-types="http://openconfig.net/yang/openconfig-types" xmlns:oc-vlan-types="http://openconfig.net/yang/vlan-types" xmlns:oc-yang="http://openconfig.net/yang/types/yang">'
  when: (item.jtox_output == "
openconfig-interfaces.jtox" and ansible_network_os == "sros")

- name: NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML HEADER
  replace:
    path: "
{{ item.path }}/oc_conf.xml"
    regexp: '<nc:.+"
>'
    replace
: '<nc:config xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:oc-rpol="http://openconfig.net/yang/routing-policy" xmlns:oc-if="http://openconfig.net/yang/interfaces" xmlns:oc-inet="http://openconfig.net/yang/types/inet" xmlns:oc-pol-types="http://openconfig.net/yang/policy-types" xmlns:oc-types="http://openconfig.net/yang/openconfig-types" xmlns:oc-yang="http://openconfig.net/yang/types/yang" xmlns:ocext="http://openconfig.net/yang/openconfig-ext">'
  when
: (item.jtox_output == "openconfig-routing-policy.jtox" and ansible_network_os == "sros")

- name
: NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML HEADER
  replace
:
    path
: "{{ item.path }}/oc_conf.xml"
    regexp
: '<nc:.+">'
    replace: '<nc:config xmlns:nc="
urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:oc-netinst="http://openconfig.net/yang/network-instance" xmlns:oc-bgp="http://openconfig.net/yang/bgp" xmlns:oc-bgp-types="http://openconfig.net/yang/bgp-types" xmlns:oc-eth="http://openconfig.net/yang/interfaces/ethernet" xmlns:oc-if="http://openconfig.net/yang/interfaces" xmlns:oc-inet="http://openconfig.net/yang/types/inet" xmlns:oc-isis="http://openconfig.net/yang/openconfig-isis" xmlns:oc-isis-lsdb-types="http://openconfig.net/yang/isis-lsdb-types" xmlns:oc-isis-types="http://openconfig.net/yang/isis-types" xmlns:oc-lag="http://openconfig.net/yang/interfaces/aggregate" xmlns:oc-loc-rt="http://openconfig.net/yang/local-routing" xmlns:oc-mpls="http://openconfig.net/yang/mpls" xmlns:oc-mpls-sr="http://openconfig.net/yang/mpls-sr" xmlns:oc-mpls-types="http://openconfig.net/yang/mpls-types" xmlns:oc-ni-types="http://openconfig.net/yang/network-instance-types" xmlns:oc-pkt-match="http://openconfig.net/yang/header-fields" xmlns:oc-pkt-match-types="http://openconfig.net/yang/packet-match-types" xmlns:oc-pol-types="http://openconfig.net/yang/policy-types" xmlns:oc-rpol="http://openconfig.net/yang/routing-policy" xmlns:oc-rsvp="http://openconfig.net/yang/rsvp" xmlns:oc-types="http://openconfig.net/yang/openconfig-types" xmlns:oc-vlan="http://openconfig.net/yang/vlan" xmlns:oc-vlan-types="http://openconfig.net/yang/vlan-types" xmlns:oc-yang="http://openconfig.net/yang/types/yang" xmlns:ocext="http://openconfig.net/yang/openconfig-ext">'
  when: (item.jtox_output == "
openconfig-network-instance.jtox" and ansible_network_os == "sros")

- name: PUSHING PARAMETERS FROM {{ item.related_config }} to {{ inventory_hostname }}
  netconf_config:
    host: "
{{ inventory_hostname }}"
    username: "
{{ ansible_user }}"
    password: "
{{ ansible_pass }}"
    port: "
{{ ansible_port }}"
    hostkey_verify: false
    look_for_keys: false
    xml: "
{{ lookup ('file', '{{ item.path }}/oc_conf.xml') }}"

- name: DELETING TEMPORARY XML FOR NETCONF
  file:
    dest: "
{{ item.path }}/oc_conf.xml"
    state: absent

- name: DELETING TEMPORARY COPY OF CONFIG FILE {{ item.related_config }}
  file:
    dest: "
{{ item.path }}/{{ item.related_config }}"
    state: absent
...

 

Just to highlight, there is no changes in master “configure.yml”, which launches configuration loop.

Actually for Nokia SR OS we are just rewriting header with proper supporting models. I hope with further development of Nokia SR OS, we’ll be able to fetch proper modules from SR OS itself, what will prevent including not supported modules in XML header.

The last point, before we test the Ansible automation with OpenConfig for Nokia (Alcatel-Lucent) SR OS 16.0.R1, to show you the JSON files with variables. As usual, we start with the 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
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
$ cat roles/nokia/132_lab/files/SR6_openconfig-interfaces.json
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "name": "1/1/c1/1",
                "config": {
                    "description": "OC Ethernet interface",
                    "enabled": true,
                    "name": "1/1/c1/1",
                    "type": "ethernetCsmacd"
                },
                "subinterfaces": {
                    "subinterface": [
                        {
                            "config": {
                                "description": "OC Ethernet sub-interfaces",
                                "enabled": true,
                                "index": 13
                            },
                            "index": 13,
                            "openconfig-if-ip:ipv4": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "10.11.33.11",
                                                "prefix-length": 24
                                            },
                                            "ip": "10.11.33.11"
                                        }
                                    ]
                                }
                            },
                            "openconfig-if-ip:ipv6": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "fc00::10:11:33:11",
                                                "prefix-length": 112
                                            },
                                            "ip": "fc00::10:11:33:11"
                                        }
                                    ]
                                }
                            },
                            "openconfig-vlan:vlan": {
                                "config": {
                                    "vlan-id": 13
                                }
                            }
                        }
                    ]
                }
            },
            {
                "name": "loopback",
                "config": {
                    "description": "OC loopback",
                    "enabled": true,
                    "name": "loopback",
                    "type": "softwareLoopback"
                },
                "subinterfaces": {
                    "subinterface": [
                        {
                            "config": {
                                "description": "OC loopback 0",
                                "enabled": true,
                                "index": 0
                            },
                            "index": 0,
                            "openconfig-if-ip:ipv4": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "10.0.0.11",
                                                "prefix-length": 32
                                            },
                                            "ip": "10.0.0.11"
                                        }
                                    ]
                                }
                            },
                            "openconfig-if-ip:ipv6": {
                                "addresses": {
                                    "address": [
                                        {
                                            "config": {
                                                "ip": "fc00::10:0:0:11",
                                                "prefix-length": 128
                                            },
                                            "ip": "fc00::10:0:0:11"
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        ]
    }
}

 

Here we have exactly the same configuration structure, as we have had previously for Arista EOS 4.20.7M. That’s pretty cool, as this config will be implemented later on fully including IP addresses.

For OpenConfig routing policies we have the next JSON parameters:

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
$ cat roles/nokia/132_lab/files/SR6_openconfig-routing-policy.json
{
    "openconfig-routing-policy:routing-policy": {
        "defined-sets": {
            "prefix-sets": {
                "prefix-set": [
                    {
                        "config": {
                            "name": "PS_ANSIBLE"
                        },
                        "name": "PS_ANSIBLE",
                        "prefixes": {
                            "prefix": [
                                {
                                   "config": {
                                        "ip-prefix": "20.0.0.0/24",
                                        "masklength-range": "32..32"
                                    },
                                    "ip-prefix": "20.0.0.0/24",
                                    "masklength-range": "32..32"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "policy-definitions": {
            "policy-definition": [
                    {
                    "config": {
                        "name": "RP_ANSIBLE"
                    },
                    "name": "RP_ANSIBLE",
                    "statements": {
                        "statement": [
                            {
                                "actions": {
                                    "config": {
                                        "policy-result": "ACCEPT_ROUTE"
                                    }
                                },
                                "conditions": {
                                    "match-prefix-set": {
                                        "config": {
                                            "match-set-options": "ANY",
                                            "prefix-set": "PS_ANSIBLE"
                                        }
                                    }
                                },
                                "config": {
                                    "name": "statement-10"
                                },
                                "name": "statement-10"
                            }
                        ]
                    }
                }
            ]
        }
    }
}

 

Again, the config is exactly the same we’ve had for Arista EOS. And again it will be fully implemented.

For OpenConfig network instance we have that JSON:

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
121
122
123
124
125
126
127
128
$ cat roles/nokia/132_lab/files/SR6_openconfig-network-instance.json
{
    "openconfig-network-instance:network-instances": {
        "network-instance": [
            {
                "config": {
                    "name": "Base",
                    "type": "DEFAULT_INSTANCE"
                },
                "interfaces": {
                    "interface": [
                        {
                            "config": {
                                "associated-address-families": [
                                    "IPV4",
                                    "IPV6"
                                ],
                                "id": "1/1/c1/1.13",
                                "interface": "1/1/c1/1",
                                "subinterface": 13
                            },
                            "id": "1/1/c1/1.13"
                        },
                        {
                            "config": {
                                "associated-address-families": [
                                    "IPV4",
                                    "IPV6"
                                ],
                                "id": "loopback",
                                "interface": "loopback",
                                "subinterface": 0
                            },
                            "id": "loopback"
                        }
                    ]
                },
                "name": "Base",
                "protocols": {
                    "protocol": [
                        {
                            "identifier": "BGP",
                            "name": 0,
                            "config": {
                                "enabled": true,
                                "identifier": "BGP",
                                "name": 0
                            },
                            "bgp": {
                                "global": {
                                    "afi-safis": {
                                        "afi-safi": [
                                            {
                                                "afi-safi-name": "IPV4_UNICAST",
                                                "config": {
                                                    "afi-safi-name": "IPV4_UNICAST",
                                                    "enabled": true
                                                }
                                            }
                                        ]
                                    },
                                    "config": {
                                        "as": 65011,
                                        "router-id": "10.0.0.11"
                                    }
                                },
                                "neighbors": {
                                    "neighbor": [
                                        {
                                            "afi-safis": {
                                                "afi-safi": [
                                                    {
                                                        "afi-safi-name": "IPV4_UNICAST",
                                                        "config": {
                                                            "afi-safi-name": "IPV4_UNICAST",
                                                            "enabled": true
                                                        }
                                                    }
                                                ]
                                            },
                                            "config": {
                                                "neighbor-address": "10.11.33.33",
                                                "peer-as": 65001,
                                                "peer-group": "FABRIC"
                                            },
                                            "neighbor-address": "10.11.33.33"
                                        }
                                    ]
                                },
                                "peer-groups": {
                                    "peer-group": [
                                        {
                                            "afi-safis": {
                                                "afi-safi": [
                                                    {
                                                        "afi-safi-name": "IPV4_UNICAST",
                                                        "config": {
                                                            "afi-safi-name": "IPV4_UNICAST",
                                                            "enabled": true
                                                        }
                                                    }
                                                ]
                                            },
                                            "apply-policy": {
                                                "config": {
                                                    "export-policy": [
                                                        "RP_ANSIBLE"
                                                    ],
                                                    "import-policy": [
                                                        "RP_ANSIBLE"
                                                    ]
                                                }
                                            },
                                            "config": {
                                                "peer-group-name": "FABRIC"
                                            },
                                            "peer-group-name": "FABRIC"
                                        }
                                    ]
                                }
                            }
                        }
                    ]
                }
            }
        ]
    }
}

 

Again, config is very similar to Arista EOS 4.20.7M, the only difference we need to define in Nokia SR OS OpenConfig models interfaces on network instance as well in order they installed in the routing table.

Final check, we don’t have any OpenConfig interfaces in the SR6:

1
2
3
4
A:admin@SR6# admin show configuration | match openconfig
                openconfig-modules true

[]

 

Let’s test our Ansible playbook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ansible-playbook 132_lab.yml --limit=SR6 --tag=nokia_configure
 [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
!
! OUTPUT IS OMITTED
!
PLAY [nokia] *************************************************************************************************************************************************
!
! OUTPUT IS OMITTED
!
TASK [nokia/132_lab : NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML HEADER] **********************************************************************************
changed: [SR6]

TASK [nokia/132_lab : PUSHING PARAMETERS FROM SR6_openconfig-network-instance.json to SR6] *******************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was:             MINOR: MGMT_CORE #224: Entry does not exist
fatal: [SR6]: FAILED! => {"changed": false, "msg": "error editing configuration: \n            MINOR: MGMT_CORE #224: Entry does not exist\n        "}
    to retry, use: --limit @/home/aaa/ansible/132_lab.retry

PLAY RECAP ***************************************************************************************************************************************************
SR6                        : ok=29   changed=22   unreachable=0    failed=1

 

The final play for “network-instance” context fails. There is very strange behaviour that we aren’t able configure BGP as instance doesn’t exist, though we are creating it. I hope this is bug and Nokia will fix it in the upcoming releases. There is workaround, where we create BGP instance manually:

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
(gl)[]
A:admin@SR6# compare
    configure {
        openconfig {
+           network-instances {
+               network-instance "Base" {
+                   config {
+                       name "Base"
+                   }
+                   protocols {
+                       protocol BGP name "0" {
+                           config {
+                               identifier BGP
+                               name "0"
+                               enabled true
+                           }
+                           bgp {
+                               global {
+                                   config {
+                                       as 65011
+                                   }
+                               }
+                           }
+                       }
+                   }
+               }
+           }
        }
    }

 

Then we execute Ansible playbook again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ ansible-playbook 132_lab.yml --limit=SR6 --tag=nokia_configure
 [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
!
! OUTPUT IS OMITTED
!
TASK [nokia/132_lab : NOKIA SR OS 16.0.R1 SPECIFIC // MODIFYING XML HEADER] **********************************************************************************
changed: [SR6]

TASK [nokia/132_lab : PUSHING PARAMETERS FROM SR6_openconfig-network-instance.json to SR6] *******************************************************************
changed: [SR6]

TASK [nokia/132_lab : DELETING TEMPORARY XML FOR NETCONF] ****************************************************************************************************
changed: [SR6]

TASK [nokia/132_lab : DELETING TEMPORARY COPY OF CONFIG FILE SR6_openconfig-network-instance.json] ***********************************************************
changed: [SR6]

PLAY RECAP ***************************************************************************************************************************************************
SR6                        : ok=32   changed=21   unreachable=0    failed=0  

[aaa@sandbox ansible]$

 

Now everything is fine. Dear Nokia colleagues, please, fix that issue.

In the rest, the configuration using OpenConfig YANG models were pushed to Nokia (Alcatel-Lucent) SR OS device and it’s working fine. I don’t provide output of the “admin show configuration” to save the space.

The final Ansible playbooks are here: 132_lab.tar

Lessons learned

Usage of Python tools like “pyang” and “json2xml” provides good results in general. As you might have seen, there is still plenty of manual or automated modification of the outcome of the tools to make it usable for the devices. That’s why it is not necessary bad approach to write your own jijna2 templates (link) to solve your tasks.

Nevertheless, I believe once the will be dedicated Ansible module for OpenConfig, which will take a lot of such activities over and we’d be able to focus solely on YANG data models (including OpenConfig) themselves.

Conclusion

In this article we’ve reviewed the general approach for network automation and programmability based on OpenConfig. So you easily extended it to other OpenConfig YANG modules to fulfil your tasks. All reviewed vendors (Arista, Cisco and Nokia) show moderate to good support of OpenConfig YANG and there used data models are quite similar with the remark that Cisco IOS XR 6.1.2 has quite old OpenConfig modules comparing to Arista and Nokia, which OSes EOS 4.20.6F and SROS 16.0.R1 have been released recently in this year (2018). I hope, all the vendor continues to work on OpenConfig implementation and also that they will share information about their OpenConfig implementations and NETCONF operation. Some of them are already doing great job in this direction, some aren’t yet. 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. Also don’t forget to share the article on your social media, if you like it.

BR,

Anton Karneliuk