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.
1
2
3
4
5 No part of this blogpost could be reproduced, stored in a
retrieval system, or transmitted in any form or by any
means, electronic, mechanical or photocopying, recording,
or otherwise, for commercial purposes without the
prior permission of the author.
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 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 $ cat group_vars/cisco/cisco_host.yml
---
ansible_network_os: iosxr
ansible_user: cisco
ansible_pass: cisco
ansible_ssh_pass: cisco
...
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 $ pwd
/tmp/SR6
$ 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
$ 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
Hey, Anton,
I have been reading through your blog…fantastic work you have. Quite inspiring to see you’re young yet with huge wealth of experience….I would be glad to network with you if you wouldn’t mind, please reply my email as submitted. Would expect to here from you soon. Thanks alot.
hear* apologies, the keyboard messed with me.