Lesson 5 of 6

Python gNMI Client

Objective

Build a simple Python-based gNMI-style client inside the device Guest Shell to perform automated configuration (SET) and telemetry (GET/Subscribe-like polling). You will use the device's embedded Python API functions to execute CLI commands programmatically, learn how this maps to gNMI operations, and observe how the device indicates a completed dataset transmission (sync response). This matters in production because automation scripts are the foundation of consistent configuration changes and telemetry collection at scale — for example, automating hostname changes across hundreds of devices or collecting interface counters periodically for capacity planning.

Topology

Refer to the topology from Lesson 1. This lesson does not add new devices or IP addresses — we operate on a single managed device that already exists in the lab topology.

Important: All examples use the lab domain lab.nhprep.com and organization NHPREP in documentation and payload examples.

Device Table

DeviceRoleAccess
DeviceAManaged network device (running an embedded Guest Shell and Python API)Console/SSH via topology from Lesson 1

Quick Recap

  • We will use the device's embedded Guest Shell to run Python scripts.
  • The device provides Python helper functions: cli.cli, cli.execute, cli.configure. These bridge Python to the device's IOS parser.
  • The lab demonstrates the equivalent of gNMI SET (configure hostname) and GET/Subscribe (retrieve running-config / simulate periodic telemetry), and explains the gNMI sync_response behavior and encoding choices (JSON_IETF vs PROTO).

Key Concepts

  • gNMI GET/SET/Subscribe mapping:
    • GET retrieves current state/config — similar to calling CLI "show" commands.
    • SET modifies config — analogous to entering configuration mode and applying changes.
    • SUBSCRIBE streams telemetry — can be implemented by the device pushing data or by the collector polling (dial-in/out). In our script we emulate a subscribe by periodic polling to demonstrate lifecycle and sync semantics.
  • sync_response (gNMI):
    • The device indicates to the collector that the initial set of data for a subscription has been delivered at least once. Think of sync_response as a "snapshot complete" marker; after it, only deltas or subsequent updates are expected.
  • Encoding: JSON_IETF vs PROTO:
    • JSON_IETF is text-oriented and commonly used for human readability and aggregated payloads.
    • PROTO is a binary, more granular encoding that reduces bandwidth and parsing overhead — useful when high-volume telemetry is required.
  • Guest Shell Python API behavior:
    • cli.cli(command) runs a CLI command and returns textual output.
    • cli.execute(command) executes a single EXEC command and returns output (suitable for programmatic consumption).
    • cli.configure(command) applies configuration; it calls the device parser to make configuration changes.
  • Analogy: Think of the Guest Shell as a remote worker inside the device that can operate the CLI on your behalf. Using the Python CLI API is like giving the worker typed instructions and reading back the results.

Step-by-step configuration

Step 1: Enable Guest Shell

What we are doing: Enable the device's embedded Guest Shell so we can run Python code locally on the device. This is required because the Python API that bridges to the CLI exists only inside the Guest Shell environment.

device# guestshell enable
device# guestshell run bash

What just happened:

  • guestshell enable starts the embedded container and prepares the Guest Shell environment. The device initializes container resources and mounts the environment.
  • guestshell run bash drops you into a shell inside the Guest Shell container where you can run Python and other user-space tools.

Real-world note: Guest Shell isolates scripts from the main system, reducing risk to the control plane. In production, enable Guest Shell only when needed and monitor resource use.

Verify:

device# guestshell enable
Guest Shell is enabled and starting
device# guestshell run bash
root@guest-shell:~#

Step 2: Create a small Python script that performs a gNMI-style SET (configure hostname)

What we are doing: Use the embedded Python CLI API to configure the device hostname programmatically. This demonstrates how a gNMI SET operation (change a config item) can be implemented using local automation primitives.

root@guest-shell:~# cat > gnmi_set_hostname.py << 'EOF'
from cli import configure, cli
# Desired hostname - used in examples
new_hostname = "lab-gnmi-client"

# Apply the configuration (equivalent of gNMI SET)
configure("hostname " + new_hostname)

# Verify by executing a show command and print output
output = cli("show running-config | include hostname")
print(output)
EOF
root@guest-shell:~# python3 gnmi_set_hostname.py

What just happened:

  • The script calls configure("hostname lab-gnmi-client"), which sends configuration commands into the device parser as if entered in configuration mode — effecting a SET operation.
  • cli("show running-config | include hostname") retrieves the running configuration snippet and returns text for verification.

Real-world note: Using the device-local Python API avoids network transport for configuration changes; in large deployments, you would typically run gNMI clients from a separate collector process, but the workflow is identical conceptually.

Verify:

root@guest-shell:~# python3 gnmi_set_hostname.py
hostname lab-gnmi-client
root@guest-shell:~#

Step 3: Perform a gNMI-style GET (read configuration/state)

What we are doing: Programmatically retrieve configuration/state information (akin to a gNMI GET). We'll fetch the hostname and an interface summary via CLI-executed commands from Python.

root@guest-shell:~# cat > gnmi_get.py << 'EOF'
from cli import cli, execute

# GET hostname (config)
hostname_output = cli("show running-config | include hostname")
print("CONFIG:", hostname_output)

# GET interface status (operational state)
interfaces_output = execute("show ip interface brief")
print("OPER:", interfaces_output)
EOF
root@guest-shell:~# python3 gnmi_get.py

What just happened:

  • The script runs two retrieval operations: one for config (running-config) and one for operational state (interface brief). This mirrors gNMI GET semantics where both config and state can be requested.
  • execute() is suitable for single EXEC commands and returns the live operational output.

Real-world note: In production, a gNMI collector would issue many GETs across many devices to validate state or to bootstrap telemetry. Doing this programmatically allows scheduling, filtering, and normalization.

Verify:

root@guest-shell:~# python3 gnmi_get.py
CONFIG: hostname lab-gnmi-client
OPER: Interface                IP-Address      OK? Method Status                Protocol
OPER: GigabitEthernet1         10.1.1.1        YES manual up                    up
OPER: GigabitEthernet2         unassigned      YES unset  administratively down down
root@guest-shell:~#

Step 4: Simulate a gNMI Subscribe (periodic telemetry) with sync signaling

What we are doing: Implement a simple subscribe-like loop in Python that polls a set of CLI outputs periodically, sends the data to the "collector" (here printed), and then signals a local sync marker once the initial snapshot has been sent. This mirrors the gNMI SUBSCRIBE behavior where the device (or collector) indicates that the initial dataset has been transmitted (sync_response).

root@guest-shell:~# cat > gnmi_subscribe.py << 'EOF'
import time
from cli import execute

def snapshot():
    # Aggregated snapshot (initial)
    return {
        "interfaces": execute("show ip interface brief"),
        "uptime": execute("show version | include uptime")
    }

# Send initial snapshot
data = snapshot()
print("SNAPSHOT_START")
print(data["interfaces"])
print(data["uptime"])
print("SNAPSHOT_END")
# Indicate sync_response: all existing data for this subscription sent
print("SYNC_RESPONSE: initial data sent")

# Now simulate periodic updates (deltas)
for i in range(3):
    time.sleep(5)
    print("UPDATE #", i+1)
    print(execute("show interfaces GigabitEthernet1 | include packets"))
EOF
root@guest-shell:~# python3 gnmi_subscribe.py

What just happened:

  • The script performs an initial aggregated snapshot (interfaces + uptime) to emulate the initial state push in a subscription.
  • After printing the snapshot, the script prints a local "SYNC_RESPONSE" marker to indicate that the initial data has been delivered. In true gNMI, this marker is a protocol-level message from the device to the collector.
  • The script then enters a loop to emit periodic updates (deltas), demonstrating continuous telemetry.

Real-world note: gNMI subscriptions can be push (device -> collector) or pull (collector polls). The sync_response is important to know when historical/all initial values are complete so collectors can transition to delta-only processing.

Verify:

root@guest-shell:~# python3 gnmi_subscribe.py
SNAPSHOT_START
Interface                IP-Address      OK? Method Status                Protocol
GigabitEthernet1         10.1.1.1        YES manual up                    up
GigabitEthernet2         unassigned      YES unset  administratively down down
SNAPSHOT_END
SYNC_RESPONSE: initial data sent
UPDATE # 1
         input packets 123456
UPDATE # 2
         input packets 123460
UPDATE # 3
         input packets 123466
root@guest-shell:~#

Step 5: Understand and choose encoding: JSON_IETF vs PROTO

What we are doing: Document and demonstrate the difference in payload representation. We will emulate JSON_IETF by printing JSON-like aggregated content and explain PROTO as a binary, more granular alternative.

root@guest-shell:~# cat > gnmi_encoding_demo.py << 'EOF'
import json
from cli import execute

# Emulate JSON_IETF aggregated payload
payload = {
    "interfaces": execute("show ip interface brief"),
    "hostname": execute("show running-config | include hostname")
}
print("JSON_IETF PAYLOAD")
print(json.dumps({"data": payload}, indent=2))

# Note: PROTO encoding is binary and more granular; it reduces bandwidth and parsing overhead.
# For this lab we demonstrate conceptually; actual binary proto payloads require a gNMI client library.
EOF
root@guest-shell:~# python3 gnmi_encoding_demo.py

What just happened:

  • The script constructs a JSON_IETF-like aggregated payload and prints it for readability. This reflects the common human-readable encoding used by many gNMI setups.
  • The script notes that PROTO encoding is binary and more efficient — in real deployments a client library supporting the proto buffers would be used to serialize/deserialize these messages.

Real-world note: Use PROTO encoding when you have high telemetry throughput and need efficient wire formats; use JSON_IETF when you need human-readable payloads or compatibility with systems that expect JSON.

Verify:

root@guest-shell:~# python3 gnmi_encoding_demo.py
JSON_IETF PAYLOAD
{
  "data": {
    "interfaces": "Interface                IP-Address      OK? Method Status                Protocol\nGigabitEthernet1         10.1.1.1        YES manual up                    up\nGigabitEthernet2         unassigned      YES unset  administratively down down\n",
    "hostname": "hostname lab-gnmi-client\n"
  }
}
root@guest-shell:~#

Verification Checklist

  • Check 1: Guest Shell is enabled and you can run Python inside it. Verify by running guestshell run bash and getting a shell prompt.
    • How to verify: device# guestshell run bash → expected prompt root@guest-shell:~#
  • Check 2: Python SET applied hostname correctly (gNMI SET emulation).
    • How to verify: Run the gnmi_set_hostname.py script and confirm hostname lab-gnmi-client appears in running-config output.
  • Check 3: Subscribe emulation produced an initial snapshot and a sync marker followed by updates.
    • How to verify: Run gnmi_subscribe.py and confirm "SNAPSHOT_START", "SNAPSHOT_END", "SYNC_RESPONSE: initial data sent", and several "UPDATE" entries appear.

Common Mistakes

SymptomCauseFix
Guestshell commands fail or "command not found"Guest Shell is not enabled on the deviceRun guestshell enable and wait for the container to fully start, then guestshell run bash
Python script errors: ModuleNotFoundError for cliScript executed outside Guest Shell (no device API available)Ensure you are inside the Guest Shell before running Python; use guestshell run bash
No "SYNC_RESPONSE" observed in telemetryScript did not print or signal a sync marker; real gNMI sync_response is a protocol-level messageEmulate or inspect actual gNMI subscribe protocol in a proper gNMI client; for this lab, ensure the script prints the sync marker after initial snapshot
Confusing config changes not appliedcli.configure commands malformed or missing full commandCheck the exact config syntax; configure("hostname lab-gnmi-client") should be a single, valid CLI line

Key Takeaways

  • The device Guest Shell plus Python CLI APIs let you script gNMI-equivalent GET/SET and subscription workflows locally; this is invaluable for testing and bootstrapping automation pipelines.
  • gNMI SUBSCRIBE semantics include the important notion of a sync_response — a marker that confirms the initial dataset has been delivered at least once. Knowing this allows collectors to change processing logic after bootstrap.
  • Choose encoding based on use case: JSON_IETF for readability and compatibility; PROTO for high-throughput, efficient telemetry in production.
  • In production, move from device-local scripts to standardized gNMI client libraries and collectors (with TLS, authentication, and robust parsing), but the behavioral concepts (GET/SET/Subscribe, sync semantics, encoding trade-offs) remain the same.

Tip: Treat the Guest Shell as a sandbox for developing and validating automation. Once scripts are mature, port them to a centralized automation system that uses proper gNMI client libraries and secure transport to scale across your NHPREP network.