Hello my friend,
While I’m still working on article for OpenConfig and BGP for automated and unified configuration of DC fabric, I’ve decided to show you operation of two new NETCONF modules, which are available in Ansible 2.6, which is the latest release for the time of writing.
1 2 3 4 5 | No part of this blogpost could be reproduced, stored in a<br> retrieval system, or transmitted in any form or by any<br> means, electronic, mechanical or photocopying, recording,<br> or otherwise, for commercial purposes without the<br> prior permission of the author.<br> |
Brief description
Frankly speaking, I planned to look at these modules later on, but my friend Nicola Arnoldi told me that there are some Red Hat videos, which explain how OpenConfig could be used in Ansible in more efficient way, so I decided to take a look on two new NETCONF modules, which are available in Asible 2.6:
In this blogpost, we’ll take a look capability of these modules to create data structure and collect variables (like IP addresses, interface names, etc) and operational data directly from the network elements.
What we are going to test?
We’ll test, how we can extract information (both configurational and operational data) in YANG data model through NETCONF and what to do further with this information.
Software version
The following infrastructure is used in my lab:
- CentOS 7 with python 2.7.
- Ansible 2.6.0
- Arista EOS 4.20.5F
- 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
As physical topology we are taking our standard one, with all network elements (Nokia (Alcatel-Lucent) VSR, Arista vEOS and Cisco IOS XRv) connected to our management host with Linux CentOS 7
For the logical topology we take the one we have created in the previous lab:
Correspondingly the initial configuration for this lab is the one we have created in the previous one with the exception of Arista EOS based leaf vEOS2, as we haven’t managed to get it configured on NETCONF using OpenConfig data model, so we have configured it manually:
Module #1. Usage of netconf_get.
As you might guess from the name of the module, it’s used to send “get” RPC requests to the network device using desired XMLNS schema via NETCONF protocol. As a transport plugin it uses “netconf” , so the vendor must be supported. Let’s check. How it works.
1.1#. netconf_get and Nokia (Alcatel-Lucent) SR OS
If we look at the list of the authors of the module , we see that one of them is working in Red Hat and another is working in Nokia. That’s why we start investigations with Nokia (Alcatel-Lucent) SR OS
Following the same approach, we did in previous lab, we’ll create the Ansible structure with roles, so that we easily copy the same scripts between per vendor folders, if they really will work without modifications:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 +--ansible
+--130_lab.yml
+--130_lab_bonus.yml
+--group_vars
| +--arista
| | +--arista_host.yml
| +--cisco
| | +--cisco_host.yml
| +--nokia
| +--nokia_host.yml
+--roles
+--arista
| +--130_lab
| +--tasks
| +--main.yml
+--cisco
| +--130_lab
| +--tasks
| +--main.yml
+--nokia
+--130_lab
+--tasks
+--main.yml
The main Ansible playbook, which we use to call the rest of the docs is very similar to one we did in OpenConfig article:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 $ cat 130_lab.yml
---
- hosts: cisco
connection: netconf
tags: cisco
roles:
- { role: cisco/130_lab }
- hosts: nokia
connection: netconf
tags: nokia
roles:
- { role: nokia/130_lab }
- hosts: arista
connection: netconf
tags: arista
roles:
- { role: arista/130_lab }
...
In “group_vars” we store information related to authentication and another important parameter, which is called “ansible_network_os”. When we review the documentation for ncclient module, which is used as our NETCONF transport, we see that part of the input is the code, which network operation system (NOS) is used, as appropriate hello/capabilities are used. So, for Nokia such details look like:
1
2
3
4
5
6
7 $ cat group_vars/nokia/nokia_host.yml
---
ansible_network_os: sros
ansible_user: admin
ansible_pass: admin
ansible_ssh_pass: admin
...
Actually, we don’t have any variables or templates, as you might see from the structure, so we just create the Ansible playbook with tasks:
1
2
3
4
5
6
7
8
9
10
11
12
13 $ cat roles/nokia/130_lab/tasks/main.yml
---
- name: OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET
netconf_get:
display: json
filter: <interfaces xmlns="http://openconfig.net/yang/interfaces"/>
register: output_json
- name: OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE
copy:
content: "{{ output_json.output | to_nice_json }}"
dest: /tmp/{{ inventory_hostname }}_oc_conf.json
...
The first task is exactly the module, we are using. In “display” key we configure, which format should output have, whereas in the “filter” we point out, what exactly we want to see in the output. The second task is used to save the output of the first task. Filter “to_nice_json” is used to split the output per line in easy readable format, whereas variable “output_json” is used to copy the content of the output from the first task to the input of the second one.
Before using Ansible playbook, I need to add “export ANSIBLE_HOST_KEY_CHECKING=False”, as somehow this parameter isn’t read properly from ansible.cfg.
Now it’s time to verify if our Ansible automation is working:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 $ ansible-playbook 130_lab.yml --limit=SR6
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [nokia] *************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [SR6]
TASK [nokia/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] *****************************************************************************
ok: [SR6]
TASK [nokia/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ****************************************************************************************
changed: [SR6]
PLAY [arista] ************************************************************************************************************************************************
skipping: no hosts matched
PLAY RECAP ***************************************************************************************************************************************************
SR6 : ok=3 changed=1 unreachable=0 failed=0
Looks ok. So we go to the output, what we have collected:
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 $ cat /tmp/SR6_oc_conf.json
{
"data": {
"interfaces": {
"interface": [
{
"config": {
"enabled": "true",
"name": "1/1/c1/1",
"type": "ethernetCsmacd"
},
"ethernet": {
"state": {
"auto-negotiate": "false",
"counters": {
"in-crc-errors": "0",
"in-fragment-frames": "0",
"in-jabber-frames": "0",
"in-mac-pause-frames": "0",
"in-oversize-frames": "0",
"out-mac-pause-frames": "0"
},
"hw-mac-address": "fa:ac:a6:16:02:02",
"mac-address": "00:00:00:00:00:00",
"negotiated-duplex-mode": "FULL",
"negotiated-port-speed": "SPEED_10GB"
}
},
"hold-time": {
"state": {
"down": "0",
"up": "0"
}
},
"name": "1/1/c1/1",
"state": {
"admin-status": "UP",
"counters": {
"carrier-transitions": "0",
"in-broadcast-pkts": "0",
"in-discards": "0",
"in-errors": "0",
"in-fcs-errors": "0",
"in-multicast-pkts": "1",
"in-octets": "85",
"in-unicast-pkts": "0",
"in-unknown-protos": "0",
"out-broadcast-pkts": "4",
"out-discards": "0",
"out-errors": "0",
"out-multicast-pkts": "4",
"out-octets": "616",
"out-unicast-pkts": "0"
},
"description": "10-Gig Ethernet",
"enabled": "true",
"ifindex": "1610899521",
"last-change": "33400000",
"mtu": "9212",
"name": "1/1/c1/1",
"oper-status": "UP",
"type": "ethernetCsmacd"
},
"subinterfaces": {
"subinterface": [
{
"config": {
"enabled": "true",
"index": "13"
},
"index": "13",
"ipv4": {
"addresses": {
"address": {
"config": {
"ip": "10.11.33.11",
"prefix-length": "24"
},
"ip": "10.11.33.11",
"state": {
"ip": "10.11.33.11",
"origin": "STATIC",
"prefix-length": "24"
}
}
}
},
"ipv6": {
"addresses": {
"address": {
"config": {
"ip": "fc00::10:11:33:11",
"prefix-length": "112"
},
"ip": "fc00::10:11:33:11",
"state": {
"ip": "fc00::10:11:33:11",
"origin": "STATIC",
"prefix-length": "112",
"status": "PREFERRED"
}
}
},
"state": {
"dup-addr-detect-transmits": "1"
}
},
"state": {
"admin-status": "UP",
"counters": {
"carrier-transitions": "0",
"in-broadcast-pkts": "0",
"in-fcs-errors": "0",
"in-multicast-pkts": "0",
"in-octets": "0",
"in-unicast-pkts": "0",
"last-clear": "0",
"out-broadcast-pkts": "0",
"out-discards": "0",
"out-multicast-pkts": "2",
"out-octets": "172",
"out-unicast-pkts": "2"
},
"enabled": "true",
"ifindex": "3",
"index": "13",
"last-change": "1532638002600000000",
"name": "oc_1/1/c1/1_13"
},
"vlan": {
"config": {
"vlan-id": "13"
},
"state": {
"vlan-id": "13"
}
}
},
{
"config": {
"enabled": "true",
"index": "14"
},
"index": "14",
"ipv4": {
"addresses": {
"address": {
"config": {
"ip": "10.11.44.11",
"prefix-length": "24"
},
"ip": "10.11.44.11",
"state": {
"ip": "10.11.44.11",
"origin": "STATIC",
"prefix-length": "24"
}
}
}
},
"ipv6": {
"addresses": {
"address": {
"config": {
"ip": "fc00::10:11:44:11",
"prefix-length": "112"
},
"ip": "fc00::10:11:44:11",
"state": {
"ip": "fc00::10:11:44:11",
"origin": "STATIC",
"prefix-length": "112",
"status": "PREFERRED"
}
}
},
"state": {
"dup-addr-detect-transmits": "1"
}
},
"state": {
"admin-status": "UP",
"counters": {
"carrier-transitions": "0",
"in-broadcast-pkts": "0",
"in-fcs-errors": "0",
"in-multicast-pkts": "0",
"in-octets": "0",
"in-unicast-pkts": "0",
"last-clear": "0",
"out-broadcast-pkts": "0",
"out-discards": "0",
"out-multicast-pkts": "2",
"out-octets": "172",
"out-unicast-pkts": "2"
},
"enabled": "true",
"ifindex": "2",
"index": "14",
"last-change": "1532638002600000000",
"name": "oc_1/1/c1/1_14"
},
"vlan": {
"config": {
"vlan-id": "14"
},
"state": {
"vlan-id": "14"
}
}
}
]
}
},
{
"config": {
"enabled": "true",
"name": "system",
"type": "softwareLoopback"
},
"hold-time": "",
"name": "system",
"state": {
"counters": "",
"name": "system",
"type": "softwareLoopback"
},
"subinterfaces": {
"subinterface": {
"index": "0",
"ipv4": {
"addresses": {
"address": {
"config": {
"ip": "10.0.0.11",
"prefix-length": "32"
},
"ip": "10.0.0.11",
"state": {
"ip": "10.0.0.11",
"origin": "STATIC",
"prefix-length": "32"
}
}
}
},
"ipv6": {
"addresses": {
"address": {
"config": {
"ip": "fc00::10:0:0:11",
"prefix-length": "128"
},
"ip": "fc00::10:0:0:11",
"state": {
"ip": "fc00::10:0:0:11",
"origin": "STATIC",
"prefix-length": "128",
"status": "PREFERRED"
}
}
},
"state": {
"dup-addr-detect-transmits": "1"
}
},
"state": {
"admin-status": "UP",
"counters": {
"carrier-transitions": "0",
"in-broadcast-pkts": "0",
"in-fcs-errors": "0",
"in-multicast-pkts": "0",
"in-octets": "0",
"in-unicast-pkts": "0",
"last-clear": "0",
"out-broadcast-pkts": "0",
"out-discards": "0",
"out-multicast-pkts": "0",
"out-octets": "0",
"out-unicast-pkts": "0"
},
"enabled": "true",
"ifindex": "1",
"index": "0",
"last-change": "1532637970900000000",
"name": "system"
}
}
}
}
]
}
}
}
- Everything within “state” categories is coming from read-only fields and represent operational information. This information forms the basis of YANG-based telemetry
- Everything outside “state” are configurable parameters, which can be tuned
Looks amazing, isn’t it? For developers of the automation that is really great source of the information, I think.
1.2#. netconf_get and Cisco IOS XR
We just copy created task to appropriate folder in Cisco roles and that is it:
1 $ cp -R roles/nokia/130_lab roles/cisco
As we don’t modify this Ansible playbook with tasks or initial one with roles, I will only show parameters from “group_vars:”
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
...
Basically we are ready to get some info in this OpenConfig YANG data model through NETCONF:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 $ ansible-playbook 130_lab.yml --limit=XR3
[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/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] *****************************************************************************
ok: [XR3]
TASK [cisco/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ****************************************************************************************
changed: [XR3]
PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [arista] ************************************************************************************************************************************************
skipping: no hosts matched
PLAY RECAP ***************************************************************************************************************************************************
XR3 : ok=3 changed=1 unreachable=0 failed=0
The log of Ansible playbook execution looks nice, so we can verify the output of collected data:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 $ cat /tmp/XR3_oc_conf.json
{
"rpc-reply": {
"data": {
"interfaces": {
"interface": [
{
"config": {
"enabled": "true",
"name": "Loopback0",
"type": "idx:softwareLoopback"
},
"name": "Loopback0",
"state": {
"admin-status": "UP",
"description": "",
"enabled": "true",
"ifindex": "7",
"last-change": "735",
"mtu": "1500",
"name": "Loopback0",
"oper-status": "UP",
"type": "idx:softwareLoopback"
},
"subinterfaces": {
"subinterface": {
"index": "0",
"ipv4": {
"address": {
"config": {
"ip": "10.0.0.33",
"prefix-length": "32"
},
"ip": "10.0.0.33",
"state": {
"ip": "10.0.0.33",
"origin": "STATIC",
"prefix-length": "32"
}
}
},
"ipv6": {
"address": {
"config": {
"ip": "fc00::10:0:0:33",
"prefix-length": "128"
},
"ip": "fc00::10:0:0:33"
}
}
}
}
},
!
! FURTHER OUTPUT IS OMITTED
!
I provide only beginning of the output for the sake of brevity, but you might see that output structure and vast majority of the keys are same, because we are using the same OpenConfig YANG model and the same XMLNS scheme
1.3#. netconf_get and Arista EOS
The successful operation of this “netconf_get” module for the Nokia (Alcatel-Lucent) and Cisco IOS XR gives me the feeling that for Arista EOS it also should work.
1 $ cp -R roles/nokia/130_lab roles/arista
Here are the “group_vars” for Arista:
1
2
3
4
5
6
7
8 $ cat group_vars/arista/arista_host.yml
---
ansible_network_os: eos
ansible_user: aaa
ansible_ssh_pass: aaa
ansible_become: yes
ansible_become_method: enable
...
And we go straight to verification:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 $ ansible-playbook 130_lab.yml --limit=vEOS2
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [arista] ************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
fatal: [vEOS2]: FAILED! => {"msg": " [WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.\n{"socket_path": "/home/aaa/.ansible/pc/eee96979d7", "exception": "Traceback (most recent call last):\\n File \\"/usr/bin/ansible-connection\\", line 87, in start\\n self.connection._connect()\\n File \\"/usr/lib/python2.7/site-packages/ansible/plugins/connection/netconf.py\\", line 274, in _connect\\n raise AnsibleError(\\"connection=netconf is not supported on {0}\\".format(network_os))\\nAnsibleError: connection=netconf is not supported on eos\\n", "messages": ["local domain socket does not exist, starting it", "control socket path is /home/aaa/.ansible/pc/eee96979d7", ""], "error": "connection=netconf is not supported on eos"}"}
to retry, use: --limit @/home/aaa/ansible/130_lab.retry
PLAY RECAP ***************************************************************************************************************************************************
vEOS2
Unfortunately, we fail. The good thing about Ansible is that it usually provides quite good debug information, so we can read from the output above that “eos” isn’t supported in case of using netconf transport plugin based on ncclient. But that’s strange and seems to be bug or the module must be updated.
But I have found workaround for it. We need to modify “group_vars” in the following way:
1
2
3
4
5
6
7
8
9
10 $ cat group_vars/arista/arista_host.yml
---
# ansible_network_os: eos
ansible_network_os: nexus
ansible_port: 22
ansible_user: aaa
ansible_ssh_pass: aaa
ansible_become: yes
ansible_become_method: enable
...
So we put the port to 22 (instead of default 830) and NOS version to Cisco Nexus. Then the playbook starts working:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 $ ansible-playbook 130_lab.yml --limit=vEOS2
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [arista] ************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [vEOS2]
TASK [arista/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] ****************************************************************************
ok: [vEOS2]
TASK [arista/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ***************************************************************************************
changed: [vEOS2]
PLAY RECAP ***************************************************************************************************************************************************
vEOS2 : ok=3 changed=1 unreachable=0 failed=0
And we are able to collect proper output:
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 $ cat /tmp/vEOS2_oc_conf.json
{
"data": {
"interfaces": {
"interface": [
{
"config": {
"description": "",
"enabled": "true",
"load-interval": "300",
"mtu": "0",
"name": "Management1",
"type": "ethernetCsmacd"
},
"ethernet": {
"config": {
"fec-encoding": {
"disabled": "false",
"fire-code": "false",
"reed-solomon": "false"
},
"mac-address": "00:00:00:00:00:00",
"port-speed": "SPEED_UNKNOWN",
"sfp-1000base-t": "false"
},
"state": {
"auto-negotiate": "true",
"counters": {
"in-crc-errors": "0",
"in-fragment-frames": "0",
"in-jabber-frames": "0",
"in-mac-control-frames": "0",
"in-mac-pause-frames": "0",
"in-oversize-frames": "0",
"out-mac-control-frames": "0",
"out-mac-pause-frames": "0"
},
"duplex-mode": "FULL",
"enable-flow-control": "true",
"hw-mac-address": "00:50:56:31:07:7c",
"mac-address": "00:50:56:31:07:7c",
"negotiated-duplex-mode": "FULL",
"negotiated-port-speed": "SPEED_1GB",
"port-speed": "SPEED_1GB"
}
},
"name": "Management1",
"state": {
"admin-status": "UP",
"counters": {
"in-broadcast-pkts": "0",
"in-discards": "0",
"in-errors": "0",
"in-multicast-pkts": "0",
"in-octets": "24673",
"in-unicast-pkts": "194",
"out-broadcast-pkts": "0",
"out-discards": "0",
"out-errors": "0",
"out-multicast-pkts": "0",
"out-octets": "26155",
"out-unicast-pkts": "91"
},
"description": "",
"enabled": "true",
"ifindex": "999001",
"inactive": "false",
"last-change": "153277125116",
"mtu": "0",
"name": "Management1",
"oper-status": "UP",
"type": "ethernetCsmacd"
},
"subinterfaces": {
"subinterface": {
"config": {
"index": "0"
},
"index": "0",
"ipv4": {
"addresses": {
"address": {
"config": {
"ip": "192.168.44.82",
"prefix-length": "24"
},
"ip": "192.168.44.82",
"state": {
"ip": "192.168.44.82",
"prefix-length": "24"
}
}
},
"config": {
"enabled": "false",
"mtu": "1500"
},
"state": {
"enabled": "false",
"mtu": "1500"
}
},
"ipv6": {
"config": {
"enabled": "false",
"mtu": "1500"
},
"state": {
"enabled": "false",
"mtu": "1500"
}
},
"state": {
"index": "0"
}
}
}
},
!
! FURTHER OUTPUT IS OMITTED
!
Now it works as proper, and we collect both configuration and operational info using OpenConfig YANG data model through NETCONF.
So, if our goal is to collect some data, that this “netconf_get” module perfectly matches. If we want to do more, let’s go to the next one.
Module #2. Usage of netconf_rpc.
This module is written by the same author and uses the same transport netconf plugin based on ncclient. But it provides the possibility to send arbitrary RPC request and get proper response in case that network element has such capabilities.
2.1#. netconf_rpc and Cisco IOS XR
One of the useful requests, which might save a lot of time to you if proper implemented in vendor, is “get-schema”. In RFC 6022 there is some explanation, what this request is doing. In a short word, it extracts from the device content of particular supported YANG module, which can be converted in “.yang” model and read by “pyang”.
So, from Ansible prospective, we extend the playbook with tasks “main.yml” with 4 new 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 $ cat roles/cisco/130_lab/tasks/main.yml
---
- name: OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET
netconf_get:
display: json
filter: <interfaces xmlns="http://openconfig.net/yang/interfaces"/>
register: output_json
- name: OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE
copy:
content: "{{ output_json.output | to_nice_json }}"
dest: /tmp/{{ inventory_hostname }}_oc_conf.json
- name: ALL SCHEMAS // FETCHING INFORMATION THROUGH GET
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: ALL SCHEMAS // SAVING OUTPUT TO FILE
copy:
content: "{{ list_of_schemas.output | to_nice_json }}"
dest: /tmp/{{ inventory_hostname }}_list_of_schemas.json
- name: OPENCONFIG-INTERFACES SCHEMA // FETCHING INFORMATION THROUGH GET-SCHEMA
netconf_rpc:
rpc: get-schema
xmlns: "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
content: "{'identifier': 'openconfig-interfaces'}"
display: json
register: openconfig_interfaces
- name: OPENCONFIG-INTERFACES SCHEMA // SAVING OUTPUT TO FILE
copy:
content: "{{ openconfig_interfaces.stdout }}"
dest: /tmp/{{ inventory_hostname }}_openconfig_interfaces.json
...
There are no other changes in other files.
You see here 4 new entries:
- Two of them are using the same “copy” module to save the output
- One of them is using “netconf_get” module, which is already covered
- One of them is using new “netconf_rpc”
What is interesting, the content in “netconf_rpc” module could be filled in with either JSON or YAML structure, whatever you prefer more.
The information used in playbook comes from RFC 6022:
- One example there shows how to construct “get” message to obtain list of schemas
- Another example shows how to construct “get-schema” message to get the particular YANG module from this device
Let’s execute this 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 $ ansible-playbook 130_lab.yml --limit=XR3
[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/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] *****************************************************************************
ok: [XR3]
TASK [cisco/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ****************************************************************************************
changed: [XR3]
TASK [cisco/130_lab : ALL SCHEMAS // FETCHING INFORMATION THROUGH GET] ***************************************************************************************
ok: [XR3]
TASK [cisco/130_lab : ALL SCHEMAS // SAVING OUTPUT TO FILE] **************************************************************************************************
changed: [XR3]
TASK [cisco/130_lab : OPENCONFIG-INTERFACES SCHEMA // FETCHING INFORMATION THROUGH GET-SCHEMA] ***************************************************************
ok: [XR3]
TASK [cisco/130_lab : OPENCONFIG-INTERFACES SCHEMA // SAVING OUTPUT TO FILE] *********************************************************************************
changed: [XR3]
PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [arista] ************************************************************************************************************************************************
skipping: no hosts matched
PLAY RECAP ***************************************************************************************************************************************************
XR3 : ok=7 changed=3 unreachable=0 failed=0
As the first two tasks were successful, I won’t review their result. That’s why we start with the list of schemas:
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 $ more 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"
},
{
"format": "yang",
"identifier": "Cisco-IOS-XR-aaa-locald-admin-cfg",
"location": "NETCONF",
"namespace": "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-locald-admin-cfg",
"version": "2015-11-09"
},
{
"format": "yang",
"identifier": "Cisco-IOS-XR-aaa-locald-cfg",
"location": "NETCONF",
"namespace": "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-locald-cfg",
"version": "2015-11-09"
},
{
"format": "yang",
"identifier": "Cisco-IOS-XR-aaa-locald-oper",
"location": "NETCONF",
"namespace": "http://cisco.com/ns/yang/Cisco-IOS-XR-aaa-locald-oper",
"version": "2015-11-09"
},
!
! OUTPUT IS OMITTED
!
{
"format": "yang",
"identifier": "openconfig-interfaces",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/interfaces",
"version": "2015-11-20"
},
!
! FURTHER OUTPUT IS OMITTED
!
In such a format all the supported YANG modules are listed. I’ve shown also some details about “openconfig-interfaces” module, which is used in the same Ansible playbook to extract data for particular YANG module, whch is saved in another file:
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 $ more XR3_openconfig_interfaces.json
module openconfig-interfaces {
yang-version "1";
// namespace
namespace "http://openconfig.net/yang/interfaces";
prefix "ocif";
// import some basic types
import ietf-interfaces { prefix if; }
import ietf-yang-types { prefix yang; }
import openconfig-extensions { prefix oc-ext; }
// meta
organization "OpenConfig working group";
contact
"OpenConfig working group
netopenconfig@googlegroups.com";
description
"Model for managing network interfaces.
This model reuses data items defined in the IETF YANG model for
interfaces described by RFC 7223 with an alternate structure
(particularly for operational state data) and with additional
configuration items.";
oc-ext:openconfig-version "0.2.0";
revision "2015-11-20" {
description
"OpenConfig public release";
reference "0.2.0";
}
revision "2015-10-09" {
description
!
!
! FURTHER OUTPUT IS OMITTED
!
It’s exactly the file, we have shown in the previous article, which we have downloaded from GitHub OpenConfig folder.
It looks amazing, isn’t it? Basically we can fetch all YANG modules from the device to reconstruct all YANG trees with dependencies, what in its turn could be used to construct proper configuration files and jijna2 templates.
2.2#. netconf_rpc and Arista EOS
In the same fashion we did previously, we just copy Ansible playbook with tasks from Cisco to Ansible folder:
1 $ cp roles/cisco/130_lab/tasks/main.yml roles/arista/130_lab/tasks/main.yml
We don’t change anything into any file more, just execute the playbook directly:
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 $ ansible-playbook 130_lab.yml --limit=vEOS1
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [nokia] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [arista] ************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [vEOS1]
TASK [arista/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] ****************************************************************************
ok: [vEOS1]
TASK [arista/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ***************************************************************************************
changed: [vEOS1]
TASK [arista/130_lab : ALL SCHEMAS // FETCHING INFORMATION THROUGH GET] **************************************************************************************
ok: [vEOS1]
TASK [arista/130_lab : ALL SCHEMAS // SAVING OUTPUT TO FILE] *************************************************************************************************
changed: [vEOS1]
TASK [arista/130_lab : OPENCONFIG-INTERFACES SCHEMA // FETCHING INFORMATION THROUGH GET-SCHEMA] **************************************************************
ok: [vEOS1]
TASK [arista/130_lab : OPENCONFIG-INTERFACES SCHEMA // SAVING OUTPUT TO FILE] ********************************************************************************
changed: [vEOS1]
PLAY RECAP ***************************************************************************************************************************************************
vEOS1 : ok=7 changed=3 unreachable=0 failed=0
As you see, everything is working fine, so we briefly check the available schemes:
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 $ more vEOS1_list_of_schemas.json
{
"data": {
"netconf-state": {
"schemas": {
"schema": [
{
"format": "yang",
"identifier": "openconfig-network-instance-l2",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/network-instance",
"version": "2017-12-13"
},
{
"format": "yang",
"identifier": "ietf-yang-types",
"location": "NETCONF",
"namespace": "urn:ietf:params:xml:ns:yang:ietf-yang-types",
"version": "2013-07-15"
},
{
"format": "yang",
"identifier": "openconfig-isis",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/openconfig-isis",
"version": "2017-08-24"
},
{
"format": "yang",
"identifier": "arista-intf-augments",
"location": "NETCONF",
"namespace": "http://arista.com/yang/openconfig/interfaces/augments",
"version": "2017-10-01"
},
{
"format": "yang",
"identifier": "openconfig-mpls-rsvp",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/rsvp",
"version": "2017-08-24"
},
!
! OUTPUT IS OMITTED
!
{
"format": "yang",
"identifier": "openconfig-interfaces",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/interfaces",
"version": "2017-12-21"
},
!
! FURTHER OUTPUT IS OMITTED
!
And what we have captured from the Arista EOS device as YANG module for “openconfig-interfaces”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 $ more vEOS1_openconfig_interfaces.json
module "openconfig-interfaces" {
yang-version "1";
namespace "http://openconfig.net/yang/interfaces";
prefix "oc-if";
import "ietf-interfaces" {
prefix "ietf-if";
}
import "openconfig-yang-types" {
prefix "oc-yang";
}
import "openconfig-types" {
prefix "oc-types";
}
import "openconfig-extensions" {
prefix "oc-ext";
}
organization "OpenConfig working group";
contact "OpenConfig working group
netopenconfig@googlegroups.com";
description "Model for managing network interfaces and subinterfaces. This
module also defines convenience types / groupings for other
models to create references to interfaces:
base-interface-ref (type) - reference to a base interface
interface-ref (grouping) - container for reference to a
interface + subinterface
interface-ref-state (grouping) - container for read-only
(opstate) reference to interface + subinterface
This model reuses data items defined in the IETF YANG model for
interfaces described by RFC 7223 with an alternate structure
(particularly for operational state data) and with
additional configuration items.
Portions of this code were derived from IETF RFC 7223.
Please reproduce this note if possible.
IETF code is subject to the following copyright and license:
Copyright (c) IETF Trust and the persons identified as authors of
!
! FURTHER OUTPUT IS OMITTED
!
Perfect. Here NETCONF in general shows its power of interoperability across different vendors, so that the same Ansible playbook with tasks provides the same result for Cisco IOS XR and Arista EOS network functions.
2.3#. netconf_rpc and Nokia (Alcatel-Lucent) SR OS
Nokia (Alcatel-Lucent) SR OS comes third in a row for this “netconf_rpc” module, because in current release (SR OS 16.0.R1) not everything worls properly, but step by step. First of all we copy the Ansible playbook to folder with Nokia roles:
1 $ cp roles/cisco/130_lab/tasks/main.yml roles/nokia/130_lab/tasks/main.yml
Then we execute 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 $ ansible-playbook 130_lab.yml --limit=SR6
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [cisco] *************************************************************************************************************************************************
skipping: no hosts matched
PLAY [nokia] *************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [SR6]
TASK [nokia/130_lab : OPENCONFIG-INTERFACES // FETCHING INFORMATION THROUGH GET] *****************************************************************************
ok: [SR6]
TASK [nokia/130_lab : OPENCONFIG-INTERFACES // SAVING OUTPUT TO FILE] ****************************************************************************************
changed: [SR6]
TASK [nokia/130_lab : ALL SCHEMAS // FETCHING INFORMATION THROUGH GET] ***************************************************************************************
ok: [SR6]
TASK [nokia/130_lab : ALL SCHEMAS // SAVING OUTPUT TO FILE] **************************************************************************************************
ok: [SR6]
TASK [nokia/130_lab : OPENCONFIG-INTERFACES SCHEMA // FETCHING INFORMATION THROUGH GET-SCHEMA] ***************************************************************
fatal: [SR6]: FAILED! => {"changed": false, "msg": "Element is not valid in the specified context."}
to retry, use: --limit @/home/aaa/ansible/130_lab.retry
PLAY RECAP ***************************************************************************************************************************************************
SR6 : ok=5 changed=1 unreachable=0 failed=1
The “get-schema” request is somewhat not working for Nokia VSR 16.0.R1, but according to Nokia it will be fixed in one of the upcoming releases.
Nevertheless the list of supported schemas is fetched:
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 $ more SR6_list_of_schemas.json
{
"data": {
"netconf-state": {
"schemas": {
"schema": [
{
"format": "yang",
"identifier": "nokia-conf",
"location": "NETCONF",
"namespace": "urn:nokia.com:sros:ns:yang:sr:conf",
"version": "2016-07-06"
},
{
"format": "yang",
"identifier": "nokia-conf-aaa",
"location": "NETCONF",
"namespace": "None",
"version": "2018-05-09"
},
{
"format": "yang",
"identifier": "nokia-conf-bfd",
"location": "NETCONF",
"namespace": "None",
"version": "2018-04-02"
},
{
"format": "yang",
"identifier": "nokia-conf-bmp",
"location": "NETCONF",
"namespace": "None",
"version": "2018-04-02"
},
{
"format": "yang",
"identifier": "nokia-conf-call-trace",
"location": "NETCONF",
"namespace": "None",
"version": "2018-05-09"
},
!
! OUTPUT IS OMITTED
!
{
"format": "yang",
"identifier": "openconfig-interfaces",
"location": "NETCONF",
"namespace": "http://openconfig.net/yang/interfaces",
"version": "2016-12-22"
},
!
! FURTHER OUTPUT IS OMITTED
!
Despite the failure in the last execution of the playbook, the total result is very optimistic as it gives the opportunity for further automation using Ansible.
BONUS# Replacement of “netconf_config” by “netconf_rpc”
As I said in the beginning, “netconf_rpc” could be used to construct any RPC message. In previous article I have shown you the message flow, when we were testing OpenConfig messages. In Ansible playbook, we have used “netconf_config” module, which is created for configuration of the devices. But “netconf_rpc” also could be used, if we know the exact sequence of the actions, which must be done. For Nokia (Alcatel-Lucent) SR OS we have the following sequence:
- Lock the configuration
- Perform the changes
- Commit the changes
- Unlock the configuration
- Save the running configuration to start-up
Here is the example how it looks like in Ansible playbook using “netconf_rpc” module:
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 ---
- hosts: nokia
connection: netconf
tasks:
- name: lock candidate
netconf_rpc:
rpc: lock
content: "{'target': {'candidate': None}}"
- name: config interface
netconf_rpc:
display: json
rpc: edit-config
content: |
<target>
<candidate/>
</target>
<config>
<interfaces xmlns="http://openconfig.net/yang/interfaces">
<interface>
<name>loopback</name>
<config>
<name>loopback</name>
<type>softwareLoopback</type>
<enabled>true</enabled>
</config>
<subinterfaces>
<subinterface>
<index>0</index>
<ipv4>
<addresses>
<address>
<ip>10.0.11.11</ip>
<config>
<ip>10.0.11.11</ip>
<prefix-length>32</prefix-length>
</config>
</address>
</addresses>
</ipv4>
<ipv6>
<addresses>
<address>
<ip>fc00::10:0:11:11</ip>
<config>
<ip>fc00::10:0:11:11</ip>
<prefix-length>128</prefix-length>
</config>
</address>
</addresses>
</ipv6>
</subinterface>
</subinterfaces>
</interface>
</interfaces>
<network-instances xmlns="http://openconfig.net/yang/network-instance">
<network-instance>
<name>Base</name>
<config>
<name>Base</name>
<type>DEFAULT_INSTANCE</type>
</config>
<interfaces>
<interface>
<id>loopback</id>
<config>
<id>loopback</id>
<interface>loopback</interface>
<subinterface>0</subinterface>
<associated-address-families>IPV4</associated-address-families>
<associated-address-families>IPV6</associated-address-families>
</config>
</interface>
</interfaces>
</network-instance>
</network-instances>
</config>
- name: commit changes
netconf_rpc:
rpc: commit
- name: unlock candidate
netconf_rpc:
rpc: unlock
content: "{'target': {'candidate': None}}"
- name: copy running to startup
netconf_rpc:
rpc: copy-config
content: "{'source': {'running': None}, 'target': {'startup': None}}"
...
You see different RPC messages, which is used to fulfil the particular operation. Let’s execute this Ansible playbooks:
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 $ ansible-playbook 130_lab_bonus.yml --limit=SR6
[WARNING] Ansible is in a world writable directory (/home/aaa/ansible), ignoring it as an ansible.cfg source.
PLAY [nokia] *************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [SR6]
TASK [lock candidate] ****************************************************************************************************************************************
ok: [SR6]
TASK [config interface] **************************************************************************************************************************************
ok: [SR6]
TASK [commit changes] ****************************************************************************************************************************************
ok: [SR6]
TASK [unlock candidate] **************************************************************************************************************************************
ok: [SR6]
TASK [copy running to startup] *******************************************************************************************************************************
ok: [SR6]
PLAY RECAP ***************************************************************************************************************************************************
SR6 : ok=6 changed=0 unreachable=0 failed=0
It works! If we check the CLI output of the SR6, we see the new 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 []
A:admin@SR6# show router interface
===============================================================================
Interface Table (Router: Base)
===============================================================================
Interface-Name Adm Opr(v4/v6) Mode Port/SapId
IP-Address PfxState
-------------------------------------------------------------------------------
oc_1/1/c1/1_13 Up Up/Up Network 1/1/c1/1:13
10.11.33.11/24 n/a
fc00::10:11:33:11/112 PREFERRED
fe80::a6:ffff:fe00:0/64 PREFERRED
oc_1/1/c1/1_14 Up Up/Up Network 1/1/c1/1:14
10.11.44.11/24 n/a
fc00::10:11:44:11/112 PREFERRED
fe80::a6:ffff:fe00:0/64 PREFERRED
oc_loopback_0 Up Up/Up Network loopback
10.0.11.11/32 n/a
fc00::10:0:11:11/128 PREFERRED
fe80::a6:ffff:fe00:0/64 PREFERRED
system Up Up/Up Network system
10.0.0.11/32 n/a
fc00::10:0:0:11/128 PREFERRED
-------------------------------------------------------------------------------
Interfaces : 4
===============================================================================
On your own you can play with other vendors, just pay attention to necessary actions and target data store (which is “running” for Arista EOS and “candidate” for Cisco IOS XR and Nokia (Alcatel-Lucent) SR OS).
Playbooks from this lab: 130_lab.tar
Lessons learned
We are never done. We can stop developing something, just because we want to stop or don’t want to continue, but we can’t finish, because everything is done. Which is good in research areas.
Using “get” operation we have collected both operational and configuration data. This configuration data is exactly the structure of variables; we can use for device description.
Conclusion
With the overall trend for network automation and programmability all these YANG data models stars playing the crucial role, as they can really provide the possibility to automate multivendor environment in a predictable way. Now we have more information, which ultimately can be used for other activates, which are on our list, like using OpenConfig to create BGP fabric in data centre. 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
Hi Anton,
Thank you for this blog post; even six years later, it’s still beneficial to those getting to grips with Ansible. One thing to note though is that the code formatting is a bit broken, likely after some code changes on your website. For example the `filter:` statements are empty when they can’t be as I’ve just found out.
Hey Djerk,
glad you find it useful and thanks for feedback about formatting. Indeed, we changed the blog theme around 2019, which broke formatting for blog posts written before. We’ve restored some, but not all. So, we have restored now formatting of this post. Also, there is an attachment at the end of the blog containing ansible roles for this lab, which you can download and use for your tests. Happy labbing, sir.
Best,
Anton