Introduction to RADKit Client
Note
A more detailed user guide is also available at Client user guide. The present intro is only meant as a really quick overview of the most commonly used features within the RADKit Client interactive CLI. After reading this page, make sure to also check out the RADKit Network Console feature for a simplified, non-Python CLI.
The RADKit Client is a CLI that runs on top of a Python interpreter, inside what is called a REPL (Read/Evaluate/Print/Loop) user interface. This allows a programmatic approach to working with RADKit – but you do NOT need to be a Python wizard at all in order to use RADKit Client.
The radkit-interactive tool
With the RADKit Client, the simplest way to connect to a device with SSH/Telnet capability
is to use the radkit-interactive
CLI. This will create an SSH or Telnet session to the
device just like any traditional terminal client.
To display the device names defined for a RADKit Service:
$ radkit-interactive --service-sn xxxx-yyyy-zzzz --sso-email radkit-user@example.com --show-inventory
<radkit_client.sync.device.DeviceDict object at 0x10db0df50>
name host device_type Terminal Netconf SNMP Swagger HTTP description failed
---------------------- ---------------------------------- ------------- ---------- --------- ------ --------- ------ ------------- --------
lab-radkit-cat8kv-1 lab-radkit-cat8kv-1.example.com IOS_XE True True False False False False
lab-radkit-cat9800cl-1 lab-radkit-cat9800cl-1.example.com IOS_XE True True False False False False
lab-radkit-linux-1 lab-radkit-linux-1.example.com LINUX True False False False False False
Untouched inventory from service xxxx-yyyy-zzzz.
To create an SSH or Telnet interactive session with one of the devices in the inventory:
$ radkit-interactive --service-sn xxxx-yyyy-zzzz --sso-email radkit-user@example.com --device-name lab-radkit-linux-1
Attaching to lab-radkit-linux-1 ...
Type: ~. to terminate.
~? for other shortcuts.
When using nested SSH sessions, add an extra ~ per level of nesting.
Warning: all sessions are logged. Never type passwords or other secrets, except at an echo-less password prompt.
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-89-generic x86_64)
Last login: Wed Dec 20 11:31:23 2023 from 10.209.195.246
radkit@lab-radkit-linux-1:~$
To terminate the interactive session, use the SSH escape sequence of ~.
, or the remote device
exit command, e.g. exit
on an Linux host or an IOS XE device.
radkit@lab-radkit-linux-1:~$ exit
logout
Client and Service
Start the Client using SSO authentication and connect to a Service where some devices are already defined:
$ radkit-client
15:48:08.374Z INFO | Logging configured [root_level='ERROR' level='INFO' con_level='TRACE' con_json=False file_level='TRACE' file_json=False file_name='/Users/radkit-user/.radkit/logs/client/client.log' file_rotate=True with_rate_limiting=True]
<RADKit banner removed for brevity>
>>> sso_login("radkit-user@example.com")
>>> service = service_cloud("xxxx-yyyy-zzzz")
15:49:59.060Z INFO | Connecting to forwarder [uri='wss://prod.radkit-cloud.cisco.com/forwarder-2/websocket/']
15:49:59.494Z INFO | Connection to forwarder successful [uri='wss://prod.radkit-cloud.cisco.com/forwarder-2/websocket/']
15:50:02.158Z INFO | Connecting to forwarder [uri='wss://prod.radkit-cloud.cisco.com/forwarder-3/websocket/']
15:50:02.527Z INFO | Connection to forwarder successful [uri='wss://prod.radkit-cloud.cisco.com/forwarder-3/websocket/']
>>> service
[READY] Service(name='xxxx-yyyy-zzzz', service_id='xxxx-yyyy-zzzz')
----------------------------- ---------------------------------------------------------
name xxxx-yyyy-zzzz
domain PROD
client_id radkit-user@example.com
service_id xxxx-yyyy-zzzz
#devices 11
#capabilities 18
version 1.8.0
e2ee_supported Yes, required
e2ee_active Yes, using E2EE. (E2EE setting is set to WHEN_AVAILABLE).
e2e_verify_required No
supported_compression_methods ['zstd']
supports_h2_multiplexing Yes
connection_method None
direct_rpc_url None
----------------------------- ---------------------------------------------------------
Check out the Service inventory, which contains a list of all the devices defined and enabled (any devices that are disabled in the Service Web UI will not appear in the Client):
>>> service.inventory
<radkit_client.sync.device.DeviceDict object at 0x111475190>
name host device_type Terminal Netconf Swagger HTTP description failed
--------- -------------- ------------- ---------- --------- --------- ------ ------------- --------
asa-1 172.18.124.217 ASA True False False False False
asa-2 172.18.124.56 ASA True False False False False
c8kv1 172.18.124.43 IOS_XE True False False False False
cEdge-1 172.18.124.208 CEDGE True False False False cEdge-1 False
cEdge-2 172.18.124.209 CEDGE True False False False cEdge-2 False
cEdge-3 172.18.124.210 CEDGE True False False False cEdge-3 False
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
csr3 172.18.124.39 IOS_XE True True False False False
csr4 172.18.124.55 IOS_XE True True False False False
csr5 172.18.124.42 IOS_XE True True False False False
ubuntu-1 10.122.149.118 LINUX True False False False False
ubuntu-2 172.18.124.126 LINUX True False False False False
ubuntu-3 10.122.149.90 LINUX True False False False False
vManage-1 172.18.124.60 VMANAGE False False True True vManage False
The values True
and False
in the Terminal
, Netconf
, Swagger
and HTTP
columns indicate whether the corresponding protocol is configured for that device on the
RADKit Service. Please note that this does not mean that the protocol is correctly
configured on the device itself - only that the Service has credentials and other
required information for that particular protocol on that particular device.
Device information
Select a device, assign it to a Python variable, and display its details:
>>> csr1 = service.inventory['csr1']
>>> csr1
<radkit_client.sync.device.Device object {name='csr1', host='172.18.124.37', device_type='IOS_XE', forwarded_tcp_ports='', failed=False} at 0x11176b340>
Object parameters
-------- ----------------
identity rdkit-user@example.com
serial xxxx-yyyy-zzzz
name csr1
-------- ----------------
Internal attributes
key value
------------------- -------------
description csr1
device_type IOS_XE
forwarded_tcp_ports
host 172.18.124.37
http_config False
netconf_config True
snmp_version None
swagger_config False
terminal_config True
External attributes
APIs
------- -------
Netconf UNKNOWN
Swagger UNKNOWN
------- -------
Single command execution
Now select another device, run Cisco IOS (EXEC) command on that device and wait for it to complete:
>>> req = service.inventory['csr4'].exec("show ip int brie").wait()
The req
object represents the command execution request we have sent to the device.
The status of this request is SUCCESS
, as displayed on the first line:
>>> req
[SUCCESS] <radkit_client.sync.request.TransformedFillerRequest object at 0x1115e6af0>
-------------- --------------------------------------------------------------------------------
sent_timestamp 2022-07-26 12:05:30
request_type Command execution
identity radkit-user@example.com
service_serial xxxx-yyyy-zzzz
result <radkit_client.sync.command.ExecSingleCommandResult object {command='show ip int b...
forwarder wss://prod.radkit-cloud.cisco.com/forwarder-3/
-------------- --------------------------------------------------------------------------------
The req
object has an attribute, req.result
, that represents the
command execution result that came back from the device. Its status is also SUCCESS
:
>>> req.result
[SUCCESS] <radkit_client.sync.command.ExecSingleCommandResult object {command='show ip int brie', status='SUCCESS'} at 0x10b34aa30>
----------- -----------------------------------------------------------------------------------
identity radkit-user@example.com
serial xxxx-yyyy-zzzz
device csr4
device_uuid d4956c11-f649-4596-9d6d-9bacb9d89995
command show ip int brie
data csr4#sh ip int brie\nInterface IP-Address OK? Method Status ...
----------- -----------------------------------------------------------------------------------
The original command is found under req.result.command
:
>>> req.result.command
'show ip int brie'
The raw output of the command can be viewed with req.result.data
:
>>> req.result.data
'csr4#show ip int brie\nInterface IP-Address OK? Method Status Protocol\n
GigabitEthernet1 172.18.124.55 YES NVRAM up up\nTunnel5 45.1.1.1 YES NVRAM up down\ncsr4#'
Now use the Client pipe commands (|
, similar to UNIX shell pipes) to easily
extract and display the information you need:
# Extract lines containing the string "tunnel", with case-insensitive match by default
>>> req.result.data | grep('tunnel')
'Tunnel5 45.1.1.1 YES NVRAM up down'
# Extract and print lines matching a regular expression
>>> req.result.data | regrep(r'.+up\s+up') | print
GigabitEthernet1 172.18.124.55 YES NVRAM up up
# Print the first 3 lines of the output (includes the prompt and the command itself):
>>> req.result.data | head(3) | print
csr4#show ip int brie
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.18.124.55 YES NVRAM up up
Multiple command execution
Now run two commands in a single request on the same device:
>>> req = service.inventory['csr1'].exec(["show clock", "show version"]).wait()
Since we passed a list of commands, we get back a dictionary of results:
# Display the output of "show clock"
>>> req.result['show clock'].data | print
csr1#show clock
12:46:31.178 EDT Tue Jul 26 2022
csr1#
# Display the first 3 lines of output in "show version"
>>> req.result['show version'].data | head(3) | print
csr1#show version
Cisco IOS XE Software, Version 17.06.02
Cisco IOS Software [Bengaluru], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.6.2, RELEASE SOFTWARE (fc7)
Now create a set of two devices and run a command on both those devices. This is done by first isolating a subset of the service inventory (this is called a device dictionary):
>>> all_cedges = service.inventory.filter('name', 'cEdge')
>>> all_cedges
<radkit_client.sync.device.DeviceDict object at 0x11506fe50>
name host device_type Terminal Netconf Swagger HTTP description failed
------- -------------- ------------- ---------- --------- --------- ------ ------------- --------
cEdge-1 172.18.124.208 IOS_XE True False False False cEdge-1 False
cEdge-2 172.18.124.209 IOS_XE True False False False cEdge-2 False
cEdge-3 172.18.124.210 IOS_XE True False False False cEdge-3 False
>>> req = all_cedges.exec("show ip int brie").wait()
>>> req.result['cEdge-1'].data | head(3) | print
branch1-cedge1#show ip int brie
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 172.18.124.208 YES other up up
Note
You can even run multiple commands on multiple devices in a single request, in which case you would get back a 2-level dictionary of results (first by device, then by command).
Netconf API queries
Now take a device with Netconf enabled and fetch its Netconf capabilities (this can take a while, several minutes or more, depending on how much processing the Service has to do on the data model):
>>> csr1 = service.inventory['csr1']
>>> csr1.update_netconf().wait()
[SUCCESS] FillerRequest(status='SUCCESS', rpc_name='get-netconf-capabilities')
-------------- ----------------------------------------------
sent_timestamp 2022-07-26 13:17:04
request_type Netconf capabilities
identity radkit-user@example.com
service_serial xxxx-yyyy-zzzz
result None
forwarder wss://prod.radkit-cloud.cisco.com/forwarder-3/
-------------- ----------------------------------------------
>>> csr1.netconf
[AVAILABLE] <radkit_client.sync.netconf.netconf_api.SingleDeviceNetconfAPI object at 0x115b7b160>
The Netconf API status has changed from CONFIGURED
to AVAILABLE
. Now we can
navigate the data model for the device by accessing its .netconf.yang
attribute
(returns a dict of dicts, tab completion is supported):
>>> csr1.netconf.yang['openconfig-system']['system']['state']
<radkit_client.sync.netconf.yang_model.SingleDeviceYangNode object {device_name='csr1', xpath='/openconfig-system:system/state'} at 0x117e78ca0>
{
"domain-name": {},
"login-banner": {},
"motd-banner": {},
"current-datetime": {},
"boot-time": {},
"oc-system-cisco:license": {}
}
>>> csr1.netconf.yang['Cisco-IOS-XE-process-cpu-oper']
<radkit_client.sync.netconf.yang_model.SingleDeviceYangNode object {device_name='csr1', xpath='/Cisco-IOS-XE-process-cpu-oper:'} at 0x117a34520>
{
"cpu-usage": {
"cpu-utilization": {
"five-seconds": {},
"five-seconds-intr": {},
"one-minute": {},
"five-minutes": {},
"cpu-usage-processes": {
"cpu-usage-process": {
"pid": {},
"name": {},
"tty": {},
"total-run-time": {},
"invocation-count": {},
"avg-run-time": {},
"five-seconds": {},
"one-minute": {},
"five-minutes": {}
}
}
}
}
}
After selecting a node in the data model, query it using its .get()
method. This output
is intended more for machine consumption, but the |
command can also be used to display
more user friendly output when working with the interactive CLI.
>>> cpu = csr1.netconf.yang['Cisco-IOS-XE-process-cpu-oper']['cpu-usage']['cpu-utilization'].get().wait()
>>> cpu.result.yang | to_json | print
{
"cpu-usage": {
"@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XE-process-cpu-oper",
"cpu-utilization": {
"five-seconds": "1",
"five-seconds-intr": "0",
"one-minute": "3",
"five-minutes": "7",
"cpu-usage-processes": {
"cpu-usage-process": {
"1": {
"pid": "1",
"name": "Chunk Manager",
"tty": "0",
"total-run-time": "4",
"invocation-count": "356",
"avg-run-time": "11",
"five-seconds": "0.0",
"one-minute": "0.0",
"five-minutes": "0.0"
},
...
Swagger API queries
Similar to Netconf, you can query a vManage device that provides a Swagger API:
>>> vman = service.inventory['vManage-1']
>>> req = vman.update_swagger().wait()
Under the vman.swagger.paths
dictionary you can find a list of API paths as well as
the supported HTTP methods for each path and a description of what it does. Query one of
these paths using the HTTP GET method:
>>> req = vman.swagger.paths['/device/tloc'].get(deviceId="6.1.1.11")
>>> req.result.text
'{"header":{"generatedOn":1658862446121,"title":"tlocStatus"},"data":[{"color":"mpls",
"system-ip":"6.1.1.11","bfdSessionsDown":0,"controlConnectionsUp":2,
"controlConnectionsToVsmarts":"--","bfdSessionsUp":5,"controlConnectionsDown":0}]}'
Since the output data is JSON, we can display it as a Python object:
>>> req.result.text | print
{"header":{"generatedOn":1658862446121,"title":"tlocStatus"},"data":[{"color":"mpls","system-ip":"6.1.1.11","bfdSessionsDown":0,"controlConnectionsUp":2,"controlConnectionsToVsmarts":"--","bfdSessionsUp":5,"controlConnectionsDown":0}]}
>>> req.result.json
{
"header": {
"generatedOn": 1658862446121,
"title": "tlocStatus"
},
"data": [
{
"color": "mpls",
"system-ip": "6.1.1.11",
"bfdSessionsDown": 0,
"controlConnectionsUp": 2,
"controlConnectionsToVsmarts": "--",
"bfdSessionsUp": 5,
"controlConnectionsDown": 0
}
]
}
We can also retrieve a specific data element from the dictionary:
>>> req.result.json['data'][0]['bfdSessionsUp']
5
To be continued…
That’s all for now. More examples will come as we add more features.
In the meantime, please check out the Client user guide for detailed information on how to use the RADKit Client to the full extent of its capabilities, or check out the RADKit Network Console feature for a simplified CLI.