Resolve conflicts against the upstream wave (PRs #2952-#2965, incl. the
typed-response/schema hardening and the drop of Python 3.10):
- controller/node.py: keep upstream's _transitioning/_confirm_status
lifecycle guard; the kernel-anchor link wiring (node_started) runs
after the transition completes.
- controller/link.py: keep upstream's add_node rollback (release_ports
on create failure) and delete via release_ports; release_ports now
resolves ports live (same staleness reason as _bind_members) and
discards the node link unconditionally.
- schemas/controller/links.py: extend LinkFilterType with the eight
kernel-datapath filter types and LinkFilterParameter.type with "str"
so the newly typed /available_filters response validates; Link.filters
stays a free-form dict — the vocabulary is capability-gated per link
and validated by the controller (409), not by a strict request schema.
- schemas/controller/topology.py: drop the type: ignore made redundant
by the dict filters field.
- tests: route-level filter validation now expects the controller's 409
(unknown types, out-of-range values); packet_loss correlation is a
valid kernel parameter (positive test added); OpenAPI assertions match
the free-form filters property.
Both fixes from the code-review pass were carried by other tests'
side effects: the move_to_ns failure path (the branch that used to skip
the delete_veth) and the required-but-nullable bridge field on the NIO
create routes.
Regression coverage added with the fixes:
- capture-slot semantics: second-port refusal, failed-start rollback, and
a non-owner port's stop not reaching `capture stop_kernel`;
- QEMU stop/start teardown on a mid-body or pre-launch failure, IOU start
cleanup and filter guards, Dynamips half-attach undo plus retry;
- switch VLAN reconcile skipping a deferred anchor and rolling the
ports_mapping back on failure;
- Docker/QEMU occupied-port refusal, probe ctime invalidation, probe
survival of a failing uBridge start;
- env-marker dialects (mid-line mentions, falsy values) and the agent
merge rejecting entries without a device_name.
Test-body imports of product modules move to the shared header.
The rebase onto upstream 3.1 (mypy + ruff I001/F401 waves) left the tree
short of the newly enforced baselines in three ways:
* contextlib / UbridgeError imports dropped as false duplicates during
conflict resolution left NameErrors in iou_vm and qemu_vm (F821);
the Dynamips router grabbed the compute-level NIOTAP instead of the
hypervisor one, breaking TAP anchoring (test failure).
* unused imports (KERNEL_UNSUPPORTED_FILTERS, UbridgeError, List) and
unsorted blocks left behind where the stack's later commits moved the
last usage elsewhere — ruff I001/F401/RUF022 now enforce these.
* mypy, now baseline-free: cache dict annotations in tc_probe, the
locators candidate list, the resolved-params None branch in the MCP
runner, and validate_bpf_syntax keeps the upstream Dict[str, Any]
return (the strict Optional[str] was wrong for the "valid" bool entry).
The e2e raw-frame probe resolves its compiler via shutil.which (S607)
and zips with strict= (B905); the remaining subprocess call is S603 by
nature and gets a per-file ignore like other test files.
ruff check/format clean, mypy at upstream parity (no new errors),
full unit suite 2167 passed.
docker create_veth hardens only its FIRST end (the Docker shape: host
anchor plus a container leg that keeps its L3 life). A cascade end is
that second, unhardened end whenever the peer switch's create won the
race, and the kernel gives it an IPv6 link-local — a silent L3 identity
on the switch fabric (ubridge-l2-anchor-spec §E). Each switch now
hardens its OWN end with 'link l2only <end> on' once the pair exists,
whatever branch brought it there (fresh create, lost race, reuse);
idempotent, and best-effort with a warning on a uBridge without the
command.
The bridge module gained delete_nio_tap (uBridge's gns3server-integration
contract, section 'bridge — generic NIO relay with a swappable TAP leg'):
a per-port bridge can swap its topology leg between UDP and a persistent
TAP through stop -> delete -> add -> start, which is what lets an IOL
runner container's port bridge carry a kernel-link anchor.
The probe needs no scratch objects and no capabilities: one command
against a bridge that cannot exist — 202 (unknown command) is an old
build, 214 (bridge doesn't exist) is a new one. Cached by binary identity
like every other probe; any failure reports unknown.
GET /v3/compute/capabilities gains ubridge_bridge_tap; the controller's
default capability dict carries it too (the single schema definition, per
the 1e67fbf12 lesson).