Configuring Model-Driven Telemetry (MDT) with YDK

4 minutes read

YDK: Automating YANG without XML

In an earlier tutorial, I wrote about how to configure MDT using the OpenConfig Telemetry YANG model with ncclient and a lot of XML. An even simpler way to do this is to use 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.

In this blog, I’ll explain how to configure telemetry for gRPC dialin using YDK and the OpenConfig Telemetry YANG model.

Connect to the router

YDK leverages ncclient to handle the NETCONF connection, so this bit of code might look similar to what you’ve done before, using the YDK provider library instead of ncclient:

from ydk.providers import NetconfServiceProvider
from ydk.services import CRUDService 

HOST = '10.30.111.9'
PORT = 830
USER = 'cisco'
PASS = 'cisco'

xr = NetconfServiceProvider(address=HOST,
	port=PORT,
	username=USER,
	password=PASS,
	protocol = 'ssh')

With that, we are now connected to the router:

CLI Output:

RP/0/RP0/CPU0:SunC#show netconf-yang clients
Mon Aug  8 23:01:48.210 UTC
Netconf clients
client session ID|     NC version|    client connect time|        last OP time|        last OP type|    <lock>|
       1386485520|            1.1|         0d  0h  0m  5s|                    |                    |        No|
RP/0/RP0/CPU0:SunC#

Define the sensor group

Now that our script is connected to the router, we’ll start by defining the sensor-group. Here’s the first bit of YDK for that:

import ydk.models.openconfig.openconfig_telemetry as oc_telemetry 

sgroup = oc_telemetry.TelemetrySystem.SensorGroups.SensorGroup()
sgroup.sensor_group_id="SGroup4"
sgroup.config.sensor_group_id="SGroup4"

So how did I come up with that? Look back at the first part of the OC Telemetry YANG model:

PYANG Output:

module: openconfig-telemetry
   +--rw telemetry-system
      +--rw sensor-groups
      |  +--rw sensor-group* [sensor-group-id]
      |     +--rw sensor-group-id    -> ../config/sensor-group-id
      |     +--rw config
      |     |  +--rw sensor-group-id?   string
   

Start from the top and walk down from telemetry-system to sensor-groups to sensor-group. Replace the dashes and lowercase syntax with CamelCase syntax and you get the class that instantiates that first object: TelemetrySystem.SensorGroups.SensorGroup(). Down to the next level, we have the leaf “sensor-group-id.” YDK converts this to an object attribute by replacing the hyphens with underscores. The sensor-group-id list key is actually a leaf-ref to config/sensor-group-id, both of which are required (hence the two lines that seem redundant but are actually required for syntactic validation because of the structure of the YANG model).

Going down a little farther in the YANG model with some help from some pyang options, we see that the sensor-group contains a list of sensor-paths.

PYANG Output:

$ pyang -f tree openconfig-telemetry.yang --tree-path=telemetry-system/sensor-groups/sensor-group/sensor-paths/sensor-path/config
module: openconfig-telemetry
   +--rw telemetry-system
      +--rw sensor-groups
         +--rw sensor-group* [sensor-group-id]
            +--rw sensor-paths
               +--rw sensor-path* [path]
                  +--rw config
                     +--rw path?             string
$

This is how that maps to YDK code:

sgroup.sensor_paths = sgroup.SensorPaths()
new_sensorpath = sgroup.SensorPaths.SensorPath()

new_sensorpath.path = 'Cisco-IOS-XR-infra-statsd-oper:infra-statistics%2finterfaces%2finterface%2flatest%2fgeneric-counters'
new_sensorpath.config.path = 'Cisco-IOS-XR-infra-statsd-oper:infra-statistics%2finterfaces%2finterface%2flatest%2fgeneric-counters'

sgroup.sensor_paths.sensor_path.append(new_sensorpath)

So again, following the YANG model, we define the top-level SensorPaths object, then a SensorPath with an object attribute “path” that actually specifies the YANG model that we want to stream (in this case, our old friend, interface statistics).

Note that the “%2f” in the path attributes represent URL encodings of the forward slash character (“/”). The code would look a little better if you used a utility such as urllib to do the string substitution for you, so you can use the more natural looking path with “/” characters like this:

import urllib

sgroup.sensor_paths = sgroup.SensorPaths()
new_sensorpath = sgroup.SensorPaths.SensorPath()

interface_stats_path = urllib.quote('Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/generic-counters', safe=':')
new_sensorpath.path = interface_stats_path
new_sensorpath.config.path = interface_stats_path

sgroup.sensor_paths.sensor_path.append(new_sensorpath)

Apply the SensorGroup object to the router

Once you’ve populated the object, it’s trivial to apply it to the router using the create method on the CRUDService object from YDK:

from ydk.services import CRUDService

rpc_service = CRUDService()
rpc_service.create(xr, sgroup)

Instantiate a Subscription object and apply it

The Subscription is the final piece of the config. Again, refer to the YANG model to understand the Python class that you should use. I’ll use pyang with the tree-path option to make it clearer:

PYANG Output:

$pyang -f tree --tree-path telemetry-system/subscriptions/persistent/subscription/sensor-profiles/sensor-profile openconfig-telemetry.yang

module: openconfig-telemetry
   +--rw telemetry-system
      +--rw subscriptions
         +--rw persistent
            +--rw subscription* [subscription-id]
               +--rw sensor-profiles
                  +--rw sensor-profile* [sensor-group]
                     +--rw sensor-group    -> ../config/sensor-group
                     +--rw config
                     |  +--rw sensor-group?         -> /telemetry-system/sensor-groups/sensor-group/config/sensor-group-id
                     |  +--rw sample-interval?      uint64
                     |  +--rw heartbeat-interval?   uint64
                     |  +--rw suppress-redundant?   boolean
                     +--ro state
                        +--ro sensor-group?         -> /telemetry-system/sensor-groups/sensor-group/config/sensor-group-id
                        +--ro sample-interval?      uint64
                        +--ro heartbeat-interval?   uint64
                        +--ro suppress-redundant?   boolean
                     

This is how that ends up in YDK code:

sub = oc_telemetry.TelemetrySystem.Subscriptions.Persistent.Subscription()
sub.subscription_id = 4
sub.config.subscription_id = 4

sub.sensor_profiles = sub.SensorProfiles()

new_sgroup = sub.SensorProfiles.SensorProfile()
new_sgroup.sensor_group = 'SGroup4'
new_sgroup.config.sensor_group = 'SGroup4'
new_sgroup.config.sample_interval = 30000

sub.sensor_profiles.sensor_profile.append(new_sgroup)

rpc_service.create(xr, sub)

What did all that code do?

So this is how all that shows up in CLI:

CLI Output:

RP/0/RP0/CPU0:SunC#show run telemetry model-driven
Tue Aug  9 17:52:38.462 UTC
telemetry model-driven
 sensor-group SGroup4
  sensor-path Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/generic-counters
 !
 subscription 4
  sensor-group-id SGroup4 sample-interval 30000
 !
!

RP/0/RP0/CPU0:SunC#

And that is all you need for Model-Driven Telemetry using gRPC dialin.

Clean up, clean up, everybody clean up!

Let’s delete the telemetry config completely and disconnect the NETCONF session:

rpc_service.delete(xr, oc_telemetry.TelemetrySystem())
xr.close()

Conclusion

In this tutorial, we looked at a couple dozen lines of YDK code that added and then removed five lines of CLI. So you might be thinking “and that helps me be more efficient…how?” But the power of automation in general and YDK in particular can’t be fully revealed in a single, simple example like this. The real power of YDK is that it allows you to do this for any YANG model on the box, automatically generating Python classes that inherit the syntactic checks and requirements of the underlying model, while also handling all the details of the underlying encoding and transport (no understanding of XML or NETCONF chunk framing required!). Give a try and see what you think!

Leave a Comment