mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
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)
This commit is contained in:
+4
-3
@@ -17,9 +17,6 @@
|
||||
infamy:
|
||||
specification: False
|
||||
|
||||
- name: "Miscellaneous"
|
||||
suite: misc/all.yaml
|
||||
|
||||
|
||||
- name: "System"
|
||||
suite: system/all.yaml
|
||||
@@ -62,3 +59,7 @@
|
||||
|
||||
- name: "Use Case Tests"
|
||||
suite: use_case/all.yaml
|
||||
|
||||
- name: "Miscellaneous"
|
||||
suite: misc/all.yaml
|
||||
|
||||
|
||||
@@ -4,16 +4,28 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/operational_all]
|
||||
|
||||
==== Description
|
||||
|
||||
Basic test just to get operational from test-config without errors.
|
||||
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:
|
||||
|
||||
==== Topology
|
||||
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.
|
||||
|
||||
image::topology.svg[Get Operational topology, align=center, scaledwidth=75%]
|
||||
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.
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Copy test-config to running configuration
|
||||
. Get all Operational data from 'target', verify there are no errors
|
||||
. Attach to all Infix DUTs in the topology
|
||||
. Verify operational interfaces match the test-config
|
||||
. Verify unconfigured feature subtrees are absent
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test that it is possible to get all operational data
|
||||
"""
|
||||
Get operational
|
||||
|
||||
Basic test just to get operational from test-config without errors.
|
||||
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
|
||||
import infamy.iface as iface
|
||||
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("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
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("Copy test-config to running configuration"):
|
||||
pass
|
||||
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))
|
||||
|
||||
with test.step("Get all Operational data from 'target', verify there are no errors"):
|
||||
target.get_data(parse=False)
|
||||
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()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../infamy/topologies/1x1.dot
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x1 Pages: 1 -->
|
||||
<svg width="424pt" height="45pt"
|
||||
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
|
||||
<title>1x1</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
</g>
|
||||
<!-- target -->
|
||||
<g id="node2" class="node">
|
||||
<title>target</title>
|
||||
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
|
||||
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
|
||||
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
|
||||
</g>
|
||||
<!-- host--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--target:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
Reference in New Issue
Block a user