mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
test: add generic boot-from-factory-config test
Verify that the device's factory-default configuration boots cleanly and that the device remains usable afterwards -- i.e. it does not fall back to the fail-secure failure-config. The test exercises the device's own first-boot bootstrap path: it clears the startup-config and reboots with the test-mode startup override set, so confd initialises running from the factory-config (its first-boot path). That mirrors a factory-fresh / factory-reset device and avoids applying a full config swap over the live management session -- confd cannot replace the whole running datastore from within an RPC without timing out, and a factory config may reconfigure the very transport we are connected over. The test is image-generic, using whatever factory-config the running image was built with, so it covers both the stock Infix factory config and any spin factory config. A single-node (1x1) topology is used on purpose: a factory config is not written with a lab full of peers in mind, so booting it across a multi-node topology could trigger broadcast storms or similar. NETCONF only, as it is the one management transport present in every factory config. Signed-off-by: Richard Alpe <richard@bit42.se>
This commit is contained in:
@@ -25,3 +25,6 @@
|
||||
|
||||
- name: Schedule Reboot
|
||||
case: schedule_reboot/test.py
|
||||
|
||||
- name: Boot From Factory Config
|
||||
case: factory_config/test.py
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
=== Boot From Factory Config
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/system/factory_config]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify that the device's factory-default configuration boots cleanly and
|
||||
that the device remains usable afterwards -- i.e. it does not fall back to
|
||||
the fail-secure failure-config.
|
||||
|
||||
This exercises the device's own first-boot bootstrap path: with no
|
||||
startup-config present, confd initialises running from the factory-config.
|
||||
That is exactly what a factory-fresh (or factory-reset) device does, and
|
||||
it avoids applying a full config swap over the live management session.
|
||||
|
||||
The test is image-generic: it uses whatever factory-config the running
|
||||
image was built with, so it covers both the stock Infix factory config and
|
||||
any spin factory config.
|
||||
|
||||
A single-node topology is used on purpose: a factory config is not written
|
||||
with a lab full of peers in mind, so applying it across a multi-node
|
||||
topology could trigger broadcast storms or similar.
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[Boot From Factory Config topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Determine factory-config hostname
|
||||
. Clear startup-config so the device boots from factory
|
||||
. Reboot onto the factory config
|
||||
. Verify device is usable and not in failure-config
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Boot From Factory Config
|
||||
|
||||
Verify that the device boots cleanly from its factory-config and stays
|
||||
usable, i.e. it does not fall back to the fail-secure failure-config.
|
||||
Clearing the startup-config makes confd bootstrap running from the
|
||||
factory-config on the next boot, as on a factory-fresh device.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import infamy
|
||||
from infamy.util import wait_boot
|
||||
|
||||
STARTUP = "/cfg/startup-config.cfg"
|
||||
FACTORY = "/etc/factory-config.cfg"
|
||||
|
||||
|
||||
def factory_hostname(tgtssh):
|
||||
"""Read the hostname the factory-config will boot with."""
|
||||
cfg = json.loads(tgtssh.runsh(f"cat {FACTORY}").stdout)
|
||||
return cfg.get("ietf-system:system", {}).get("hostname")
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
target = env.attach("target", "mgmt")
|
||||
tgtssh = env.attach("target", "mgmt", "ssh")
|
||||
|
||||
with test.step("Determine factory-config hostname"):
|
||||
expected = factory_hostname(tgtssh)
|
||||
assert expected, "Could not read hostname from factory-config"
|
||||
print(f"Factory config hostname is {expected!r}")
|
||||
|
||||
with test.step("Clear startup-config so the device boots from factory"):
|
||||
# No startup-config on the startup boot path -> confd bootstraps
|
||||
# running from the factory-config.
|
||||
tgtssh.runsh(f"rm -f {STARTUP}")
|
||||
target.startup_override()
|
||||
|
||||
with test.step("Reboot onto the factory config"):
|
||||
target.reboot()
|
||||
if not wait_boot(target, env):
|
||||
test.fail("Device did not boot from factory config")
|
||||
|
||||
with test.step("Verify device is usable and not in failure-config"):
|
||||
target = env.attach("target", "mgmt", test_reset=False)
|
||||
|
||||
# A failed bootstrap reverts to failure-config, which has a
|
||||
# different hostname; matching the factory hostname proves we
|
||||
# booted on the factory config, not the fail-secure fallback.
|
||||
running = target.get_config_dict("/ietf-system:system")
|
||||
assert running.get("system", {}).get("hostname") == expected, \
|
||||
"Device did not boot on the factory config (failure-config fallback?)"
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1 @@
|
||||
../../../infamy/topologies/1x1.dot
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<!-- Generated by graphviz version 2.43.0 (0)
|
||||
-->
|
||||
<!-- 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>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Reference in New Issue
Block a user