Skip to content
Merged
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ recomputes fabric-wide facts. `scripts/kind-down.sh` tears it all down.
itself: AVD resolves the peering, so `avd-topology` derives the netclab topology from the
same model that renders the configs, and the two cannot drift apart.

The default subset's topology is generated once and committed as
[`examples/lab/topology.yaml`](examples/lab/topology.yaml), so the lab boots a reviewed
file and an AVD upgrade that changes the cabling fails `test_topology_golden` instead of
passing unnoticed. Regenerate it with the command in that test's docstring.

```bash
WITH_NETCLAB=1 scripts/kind-up.sh # + CNI plugins, Multus, netclab-chart, cEOS nodes
kubectl --context kind-avd apply -k examples/lab/
Expand All @@ -56,6 +61,9 @@ kubectl --context kind-avd apply -k examples/lab/
cEOS is licensed and cannot be pulled — import it once and it outlives teardown in the
registry's data volume; `kind-up.sh` says how if it is missing. `LAB_HOSTS` picks the
subset, defaulting to the two that make one link, because all eight is 16Gi of cEOS.
Setting it to anything else regenerates the topology, since `--hosts` is an input to
generation rather than a filter applied to the result — a link survives only when both
of its ends are selected.

`examples/lab/` is that same fabric with eAPI bound to the default VRF, which is the one
thing a lab genuinely needs and production does not: AVD binds eAPI to VRF MGMT on
Expand Down
22 changes: 22 additions & 0 deletions examples/lab/topology.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated from examples/fabric/single-dc-l3ls.yaml by avd-topology -- do not edit.
# b1: dc1-leaf1a:Ethernet1 <-> dc1-spine1:Ethernet1
topology:
networks:
- name: b1
nodes:
- name: dc1-spine1
type: ceos
image: localhost:5001/netclab/ceos:4.36.1F
memory: 2Gi
cpu: 1000m
interfaces:
- name: eth1
network: b1
- name: dc1-leaf1a
type: ceos
image: localhost:5001/netclab/ceos:4.36.1F
memory: 2Gi
cpu: 1000m
interfaces:
- name: eth1
network: b1
31 changes: 24 additions & 7 deletions scripts/kind-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ NETCLAB_CHART=${NETCLAB_CHART:-0.5.9}
PROVIDER_HTTP=${PROVIDER_HTTP:-v1.0.14}
# The subset of the fabric to actually run. All 8 devices is 16Gi of cEOS; two
# make a link, and a link is enough to prove the config reached the device.
LAB_HOSTS=${LAB_HOSTS:-dc1-spine1,dc1-leaf1a}
# At the default, the committed topology is used as-is; anything else is
# regenerated from LAB_FABRIC below.
LAB_HOSTS_DEFAULT=dc1-spine1,dc1-leaf1a
LAB_HOSTS=${LAB_HOSTS:-$LAB_HOSTS_DEFAULT}
# The Fabric the lab runs, and the topology derived from it. Keep the two in
# step: regenerate with
# uv run avd-topology "$LAB_FABRIC" --hosts "$LAB_HOSTS_DEFAULT" > "$LAB_TOPOLOGY"
LAB_FABRIC=${LAB_FABRIC:-examples/fabric/single-dc-l3ls.yaml}
LAB_TOPOLOGY=${LAB_TOPOLOGY:-examples/lab/topology.yaml}

echo ">> local registry (data volume: ${REG_VOL})"
if [ -z "$(docker ps -q -f name="^${REG}$")" ]; then
Expand Down Expand Up @@ -167,12 +175,21 @@ EOF

# The topology is derived from the lab fabric, not written by hand: AVD resolves
# the cabling, so the lab cannot be wired differently from the config it runs.
echo ">> lab topology from examples/lab (${LAB_HOSTS})"
TOPO="$(mktemp -t netclab-topology.XXXXXX.yaml)"
LAB_FABRIC="$(mktemp -t lab-fabric.XXXXXX.yaml)"
trap 'rm -f "$TOPO" "$LAB_FABRIC"' EXIT
kubectl kustomize examples/lab > "$LAB_FABRIC"
uv run avd-topology "$LAB_FABRIC" --hosts "$LAB_HOSTS" > "$TOPO"
#
# The default subset is generated once and committed, so this boots a
# reviewed artifact and `test_topology_golden` can fail when an AVD upgrade
# changes the peering. Asking for a different subset regenerates, because
# --hosts is an input to generation and not a filter applied afterwards: a
# link survives only when both of its ends are selected.
if [ "$LAB_HOSTS" = "$LAB_HOSTS_DEFAULT" ]; then
echo ">> lab topology from ${LAB_TOPOLOGY} (${LAB_HOSTS})"
TOPO="$LAB_TOPOLOGY"
else
echo ">> lab topology regenerated for ${LAB_HOSTS}"
TOPO="$(mktemp -t netclab-topology.XXXXXX.yaml)"
trap 'rm -f "$TOPO"' EXIT
uv run avd-topology "$LAB_FABRIC" --hosts "$LAB_HOSTS" > "$TOPO"
fi

# cEOS cannot be pulled: it is licensed and needs an Arista login. Fail here
# with the tag the topology asks for, rather than as an ImagePullBackOff later.
Expand Down
52 changes: 52 additions & 0 deletions tests/test_topology_golden.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""`examples/lab/topology.yaml` must be what the generator produces today.

The lab topology is derived from the AVD model rather than written by hand, so
the cabling cannot drift from the config it runs. Committing the result buys
two things a temp file could not:

- `scripts/kind-up.sh` boots a reviewed artifact, and consumers outside this
repo can fetch it at a tag instead of running the generator themselves.
- **An AVD upgrade that changes the cabling shows up as a failing test here.**
Without it, the topology is regenerated at bring-up on a developer's laptop
and a changed peering is invisible in review. This is the only guard on
`netclab_topology`, whose sole other consumer is a script CI never runs --
the cEOS path cannot run in Actions, since the image is licensed.

If this fails because the model legitimately moved, regenerate:

uv run avd-topology examples/fabric/single-dc-l3ls.yaml \
--hosts dc1-spine1,dc1-leaf1a > examples/lab/topology.yaml

and read the diff before committing it -- that diff is the point.
"""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
GOLDEN = ROOT / "examples" / "lab" / "topology.yaml"

# The arguments the committed file was generated with. `kind-up.sh` runs the
# same subset by default; `--hosts` is an input to generation, not a filter
# applied afterwards, because a link is kept only when both of its ends are
# selected.
FABRIC = "examples/fabric/single-dc-l3ls.yaml"
HOSTS = "dc1-spine1,dc1-leaf1a"


def test_committed_topology_matches_the_generator() -> None:
generated = subprocess.run(
[sys.executable, "-m", "function.netclab_topology", FABRIC, "--hosts", HOSTS],
cwd=ROOT,
capture_output=True,
text=True,
check=True,
).stdout

assert generated == GOLDEN.read_text(), (
f"{GOLDEN.relative_to(ROOT)} is stale -- regenerate it (see this "
f"module's docstring) and review the diff."
)