248 Commits

Author SHA1 Message Date
Guobin Yue
7df7ce1c96
Merge branch '3.1' into fix/duplicate-device-configs-merge 2026-10-09 21:23:38 +08:00
Jeremy Grossmann
fbe93c4c4e
Merge pull request #2954 from markparonyan/api-issue-05-2xftmd
feat(api): add strict_names and batch node and link creation
2026-10-09 14:39:10 +02:00
YueGuobin
1e0d123f4b
Merge branch '3.1' into fix/duplicate-device-configs-merge
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.
2026-10-09 10:03:50 +08:00
Jeremy Grossmann
2dd1652e8a
Merge branch '3.1' into api-issue-07-x7z6we 2026-10-08 19:10:42 +02:00
Jeremy Grossmann
57e4d1632d
Merge branch '3.1' into api-issue-05-2xftmd 2026-10-08 19:04:36 +02:00
Jeremy Grossmann
7a3a34d3fc
Merge branch '3.1' into api-issue-06-8s9swu 2026-10-08 19:01:57 +02:00
Jeremy Grossmann
8dd438190d
Merge pull request #2960 from markparonyan/api-issue-12-cu0jkp
feat(api): add discriminators to link iface, appliance version and template settings schemas
2026-10-08 17:17:02 +02:00
Jeremy Grossmann
cea858acc4
Merge branch '3.1' into api-issue-12-cu0jkp 2026-10-08 16:46:37 +02:00
Jeremy Grossmann
029983260c
Merge branch '3.1' into api-issue-15-r12z73 2026-10-08 16:42:57 +02:00
Jeremy Grossmann
ddcf661e11
Merge branch '3.1' into api-issue-07-x7z6we 2026-10-08 15:51:11 +02:00
Jeremy Grossmann
3187828325
Merge branch '3.1' into api-issue-06-8s9swu 2026-10-08 15:02:51 +02:00
Jeremy Grossmann
a9e5f859c8
Reorder import statements in test_nodes.py 2026-10-08 14:49:12 +02:00
Jeremy Grossmann
71da996362
Merge branch '3.1' into api-issue-05-2xftmd 2026-10-08 14:40:53 +02:00
Jeremy Grossmann
a32f466183
Merge branch '3.1' into api-issue-04-burx2g 2026-10-08 13:59:00 +02:00
Jeremy Grossmann
48b6257410
Merge pull request #2952 from markparonyan/api-issue-02-rp8mx3
feat(api): return node and add wait option on node lifecycle calls
2026-10-08 11:05:28 +02:00
Jeremy Grossmann
5055664456
Merge pull request #2957 from markparonyan/api-issue-08-67pip0
feat(api): add machine-readable error codes and align OpenAPI error schemas
2026-10-08 09:30:13 +02:00
Jeremy Grossmann
ed8b0c3e20
Merge pull request #2958 from markparonyan/api-issue-09-aef9as
feat(api): return token lifetimes and document API keys as bearer tokens
2026-10-08 04:35:19 +02:00
Jeremy Grossmann
dc57c95f1c
Merge pull request #2963 from markparonyan/api-issue-16-0gmylm
feat(api): add Snapshot.created and type NodeFile timestamps as date-time
2026-10-08 04:15:01 +02:00
Jeremy Grossmann
d797069921
Merge pull request #2964 from markparonyan/api-issue-17-asr039
feat: add optional ETag/If-Match concurrency for projects, nodes and links
2026-10-08 04:14:04 +02:00
Jeremy Grossmann
0a78a2bf12
Merge pull request #2961 from markparonyan/api-issue-13-bfbm9z
feat(api): add Notification schema and document notification streams
2026-10-08 04:11:14 +02:00
Jeremy Grossmann
bcf38d74f8
Merge pull request #2951 from markparonyan/api-issue-11-gqdty1
feat(api): type link filters and available_filters response
2026-10-07 18:25:30 +02:00
Guobin Yue
5238bd3b39
Merge branch '3.1' into fix/duplicate-device-configs-merge 2026-10-07 23:59:30 +08:00
Mark Paronyan
241c2b65e7
feat: add optional ETag/If-Match concurrency for projects, nodes and links 2026-10-07 16:53:11 +03:00
Mark Paronyan
6ed9e44f00
feat(api): generate unique readable OpenAPI operationIds 2026-10-07 15:53:00 +03:00
Mark Paronyan
11a310dd9b
feat(api): type link filters and available_filters response 2026-10-07 15:48:52 +03:00
Mark Paronyan
a9e87df644
fix(links): reserve ports on add, accept port name and auto, add port_in_use code 2026-10-07 15:41:04 +03:00
Mark Paronyan
e1a27dadcf
feat(api): add strict_names and batch node and link creation 2026-10-07 15:35:45 +03:00
Mark Paronyan
6a416d1a2a
feat(api): return node and add wait option on node lifecycle calls 2026-10-07 15:35:11 +03:00
Mark Paronyan
ead44787f3
feat(api): add Snapshot.created and type NodeFile timestamps as date-time 2026-10-07 15:25:15 +03:00
Mark Paronyan
8cb92e7685
fix(api): derive OpenAPI version from package, add servers, hide AI stub routes 2026-10-07 15:21:30 +03:00
Mark Paronyan
86a279330d
feat(api): add Notification schema and document notification streams 2026-10-07 15:14:50 +03:00
Jeremy Grossmann
0836e01525
Merge pull request #2950 from markparonyan/api-issue-14-yywwso
fix(api): return 200 instead of 201 for ACL endpoints, IOU license up…
2026-10-07 13:50:53 +02:00
Mark Paronyan
a4f20e0966
feat(api): add discriminators to link iface, appliance version and template settings schemas 2026-10-07 14:22:18 +03:00
Mark Paronyan
fcfa968cc5
feat(api): return token lifetimes and document API keys as bearer tokens 2026-10-07 14:18:18 +03:00
Mark Paronyan
289f9394b6
feat(api): add machine-readable error codes and align OpenAPI error schemas 2026-10-07 14:15:41 +03:00
Mark Paronyan
ba0465f191
fix(api): declare binary and streaming bodies in OpenAPI 2026-10-07 14:09:02 +03:00
Mark Paronyan
97d5480241
fix(api): return 200 instead of 201 for ACL endpoints, IOU license update 2026-10-07 13:56:08 +03:00
Mark Paronyan
852dc5be0f
feat(api): add exact-match name filter to list endpoints 2026-10-07 13:40:34 +03:00
Jeremy Grossmann
49f047d07a
Merge branch '3.1' into fix/duplicate-device-configs-merge 2026-10-06 17:09:32 +02:00
Jeremy Grossmann
f184883aa7
Merge branch '3.1' into images-nested-folders-support 2026-10-06 16:50:40 +02:00
YueGuobin
96be37baa1
test: cover the namespace-failure veth cleanup and BridgeNIO field validation
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.
2026-10-06 22:42:31 +08:00
Jeremy Grossmann
0170d2c8ce
Merge branch '3.1' into fix/duplicate-device-configs-merge 2026-10-06 16:10:23 +02:00
Mark Paronyan
d9678ce98c
refactor: derive update schemas with PartialUpdateModel 2026-10-06 16:46:22 +03:00
YueGuobin
35cf964882
test: cover the kernel-datapath review fixes
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.
2026-10-06 19:55:31 +08:00
Cristi
a7f0c7174b fix(templates): Fixed template test formatting to pass CI 2026-10-06 00:04:26 +03:00
YueGuobin
7d04d08dba
fix: repair imports lost in the 3.1 rebase and meet the new lint baseline
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.
2026-10-06 00:21:11 +08:00
YueGuobin
cb7f74c77b
fix(esw): harden the cascade end the create race leaves unhardened
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.
2026-10-06 00:21:10 +08:00
YueGuobin
c20125c180
feat(compute): probe uBridge's swappable bridge TAP leg
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).
2026-10-06 00:21:09 +08:00
YueGuobin
90bcb32473
feat(esw): cascade two switches through a veth pair
Switch-to-switch links were the last shape left on the relay: the switch
fast path works by absorbing the *peer's* anchor, and a switch has none —
its ports are its own bridge, and a kernel interface belongs to exactly
one bridge, so two bridges cannot be joined by enslaving anything.

Join them with a veth pair instead: one end per bridge, each side
enslaving its own end and applying its own port mode to it — an access
port on one side and a trunk on the other behaves like the real cable it
models. uBridge's docker create_veth/delete_veth (the primitive every
Docker adapter is already born from) creates and destroys the pair; the
names derive from the link id (utils.kernel_anchor.kernel_cascade_names),
so each side computes its own end and the names survive a restart.

The pair belongs to the link, and the two ends are wired by two
independent uBridge processes — the project-open batch drives different
nodes concurrently — so the protocol is order-free: both sides create the
pair when their own end is missing (one create wins; the loser's failure
is re-checked against the kernel and "the pair is there" is not an
error), and either side's teardown destroys it (deleting one veth end
takes the other with it, so both converge on the same gone pair and the
second delif answers ENOENT). A pre-existing end — this switch restarted,
or a leftover from a previous life of the link id — is reused rather than
recreated, because the other end may still be a live port of the peer
switch; a stale tc qdisc is all a reused end can carry.

No new wire format was needed: a cascade is an anchor NIO on both ends,
the absorbed-anchor case plus the peer end's name. That also corrects the
filter routing — the single-sided rule exists because an absorbed anchor
is one interface, while a cascade has one per switch, so impairments go
to both ends like any other kernel link.

Tests: the wire format, the eligibility flip and the both-ends re-push on
node start (controller); the naming contract (utils); the switch-side
lifecycle — create, reuse, lost create race, delete destroys the pair
(compute routes).

Drive-by: the relay filter test's compute mock carried no awaitable
`put`, so tests/controller/test_kernel_datapath_link.py failed when run
alone while an argument-less full run passed; callers now get one.
2026-10-06 00:21:09 +08:00
YueGuobin
c343ce0b8f
feat(esw): wire peer anchors straight into the switch bridge
The Ethernet switch's kernel fast path: instead of a link crossing the
switch's per-port uBridge relay, the switch absorbs the peer's anchor into
its own kernel bridge. The peer's interface (Docker veth host end, QEMU
TAP, IOU port TAP, Dynamips port TAP) becomes the switch port, and frames
cross anchor -> switch bridge -> switch ports entirely in the kernel.

Wire format: the switch end receives nio_anchor ('join this interface to
my own bridge' — the mirror of nio_bridge), the peer end nio_bridge with a
null bridge ('your anchor is bridged elsewhere': carrier, markers and
impairments only — never bridge membership, and never a bridge delete, on
an interface this node does not own). Anchor names come from the shared
naming contract (utils.kernel_anchor), a pure function of node type, id,
adapter and port — the controller computes the peer's anchor while the
node may be stopped, which is what makes links to stopped nodes wireable.

Stopped peers defer the join; after any node of a link starts
(UDPLink.node_started) the controller re-pushes the switch NIO, and the
switch completes — or repairs, when a peer restart replaced its interface
under the same name — the join against the kernel's actual membership
instead of assuming it.

VLAN changes now edit membership in place: only the ports whose mode/VLAN
actually changed are touched, each losing exactly the previous mode's
entries the new mode does not want (the dot1q admits-all entry is one
range operation in both directions), and no port ever leaves the bridge to
change its VLANs. This replaces the old 'reset by re-enslaving' shortcut
(delif+addif on every wired port, added when the ports were the switch's
own TAPs): on absorbed anchors, re-enslaving an interface owned by another
node is where live testing watched links die — anchors occasionally lost
their bridge membership right after a ports_mapping update (later updates
then failed with delif EINVAL), reproducibly with qemu, IOU and Dynamips
peers and never with Docker's veths.

Eligibility: the switch side needs no capability of its own (its bridge
exists by construction); the peer must be able to anchor. Switch-to-switch
cascades stay on the relay — a kernel interface belongs to one bridge, and
joining two bridges needs a veth pair, deferred.

Verified live on a real server: qemu peers through four VLAN cycles with
both anchors enslaved throughout; a mixed switch (2 IOU + 2 Dynamips, four
kernel ports); Docker peers with real ICMP isolating and recovering (with
both anchors still enslaved); VPCS relay ports likewise in place.
2026-10-06 00:21:09 +08:00