YueGuobin 30cb7119e5
test(e2e): assert the L2-anchor spec's server-side guarantees
The spec's gns3-server alignment section promised three assertions the
suites never carried; they land in harness.py and run in every kernel
scenario (plus the relay controls' anchor checks):

- E.1 assert_pure_l2: anchors and bridges carry no L3 identity (addrgenmode
  none, no address in /proc/net/if_inet6, no ip -o -4 addr) -- the kernel
  scenarios' anchors and per-link/switch bridges, relay-mode anchors and
  the switch's relay TAPs alike (hardening is a creation-time property,
  not a datapath one);
- E.2 assert_idle_silence (Dynamips suite): guest interfaces shut to
  silence IOS's own CDP/keepalives, settle 2.5 s, then a 5 s window of
  zero traffic over both anchors and the per-link bridge;
- assert_forwarding: every attached port reports brport/state == 3 -- a
  bridge left DOWN keeps its ports disabled and forwards nothing, with no
  error anywhere; the assertion is the only signal.

harness.l2only_supported() probes a throwaway uBridge once (link l2only
against a nonexistent device: 208 = command present, 202-Unknown command
= old build); without the command the assertions skip -- the spec's
degradation contract, mirroring the tc-capability skip.

The first live run caught a fifth-creator gap: the switch's relay port
TAPs (minted implicitly by bridge add_nio_tap) carried a live fe80::.
Delivered uBridge-side per the spec (fb72758: the transient TAP is
hardened at creation, attaches untouched); the server needed no change,
and the relay-control E.1 check is that creator's end-to-end coverage.
2026-10-06 00:21:10 +08:00
..

Live end-to-end tests

Tests under this directory run against a real server: real uBridge, real kernel interfaces (privileged), real emulator binaries and real images. They verify what unit tests cannot — that a datapath actually forwards traffic, that kernel bridges actually enslave anchors, that tc impairments actually shape packets. They are marked e2e and excluded from normal test runs (see tests/pytest.ini); run them explicitly:

pytest -m e2e tests/e2e -s            # -s shows the progress prints
pytest -m e2e tests/e2e -k dynamips   # one scenario

Two run modes, chosen per test through harness.live_server(kernel=...):

  • Target an existing server — set GNS3_E2E_URL (e.g. http://127.0.0.1:3080/v3, plus GNS3_E2E_USER/GNS3_E2E_PASSWORD, default admin/admin). The server must already be configured for the datapath under test (kernel links need enable_kernel_datapath = True).
  • Isolated instance (the default) — the harness starts its own server process on a free port with its own config directory (the controller DB and every path follow the config), so nothing on the machine is touched. It needs ubridge, dynamips and an images directory (GNS3_E2E_IMAGES, default ~/GNS3/images) available locally.

Either way each test creates its own project and deletes it again. Set GNS3_E2E_KEEP=1 to keep the project (and a started instance) on failure and print where to look at it.

harness.py holds the shared pieces — REST client, server lifecycle, the IOS console driver, host-side kernel inspections (bridge membership, tap admin state, tc qdiscs, the L2-anchor spec §E assertions) — so a new node type adds only its own scenario module.

Current scenarios:

  • test_dynamips_kernel_datapath.py — two real c7200 routers: kernel link (anchors, per-link bridge, netem delay, suspend, capture, delete/re-create, stop/start) plus a serial link staying on the relay, and a negative control on an isolated relay-configured instance.
  • test_ethernet_switch_kernel_datapath.py — a real Ethernet switch (kernel bridge) between two c7200 routers: deferred joins on boot, VLAN isolation, absorbed-anchor filters/suspend/capture, VPCS peers staying on the relay, and a relay negative control.
  • test_iol_docker_kernel_datapath.py — two real iol-xe containers (Cisco CML iol-runner images): kernel link through the port bridge's swappable TAP leg (anchors at start, netem delay, suspend, capture, link delete/re-create swap, node stop/start rewiring) and a relay negative control.

Every kernel scenario also asserts the L2-anchor spec's server-side guarantees (docs/design/ubridge-l2-anchor-spec.md §E): anchors and bridges carry no L3 identity (assert_pure_l2), joined ports are FORWARDING (assert_forwarding), and — in the Dynamips suite — an idle link with the guests silenced stays silent for 5 s (assert_idle_silence). These skip on a uBridge without link l2only (probed once by harness.l2only_supported()).

Set GNS3_E2E_DEBUG=1 to run the isolated instance with debug logging (the uBridge command stream lands in its server.log).