Skip to content

JSON-RPC Basics#

Summary
Tutorial name JSON-RPC Basics
Lab components Single Nokia SR Linux node
Resource requirements 2 vCPU
4 GB
Lab single-srlinux
Main ref documents JSON-RPC Configuration, JSON-RPC Management
Version information1 srlinux:22.11.1, containerlab:0.33.0
Authors Roman Dodin
Discussions Twitter · LinkedIn

As of release 22.11.1, Nokia SR Linux Network OS employs three fully modeled management interfaces:

  • gNMI
  • JSON-RPC
  • CLI

Not only these interfaces are modeled, but they all use the same set of models and therefore enable one of the key differentiators of SR Linux - every management interface has access to the state and configuration datastores and provides the same visibility and configuration capabilities2.

yang1

Every management interface is a client of the same core API

Every management interface, in essence, uses the same API provided by the management server of SR Linux which makes interfaces equal in access rights and visibility.

JSON-RPC as just another client of the same management API

In this tutorial, we are going to meet SR Linux's JSON-RPC interface and learn how to achieve basic management tasks using the curl utility. In the subsequent tutorials, our focus will shift from the JSON-RPC towards the different tooling that leverages it; think of Ansible, Postman tools and integrations with programming languages like Go and Python.

Why JSON-RPC?#

But first, why even bother using JSON-RPC if SR Linux sports a more performant-on-the-wire and modern gNMI interface? While it is true, that gNMI can be considered more performant on the wire by leveraging HTTP2 multiplexing and protobuf encoding, some well established automation stacks may not be able to offer gRPC/gNMI support just yet.
To make SR Linux accessible to non-hyperscalers and network teams who have been using HTTP/JSON-based management tools we offered a JSON-RPC management interface that can be easily integrated with a wide variety of higher-level Network Management Systems (NMS) and automation stacks.

JSON-RPC methods#

Being a custom management interface, JSON-RPC offers both standard methods like get and set to work with the state and configuration datastores of srlinux, as well as custom functions like validate for validating the config and cli to invoke CLI commands on the system.
All of that uses JSON-encoded messages exchanged over HTTP transport.

As the RPC part of the name suggests, users are able to execute certain remote procedures on SR Linux via JSON-RPC interface. We refer to these procedures as methods; the following table summarizes the JSON-RPC provided methods as stated in the JSON-RPC Management Guide.

Method Description
Get Used to retrieve configuration and state details from the system. The get method can be used with candidate, running, and state datastores, but cannot be used with the tools datastore.
Set Used to set a configuration or run operational transaction. The set method can be used with the candidate and tools datastores.
Validate Used to verify that the system accepts a configuration transaction before applying it to the system.
CLI Used to run CLI commands. The get and set methods are restricted to accessing data structures via the YANG models, but the cli method can access any commands added to the system via python plugins or aliases.

We will introduce all of these methods in detail during the practical section of this tutorial.

Configuring JSON-RPC#

SR Linux factory configuration doesn't have JSON-RPC management enabled, but it is easy to configure one. JSON-RPC Configuration Guide does a good job explaining all the bits and pieces of interface configuration, so to not repeat ourselves let's look at what containerlab configures automatically for every SR Linux node based on a single-node lab that we use in this tutorial.

--{ running }--[  ]--
A:srl# info /system json-rpc-server
    system {
        json-rpc-server {
            admin-state enable
            network-instance mgmt {
                http {
                    admin-state enable
                }
                https {
                    admin-state enable
                    tls-profile clab-profile
                }
            }
        }
    }

By default, containerlab enables JSON-RPC management interface in the management network instance4 by configuring json-rpc-server instance running both in secure/https and plain-text/http modes on ports 80 and 443 accordingly. For https endpoint, containerlab uses the tls-profile clab-profile that it generates on lab startup.

Note

JSON-RPC management interface runs on the /jsonrpc HTTP(S) endpoint of the SR Linux, which means that to access this interface, users should use the following URI:

http(s)://<srlinux-address>/jsonrpc #(1)!
  1. where srlinux-address is the address of the management interface of SR Linux. The lab used in this tutorial has a deterministic name for the srlinux node - clab-srl01-srl - which we will use as the address.

With this configuration in place, users can leverage JSON-RPC immediately after containerlab finishes deploying the lab.

Request/response structure#

The management interface sends requests3 to the JSON-RPC server and receives responses. The request/response format is a JSON encoded string and is detailed in the docs. Let's look at the skeleton of the request/response messages as it will help us getting through practical exercises:

JSON-RPC request structure
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "get",
  "params": {
    "commands": [],
    "output-format": "" //(1)!
  }
}
  1. Only applicable for CLI method.

where:

  • jsonrpc - selects the version of the management interface and at the moment of this writing should be always set to 2.0.
  • id - sets the ID of a request which is echoed back in the response to help correlate the message flows.
  • method - sets one of the supported RPC methods used for this request.
  • params - container for RPC commands. We will cover the contents of this container through the practical exercises.
{
  "result": [],
  "id": 0,
  "jsonrpc": "2.0"
}

The response object structure provides a result list that contains the result of the invoked RPC. Additionally, the response object contains the RPC version and request ID.

Authentication#

JSON-RPC server uses basic authentication for both HTTP and HTTPS transports, which means user information must be provided in a request.

User credentials are passed in a request
curl -s -X POST 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc'

In the example above, the user admin with a password NokiaSrl1! is used to authenticate with the JSON-RPC API.

Methods#

Enough with the boring theory, let's have some handson fun firing off requests, and learn how JSON-RPC works in real life. To keep things focused on the JSON-RPC itself, we will be using curl utility as our HTTP client with jq helping format the responses.

Tip

Keep the JSON-RPC Management Guide tab open, as additional theory is provided there which we won't duplicate in this tutorial.

Get#

Starting with the basics, let's see how we can query SR Linux configuration and state datastores using get method of JSON-RPC. How about we start with a simple management task of getting to know the software version we're running in our lab container?

curl by default uses HTTP POST method, thus we don't explicitly specify it. With -d @- <<EOF argument we pass the heredoc-styled body of the request in a JSON format of this POST request.

Our commands list contains a single object with path and datastore values set.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/system/information/version",
                "datastore": "state"
            }
        ]
    }
}
EOF

jq used in the request command displays the json response in a formatted way.

{
  "result": [
      "v22.11.1-184-g6eeaa254f7"
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

There is something to unpack in the message used in this exchange. First, note that in the params container we specified the commands list. Each element in this list is an object that contains a path and datastore values.

Path#

The path is a string that follows gNMI Path Conventions5 and used to point to an element that is . It is not hard to spot that the path follows the SR Linux YANG model and allows us to select a certain leaf that contains the version information.

Datastore#

The datastore value sets the SR Linux datastore we would like to use with our RPC. SR Linux offers four datastores that JSON-RPC users can choose from:

Datastore Description
Candidate Used to change the configuration of the system with the get, set, and validate methods; default datastore is used if the datastore parameter is not provided.
Running Used to retrieve the active configuration with the get method.
State Used to retrieve the running (active) configuration along with the operational state.
tools Used to perform operational tasks on the system; only supported with the update action command and the set method.

By specifying path=/system/information/version and datastore=state in our request, we instruct SR Linux to return the value of the targeted leaf using the state datastore. An equivalent CLI command on SR Linux to retrieve the same would be:

--{ running }--[  ]--
A:srl# info from state system information version  
    system {
        information {
            version v22.11.1-184-g6eeaa254f7
        }
    }
--{ running }--[  ]--

Note

Datastore value can be set either per-command as in the example above, or on the params level. Command-scope datastore value takes precedence over the params-scope value.

When datastore value is omitted, running datastore is assumed. For example, repeating the same request without specifying the datastore will error, as running datastore doesn't hold state leaves and thus can't return the version leaf under the /system/information container.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/system/information/version"
            }
        ]
    }
}
EOF

An error is returned since running datastore holds configuration, not state, and version leaf is a state one.

{
    "error": {
        "code": -1,
        "message": "Path not valid - unknown element 'version'. Options are [contact, location]"
    },
    "id": 0,
    "jsonrpc": "2.0"
}

The response object contains the same ID used in the request, as well as the list of results. The number of entries in the results list will match the number of commands specified in the request.

How to get entire configuration?

It is quite easy, actually. Just send the request with the / path:

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/"
            }
        ]
    }
}
EOF

To get rid of the response fields and only get the value of the result, change the jq expression to jq '.result[]'.

In the same way, to get the full state of the switch, add state datastore:

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq '.result[]' > /tmp/test.json
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/",
                "datastore": "state"
            }
        ]
    }
}
EOF

Multiple commands#

JSON-RPC allows users to batch commands of the same method in the same request. Just add elements to the commands list of the body message. In the following example we query the state datastore for two elements inside the same request:

  1. system version
  2. statistics data of the mgmt0 interface
curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/system/information/version",
                "datastore": "state"
            },
            {
                "path": "/interface[name=mgmt0]/statistics",
                "datastore": "state"
            }
        ]
    }
}
EOF

Response message will contain a list of results with results being ordered the same way as the commands in the request.

{
    "result": [
        "v22.11.1-184-g6eeaa254f7",
        {
            "in-octets": "140285",
            "in-unicast-packets": "1389",
            "in-broadcast-packets": "0",
            "in-multicast-packets": "1",
            "in-discarded-packets": "0",
            "in-error-packets": "5",
            "in-fcs-error-packets": "0",
            "out-octets": "748587",
            "out-mirror-octets": "0",
            "out-unicast-packets": "2349",
            "out-broadcast-packets": "6",
            "out-multicast-packets": "30",
            "out-discarded-packets": "0",
            "out-error-packets": "0",
            "out-mirror-packets": "0",
            "carrier-transitions": "1"
        }
    ],
    "id": 0,
    "jsonrpc": "2.0"
}

Note, that when the path in a request points to a leaf (like /system/information/version), then the result entry will be just the value of this leaf. In contrast with that, when the path is pointing to a container, then a JSON object is returned, like in the case of the result for the /interface[name=mgmt0]/statistics path.

Set#

JSON-RPC is quite flexible when it comes to creating, updating and deleting configuration on SR Linux7. And additionally, Set method allows users to execute operational (aka /tools) commands.

When changing configuration with the Set method, the JSON-RPC server creates a private candidate datastore, applies the changes and performs an implicit commit. Thus, changes are commited automatically (if they are valid) for each RPC.

Update#

Switching to the 1st gear, let's just add a description to our mgmt0 interface.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "update",
                "path": "/interface[name=mgmt0]/description:set-via-json-rpc"
            }
        ]
    }
}
EOF
{
    "result": [
        {}
    ],
    "id": 0,
    "jsonrpc": "2.0"
}

Checking that the interface description has been set successfully.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/interface[name=mgmt0]/description",
                "datastore": "state"
            }
        ]
    }
}
EOF

Response:

{
    "result": [
        "set-via-json-rpc"
    ],
    "id": 0,
    "jsonrpc": "2.0"
}

Action#

As you can see, the request message now contains the set method, and in the list of commands we have a new field - action. Action field is only set with set and validate methods and can take the following values:

  • update - updates a leaf or container with the new value.
  • delete - deletes a leaf or container.
  • replace - replaces configuration with the supplied new configuration blob for a specified path. This is equivalent to a delete+update operation tandem.

Since we wanted to set a description on the interface, the update action was just enough.

The response object for a successful Set method contains a single empty JSON object regardless of how many commands were in the request.

Path value formats#

I bet you noticed the peculiar path value used in the Set request message - "path": "/interface[name=mgmt0]/description:set-via-json-rpc". This path notation follows the <path>:<value> schema, where a scalar value of a leaf is provided in the path itself separated by a : char.

Alternatively, users can specify the value using the "value" field inside the command. This allows to provide structutred values for a certain path. For example, lets set multiple fields under the /system/information container:

Set two leaves - location and contact under the /system/information container by using the value field of the command.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "update",
                "path": "/system/information",
                "value": {
                  "location": "the Netherlands",
                  "contact": "Roman Dodin"
                }
            }
        ]
    }
}
EOF
{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}
curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "datastore": "state",
        "commands": [
            {
                "path": "/system/information/location"
            },
            {
                "path": "/system/information/contact"
            }
        ]
    }
}
EOF

Result:

{
  "result": [
    "the Netherlands",
    "Roman Dodin"
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Replace#

With the replace action it is possible to replace the entire configuration block for a given path with another configuration blob supplied in the request message. In essense, a replace operation is a combination of delete + update actions for a given path.

To demonstrate replace operation in action, we will use the same /system/information container, that by now contains the contact and location leaves:

Verify current configuration of /system/information container
 docker exec clab-srl01-srl sr_cli info from running /system information
    system {
        information {
            contact "Roman Dodin"
            location "the Netherlands"
        }
    }

Let's replace this conatiner with setting just the contact leaf to "John Doe" value.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "replace",
                "path": "/system/information",
                "value": {
                  "contact": "John Doe"
                }
            }
        ]
    }
}
EOF
{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}
 docker exec clab-srl01-srl sr_cli info from running /system information
system {
    information {
        contact "John Doe"
    }
}

Notice, how the verification command proves that the whole configuration under /system/information has been replaced with a single contact leaf value, there is no trace of location leaf.

Replacing the whole configuration

One of the common management tasks is to replace the entire config with a golden or intended configuration. To do that with JSON-RPC use / path and a file with JSON-formatted config:

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "replace",
                "path": "/",
                "value": $(cat /path/to/config.json)
            }
        ]
    }
}

Delete#

To delete a configuration region for a certain path use delete action of the Set method. For example, let's delete everythin under the /system/information container:

We start with information container containing contact leaf.

 docker exec clab-srl01-srl sr_cli info from running /system information 
    system {
        information {
            contact "John Doe"
        }
    }

Delete the configuration under the /system/information container.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "delete",
                "path": "/system/information"
            }
        ]
    }
}
EOF

{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Verify that the container is now empty:

 docker exec clab-srl01-srl sr_cli info from running /system information
system {
    information {
    }
}

Note

Delete operation will not error when trying to delete a valid but non-existing node.

Multiple actions#

For advanced configuration management tasks JSON-RPC interface allows to batch different actions in a single RPC. Multiple commands with various actions can be part of an RPC message body; these actions are going to be applied to the same private candidate datastore that JSON-RPC interfaces uses and will be committed together as a single transaction.

For example, let's create an RPC that will have all the actions batched together:

In this composite request we replace the description for the management interface, then create a new network-instance vrf-red and finally deleteing a login banner. All those actions will be committed together as a single transaction.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "commands": [
            {
                "action": "replace",
                "path": "/interface[name=mgmt0]/description:set-via-multi-cmd-json-rpc"
            },
            {
                "action": "update",
                "path": "/network-instance[name=vrf-red]",
                "value": {
                    "name": "vrf-red",
                    "description": "set-via-json-rpc"
                }
            },
            {
                "action": "delete",
                "path": "/system/banner/login-banner"
            }
        ]
    }
}
EOF
This example also shows how to create an element of a list (like a new network instance) - specify the key in the path and the content of the list member in the value.

{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Tools commands#

Set method allows users to invoke operational commands that use a specific tools datastore. These commands are typically RPCs themselves, as they invoke some action on the SR Linux NOS.

For example, /tools interface mgmt0 statistics clear command when invoked via CLI will clear stats for mgmt0 interface. The same command can be called out using the Set method, as well as using the CLI method.
The difference being that with Set method users should specify the modelled path using gNMI path notations, while with the CLI method users use the syntax of the CLI.

Check the amount of incoming octets for mgmt0 interface.

--{ + running }--[  ]--
A:srl# info from state /interface mgmt0 statistics in-octets  
    interface mgmt0 {
        statistics {
            in-octets 383557
        }
    }

Clearing statistics of mgmt0 interface by calling the /tools command using the modelled path6.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "set",
    "params": {
        "datastore": "tools",
        "commands": [
            {
                "action": "update",
                "path": "/interface[name=mgmt0]/statistics/clear"
            }
        ]
    }
}
EOF

{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

The following output shows that the stats has been cleared via the tools command executed via JSON-RPC.

--{ + running }--[  ]--
A:srl# info from state /interface mgmt0 statistics in-octets
    interface mgmt0 {
        statistics {
            in-octets 4379
        }
    }

Validate#

One of the infamous fallacies that people associate with gNMI is its inability to work with candidate datastores, do confirmed commits and validate configs. While JSON-RPC interface doesn't let you do incremental updates to an opened candidate datastore with the Set method, it allows you to validate a portion of a config using Validate method.

Under the hood, SR Linux executes commit validate command on the provided configuration blob, and no configuration changes are made to the system. The goal of Validate method is to give users a way to ensure that the config they are about to push will be accepted.

Validate method works with the same actions as Set method - update, replace and delete. For example, lets take our composite change request from the last exercise and validate it.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "validate",
    "params": {
        "commands": [
            {
                "action": "replace",
                "path": "/interface[name=mgmt0]/description:set-via-multi-cmd-json-rpc"
            },
            {
                "action": "update",
                "path": "/network-instance[name=vrf-red]",
                "value": {
                    "name": "vrf-red",
                    "description": "set-via-json-rpc"
                }
            },
            {
                "action": "delete",
                "path": "/system/banner/login-banner"
            }
        ]
    }
}
EOF
{
  "result": [
    {}
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

The empty result object indicates that changes were successfully validated and no errors were detected. What happens when the changes are not valid? Let's make some errors in our request, for example, let's try setting a description for an invalid interface:

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "validate",
    "params": {
        "commands": [
            {
                "action": "update",
                "path": "/interface[name=GigabitEthernet1/0]/description:set-via-json-rpc"
            }
        ]
    }
}
EOF
{
  "error": {
    "code": -1,
    "message": "Failed to parse value 'GigabitEthernet1/0' for key 'name' (node 'interface') - Invalid value \"GigabitEthernet1/0\": Must match the pattern '(mgmt0|mgmt0-standby|system0|lo(0|1[0-9][0-9]|2([0-4][0-9]|5[0-5])|[1-9][0-9]|[1-9])|lif-.*|vhn-.*|enp(0|1[0-9][0-9]|2([0-4][0-9]|5[0-5])|[1-9][0-9]|[1-9])s(0|[1-9]|[1-2][0-9]|3[0-1])f[0-7]|ethernet-([1-9](\\d){0,1}(/[abcd])?(/[1-9](\\d){0,1})?/(([1-9](\\d){0,1})|(1[0-1]\\d)|(12[0-8])))|irb(0|1[0-9][0-9]|2([0-4][0-9]|5[0-5])|[1-9][0-9]|[1-9])|lag(([1-9](\\d){0,1})|(1[0-1]\\d)|(12[0-8])))'"
  },
  "id": 0,
  "jsonrpc": "2.0"
}

And SR Linux immediately returns an error explaining where exactly the error was.

CLI#

One of the reasons we ended up having JSON-RPC interface and not RESTCONF was the need to support CLI-formatted operations. At SR Linux, we are big believers in all things modeled, but we can't neglect the fact that transition to model-based world may take time for some teams. In the interim, these teams can effectively accomplish operational tasks using CLI-based automation.

With JSON-RPC CLI method we allow users to remotely execute CLI commands while offering HTTP transport reliability and saving users from the burdens of screen scraping.

Tip

CLI method also allows to call CLI commands that are not modelled, such as aliases or plugins (e.g. show version). But it is not possible to execute interactive commands, e.g. ping, bash, etc.

Staring with basics, let's see what it takes to execute a simple show version command using JSON-RPC?

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "show version"
        ]
    }
}
EOF
{
  "result": [
    {
      "basic system info": {
        "Hostname": "srl",
        "Chassis Type": "7220 IXR-D3L",
        "Part Number": "Sim Part No.",
        "Serial Number": "Sim Serial No.",
        "System HW MAC Address": "1A:90:00:FF:00:00",
        "Software Version": "v22.11.1",
        "Build Number": "184-g6eeaa254f7",
        "Architecture": "x86_64",
        "Last Booted": "2022-12-06T11:38:51.482Z",
        "Total Memory": "24052875 kB",
        "Free Memory": "17004746 kB"
      }
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}
--{ + running }--[  ]--
A:srl# show version | as json  
{
  "basic system info": {
    "Hostname": "srl",
    "Chassis Type": "7220 IXR-D3L",
    "Part Number": "Sim Part No.",
    "Serial Number": "Sim Serial No.",
    "System HW MAC Address": "1A:90:00:FF:00:00",
    "Software Version": "v22.11.1",
    "Build Number": "184-g6eeaa254f7",
    "Architecture": "x86_64",
    "Last Booted": "2022-12-06T11:38:51.482Z",
    "Total Memory": "24052875 kB",
    "Free Memory": "16858484 kB"
  }
}
--{ + running }--[  ]--
A:srl# show version
---------------------------------------------------
Hostname             : srl
Chassis Type         : 7220 IXR-D3L
Part Number          : Sim Part No.
Serial Number        : Sim Serial No.
System HW MAC Address: 1A:90:00:FF:00:00
Software Version     : v22.11.1
Build Number         : 184-g6eeaa254f7
Architecture         : x86_64
Last Booted          : 2022-12-06T11:38:51.482Z
Total Memory         : 24052875 kB
Free Memory          : 16973972 kB
---------------------------------------------------

Okay, there is a lot of output here, focus first on the request message. In the request body, we have cli method set, and the commands list contains a list of strings, where each string is a CLI command as it is seen in the CLI. We have only one command to execute, hence our list has only one element - show version.

The response message contains a list of results. Since we had only one command, our results list contains a single element, which matches the output of the show version | as json command when it is invoked in the CLI.

Note

The peculiar "basic system info" key in the response is a special node name that is set in the show version plugin of the CLI as a constant.

SR Linux uses a concept of CLI plugins for all its show commands, and each such command has a root node name that has a unique name. For show version command this node name is basic system info.

Output format#

Alright, we executed a CLI command, but the returned result is formed as JSON, which is a default formatting option for JSON-RPC. Can we influence that? Turns out we can.

With output-format field of the request we can choose the formatting of the returned data:

  • json - the default format option
  • text - textual/ascii output as seen in the CLI
  • table - table view of the returned data

jq arguments used in this command filter out the result element and use the raw processing to render newlines.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq -r '.result[]'
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "show version"
        ],
        "output-format": "text"
    }
}
EOF

-------------------------------------------------------------
Hostname             : srl
Chassis Type         : 7220 IXR-D3L
Part Number          : Sim Part No.
Serial Number        : Sim Serial No.
System HW MAC Address: 1A:90:00:FF:00:00
Software Version     : v22.11.1
Build Number         : 184-g6eeaa254f7
Architecture         : x86_64
Last Booted          : 2022-12-06T11:38:51.482Z
Total Memory         : 24052875 kB
Free Memory          : 16414484 kB
-------------------------------------------------------------
curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq -r '.result[]'
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "show version"
        ],
        "output-format": "table"
    }
}
EOF
+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+
|    Hostname     |  Chassis Type   |   Part Number   |  Serial Number  |  System HW MAC  |    Software     |  Build Number   |  Architecture   |   Last Booted   |  Total Memory   |   Free Memory   |
|                 |                 |                 |                 |     Address     |     Version     |                 |                 |                 |                 |                 |
+=================+=================+=================+=================+=================+=================+=================+=================+=================+=================+=================+
| srl             | 7220 IXR-D3L    | Sim Part No.    | Sim Serial No.  | 1A:90:00:FF:00: | v22.11.1        | 184-g6eeaa254f7 | x86_64          | 2022-12-06T11:3 | 24052875 kB     | 16466207 kB     |
|                 |                 |                 |                 | 00              |                 |                 |                 | 8:51.482Z       |                 |                 |
+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+-----------------+

Context switching#

When using CLI method, the commands entered one after another work exactly the same as when you enter them in the CLI. This means that current working context changes based on the entered commands. For instance, if you first enters to the interface context and then execute the info command, it will work out nicely, since the context switch is persistent across commands in the same RPC.
The next RPC, as expected, will not maintain the context of a previous RPC; by default running datastore is activated and / context is set.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "interface mgmt0",
            "info"
        ]
    }
}
EOF
{
  "result": [
    {},
    {
      "name": "mgmt0",
      "description": "set-via-multi-cmd-json-rpc",
      "admin-state": "enable",
      "subinterface": [
        {
          "index": 0,
          "admin-state": "enable",
          "ipv4": {
            "dhcp-client": {}
          },
          "ipv6": {
            "dhcp-client": {}
          }
        }
      ]
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Note, how the result list contains two elements matching the number of commands used in the request. The first command - /interface mgmt0 - doesn't produce any output, as it just enters the context of an interface. The second command though - info - produces the output as it dumps the configuration items for the interface, and we get its output with json formatting.

Configuration#

You guessed it right, you can also perform configuration tasks with CLI method and use the CLI format of the configuration to do that. Let's configure an interface using CLI-styled commands in different ways:

Note

When using CLI method for configuration tasks explicit entering into the candidate datastore and committing is necessary.

One option to use when executing configuration tasks is to use the commands sequence that an operator would have used. This way every other command respects the present working context.

This method is error-prone, since tracking the context changes is tedious. But, still, this is an option.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "enter candidate",
            "/interface ethernet-1/1",
            "description \"this is a new interface\"",
            "admin-state enable",
            "commit now"
        ]
    }
}
EOF
{
  "result": [
    {},
    {},
    {},
    {},
    {
      "text": "All changes have been committed. Leaving candidate mode.\n"
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Flattened commands are levied from the burdens of the contextual commands, as each command starts from the root. This makes configuration snippets longer, but safer to use.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "enter candidate",
            "/interface ethernet-1/1 description \"this is a new interface\" admin-state enable",
            "commit now"
        ]
    }
}
EOF
{
  "result": [
    {},
    {},
    {
      "text": "All changes have been committed. Leaving candidate mode.\n"
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

Another popular way to use CLI-styled configs is to dump the configuration from the device, template or change a few fields in the text blob and use it for configuration. In the example below we did info from running /interface ethernet-1/1 and captured the output. We used this output as is in our request body just escaping the quotes.

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "enter candidate",
            "/interface ethernet-1/1 {
              description \"this is a new interface\"
              admin-state enable
          }",
            "commit now"
        ]
    }
}
EOF
{
  "result": [
    {},
    {},
    {
      "text": "All changes have been committed. Leaving candidate mode.\n"
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

All of the methods should result in the same configuration added:

 docker exec clab-srl01-srl sr_cli info from running /interface ethernet-1/1
    interface ethernet-1/1 {
        description "this is a new interface"
        admin-state enable
    }

Tools commands#

Remember how we executed the tools commands within the Set method? We can do the same with CLI method, but in this case we provide the command in the CLI-style. Using the same command to clear statistics counters:

curl -s 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "cli",
    "params": {
        "commands": [
            "tools interface mgmt0 statistics clear"
        ]
    }
}
EOF

The result contains the text output of the tools command which confirms that the command worked:

{
  "result": [
    {
      "text": "/interface[name=mgmt0]:\n    interface mgmt0 statistics cleared\n\n"
    }
  ],
  "id": 0,
  "jsonrpc": "2.0"
}

HTTPS#

All of the examples have been using plain HTTP schema. As was explained in the beginning of this tutorial, containerlab configures JSON-RPC server to run both HTTP and HTTPS transports.

To use the secured transport any request can be changed to https schema and skipped certificate verification:

curl -sk https://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/system/information/version",
                "datastore": "state"
            }
        ]
    }
}
EOF

If you want to verify the self-signed certificate that containerlab generates at startup use the CA certificate that containerlab keeps in the lab directory:

curl -s --cacert ./clab-srl01/ca/root/root-ca.pem https://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc -d @- <<EOF | jq
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/system/information/version",
                "datastore": "state"
            }
        ]
    }
}
EOF

Error handling#

When either of the commands specified in the RPC request message fails, the returned message will contain an error, even if other commands might be correct. This atomicity of the commands is valid for both Get and Set methods.

For example, the following request has two commands, where 2nd command uses a wrong path.

curl -v 'http://admin:NokiaSrl1!@clab-srl01-srl/jsonrpc' -d @- <<EOF
{
    "jsonrpc": "2.0",
    "id": 0,
    "method": "get",
    "params": {
        "commands": [
            {
                "path": "/interface[name=mgmt0]/statistics",
                "datastore": "state"
            },
            {
                "path": "/system/somethingwrong",
                "datastore": "state"
            }
        ]
    }
}
EOF
{
  "error": {
    "code": -1,
    "message": "Path not valid - unknown element 'somethingwrong'. Options are [features, trace-options, management, configuration, aaa, authentication, warm-reboot, boot, l2cp-transparency, lacp, lldp, mtu, name, dhcp-server, event-handler, ra-guard-policy, gnmi-server, tls, json-rpc-server, bridge-table, license, dns, ntp, clock, ssh-server, ftp-server, snmp, sflow, load-balancing, banner, information, logging, mirroring, network-instance, maintenance, app-management]"
  },
  "id": 0,
  "jsonrpc": "2.0"
}

The response will contain just an error container, even though technically the first command is correct. Note, that the HTTP response code is still 200 OK, since JSON-RPC was able to deliver and execute the RPC, it is just that the RPC lead to an error.


  1. the following versions have been used to create this tutorial. The newer versions might work; please pin the version to the mentioned ones if they don't. 

  2. differences in capabilities that different management interfaces provide are driven by the interfaces standards. 

  3. using HTTP POST method. 

  4. JSON-RPC, as gNMI, can run in a user-configured network-instance. 

  5. https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-path-conventions.md 

  6. Tools paths can be viewed in our tree YANG browser

  7. Support for setting configuration with Openconfig schema will be added at a later date. Currently only CLI method allows working with Openconfig schema via enter oc command. 

Comments