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
| Device | Role | Access |
|---|---|---|
| DeviceA | Managed 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 enablestarts the embedded container and prepares the Guest Shell environment. The device initializes container resources and mounts the environment.guestshell run bashdrops 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 bashand getting a shell prompt.- How to verify:
device# guestshell run bash→ expected promptroot@guest-shell:~#
- How to verify:
- Check 2: Python SET applied hostname correctly (gNMI SET emulation).
- How to verify: Run the
gnmi_set_hostname.pyscript and confirmhostname lab-gnmi-clientappears in running-config output.
- How to verify: Run the
- Check 3: Subscribe emulation produced an initial snapshot and a sync marker followed by updates.
- How to verify: Run
gnmi_subscribe.pyand confirm "SNAPSHOT_START", "SNAPSHOT_END", "SYNC_RESPONSE: initial data sent", and several "UPDATE" entries appear.
- How to verify: Run
Common Mistakes
| Symptom | Cause | Fix |
|---|---|---|
| Guestshell commands fail or "command not found" | Guest Shell is not enabled on the device | Run guestshell enable and wait for the container to fully start, then guestshell run bash |
Python script errors: ModuleNotFoundError for cli | Script 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 telemetry | Script did not print or signal a sync marker; real gNMI sync_response is a protocol-level message | Emulate 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 applied | cli.configure commands malformed or missing full command | Check 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.