Dear friend,
After a bit of break caused by preparation to Kubernetes exams (we will continue blogs about Kubernetes as well) we are getting back to network and network automation topics. One of the interesting things, which is gradually emerging these days, is the possibility to manage multiple aspects of network devices (not only configuration or collection of operational data), such us issuing ping/traceroute checks, copying file, etc in a model-drive way (i.e., NETCONF, RESTCONF, GNMI with YANG). Today we are going to look into such a topic.
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.
Is that About Model-Driven Automation?
It is exactly that. NETCONF/YANG all the things, my friend! Usage of model-driven network automation significantly improves the stability and manageability of the network due to much simpler way to perform all the operations remotely. You don’t need to scrape and parse CLI anymore; instead, you interact with network devices via programmable API, what makes it possible to integrate them with your wide network management framework.
Therefore, enroll to our Network Automation Trainings and get ready for the real-world networking challenges:
We offer the following training programs for you:
- Zero-to-Hero Network Automation Training
- High-scale automation with Nornir
- Ansible Automation Orchestration with Ansble Tower / AWX
- Expert-level training: Closed-loop Automation and Next-generation Monitoring
During these trainings you will learn the following topics:
- Success and failure strategies to build the automation tools.
- Principles of software developments and the most useful and convenient tools.
- Data encoding (free-text, XML, JSON, YAML, Protobuf).
- Model-driven network automation with YANG, NETCONF, RESTCONF, GNMI.
- Full configuration templating with Jinja2 based on the source of truth (NetBox).
- Best programming languages (Python, Bash) for developing automation
- The most rock-solid and functional tools for configuration management (Ansible) and Python-based automation frameworks (Nornir).
- Network automation infrastructure (Linux, Linux networking, KVM, Docker).
- Orchestration of automation workflows with AWX and its integration with NetBox, GitHub, as well as custom execution environments for better scalability.
- Collection network data via SNMP and streaming telemetry with Prometheus
- Building API gateways with Python leveraging Fast API
- Integration of alerting with Slack and your own APIs
- … and many more
Moreover, we put all mentions technologies in the context of real use cases, which our team has solved and are solving in various projects in the service providers, enterprise and data centre networks and systems across the Europe and USA. That gives you opportunity to ask questions to understand the solutions in-depts and have discussions about your own projects. And on top of that, each technology is provided with online demos and labs to master your skills thoroughly. Such a mixture creates a unique learning environment, which all students value so much. Join us and unleash your potential.
Brief Description
Traditionally, NETCONF/YANG is used to configure network devices in model-driven way, where the configuration is represented as a hierarchical tree of key-value pairs. Despite this approach exists for a while, it is still just getting popular as a mainstream operational models across network operators.
Besides configuration of network devices, NETCONF/YANG also traditionally provided possibility to retrieve in the same hierarchical key-value approach the configuration and operational data from network devices. In certain scenarios, the YANG modules (i.e., the data model or data schema) are the same both for configuration and operational modules, which eases the process of comparing the intended state (configured one) against the real configured state (operational one).
Enroll to our Zero-to-Hero Network Automation Training to master NETCONF/YANG.
What was not explicitly defined in the original NETCONF framework is how to perform various operational commands in the model-driven way. The examples of such operational commands include (but not limited to) the following items:
- Verify reachability of some distant host (ping)
- Verify the path to some distant host (traceroute)
- Generate SSH keys/SSL certificates
- Operate with files (copy, delete, modify, etc)
- Reboot the device
- and many more
If you look in the main RFC describing NETCONF, RFC 6242, you won’t find any message type, which defines how to perform the aforementioned actions.
At the same time, NETCONF, in a nutshell, is an RPC framework using SSH transport with XML encoding. As it is an RPC framework, it is possible to define certain functionality in addition to core NETCONF message types. In fact, that is what Juniper did. Their NETCONF implementation, at least in 19* train, is very specific and doesn’t include any core NETCONF calls (that’s why it is that difficult to operate Juniper via NETCONF) and fully relies on their own calls wrapped inside the NETCONF RPC.
So, folks from Nokia thought, why not to model all the operational commands in the YANG modules and not to rely on the same approach, where we would send the XML-formatted data over the NETCONF RPC not putting the message inside any existing core calls (e.g., edit-config, get, get-config, and so on). Let’s take a look how this approach works and whether you can make use of it.
Practice
Operational YANG modules
The operational YANG modules in Nokia is part of the general YANG modules pack, which you can retrieve from their GitHub repository. Clone them to your local host:
1 $ git clone https://github.com/nokia/7x50_YangModels.git
Enroll to Zero-to-Hero Network Automation Training to master Git and GitHub skills.
Navigate inside the downloaded directory, choose the version of Nokia SR OS you run and look for files having oper as part of its name:
1
2
3
4
5 $ cd 7x50_YangModels/latest_sros_22.10/
$ ls -l | grep -i '\-oper\-'
-rw-r--r-- 1 root root 61381 Nov 20 16:29 nokia-oper-admin.yang
-rw-r--r-- 1 root root 36499 Nov 20 16:29 nokia-oper-file.yang
-rw-r--r-- 1 root root 75604 Nov 20 17:12 nokia-oper-global.yang
As you can see, there are three files, each is covering some different aspect of operation. We’ll focus today on the network verification commands, such as ping and traceroute. This functionality is covered by the YANG modules named nokia-oper-global:
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 $ pyang -f tree -p . nokia-oper-global.yang
module: nokia-oper-global
+--ro global-operations
+---x md-cli-raw-command
| +---w input
| | +---w md-cli-input-line string
| +--ro output
| +--ro operation-id? types-operation:operation-id
| +--ro start-time? types-operation:operation-timestamp
| +--ro results-path? types-operation:operation-path
| +--ro results
| | +--ro md-cli-output-block? string
| +--ro status? types-operation:operation-status
| +--ro error-message* types-operation:operation-message
| +--ro warning-message* types-operation:operation-message
| +--ro info-message* types-operation:operation-message
| +--ro end-time? types-operation:operation-timestamp
+---x md-compare
| +---w input
| | +---w path
| | | +---w (path-type)?
| | | +--:(subtree-path)
| | | +---w subtree-path? <anyxml>
| | +---w configuration-region? types-sros:configuration-region
| | +---w source
| | | +---w (destination-type)?
| | | +--:(candidate)
| | | | +---w candidate? empty
| | | +--:(baseline)
| | | | +---w baseline? empty
| | | +--:(url)
| | | | +---w url? string
| | | +--:(running)
| | | | +---w running? empty
| | | +--:(startup)
| | | | +---w startup? empty
| | | +--:(booted)
| | | | +---w booted? empty
| | | +--:(rollback)
| | | +---w rollback
| | | +---w checkpoint-id? uint32
| | +---w destination
| | | +---w (destination-type)?
| | | +--:(candidate)
| | | | +---w candidate? empty
| | | +--:(baseline)
| | | | +---w baseline? empty
| | | +--:(url)
| | | | +---w url? string
| | | +--:(running)
| | | | +---w running? empty
| | | +--:(startup)
| | | | +---w startup? empty
| | | +--:(booted)
| | | | +---w booted? empty
| | | +--:(rollback)
| | | +---w rollback
| | | +---w checkpoint-id? uint32
| | +---w format? enumeration
| +--ro output
| +--ro operation-id? types-operation:operation-id
| +--ro start-time? types-operation:operation-timestamp
| +--ro results-path? types-operation:operation-path
| +--ro results
| | +--ro md-compare-output? string
| +--ro status? types-operation:operation-status
| +--ro error-message* types-operation:operation-message
| +--ro warning-message* types-operation:operation-message
| +--ro info-message* types-operation:operation-message
| +--ro end-time? types-operation:operation-timestamp
+--ro oam
| +--ro eth-cfm
| | +---x linktrace
| | | +---w input
| | | | +---w asynchronous? types-operation:operation-asynchronous
| | | | +---w retention-timeout? types-operation:operation-timeout
| | | | +---w destination union
| | | | +---w md-admin-name -> /state:state/eth-cfm/domain/md-admin-name
| | | | +---w ma-admin-name -> /state:state/eth-cfm/domain[state:md-admin-name=current()/../md-admin-name]/association/ma-admin-name
| | | | +---w mep-id types-eth-cfm:mep-id-type
| | | | +---w ttl? uint32
| | | +--ro output
| | | +--ro operation-id? types-operation:operation-id
| | | +--ro start-time? types-operation:operation-timestamp
| | | +--ro results-path? types-operation:operation-path
| | | +--ro status? types-operation:operation-status
| | | +--ro error-message* types-operation:operation-message
| | | +--ro warning-message* types-operation:operation-message
| | | +--ro info-message* types-operation:operation-message
| | | +--ro end-time? types-operation:operation-timestamp
| | +---x loopback
| | +---w input
| | | +---w asynchronous? types-operation:operation-asynchronous
| | | +---w execution-timeout? types-operation:operation-timeout
| | | +---w retention-timeout? types-operation:operation-timeout
| | | +---w destination union
| | | +---w md-admin-name -> /state:state/eth-cfm/domain/md-admin-name
| | | +---w ma-admin-name -> /state:state/eth-cfm/domain[state:md-admin-name=current()/../md-admin-name]/association/ma-admin-name
| | | +---w mep-id types-eth-cfm:mep-id-type
| | | +---w send-count? int32
| | | +---w size? uint32
| | | +---w priority? int32
| | | +---w lbm-padding? uint32
| | | +---w timeout? uint32
| | | +---w interval? uint32
| | +--ro output
| | +--ro operation-id? types-operation:operation-id
| | +--ro start-time? types-operation:operation-timestamp
| | +--ro results-path? types-operation:operation-path
| | +--ro status? types-operation:operation-status
| | +--ro error-message* types-operation:operation-message
| | +--ro warning-message* types-operation:operation-message
| | +--ro info-message* types-operation:operation-message
| | +--ro end-time? types-operation:operation-timestamp
| +--ro saa
| | +--ro owner* [owner-name test]
| | +--ro owner-name -> /state:state/saa/owner[state:test=current()/../../owner/test]/owner-name
| | +--ro test -> /state:state/saa/owner[state:owner-name=current()/../../owner/owner-name]/test
| | +---x start
| | | +---w input
| | | | +---w accounting? boolean
| | | +--ro output
| | | +--ro operation-id? types-operation:operation-id
| | | +--ro start-time? types-operation:operation-timestamp
| | | +--ro results-path? types-operation:operation-path
| | | +--ro status? types-operation:operation-status
| | | +--ro error-message* types-operation:operation-message
| | | +--ro warning-message* types-operation:operation-message
| | | +--ro info-message* types-operation:operation-message
| | | +--ro end-time? types-operation:operation-timestamp
| | +---x stop
| | +---w input
| | | +---w accounting? boolean
| | +--ro output
| | +--ro operation-id? types-operation:operation-id
| | +--ro start-time? types-operation:operation-timestamp
| | +--ro results-path? types-operation:operation-path
| | +--ro status? types-operation:operation-status
| | +--ro error-message* types-operation:operation-message
| | +--ro warning-message* types-operation:operation-message
| | +--ro info-message* types-operation:operation-message
| | +--ro end-time? types-operation:operation-timestamp
| +--ro service-activation-testhead
| +--ro service-test* [service-test-name]
| +--ro service-test-name -> /state:state/test-oam/service-activation-testhead/service-test/test-name
| +---x start
| | +--ro output
| | +--ro operation-id? types-operation:operation-id
| | +--ro start-time? types-operation:operation-timestamp
| | +--ro results-path? types-operation:operation-path
| | +--ro status? types-operation:operation-status
| | +--ro error-message* types-operation:operation-message
| | +--ro warning-message* types-operation:operation-message
| | +--ro info-message* types-operation:operation-message
| | +--ro end-time? types-operation:operation-timestamp
| +---x stop
| +--ro output
| +--ro operation-id? types-operation:operation-id
| +--ro start-time? types-operation:operation-timestamp
| +--ro results-path? types-operation:operation-path
| +--ro status? types-operation:operation-status
| +--ro error-message* types-operation:operation-message
| +--ro warning-message* types-operation:operation-message
| +--ro info-message* types-operation:operation-message
| +--ro end-time? types-operation:operation-timestamp
+---x ping
| +---w input
| | +---w destination union
| | +---w (routing-options)?
| | | +--:(case-bypass-routing)
| | | | +---w bypass-routing? empty
| | | +--:(case-interface)
| | | | +---w interface? types-sros:interface-name
| | | +--:(case-next-hop)
| | | | +---w next-hop-address? types-sros:ip-address
| | | +--:(case-subscriber)
| | | +---w subscriber? types-submgt:subscriber-id
| | +---w count? uint32
| | +---w output-format? enumeration
| | +---w do-not-fragment? empty
| | +---w fc? types-sros:fc-name
| | +---w interval? union
| | +---w pattern? union
| | +---w router-instance? string
| | +---w size? uint32
| | +---w source-address? types-sros:ip-address
| | +---w timeout? uint32
| | +---w tos? uint32
| | +---w ttl? uint32
| +--ro output
| +--ro operation-id? types-operation:operation-id
| +--ro start-time? types-operation:operation-timestamp
| +--ro results-path? types-operation:operation-path
| +--ro results
| | +--ro test-parameters
| | | +--ro destination? union
| | | +--ro (routing-options)?
| | | | +--:(case-bypass-routing)
| | | | | +--ro bypass-routing? boolean
| | | | +--:(case-interface)
| | | | | +--ro interface? types-sros:interface-name
| | | | +--:(case-next-hop)
| | | | | +--ro next-hop-address? types-sros:ip-address
| | | | +--:(case-subscriber)
| | | | +--ro subscriber? types-submgt:subscriber-id
| | | +--ro count? uint32
| | | +--ro output-format? enumeration
| | | +--ro do-not-fragment? boolean
| | | +--ro fc? types-sros:fc-name
| | | +--ro interval? union
| | | +--ro pattern? union
| | | +--ro router-instance? types-sros:router-instance-base-management-vprn-loose
| | | +--ro size? uint32
| | | +--ro source-address? types-sros:ip-address
| | | +--ro timeout? uint32
| | | +--ro tos? uint32
| | | +--ro ttl? uint32
| | +--ro probe* [probe-index]
| | | +--ro probe-index uint32
| | | +--ro status? types-oam:response-status
| | | +--ro round-trip-time? uint32
| | | +--ro response-packet
| | | +--ro size? uint32
| | | +--ro source-address? types-sros:ip-address-with-zone
| | | +--ro icmp-sequence-number? uint32
| | | +--ro ttl? uint32
| | +--ro summary
| | +--ro statistics
| | +--ro packets
| | | +--ro sent? uint32
| | | +--ro received? uint32
| | | +--ro loss? decimal64
| | +--ro round-trip-time
| | +--ro minimum? uint32
| | +--ro average? uint32
| | +--ro maximum? uint32
| | +--ro standard-deviation? uint32
| +--ro status? types-operation:operation-status
| +--ro error-message* types-operation:operation-message
| +--ro warning-message* types-operation:operation-message
| +--ro info-message* types-operation:operation-message
| +--ro end-time? types-operation:operation-timestamp
+---x traceroute
+---w input
| +---w destination union
| +---w decode? enumeration
| +---w dest-port? uint32
| +---w dest-port-udp-fixed? empty
| +---w detail? empty
| +---w min-ttl? uint32
| +---w ttl? uint32
| +---w numeric? empty
| +---w probe-count? uint32
| +---w protocol? enumeration
| +---w router-instance? string
| +---w size? uint32
| +---w source-address? types-sros:ip-address
| +---w tos? uint32
| +---w wait? uint32
+--ro output
+--ro operation-id? types-operation:operation-id
+--ro start-time? types-operation:operation-timestamp
+--ro results-path? types-operation:operation-path
+--ro results
| +--ro test-parameters
| | +--ro destination? union
| | +--ro decode? enumeration
| | +--ro dest-port? uint32
| | +--ro dest-port-udp-fixed? boolean
| | +--ro detail? boolean
| | +--ro min-ttl? uint32
| | +--ro ttl? uint32
| | +--ro numeric? boolean
| | +--ro probe-count? uint32
| | +--ro protocol? enumeration
| | +--ro router-instance? types-sros:router-instance-base-management-vprn-loose
| | +--ro size? uint32
| | +--ro source-address? types-sros:ip-address
| | +--ro tos? uint32
| | +--ro wait? uint32
| +--ro hop* [hop-index]
| +--ro hop-index uint32
| +--ro probe* [probe-index]
| +--ro probe-index uint32
| +--ro status? types-oam:response-status
| +--ro round-trip-time? uint32
| +--ro size? uint32
| +--ro response-packet
| +--ro icmp-type? uint32
| +--ro icmp-code? uint32
| +--ro mtu-exceeded? uint32
| +--ro source-address? types-sros:ip-address
| +--ro source-host-name? string
| +--ro tcp-port-status? enumeration
| +--ro mpls-label-stack-entry* [index]
| | +--ro index uint32
| | +--ro label? types-sros:mpls-label-full-range
| | +--ro traffic-class? uint32
| | +--ro bottom-of-stack? uint32
| | +--ro ttl? uint32
| +--ro original-datagram
| +--ro header* [header-index]
| +--ro header-index uint32
| +--ro (header-type-choice)?
| +--:(ipv6-case)
| | +--ro ipv6-header
| | +--ro destination-address? types-sros:ip-address
| | +--ro dscp? types-sros:named-item-or-empty
| | +--ro hop-limit? uint32
| | +--ro source-address? types-sros:ip-address
| +--:(srv6-case)
| +--ro srv6-header
| +--ro segments-left? uint32
| +--ro segment-list* [segment-index]
| +--ro segment-index uint32
| +--ro segment-address? types-sros:ip-address
+--ro status? types-operation:operation-status
+--ro error-message* types-operation:operation-message
+--ro warning-message* types-operation:operation-message
+--ro info-message* types-operation:operation-message
+--ro end-time? types-operation:operation-timestamp
Enroll to Zero-to-Hero Network Automation Training to learn how use pyang and pyangbind.
In the snippet above you can see multiple entries, including the execution of the raw md-cli commands, oam, ping and traceroute.
For majority of operations, there are two containers inside:
- input, which contains the parameters your message shall include
- output, which contains the parameters you would expect to receive
There is though one caveat with the way how you should read the tree. Normally, you would include each container name and key in your message. For examples such tree:
1
2
3 +---x ping
+---w input
+---w destination union
Would result in the following XML message:
1
2
3
4
5 <ping>
<input>
<destination>1.1.1.1</destination>
</input>
</ping>
However, it is not the case here. They way you construct the message shall NOT include the name of the container “input” and shall look like as follows:
1
2
3 <ping>
<destination>1.1.1.1</destination>
</ping>
Picture costs 1000 words, and each theory piece requires a good example. Let’s put this operational YANG modules to motion over NETCONF to Nokia SR OS.
Lab Setup
We’ll use the following simple lab to demonstrate how operational YANG modules for NETCONF works:
Our lab consists of two network devices:
- Nokir SR OS based virtual router runing SR OS 22.10.R1
- Cisco IOS XR virtual router running IOS XR 6.5.1
In addition to that we have our automation host running Debian Linux, which also have Python 3.10 and the latest version of ncclient library installed.
Manual NETCONF Interaction
First of all, let’s connect to the device directly via CLI:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 $ ssh admin@192.168.101.11 -p 830 -s netconf
admin@192.168.101.11's password:
<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.0</capability>
<capability>urn:ietf:params:netconf:base:1.1</capability>
!
! OUTPUT IS TRUNCATED FOR BREVITY
!
<capability>urn:ietf:params:xml:ns:yang:1?module=yang&amp;revision=2017-02-20</capability>
</capabilities>
<session-id>88</session-id>
</hello>
]]>]]>
Send the NETCONF hello message back to establish the session:
1
2
3
4
5
6
7 <?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.0</capability>
</capabilities>
</hello>
]]>]]>
Enroll to Zero-to-Hero Network Automation Training to master NETCONF skills via CLI directly and via Ansible and Python.
Once the session is established and capabilities are agreed, you can request a ping operation via NETCONF. To build the message, use the YANG moule mentioned before, wrapping the top level container of the nokia-oper-global YANG module isnide the action key, which belings to urn:ietf:params:xml:ns:netconf:base:1.0 XML namespace:
1
2
3
4
5
6
7
8
9
10
11
12 <?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<action xmlns="urn:ietf:params:xml:ns:yang:1">
<global-operations xmlns="urn:nokia.com:sros:ns:yang:sr:oper-global">
<ping>
<destination>10.0.255.22</destination>
<count>3</count>
</ping>
</global-operations>
</action>
</rpc>
]]>]]>
it will take a few moments for request to complete, though you will see the incremental updates:
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 <?xml version="1.0" encoding="UTF-8"?>
<rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokiaoper="urn:nokia.com:sros:ns:yang:sr:oper-global">
<nokiaoper:operation-id>45</nokiaoper:operation-id>
<nokiaoper:start-time>2022-11-27T15:17:16.7Z</nokiaoper:start-time>
<nokiaoper:results>
<nokiaoper:test-parameters>
<nokiaoper:destination>10.0.255.22</nokiaoper:destination>
<nokiaoper:count>3</nokiaoper:count>
<nokiaoper:output-format>detail</nokiaoper:output-format>
<nokiaoper:do-not-fragment>false</nokiaoper:do-not-fragment>
<nokiaoper:fc>nc</nokiaoper:fc>
<nokiaoper:interval>1</nokiaoper:interval>
<nokiaoper:pattern>sequential</nokiaoper:pattern>
<nokiaoper:router-instance>Base</nokiaoper:router-instance>
<nokiaoper:size>56</nokiaoper:size>
<nokiaoper:timeout>5</nokiaoper:timeout>
<nokiaoper:tos>0</nokiaoper:tos>
<nokiaoper:ttl>64</nokiaoper:ttl>
</nokiaoper:test-parameters>
<nokiaoper:probe>
<nokiaoper:probe-index>1</nokiaoper:probe-index>
<nokiaoper:status>response-received</nokiaoper:status>
<nokiaoper:round-trip-time>4694</nokiaoper:round-trip-time>
<nokiaoper:response-packet>
<nokiaoper:size>64</nokiaoper:size>
<nokiaoper:source-address>10.0.255.22</nokiaoper:source-address>
<nokiaoper:icmp-sequence-number>1</nokiaoper:icmp-sequence-number>
<nokiaoper:ttl>255</nokiaoper:ttl>
</nokiaoper:response-packet>
</nokiaoper:probe>
<nokiaoper:probe>
<nokiaoper:probe-index>2</nokiaoper:probe-index>
<nokiaoper:status>response-received</nokiaoper:status>
<nokiaoper:round-trip-time>1701</nokiaoper:round-trip-time>
<nokiaoper:response-packet>
<nokiaoper:size>64</nokiaoper:size>
<nokiaoper:source-address>10.0.255.22</nokiaoper:source-address>
<nokiaoper:icmp-sequence-number>2</nokiaoper:icmp-sequence-number>
<nokiaoper:ttl>255</nokiaoper:ttl>
</nokiaoper:response-packet>
</nokiaoper:probe>
<nokiaoper:probe>
<nokiaoper:probe-index>3</nokiaoper:probe-index>
<nokiaoper:status>response-received</nokiaoper:status>
<nokiaoper:round-trip-time>2103</nokiaoper:round-trip-time>
<nokiaoper:response-packet>
<nokiaoper:size>64</nokiaoper:size>
<nokiaoper:source-address>10.0.255.22</nokiaoper:source-address>
<nokiaoper:icmp-sequence-number>3</nokiaoper:icmp-sequence-number>
<nokiaoper:ttl>255</nokiaoper:ttl>
</nokiaoper:response-packet>
</nokiaoper:probe>
<nokiaoper:summary>
<nokiaoper:statistics>
<nokiaoper:packets>
<nokiaoper:sent>3</nokiaoper:sent>
<nokiaoper:received>3</nokiaoper:received>
<nokiaoper:loss>0.0</nokiaoper:loss>
</nokiaoper:packets>
<nokiaoper:round-trip-time>
<nokiaoper:minimum>1701</nokiaoper:minimum>
<nokiaoper:average>2832</nokiaoper:average>
<nokiaoper:maximum>4694</nokiaoper:maximum>
<nokiaoper:standard-deviation>1326</nokiaoper:standard-deviation>
</nokiaoper:round-trip-time>
</nokiaoper:statistics>
</nokiaoper:summary>
</nokiaoper:results>
<nokiaoper:status>completed</nokiaoper:status>
<nokiaoper:end-time>2022-11-27T15:17:18.9Z</nokiaoper:end-time>
</rpc-reply>
]]>]]>
Spend some time looking through the provided output. It contains two major parts:
- probe, which includes the details information about each issued ping
- summary, which includes the aggregated information about the completed ping operation
The amount of the information you see exceeds the one you would normally see in the CLI output. Let’s go through the process:
- You send via NETCONF an action request instructing the Nokia SR OS device to reach the destination IP address 10.0.255.22. We don’t sepcify extra parameters, such as source IP address, specific VPRN/VRF, DF-bit, etc; therefore, all of them are set to their default values. The only parameter we specify besides the target IP is the count of the ICMP packets we want to send to the destination.
- Nokia SR OS router recieves the packet and starts the ping operation.
- As soon as the first packet is received, the reponse is sent to you over the NETCONF channel.
- Such incremental repsoneses are sent as soon as each response is received in amount of the specified count of the probes.
- Once the last probe response is received and sent back you, you will also receives a summary satetment of the ping operation.
This is so-called sequential operation; however, Nokia SR OS supports also asynchronous one, which will be covered in a separate blog post.
Python Tool to Interact with Nokia SR OS
After we’ve got some experience with NETCONF and operation YANG modules over CLI, it is a time to switch to a tool development. The main benefit of NETCONF is to give opportunity for network devices to be managed in a deterministic way as part of bigger IT workflows, which are typically based on top of some software components. Therefore, we will create some software in Python, which will be able to interact with Nokia SR OS via NETCONF.
Join Network Automation Traning to learn Python with focus on Network Automation from Zero to Hero.
We’ll create a very simple demo code, which gives you idea how to build it in your environment. We are going use the following Python libraries:
- pyyaml to import the inventory data with network devices
- xmltodict to convert Python dictioanry to XML and vice versa
- ncclient to interact with network device using NETCONF
The inventory file is very simple:
1
2
3 $ cat app/inventory.yaml
---
- hostname: 192.168.101.11
For credentails, we use Linux environment variables:
1
2 $ export AUTOMATION_USER="admin"
$ export AUTOMATION_PASS="admin"
Join Zero-to-Hero Network Automation Training to learn basics of Linux administration
Now it is time to look into Python script, which utilizes NETCONF with operational YANG 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
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 """
Sample admin operation via NETCONF
"""
# Modules
import os
import yaml
import xmltodict
from ncclient import manager
from ncclient.xml_ import to_ele
# Statics
PATH_INVENTORY = "./inventory.yaml"
KIND_CREDS = "env"
# Functions
def get_inventory(path: str) -> list:
"""
High-level function to get inventory
"""
with open(file=path, mode="rt", encoding="utf-8") as filename:
return yaml.load(stream=filename.read(), Loader=yaml.FullLoader)
def get_credentials(kind: str) -> tuple:
"""
High-level function to get credentials
"""
creds_fun_dict = {
"env": _get_env_credentials,
}
return creds_fun_dict[kind]()
def _get_env_credentials() -> tuple:
"""
Private function to get credentials from variables
"""
return os.getenv(key="AUTOMATION_USER"), os.getenv(key="AUTOMATION_PASS")
def _print_result(nested_data, indent: int = 0) -> None:
"""
Private function to print result
"""
indent += 2
for key, value in nested_data.items():
if isinstance(value, str):
print(" " * indent + key.split(":")[-1] + ": " + value)
else:
print(" " * indent + key.split(":")[-1] + ":")
_print_result(indent=indent, nested_data=value)
# Body
if __name__ == "__main__":
# Get inventory
inventory = get_inventory(path=PATH_INVENTORY)
# Get credentails
creds = get_credentials(kind="env")
# Perform ping
for host in inventory:
with manager.connect(
host=host["hostname"],
username=creds[0],
password=creds[1],
device_params={"name": "sros"},
) as nco:
# Check if device supports ping
match_ping_module = False
for netconf_module in nco.server_capabilities:
if netconf_module.find("oper-global") >= 0:
match_ping_module = True
if match_ping_module:
print(f"Device {host['hostname']} supports ping via NETCONF")
# Prepare ping body
message_dict = {
"action": {
"@xmlns": "urn:ietf:params:xml:ns:yang:1",
"global-operations": {
"@xmlns": "urn:nokia.com:sros:ns:yang:sr:oper-global",
"ping": {"destination": "10.0.255.22", "count": 3},
},
}
}
message_xml = xmltodict.unparse(input_dict=message_dict)
message_xml = message_xml.split("\n")[-1]
ping_result = nco.dispatch(to_ele(message_xml))
parsed_result = xmltodict.parse(xml_input=str(ping_result))
if parsed_result:
terminal_length = os.get_terminal_size().columns
print("=" * terminal_length)
print(f"Reachability to {host['hostname']}")
print("=" * terminal_length)
_print_result(
nested_data=parsed_result["rpc-reply"]["nokiaoper:results"][
"nokiaoper:summary"
]
)
else:
print(f"Device {host['hostname']} DOES NOT support ping via NETCONF")
exit("1")
All the Python functions have descriptions explaining thier purpose, but we would elaborate a but more on them:
- Function get_inventory() reads the inventory from YAML file and coverts it from YAML format into Python dictionary.
- Python function get_credentails() gets the argument, where to take credentials from and calls private functions. In our case we use credentails from Linux enviroment, which are obtained via the private function _get_env_credentials().
- Recursive function _print_result() aims to covenrt the Python dictionary to indentent multiline string and print it into CLI to simplify the understanding of the result. We could have used pprint(), but our provide an output tailored to our needs.
- Within the body, the key component is the class manager() imported from ncclient module. Using the method manager.connect() we creates a NETCONF connection, which is used to interact with Nokia SR OS network device. To send the arbitrary RPCs we use the dispatch() method, which requires the XML element object as an input, which is produced by the to_ele() function imported from ncclient module as well
- Using the parse() and unparse() functions from xmltodict module we convert the XML string to Python dictionary and back. It has a nice and easy way to converty Python dictionaries’ key-value pairs to XML attribute if the name of the key starts with @ at.
At our Zero-To-Hero Network Automation Training you will learn both Python and NETCONF.
Let’s see how the tool works:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 $ python main.py
Device 192.168.101.11 supports ping via NETCONF
============================================================================
Reachability to 192.168.101.11
============================================================================
statistics:
packets:
sent: 3
received: 3
loss: 0.0
round-trip-time:
minimum: 1768
average: 3715
maximum: 7528
standard-deviation: 2696
As you could see, the ping verification requested via NETCONF in operational YANG module was successfully executed and you have obtained the result.
GitHub Repository
Check out our GitHub repository to get this and further examples.
Lessons Learned
The two main gotchas we’ve got during composing this blogpost:
- It took us a bit to figure out how to properly compose the request message for action opertation in NETCONF. Iinitally we have created the message including input key, but it was not working. So we tried different scenarious untill we get to the point that we shall skip it entirely.
- We originall started writing the blog using scrapli-netconf library. Unfortunatelly, since the latest release 2022-07-30 it has a few issues working with Nokia SR OS routers; therefore, we were not able to complete the tool and we have rewritten that with ncclient.
Summary
Possibility to perform operational activities with NETCONF in addition to configuration/data collection activities truly enables network devices for model-driven automation. The gap in terms of capabiltities between CLI and non-CLI way to interact with network devices is finally bridged entirely with non-CLI (i.e., NETCONF/YANG) becoming even more functional. In the upcoming blogposts we’ll cover asynchronuous operational activities via NETCONF with Nokia SR OS router. Take care and good bye!
Need Help? Contract Us
If you need a trusted and experienced partner to automate your network and IT infrastructure, get in touch with us.
P.S.
If you have further questions or you need help with your networks, we are happy to assist you, just send us a message. Also don’t forget to share the article on your social media, if you like it.
BR,
Anton Karneliuk