Files
infix/test/case/misc/operational_all/test.py
T
Mattias Walström e5f1913ccc test: operational_all: Harden to catch more
- Test so the value in operational actually is correct
- Move test to last in suite to see that there are no stray
  data in operational (yangerd)
2026-06-17 11:54:25 +02:00

87 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Get operational
For every Infix device, verify the operational datastore against the
test-config -- the running config is the source of truth, so it works
regardless of how interfaces differ between devices:
1. The operational interfaces are exactly the configured interfaces --
no missing interface, and nothing operational that was not
configured.
2. Features the test-config does not enable (NTP, containers, routing
protocols) emit no operational data.
Both checks use specific-path GETs, which behave consistently across
NETCONF and RESTCONF. A full-datastore GET is not portable: RESTCONF does
not serve the operational datastore root, and NETCONF's operational
provider errors when asked for an empty subtree.
This test has no logical topology -- it reads state from whatever Infix
DUTs the physical topology provides.
"""
import infamy
from infamy.util import parallel, until
# Feature subtrees that must be absent because the test-config does not
# enable them. /ietf-routing:routing itself is always present (its RIB
# reflects the kernel's connected/local routes), so we target the
# config-gated control-plane-protocols child rather than all of routing.
ABSENT = [
"/ietf-ntp:ntp",
"/infix-containers:containers",
"/ietf-routing:routing/control-plane-protocols",
]
def configured_interfaces(dut):
cfg = dut.get_config_dict("/ietf-interfaces:interfaces")
return {i["name"] for i in cfg["interfaces"]["interface"]}
def verify_interfaces(name, dut, want):
"""Operational interfaces must be exactly the configured set."""
oper = dut.get_data("/ietf-interfaces:interfaces")["interfaces"]["interface"]
have = {i["name"] for i in oper}
assert have == want, \
f"{name}: operational interfaces {sorted(have)} != configured {sorted(want)}"
return True
def absent(dut, xpath):
# RESTCONF returns nothing for an absent subtree; NETCONF's operational
# provider errors when asked for one -- both mean "not present".
try:
return not dut.get_data(xpath)
except Exception:
return True
def verify_absent(name, dut):
for xpath in ABSENT:
assert absent(dut, xpath), \
f"{name}: unexpected operational data at {xpath}"
return True
with infamy.Test() as test:
with test.step("Attach to all Infix DUTs in the topology"):
env = infamy.Env(ltop=False)
infixen = env.ptop.get_infixen()
assert infixen, "no Infix devices found in topology"
duts = dict(zip(infixen, parallel(*(lambda n=name: env.attach(n, "mgmt")
for name in infixen))))
with test.step("Verify operational interfaces match the test-config"):
def check(name, dut):
want = configured_interfaces(dut)
until(lambda: verify_interfaces(name, dut, want))
parallel(*(lambda n=name, d=dut: check(n, d) for name, dut in duts.items()))
with test.step("Verify unconfigured feature subtrees are absent"):
parallel(*(lambda n=name, d=dut: verify_absent(n, d)
for name, dut in duts.items()))
test.succeed()