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.