Skip to content

Bill Octavia load-balancer traffic to the tenant that owns the load balancer - #298

Merged
github-actions[bot] merged 8 commits into
developfrom
arashi.li/octavia-lb-owner
Aug 17, 2026
Merged

github-actions[bot] merged 8 commits into
developfrom
arashi.li/octavia-lb-owner

Conversation

@arasHi87

Copy link
Copy Markdown
Collaborator

Closes

Closes #131

What & why

Octavia runs a tenant's load balancer as an Amphora VM in the Octavia service project, so every byte it moves bills the operator. This bills the tenant who owns the load balancer instead.

Attribution is already a userspace lookup on the VM-side MAC, so the core of it needs no kernel change: a three-hop join (load balancer → project_id, amphora → compute_id, then every port whose device_id matches, minus the management port) rewrites the Amphora port's project. That one substitution moves both the tenant_id label and the interned tenant the kernel keys zone comparisons on.

compute_id is the join key rather than the Amphora's vrrp_port_id because Octavia plugs an Amphora into a pool member's foreign subnet by handing Nova a network with no port — Nova then mints a port with no Octavia marker and no allowed-address pair. Confirmed live on c36: device_owner=compute:nova, aap=[], service project. Any narrower join silently leaves that traffic billing the operator.

Two kernel-side pieces make Segment 2 read infra without swallowing Segment 1:

  • an Amphora marker packed into the top bit of the existing mac_tenant_map value (one AND, versus ~20–30 ns for a sidecar-map lookup on every packet whose peer resolves);
  • a small tenant-scoped amphora_base_ip set, probed only when the marker is set. HAProxy accepts Segment 1 on the VIP and originates Segment 2 from the port's base address, so the address is what separates them. Base addresses rather than VIPs because it fails safe: a gap drops Segment 2 to same_tenant ($0 either way), whereas a missing VIP would mark Segment 1 infra and stop billing it.

An intermediate commit (a4a7ba1) zoned any L2-adjacent flow touching an Amphora as infra; 1b06184 fixes it. That version made an internal cross-tenant client's load-balancer request free in both directions.

The harness gained Octavia support: a DSL primitive, eight Cloud verbs, the live driver, the fake, realize/down wiring, VIPTarget / LBFIPTarget, and a preflight-resolved optional lb_flavor_name so multi-Amphora scenarios report SKIPPED on a cluster with no ACTIVE_STANDBY flavor staged rather than failing.

Test plan

  • Unit — task test
  • Integration — task test-integration (40/40 packages)
  • Bench-gate — task bench-gate + per-packet ceiling (26 ns/packet, zero allocations)
  • Scenario — scenariotest run octavia-lb-attribution
  • Manual e2e on a live cluster

Live validation

c36 (10.32.36.x), 2026-08-17. octavia-lb-attribution: ACTIVE_STANDBY (MASTER+BACKUP), three pool members across two subnets, internal same-tenant client, internal cross-tenant client, and an external client via a floating IP on the VIP port. All 8 assertions PASS:

tenant zone dir delta
T1 same_tenant tx/rx 4.0 MiB
T2 other_tenant tx 4.0 MiB
T1 other_tenant rx 4.0 MiB
T1 external tx/rx 4.1 MiB
T1 infra tx/rx 12.1 MiB

infra = 12.1 MiB is exactly the three flows' payload — every byte through the load balancer identified as Segment 2 plumbing. other_tenant = 4.0 MiB is the regression guard for the intermediate bug above.

Two cluster prerequisites were needed and are worth knowing before reproducing:

  • [libvirt] num_pcie_ports was unset, so an Amphora booted with 8 PCIe root ports and 8 devices — no spare. Cross-subnet member plugs failed No more available PCI slots while the LB stayed ACTIVE. Set to 28 on p4/p5/p6.
  • ACTIVE_STANDBY comes from an Octavia flavorprofile, not a per-LB flag. Staged lachesis-ha.

Scope / deliberately not touched

  • Per-backend byte counts — proxy-terminating LBs never recover them from packets; the source would be Octavia's PROMETHEUS listener, a userspace collector.
  • Conntrack refinement of Segment 1 — unnecessary in the common case (external client IPs survive FIP DNAT), registered as deferred item 0 in docs/architecture/contracts.md.
  • OVN provider — different shape, and safe by construction since it has no Amphora for the join to match.

Notes

Touches the billing path. Contract 7 (monotone billing series) is preserved by keeping attribution VM-side: an earlier design branched on the peer being an Amphora, which would have put a server_id under a tenant_id that does not own it and broken the total → tenant → server → port nesting. reconcile.DesiredMACs is now the single definition of the desired metadata map, consumed by both cold-start and the reconciler, with a cross-check test — two copies of that logic is a billing bug waiting for someone to edit one.

1007ee9 fixes an observability bug found while debugging this: lachesis_neutron_amphora_ports was published only from the cold-start path, so a load balancer created after boot never moved it and the failure it exists to detect left it frozen at a healthy-looking value. It cost four wrong diagnoses before anyone grepped for its call sites.

Handbook entry: bigstack-oss/bigstack-handbook#279.

DoD

  • Linked issue's DoD met, incl. Handbook knowledge update (/bigstack-core:save-to-handbook)

…r instead of the service project

Signed-off-by: arashi.li <arashi.li@bigstack.co>
…g packed into mac_tenant_map

Signed-off-by: arashi.li <arashi.li@bigstack.co>
…e amphora base address

Signed-off-by: arashi.li <arashi.li@bigstack.co>
…tead of the conntrack design

Signed-off-by: arashi.li <arashi.li@bigstack.co>
…nt attribution live

Signed-off-by: arashi.li <arashi.li@bigstack.co>
… instead of only at cold-start

Signed-off-by: arashi.li <arashi.li@bigstack.co>
…a floating IP on its VIP port

Signed-off-by: arashi.li <arashi.li@bigstack.co>
… in WaitLBActive

Signed-off-by: arashi.li <arashi.li@bigstack.co>
@arasHi87
arasHi87 force-pushed the arashi.li/octavia-lb-owner branch from 3a2a7a1 to 8550a99 Compare August 17, 2026 07:30
@arasHi87 arasHi87 added the done Mark a PR ready to merge (triggers auto-merge once CI + reviews pass) label Aug 17, 2026
@github-actions
github-actions Bot merged commit 4ad1138 into develop Aug 17, 2026
10 checks passed
@github-actions
github-actions Bot deleted the arashi.li/octavia-lb-owner branch August 17, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

done Mark a PR ready to merge (triggers auto-merge once CI + reviews pass)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Octavia load-balancer attribution

1 participant