|
| 1 | +--- |
| 2 | +published: false |
| 3 | +date: '2016-08-08 14:26 -0600' |
| 4 | +title: '' |
| 5 | +author: Shelly Cadora |
| 6 | +--- |
| 7 | +## Configuring Model-Driven Telemetry with YDK |
| 8 | + |
| 9 | +## Model-Driven Configuration for Model-Driven Telemetry |
| 10 | + |
| 11 | +In an [earlier tutorial](https://xrdocs.github.io/telemetry/tutorials/2016-07-25-configuring-model-driven-telemetry-mdt-with-yang/), I wrote about how to configure MDT using the OpenConfig Telemetry YANG model using [ncclient](https://github.com/ncclient/ncclient) and a lot of XML. An even simpler way to do this is to use [YDK](https://developer.cisco.com/site/ydk/), a developer toolkit that automatically generates APIs directly from YANG models. The Python classes that are generated by YDK mirror the YANG model hierarchy. So if you know some Python and you understand the YANG model, you can start writing code, no knowledge of NETCONF or XML required. |
| 12 | + |
| 13 | +## The OC Telemetry Model |
| 14 | + |
| 15 | +Let's start by reviewing the struture of the [OpenConfig telemetry model](https://github.com/openconfig/public/blob/master/release/models/telemetry/openconfig-telemetry.yang). |
| 16 | + |
| 17 | +{% capture "output" %} |
| 18 | +Script Output: |
| 19 | + |
| 20 | +``` |
| 21 | +module: openconfig-telemetry |
| 22 | + +--rw telemetry-system |
| 23 | + +--rw sensor-groups |
| 24 | + | +--rw sensor-group* [sensor-group-id] |
| 25 | + | +--rw sensor-group-id -> ../config/sensor-group-id |
| 26 | + | +--rw config |
| 27 | + | | +--rw sensor-group-id? string |
| 28 | + | +--ro state |
| 29 | + | | +--ro sensor-group-id? string |
| 30 | + | +--rw sensor-paths |
| 31 | + | +--rw sensor-path* [path] |
| 32 | + | +--rw path -> ../config/path |
| 33 | + | +--rw config |
| 34 | + | | +--rw path? string |
| 35 | + | | +--rw exclude-filter? string |
| 36 | + | +--ro state |
| 37 | + | +--ro path? string |
| 38 | + | +--ro exclude-filter? string |
| 39 | + +--rw destination-groups |
| 40 | + | +--rw destination-group* [group-id] |
| 41 | + | +--rw group-id -> ../config/group-id |
| 42 | + | +--rw config |
| 43 | + | | +--rw group-id? string |
| 44 | + | +--ro state |
| 45 | + | | +--ro group-id? string |
| 46 | + | +--rw destinations |
| 47 | + | +--rw destination* [destination-address destination-port] |
| 48 | + | +--rw destination-address -> ../config/destination-address |
| 49 | + | +--rw destination-port -> ../config/destination-port |
| 50 | + | +--rw config |
| 51 | + | | +--rw destination-address? inet:ip-address |
| 52 | + | | +--rw destination-port? uint16 |
| 53 | + | | +--rw destination-protocol? telemetry-stream-protocol |
| 54 | + | +--ro state |
| 55 | + | +--ro destination-address? inet:ip-address |
| 56 | + | +--ro destination-port? uint16 |
| 57 | + | +--ro destination-protocol? telemetry-stream-protocol |
| 58 | + +--rw subscriptions |
| 59 | + +--rw persistent |
| 60 | + | +--rw subscription* [subscription-id] |
| 61 | + | +--rw subscription-id -> ../config/subscription-id |
| 62 | + | +--rw config |
| 63 | + | | +--rw subscription-id? uint64 |
| 64 | + | | +--rw local-source-address? inet:ip-address |
| 65 | + | | +--rw originated-qos-marking? inet:dscp |
| 66 | + | +--ro state |
| 67 | + | | +--ro subscription-id? uint64 |
| 68 | + | | +--ro local-source-address? inet:ip-address |
| 69 | + | | +--ro originated-qos-marking? inet:dscp |
| 70 | + | +--rw sensor-profiles |
| 71 | + | | +--rw sensor-profile* [sensor-group] |
| 72 | + | | +--rw sensor-group -> ../config/sensor-group |
| 73 | + | | +--rw config |
| 74 | + | | | +--rw sensor-group? -> /telemetry-system/sensor-groups/sensor-group/config/sensor-group-id |
| 75 | + | | | +--rw sample-interval? uint64 |
| 76 | + | | | +--rw heartbeat-interval? uint64 |
| 77 | + | | | +--rw suppress-redundant? boolean |
| 78 | + | | +--ro state |
| 79 | + | | +--ro sensor-group? -> /telemetry-system/sensor-groups/sensor-group/config/sensor-group-id |
| 80 | + | | +--ro sample-interval? uint64 |
| 81 | + | | +--ro heartbeat-interval? uint64 |
| 82 | + | | +--ro suppress-redundant? boolean |
| 83 | + | +--rw destination-groups |
| 84 | + | +--rw destination-group* [group-id] |
| 85 | + | +--rw group-id -> ../config/group-id |
| 86 | + | +--rw config |
| 87 | + | | +--rw group-id? -> ../../../../../../../destination-groups/destination-group/group-id |
| 88 | + | +--rw state |
| 89 | + | +--rw group-id? -> ../../../../../../../destination-groups/destination-group/group-id |
| 90 | + +--rw dynamic |
| 91 | + +--ro subscription* [subscription-id] |
| 92 | + +--ro subscription-id -> ../state/subscription-id |
| 93 | + +--ro state |
| 94 | + | +--ro subscription-id? uint64 |
| 95 | + | +--ro destination-address? inet:ip-address |
| 96 | + | +--ro destination-port? uint16 |
| 97 | + | +--ro destination-protocol? telemetry-stream-protocol |
| 98 | + | +--ro sample-interval? uint64 |
| 99 | + | +--ro heartbeat-interval? uint64 |
| 100 | + | +--ro suppress-redundant? boolean |
| 101 | + | +--ro originated-qos-marking? inet:dscp |
| 102 | + +--ro sensor-paths |
| 103 | + +--ro sensor-path* [path] |
| 104 | + +--ro path -> ../state/path |
| 105 | + +--ro state |
| 106 | + +--ro path? string |
| 107 | + +--ro exclude-filter? string |
| 108 | +``` |
| 109 | +{% endcapture %} |
| 110 | + |
| 111 | +<div class="notice--warning"> |
| 112 | +{{ output | markdownify }} |
| 113 | +</div> |
| 114 | + |
| 115 | +You can spend a lot of time understanding the intricacies of YANG and all the details, but all we really need to know for now is that the model has three major sections: |
| 116 | + |
| 117 | +- The **destination-group** tells the router where to send telemetry data and how. Only needed for dial-out configuration. |
| 118 | +- The **sensor-group** identifies a list of YANG models that the router should stream. |
| 119 | +- The **subscription** ties together the destination-group and the sensor-group. |
| 120 | + |
| 121 | +## Configuring the Router for gRPC Dial-in |
| 122 | + |
| 123 | +YDK leverages ncclient to handle the NETCONF connection: |
| 124 | + |
| 125 | +```python |
| 126 | +from ydk.providers import NetconfServiceProvider |
| 127 | +from ydk.services import CRUDService |
| 128 | + |
| 129 | +HOST = '10.30.111.9' |
| 130 | +PORT = 830 |
| 131 | +USER = 'cisco' |
| 132 | +PASS = 'cisco' |
| 133 | + |
| 134 | +xr = NetconfServiceProvider(address=HOST, |
| 135 | + port=PORT, |
| 136 | + username=USER, |
| 137 | + password=PASS, |
| 138 | + protocol = 'ssh') |
| 139 | +``` |
| 140 | + |
| 141 | +And here's what we get: |
| 142 | + |
| 143 | +{% capture "output" %} |
| 144 | +Script Output: |
| 145 | + |
| 146 | +``` |
| 147 | +<?xml version="1.0"?> |
| 148 | +<rpc-reply message-id="urn:uuid:939c718e-81ee-43ec-9733-565aa53fedb2" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> |
| 149 | + <data> |
| 150 | + <telemetry-system xmlns="http://openconfig.net/yang/telemetry"> |
| 151 | + <sensor-groups> |
| 152 | + <sensor-group> |
| 153 | + <sensor-group-id>SGroup3</sensor-group-id> |
| 154 | + <config> |
| 155 | + <sensor-group-id>SGroup3</sensor-group-id> |
| 156 | + </config> |
| 157 | + <sensor-paths> |
| 158 | + <sensor-path> |
| 159 | + <path>openconfig-interfaces:interfaces%2finterface</path> |
| 160 | + <config> |
| 161 | + <path>openconfig-interfaces:interfaces%2finterface</path> |
| 162 | + </config> |
| 163 | + </sensor-path> |
| 164 | + </sensor-paths> |
| 165 | + </sensor-group> |
| 166 | + </sensor-groups> |
| 167 | + <subscriptions> |
| 168 | + <persistent> |
| 169 | + <subscription> |
| 170 | + <subscription-id>Sub3</subscription-id> |
| 171 | + <config> |
| 172 | + <subscription-id>Sub3</subscription-id> |
| 173 | + </config> |
| 174 | + <sensor-profiles> |
| 175 | + <sensor-profile> |
| 176 | + <sensor-group>SGroup3</sensor-group> |
| 177 | + <config> |
| 178 | + <sensor-group>SGroup3</sensor-group> |
| 179 | + <sample-interval>30000</sample-interval> |
| 180 | + </config> |
| 181 | + </sensor-profile> |
| 182 | + </sensor-profiles> |
| 183 | + </subscription> |
| 184 | + </persistent> |
| 185 | + </subscriptions> |
| 186 | + </telemetry-system> |
| 187 | + </data> |
| 188 | +</rpc-reply> |
| 189 | +
|
| 190 | +
|
| 191 | +``` |
| 192 | +{% endcapture %} |
| 193 | + |
| 194 | +<div class="notice--warning"> |
| 195 | +{{ output | markdownify }} |
| 196 | +</div> |
| 197 | + |
| 198 | +So what does all that mean to the router? It breaks down into three parts which you'll recall from the YANG model above: |
| 199 | + |
| 200 | +- The **destination-group** tells the router where to send telemetry data and how. The absence of a destination-group in the output above alerts us to the fact that this is a dial-in configuration (the collector will initiate the session to the router). |
| 201 | +- The **sensor-group** identifies a list of YANG models that the router should stream. In this case, the router has a sensor-group called "SGroup3" that will send interface statistics data from the OpenConfig Interfaces YANG model. |
| 202 | +- The **subscription** ties together the destination-group and the sensor-group. This router has a subscription name "Sub3" that will send the list of models in SGroup3 at an interval of 30 second (30000 milleseconds). |
| 203 | + |
| 204 | +If you read the [earlier tutorial](https://xrdocs.github.io/telemetry/tutorials/2016-07-21-configuring-model-driven-telemetry-mdt/) on configuring MDT with CLI, you might recognize this as the same as the gRPC dial-in configuration described there. If you missed that thrilling installment, the XML above is the YANG equivalent of this CLI: |
| 205 | + |
| 206 | +{% capture "output" %} |
| 207 | +CLI Output: |
| 208 | + |
| 209 | +``` |
| 210 | +telemetry model-driven |
| 211 | + sensor-group SGroup3 |
| 212 | + sensor-path openconfig-interfaces:interfaces/interface |
| 213 | + ! |
| 214 | + subscription Sub3 |
| 215 | + sensor-group-id SGroup3 sample-interval 30000 |
| 216 | + ! |
| 217 | +``` |
| 218 | + |
| 219 | +{% endcapture %} |
| 220 | + |
| 221 | +<div class="notice--info"> |
| 222 | +{{ output | markdownify }} |
| 223 | +</div> |
| 224 | + |
| 225 | +## Edit-Config |
| 226 | + |
| 227 | +So let's say we want to add a second model to SGroup3 (Cisco-IOS-XR-ipv4-arp-oper). We can do that with the following NETCONF operations: |
| 228 | + |
| 229 | +```python |
| 230 | +edit_data = ''' |
| 231 | +<config> |
| 232 | +<telemetry-system xmlns="http://openconfig.net/yang/telemetry"> |
| 233 | + <sensor-groups> |
| 234 | + <sensor-group> |
| 235 | + <sensor-group-id>SGroup3</sensor-group-id> |
| 236 | + <sensor-paths> |
| 237 | + <sensor-path> |
| 238 | + <config> |
| 239 | + <path>Cisco-IOS-XR-ipv4-arp-oper:arp%2fnodes%2fnode%2fentries%2fentry</path> |
| 240 | + </config> |
| 241 | + </sensor-path> |
| 242 | + </sensor-paths> |
| 243 | + </sensor-group> |
| 244 | + </sensor-groups> |
| 245 | +</config> |
| 246 | +''' |
| 247 | + |
| 248 | +xr.edit_config(edit_data, target='candidate', format='xml') |
| 249 | +xr.commit() |
| 250 | +``` |
| 251 | + |
| 252 | +If we do a get-config operation again: |
| 253 | + |
| 254 | +```python |
| 255 | +c = xr.get_config(source='running', filter=('subtree', filter)) |
| 256 | +print(c) |
| 257 | +``` |
| 258 | + |
| 259 | +... we'll see that SGroup3 has the new addition. |
| 260 | + |
| 261 | + |
| 262 | +{% capture "output" %} |
| 263 | +Script Output: |
| 264 | + |
| 265 | +``` |
| 266 | +<?xml version="1.0"?> |
| 267 | +<rpc-reply message-id="urn:uuid:abd0a7ee-5f06-4754-b2a3-dae6e3d797aa" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> |
| 268 | + <data> |
| 269 | + <telemetry-system xmlns="http://openconfig.net/yang/telemetry"> |
| 270 | + <sensor-groups> |
| 271 | + <sensor-group> |
| 272 | + <sensor-group-id>SGroup3</sensor-group-id> |
| 273 | + <config> |
| 274 | + <sensor-group-id>SGroup3</sensor-group-id> |
| 275 | + </config> |
| 276 | + <sensor-paths> |
| 277 | + <sensor-path> |
| 278 | + <path>openconfig-interfaces:interfaces%2finterface</path> |
| 279 | + <config> |
| 280 | + <path>openconfig-interfaces:interfaces%2finterface</path> |
| 281 | + </config> |
| 282 | + </sensor-path> |
| 283 | + <sensor-path> |
| 284 | + <path>Cisco-IOS-XR-ipv4-arp-oper:arp%2fnodes%2fnode%2fentries%2fentry</path> |
| 285 | + <config> |
| 286 | + <path>Cisco-IOS-XR-ipv4-arp-oper:arp%2fnodes%2fnode%2fentries%2fentry</path> |
| 287 | + </config> |
| 288 | + </sensor-path> |
| 289 | + </sensor-paths> |
| 290 | + </sensor-group> |
| 291 | + </sensor-groups> |
| 292 | + <subscriptions> |
| 293 | + <persistent> |
| 294 | + <subscription> |
| 295 | + <subscription-id>Sub3</subscription-id> |
| 296 | + <config> |
| 297 | + <subscription-id>Sub3</subscription-id> |
| 298 | + </config> |
| 299 | + <sensor-profiles> |
| 300 | + <sensor-profile> |
| 301 | + <sensor-group>SGroup3</sensor-group> |
| 302 | + <config> |
| 303 | + <sensor-group>SGroup3</sensor-group> |
| 304 | + <sample-interval>30000</sample-interval> |
| 305 | + </config> |
| 306 | + </sensor-profile> |
| 307 | + </sensor-profiles> |
| 308 | + </subscription> |
| 309 | + </persistent> |
| 310 | + </subscriptions> |
| 311 | + </telemetry-system> |
| 312 | + </data> |
| 313 | +</rpc-reply> |
| 314 | +
|
| 315 | +``` |
| 316 | +{% endcapture %} |
| 317 | + |
| 318 | +<div class="notice--warning"> |
| 319 | +{{ output | markdownify }} |
| 320 | +</div> |
| 321 | + |
| 322 | +And if you need some CLI to reassure yourself that it worked, here it is: |
| 323 | + |
| 324 | +{% capture "output" %} |
| 325 | +CLI Output: |
| 326 | + |
| 327 | +``` |
| 328 | +RP/0/RP0/CPU0:SunC#show run telemetry model-driven |
| 329 | +Mon Aug 8 20:09:57.149 UTC |
| 330 | +telemetry model-driven |
| 331 | + sensor-group SGroup3 |
| 332 | + sensor-path openconfig-interfaces:interfaces/interface |
| 333 | + sensor-path Cisco-IOS-XR-ipv4-arp-oper:arp/nodes/node/entries/entry |
| 334 | + ! |
| 335 | + subscription Sub3 |
| 336 | + sensor-group-id SGroup3 sample-interval 30000 |
| 337 | + ! |
| 338 | +! |
| 339 | +``` |
| 340 | +{% endcapture %} |
| 341 | + |
| 342 | +<div class="notice--info"> |
| 343 | +{{ output | markdownify }} |
| 344 | +</div> |
| 345 | + |
| 346 | +## Conclusion |
| 347 | +Armed with the examples in this blog and a understanding of the telemetry YANG model, you should now be able to use YANG configuration models to configure the router to stream YANG models with the operational data you want. How's that for model-driven programmability? |
| 348 | + |
0 commit comments