Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions test/case/system/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@

- name: Schedule Reboot
case: schedule_reboot/test.py

- name: Boot From Factory Config
case: factory_config/test.py
37 changes: 37 additions & 0 deletions test/case/system/factory_config/Readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== 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.

Uses NETCONF only: it is the one management transport present in every
factory config, so booting onto the factory config cannot lock us out.

==== 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
67 changes: 67 additions & 0 deletions test/case/system/factory_config/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/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")


def cleanup(env):
"""Restore the rig to the clean per-test baseline for the next test."""
print("Restoring device to clean test baseline")
target = env.attach("target", "mgmt", "netconf")
target.reboot()
if not wait_boot(target, env):
test.fail("Device did not come back while restoring baseline")


with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt", "netconf")
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")
test.push_test_cleanup(lambda: cleanup(env))

with test.step("Verify device is usable and not in failure-config"):
target = env.attach("target", "mgmt", "netconf", 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()
1 change: 1 addition & 0 deletions test/case/system/factory_config/topology.dot
34 changes: 34 additions & 0 deletions test/case/system/factory_config/topology.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.