feat: Cisco IOS-XR gNMI support via module-anchored SetRequest encoding - #442
feat: Cisco IOS-XR gNMI support via module-anchored SetRequest encoding#442steiler wants to merge 8 commits into
Conversation
Adds first-class support for Cisco IOS-XR / XRd devices as gNMI targets by introducing a materialization layer that owns all NOS-specific encoding, keeping the gNMI transport driver a thin wire-execution layer. Companion proto change: sdcio/sdc-protos#120 (adds `device_profile` field) ┌─────────────────────────────────────────────────────────────────────────┐ │ BEFORE │ │ │ │ applyIntent │ │ │ │ │ ▼ │ │ TargetSource adapter ◄── api.Entry wrapped here │ │ │ │ │ ▼ │ │ Target.Set(TargetSource) │ │ │ │ │ ▼ │ │ gNMI driver ── serializes internally ──► SetRequest │ │ └─ Update{origin:"", /} │ │ └─ entire JSON blob │ │ │ │ (IOS-XR rejects: unknown native YANG keys in OpenConfig context) │ └─────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────┐ │ AFTER │ │ │ │ applyIntent │ │ │ api.Entry + replace flag │ │ ▼ │ │ materialize.BuildPlan(entry, device-profile) │ │ │ │ │ ├─[generic / proto]──────────────► GnmiSetPlan (single update) │ │ │ │ │ └─[cisco-ios-xr + json/json_ietf] │ │ │ │ │ ▼ │ │ permodule encoder │ │ group children by ModuleName │ │ │ │ │ ▼ │ │ GnmiSetPlan │ │ ├─ Update{origin:"Cisco-IOS-XR-ip-static-cfg", /router} │ │ ├─ Update{origin:"Cisco-IOS-XR-ifmgr-cfg", /interfaces} │ │ └─ ...one per module... │ │ │ │ ▼ │ │ Target.Set(SouthboundSetPlan) ◄── typed plan, no NOS logic here │ │ │ │ │ ▼ │ │ gNMI driver (transport only) ──────► single SetRequest │ │ └─ all module Updates atomic │ └─────────────────────────────────────────────────────────────────────────┘ Core additions: - New `pkg/datastore/target/materialize` package: translates `api.Entry` + device-profile into a typed `SouthboundSetPlan` before any transport call - New `pkg/datastore/target/gnmi/permodule` encoder: groups config tree children by YANG module name, emits one `Update` per module with `Path.origin` set to the full module name (e.g. `Cisco-IOS-XR-ip-static-cfg`) — all updates in a single `SetRequest` to preserve gNMI atomicity - New `pkg/datastore/target/types` discriminated plan type (`GnmiSetPlan` / `NetconfSetPlan`) replacing the `TargetSource` abstraction on the Set path - `device-profile: cisco-ios-xr` config field on SBI with closed-set validation: unknown profiles fail at load time; `cisco-ios-xr` + netconf is rejected; `cisco-ios-xr` + proto is accepted without IOS-XR shaping Transport/driver changes: - gNMI driver (`gnmi.go`) simplified to pure transport: receives a pre-built `GnmiSetPlan` and executes it, with no knowledge of device-profile or module grouping - Replace transactions encoded as per-module delete + per-module update pairs in one `SetRequest` (gNMI `replace` field not used, consistent with existing behavior) - Delete paths carry `origin` resolved from schema metadata or schema-client lookup for choice-case entries Removals / cleanup: - `TargetSource` interface and its adapters retired from the Set path (`targetsource.go`, `target_source_replace.go`, `entryoutputadapter.go` deleted) - `applyIntent` now passes raw `api.Entry` + replace flag to materialize instead of wrapping in a `TargetSource` adapter Test coverage added: - `permodule` encoder: multi-module tree assertions (update count, origin values, path elements, JSON body shape, replace delete emission) - `materialize`: `BuildPlan` assertions for both generic and IOS-XR profiles - `config`: validation rejection of unknown profiles and netconf+ios-xr - `noop` and gNMI `get` path: adapted to new plan-based interface
Update comment for DeviceProfile to clarify usage.
ops.ToJson/ToJsonIETF returns nil when no leaves are new or updated.
Without a nil guard, json.Marshal(nil) produces "null", and the
resulting gNMI SetRequest carries {path:{}, val:{jsonVal:"null"}}.
SROS rejects this with GMI #2052 ("Cannot set JSON value, because
last element is not leaf") because null is not valid JSON for a
container node.
Add the nil check that existed in the old gnmi.go Set switch
(pre-materialize refactor) so a no-op reconcile never sends a
spurious null update to the device.
Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
|
@giacoliva if I provide proper instructions to you how to test this, whould you be able to do so? |
|
@ipgst thank you for the extremely detailed writeup on #483 — that's exactly the kind of real-device evidence we didn't have when this PR was written. We went through the reproduction line by line against What we think is actually wrongCisco's own gNMI docs for native-YANG writes show That's exactly the shape your working What we plan to change (pending your confirmation below)
Could you run a few more probes for us?Same style as your original report — 1. Container-scoped body (validates whether per-module batching, not just per-leaf, survives on XRd): 2a. Explicit 2b. Origin = literal module name, unprefixed element (today's 3. Plain 4. Non-string leaf via PROTO (confirms scalar 5. Multi-module single 6. Delete at module-root container path (untested so far) — a No pressure to run all six if some are impractical in your lab — even a subset narrows this down a lot. We'll use whatever you can share to finalize the fix rather than guessing at the wire format again. Thanks again for such a thorough report! |
SBI.validateSetDefaults() now rejects a gnmi-type SBI configured with device-profile: cisco-ios-xr and any GnmiOptions.Encoding other than JSON_IETF. This replaces the previous silent-fallthrough (PROTO) / silent-wrong-wire-format (JSON) behavior with a fast, clear config-load error -- both shapes are confirmed to fail against real XRd hardware. netconf + cisco-ios-xr is unaffected and remains accepted. - Flip TestSBI_validateSetDefaults_DeviceProfile_CiscoIOSXRGNMIProtoIsAccepted to assert rejection, renamed to ..._CiscoIOSXRGNMIProtoIsRejected. - Add TestSBI_validateSetDefaults_DeviceProfile_CiscoIOSXRGNMIPlainJSONIsRejected. - Update DeviceProfileCiscoIOSXR's doc comment to describe the JSON_IETF-only restriction. Closes ticket 01 of .scratch/cisco-ios-xr-json-ietf-only. Co-authored-by: Cursor <cursoragent@cursor.com>
BuildPlan's cisco-ios-xr branch now only routes to permodule.Encode for JSON_IETF. Plain JSON is now unreachable via valid config (ticket 01 rejects it at config-load), but BuildPlan may still be called directly (e.g. in tests), so it falls through to the generic single-root-update path instead, matching how PROTO already falls through. - Narrow the cisco-ios-xr inner switch to case gnmi.Encoding_JSON_IETF. - Update BuildPlan's doc comment to reflect json/proto rejection at config-load. - Add TestBuildPlan_CiscoIOSXR_JSON_GenericPlan asserting the generic single-root-path shape (no per-module Origin, root path, JsonVal). permodule package is untouched, per ticket scope. Closes ticket 02 of .scratch/cisco-ios-xr-json-ietf-only. Co-authored-by: Cursor <cursoragent@cursor.com>
This ADR documents the design landed by PR #442 (the module-anchored permodule mechanism this branch is built on) but was never actually committed anywhere in the repo -- it existed only as an uncommitted file in a sibling worktree (sonic-device-profile checkout). Recovering it here so ticket 03 of .scratch/cisco-ios-xr-json-ietf-only (the live-lab-verification ADR) has real prior art to reference as "not superseded" instead of citing a nonexistent document. Co-authored-by: Cursor <cursoragent@cursor.com>
…_IETF-only decision Documents two things: 1. ADR 0001's module-anchored permodule mechanism was verified against a real Cisco XRd instance (containerlab cisco_c8000/8201-32FH, XR 7.10.1) and found correct as-is across per-leaf, module-root, cisco_native-prefixed origin, multi-module single-SetRequest, module-root delete, and delete+update-replace shapes. ADR 0001 is explicitly not superseded. 2. The JSON_IETF-only restriction (tickets 01/02) as a distinct decision, with the categorical-rejection evidence for plain JSON and PROTO against the same hardware. Explicitly flags the residual XR version gap: verified on 7.10.1, issue #483's reporter is on 26.2.1. Closes ticket 03 of .scratch/cisco-ios-xr-json-ietf-only. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@ipgst following up on our earlier comment — we ran your six probes (and a couple more) against a local Lab used: Results (mapped to the probes we asked for)
The important correction to our own hypothesisIn our last comment we suspected What we did confirm as genuinely broken, exactly matching what you found: plain What shipped
Net effect for you: set One prerequisite you'll still needYour The askWe tested on XR 7.10.1; you're on 26.2.1 — a substantially newer train. Our verification is strong evidence, not a substitute for your hardware. Once you have |
Posted the follow-up comment on PR #442 summarizing the live-lab verification (all six requested probes plus the delete+update-replace case), correcting our own earlier hypothesis about permodule's origin shape (it works as-is, no change needed), linking the landed ADRs and commits, flagging the config-server#484 prerequisite, and asking the reporter to retest on their own XR 26.2.1. #442 (comment) All four tickets in .scratch/cisco-ios-xr-json-ietf-only are now done. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hi, I’ve completed the end-to-end validation against XRd 26.2.1 and the new IOS-XR device profile works successfully. Test environment
TargetConnectionProfile: spec:
port: 9339
protocol: gnmi
encoding: JSON_IETF
deviceProfile: cisco-ios-xr
insecure: true
skipVerify: trueTest Configspec:
priority: 10
revertive: true
config:
- path: /Cisco-IOS-XR-um-interface-cfg:interfaces/interface[interface-name=MgmtEth0/RP0/CPU0/0]/description
value: SDCIO managed descriptionSDCIO reports the Config as I also verified the value directly on XRd with gNMI: gnmic -a 11.4.20.12:9339 \
--insecure \
-u clab \
-p 'xxxxxxxxxx' \
get \
--path '/Cisco-IOS-XR-um-interface-cfg:interfaces/interface[interface-name=MgmtEth0/RP0/CPU0/0]/description' \
--encoding json_ietfResult: So I can confirm that: works end-to-end on XRd 26.2.1 as well. This is especially useful because with the previous generic path on data-server v0.0.72, the same intent failed, while a direct gNMI Thanks for the implementation and for validating the behavior on XR 7.10.1. This confirms the same approach also works on the newer 26.2.1 train. |
|
nice but there are issues I ran into still... interfaces worked fine for me as well. |
Adds first-class support for Cisco IOS-XR / XRd devices as gNMI targets by introducing a materialization layer that owns all NOS-specific encoding, keeping the gNMI transport driver a thin wire-execution layer.
Companion proto change: sdcio/sdc-protos#120 (adds
device_profilefield)Core additions:
pkg/datastore/target/materializepackage: translatesapi.Entry+ device-profile into a typedSouthboundSetPlanbefore any transport callpkg/datastore/target/gnmi/permoduleencoder: groups config tree children by YANG module name, emits oneUpdateper module withPath.originset to the full module name (e.g.Cisco-IOS-XR-ip-static-cfg) — all updates in a singleSetRequestto preserve gNMI atomicitypkg/datastore/target/typesdiscriminated plan type (GnmiSetPlan/NetconfSetPlan) replacing theTargetSourceabstraction on the Set pathdevice-profile: cisco-ios-xrconfig field on SBI with closed-set validation: unknown profiles fail at load time;cisco-ios-xr+ netconf is rejected;cisco-ios-xr+ proto is accepted without IOS-XR shapingTransport/driver changes:
gnmi.go) simplified to pure transport: receives a pre-builtGnmiSetPlanand executes it, with no knowledge of device-profile or module groupingSetRequest(gNMIreplacefield not used, consistent with existing behavior)originresolved from schema metadata or schema-client lookup for choice-case entriesRemovals / cleanup:
TargetSourceinterface and its adapters retired from the Set path (targetsource.go,target_source_replace.go,entryoutputadapter.godeleted)applyIntentnow passes rawapi.Entry+ replace flag to materialize instead of wrapping in aTargetSourceadapterTest coverage added:
permoduleencoder: multi-module tree assertions (update count, origin values, path elements, JSON body shape, replace delete emission)materialize:BuildPlanassertions for both generic and IOS-XR profilesconfig: validation rejection of unknown profiles and netconf+ios-xrnoopand gNMIgetpath: adapted to new plan-based interface