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:
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:
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 -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 -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:
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()
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!