Netconf/YANG
The Netconf protocol and YANG data modeling language together provide a standardized management interface to access a device’s configuration and operational data programmatically. This feature guide first explains how to set up RADKit Service to access devices over the Netconf protocol. Then it explains how to query nodes from the YANG data model of a device from the Client and how to process the results.
Warning
The Netconf feature as it stands today suffers from a number of limitations. It should be considered experimental until it gets reworked in a future RADKit release.
Service configuration
Netconf configuration
To enable Netconf support, when either adding a new device, or editing an existing one, check
the Netconf
box under Available Management Protocols
. Subsequently, the Netconf
configuration section will appear:

The Username
and Password
are user credentials for Netconf access to the device, and
Port
is the Netconf port, which is usually the standard port of 830.
Client operation
Devices Netconf status
When the Client loads the service inventory, it gets an internal attribute for each
device called netconf_config
which is set to either True
or False
,
depending on whether the Service has Netconf credentials configured for that device:
>>> csr1.attributes.internal
<radkit_client.sync.device._AttributesDict object at 0x1106135e0>
key value
------------------- -------------
description csr1
device_type IOS_XE
forwarded_tcp_ports 443
host 172.18.124.37
http_config False
netconf_config True
snmp_version None
swagger_config False
terminal_config True
It is easy to select all devices that support Netconf by using the following filter:
>>> nc = service.inventory.filter("netconf_config", "True")
Updating the Netconf API
Every Device
object has a .netconf
attribute which
represents the Netconf API for that device. By default this
radkit_client.sync.netconf.netconf_api.NetconfAPI
object
has a status of UNKNOWN
:
>>> csr1.netconf
[UNKNOWN] <radkit_client.sync.netconf.netconf_api.SingleDeviceNetconfAPI object at 0x110a90ac0>
For a list of all available statuses for the Netconf API, see
radkit_client.sync.netconf.netconf_api.NetconfAPIStatus
.
If the device has Netconf credentials configured on the Service side, its capabilities
can be retrieved from the Client using
Device.update_netconf()
:
>>> csr1.update_netconf().wait()
[SUCCESS] <radkit_client.sync.request.TransformedFillerRequest object at 0xffff68ed2040>
-------------- ---------------------------------
sent_timestamp 2022-08-09 22:05:37
request_type Netconf capabilities
identity radkit-user@example.com
service_serial xxxx-yyyy-zzzz
result <radkit_client.async_.device.AsyncDeviceCapabilitiesUpdate object at 0xffff5b...
forwarder wss://prod.radkit-cloud.cisco.com/forwarder-1/
-------------- ---------------------------------
If the retrieval is successful, the Netconf API status changes to AVAILABLE
:
>>> csr1.netconf
[AVAILABLE] <radkit_client.sync.netconf.netconf_api.SingleDeviceNetconfAPI object at 0x110c009d0>
We can also display the Netconf capabilities hash with:
>>> csr1.netconf.capabilities
<radkit_client.sync.netconf.netconf_api.NetconfCapabilities object at 0x1111727c0>
----------- ----------------------------------------------------------------
hash b0f602db8ffb3d18f023f660a265c0a217813ad30a703e9fc6ba0a4065610623
#namespaces 529
----------- ----------------------------------------------------------------
This Netconf capabilities hash identifies the exact set of YANG models available for that device, as well as its namespace configuration. Generally that hash will be the same for all devices of the same type and running the exact same software. We will come back to this hash in Netconf on multiple devices.
Querying a YANG node
To query a certain node in the data model, call its
get()
method. Please note that the first-level nodes representing the YANG modules cannot be queried:
>>> csr1.netconf.yang['ietf-interfaces'].get()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<frozen radkit_client.sync.netconf.yang_model>", line 286, in get
File "<frozen radkit_client.sync.netconf.yang_model>", line 299, in _validate_xpath
ValueError: A namespace node cannot be queried (go 1 level deeper)
A namespace node cannot be queried (go 1 level deeper)
Instead, you have to go at least one level deeper, for example:
>>> req = csr1.netconf.yang['ietf-interfaces']['interfaces'].get().wait()
>>> type(req)
<class 'radkit_client.sync.request.TransformedFillerRequest'>
>>> pprint(req.result.yang)
{'interfaces': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-interfaces',
'interface': {'GigabitEthernet1': {'enabled': 'true',
'ipv4': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip',
'address': {'ip': '172.18.124.37',
'netmask': '255.255.255.0'}},
'ipv6': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip'},
'name': 'GigabitEthernet1',
'type': {'#text': 'ianaift:ethernetCsmacd',
'@xmlns:ianaift': 'urn:ietf:params:xml:ns:yang:iana-if-type'}},
...
The .get()
operation returns an instance of
TransformedFillerRequest
.
That object represents the Netconf
request sent to the Service to run the query on the device:
>>> req
[SUCCESS] <radkit_client.sync.request.TransformedFillerRequest object at 0x11536e2e0>
-------------- --------------------------------------------------------------------------------
sent_timestamp 2022-08-10 11:16:37
request_type Netconf XPath query
identity radkit-user@example.com
service_serial xxxx-yyyy-zzzz
result <radkit_client.sync.netconf.xpath_results.GetSingleXPathResult object at 0x1154f0fa0>
forwarder wss://prod.radkit-cloud.cisco.com/forwarder-1/
-------------- --------------------------------------------------------------------------------
Querying a specific XPath
Another way to run a Netconf query is to manually specify the XPath without going
through the YANG model tree. This is done using the
get_xpaths()
method of the device’s Netconf API, which accepts either a single XPath as a string,
or multiple XPaths as a list of strings:
Single XPath:
req = csr1.netconf.get_xpaths("/openconfig-system:system/state/current-datetime")
Multiple Xpaths:
req = csr1.netconf.get_xpaths([ "/openconfig-system:system/state/current-datetime", "/ietf-interfaces:interfaces/interface[name='Loopback0']/ipv4" ]).wait()
Note how the second XPath query includes a predicate to filter by interface name, as in
our example we are only interested in the Loopback0
interface:
"/ietf-interfaces:interfaces/interface[name='Loopback0']/ipv4"
Note
get_xpaths()
automatically populates the list of required
namespaces based on the XPath(s) being queried and the device capabilities, if
known. In case the device capabilities are not known, or if any namespaces are
referenced from deep inside the XPath, you may need to provide additional
namespaces as the second argument. Please see the Client API reference for details.
Response processing
When the Netconf response comes back, the RADKit Client performs some post-processing on the data to make it easier to consume; the main thing it does is turn list of dicts into dicts of dicts.
For example, this query retrieves information about IPv4 configuration on loopback interfaces:
>>> req = csr1.netconf.get_xpaths("/ietf-interfaces:interfaces/interface[starts-with(name, 'Loopback')]/ipv4").wait()
The raw response from the device is stored under req.result.raw
. It contains a list
of dicts, one for each loopback:
>>> pprint(req.result.raw)
{'interfaces': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-interfaces',
'interface': [{'ipv4': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip',
'address': {'ip': '1.1.1.1',
'netmask': '255.255.255.255'}},
'name': 'Loopback0'},
{'ipv4': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip',
'address': {'ip': '3.3.3.3',
'netmask': '255.255.255.255'}},
'name': 'Loopback3'}]}}
This raw response is not very convenient to navigate, as there is no way to directly
access the data for Loopback0
without first looking up the corresponding index in
the list (0
, in this case):
>>> req.result.raw['interfaces']['interface'][0]['ipv4']['address']
{'ip': '1.1.1.1', 'netmask': '255.255.255.255'}
To make things easier, the RADKit Client goes through the list and tries to guess which field
in the dict can be used as a key to turn the list into a dict. In this case, name
is picked and the list is turned into a dict indexed by interface name. This
post-processed result is stored under req.result.yang
:
>>> pprint(req.result.yang)
{'interfaces': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-interfaces',
'interface': {'Loopback0': {'ipv4': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip',
'address': {'ip': '1.1.1.1',
'netmask': '255.255.255.255'}},
'name': 'Loopback0'},
'Loopback3': {'ipv4': {'@xmlns': 'urn:ietf:params:xml:ns:yang:ietf-ip',
'address': {'ip': '3.3.3.3',
'netmask': '255.255.255.255'}},
'name': 'Loopback3'}}}}
The data for Loopback0
can now be accessed without the need for a lookup:
>>> req.result.yang['interfaces']['interface']['Loopback0']['ipv4']['address']
{'ip': '1.1.1.1', 'netmask': '255.255.255.255'}
Note
There may be situations where this logic fails and no candidate key is found. In
those rare situations, the post-processed data will still contain a list.
Conversely, if multiple candidate keys are found, if there is one named name
it
will be preferred; else the first candidate key that was found will be selected.
If in your particular case the wrong key is selected, you can always fall back on .raw
.
Netconf on multiple devices
When dealing with multiple devices in a DeviceDict
,
Netconf can only be used on all devices in the dict if their capabilities are exactly the same:
all devices in the DeviceDict must support Netconf;
their capabilities must have been downloaded by the Client; and
their capabilities hashes must all be identical.
The DeviceDict
object has a Netconf API of its own,
which initially has the status HETEROGENOUS
i.e., the Netconf capabilities of the devices
it contains are either missing or incompatible.
>>> csrs
<radkit_client.sync.device.DeviceDict object at 0x10fc972b0>
name host device_type Terminal Netconf Swagger HTTP description failed
------ ------------- ------------- ---------- --------- --------- ------ ------------- --------
csr1 172.18.124.37 IOS_XE True True False False csr1 False
csr2 172.18.124.38 IOS_XE True True False False False
>>> csrs.netconf
[HETEROGENOUS] <radkit_client.sync.netconf.netconf_api.NetconfAPI object at 0x10faa54c0>
-------- ------------
status HETEROGENOUS
#devices 2
-------- ------------
The homogeneity of the dict’s Netconf API is re-evaluated every time the Netconf capabilities of any of the devices it contains are updated, and every time a device is added to or removed from the dict:
>>> csrs.update_netconf().wait()
[SUCCESS] FillerRequest(status='SUCCESS', rpc_name='get-netconf-capabilities')
-------------- ---------------------------------
sent_timestamp 2022-08-11 09:53:57
request_type Netconf capabilities
identity radkit-user@example.com
service_serial xxxx-yyyy-zzzz
result {'csr1': <radkit_client.async_.device.AsyncDeviceCapabilitiesUpdate o...
forwarder wss://prod.radkit-cloud.cisco.com/forwarder-4/
-------------- ---------------------------------
The result
attribute of this request contains more detailed information
about the capabilities update:
>>> csrs.update_netconf().wait().result
>>> s.inventory.update_netconf().wait().result
[SUCCESS] <radkit_client.sync.device.DeviceDictCapabilitiesUpdate object at 0xffff68f02d00>
device_name status result
------------- -------- --------------------------------------------------
csr1 SUCCESS Netconf capabilities loaded.
csr2 SUCCESS Netconf capabilities loaded.
In this case after the update, the Netconf capabilities of the two devices in the dict
are identical, so the status of the dict’s Netconf API object changes to AVAILABLE
:
>>> csrs.netconf
[AVAILABLE] <radkit_client.sync.netconf.netconf_api.NetconfAPI object at 0x1148600a0>
-------- ---------
status AVAILABLE
#devices 2
-------- ---------
>>> csrs.netconf.capabilities.hash
'b0f602db8ffb3d18f023f660a265c0a217813ad30a703e9fc6ba0a4065610623'
It is now possible to access the YANG model tree under the dict’s Netconf API object and query multiple devices in a single request:
>>> req = csrs.netconf.yang['openconfig-system']['system']['state']['current-datetime'].get().wait()
>>> pprint(req.result['csr1'].yang)
{'system': {'@xmlns': 'http://openconfig.net/yang/system',
'state': {'current-datetime': '2022-08-11T14:01:56.661Z+00:00'}}}
>>> pprint(req.result['csr2'].yang)
{'system': {'@xmlns': 'http://openconfig.net/yang/system',
'state': {'current-datetime': '2022-08-11T14:01:52.107Z+00:00'}}}
Querying heterogenous sets
If a set of devices is heterogenous but you know that they all support a given XPath,
it is possible to query those devices manually as a set by using the
get_xpaths()
method
of the DeviceDict
’s Netconf API.
In this case, you will have to specify the namespace(s) manually, as those cannot be automatically populated based on the device capabilities (see Querying a specific XPath earlier in this document for details).