Every node lifecycle call (start/stop) emitted a "starting"/"stopping"
node.updated notification on entry, and for the whole duration of the
call asdict() masked the real status with the transitional one — every
compute event forwarded in that window was re-emitted as a duplicate
transitional status.
Stop-all/start-all on a 13-node project pushed two transitional events
per node before the final state. Clients that do not render these
transitional statuses (the Web UI) showed nodes flickering through an
unexpected suspended-looking state before reaching started/stopped.
Remove the transitional notification and the status masking: clients
see the real statuses again (started/stopped), from the forwarded
compute events plus the controller-side confirmation. The wait option
on the lifecycle routes, the strict stop error handling and
wait_for_status() are unchanged.
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.
The kernel's correlated-random generator (get_crandom, sch_netem.c) truncates
instead of wrapping, starving both edges of the stationary distribution: an
event threshold below the correlation value is almost never reached. Measured
with stock iproute2 on this kernel — uBridge's netlink encoding is identical:
loss 1% + correl 25% drops nothing over 2000 pings (expected ~2%), 10%/25%
measures ~1.8% one way, only the 50% symmetry point survives. duplicate and
reorder correl share the same comparison structure and collapse the same way;
gemodel is immune (Markov chain over plain uniform draws) and is the honest
tool for bursty loss at a chosen mean rate.
Documented in the packet-filters correlation-bias bullet, and surfaced in the
two machine-facing descriptions (MCP link_update packet_loss note, controller
filter schema) so agents and clients stop walking into the trap.
A "Combining filters" section for packet-filters.md: deterministic drops
are an OR-gate (first kill wins — a clsact-dropped frame consumes no rate
serialization, no limit capacity and no netem counter), netem draws are
independent probabilities that multiply in one qdisc pass, and a round
trip squares everything. The couplings that do exist: quota's byte count
sees only frequency_drop survivors (ordered evaluation slows the burn),
delay/reorder/rate fold into one time_to_send, limit x rate x delay is
the tail-drop triangle to watch, and seed pins netem's draws but not the
eBPF program's own PRNG. Closes with a fully-loaded 12-filter walkthrough
of one frame through the gauntlet.
The Packet filters section of docker-kernel-datapath.md was the de-facto
filter documentation every other datapath doc pointed at; move it to
features/packet-filters.md as the node-type-neutral home (veth or TAP
anchor), centered on a mermaid walkthrough of all 13 types through the
anchor's egress chain: clsact first (the eBPF stateful classifier's nth /
quota / window modes at prio 1, then the bpf expression lines at 10-99,
first drop wins), the single netem qdisc behind it (enqueue draws, one
time_to_send drained by the delay queue).
Clarify a naming trap the table verbs invite: uBridge's tc module only
borrows the iproute2 vocabulary — the tc ... tokens are its own hypervisor
command names over hand-built netlink messages (RTM_NEWQDISC /
RTM_NEWTFILTER), raw bpf() syscalls, embedded distribution tables and
instruction array, libpcap as a library only; the tc binary is never
invoked outside the test suite's oracles. Verified against the uBridge
sources (hypervisor_tc.c, tc_impair.bpf.c, tc_ebpf.c), not just the
commands gns3-server sends.
docker-kernel-datapath.md keeps a pointer section; the iol doc's filter
reference and docs/README.md's tree and catalog point at the new doc.