Site icon Karneliuk

Automation 17. Using Operational Commands via NETCONF at Nokia SR OS in Sequential Mode

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:

During these trainings you will learn the following topics:

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.

Start your automation training today.

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:

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:

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:

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&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:

The amount of the information you see exceeds the one you would normally see in the CLI output. Let’s go through the process:

  1. 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.
  2. Nokia SR OS router recieves the packet and starts the ping operation.
  3. As soon as the first packet is received, the reponse is sent to you over the NETCONF channel.
  4. Such incremental repsoneses are sent as soon as each response is received in amount of the specified count of the probes.
  5. 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:

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:

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:

  1. 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.
  2. 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

Exit mobile version