Expand description
Provider-abstracted floating/reserved-IP mobility (R594-F5).
FloatingIpProvider is the domain-level trait each vendor adapter
(HetznerFloatingIp, OvhFloatingIp, VultrFloatingIp — sibling
modules in this directory) implements. reconcile_assignment is the
shared idempotent + zone-checked core all three run through, so the
“no-op when already assigned” / “reject a cross-zone move” behavior is
written and tested exactly once instead of three times.
on_ingress_owner_changed is the Rust-level callable entry point for
R594-F5’s ask: given the raft ingress_owner seam
(oss/yubaba/crates/yubaba/src/raft/mod.rs’s YubabaRequest::SetIngressOwner
/ ClearIngressOwner, RaftAppState::ingress_owner) and the machine it
currently names, command the provider floating IP to follow. Wiring
this to fire automatically whenever ingress_owner transitions lives
in the raft-apply / leadership-reconcile path
(oss/yubaba/crates/yubaba/src/leader.rs), which is peer-owned and
off-limits to this ticket — see on_ingress_owner_changed’s doc
comment for the exact call site a follow-up should add. This is the
same “mechanism now, wiring later” shape R594-F3 used for service
records.
This mirrors, at the sovereign-ingress tier, the “external identity
follows placement” property R591 names for
Headscale via a Cloudflare Tunnel. R591 is peer-owned and gated on R570
(real multi-node raft HA); this module is not blocked on either — it
builds directly on the ingress_owner seam, which already exists.
@yah:ticket(R859-F2, “Wire floating-ip.* provider adapters to ingress_owner transitions + health-checked DNS withdrawal for dead origins”) @yah:at(2026-09-04T19:06:57Z) @yah:status(open) @yah:assignee(agent:user-custom-char-gul2) @yah:parent(R859) @yah:next(“The verbs and adapters exist with zero callers: envoy/floating_ip.rs + provider/{vultr,hetzner,ovh}_floating_ip.rs are dead code today. Raft already holds and applies ingress_owner (raft/mod.rs:597,1182) — the missing piece is the effector that commands the provider when it changes, which is exactly W267 Tier 1’s ‘external identity follows placement’ (the R591 property).”) @yah:next(“Two failover speeds, both currently manual: intra-provider = floating-IP reassign (seconds, no DNS propagation, no cert re-mint — mind the W267-verified mobility constraints: Hetzner per network zone, OVH per DC region, Vultr region-bound); cross-provider = short-TTL DNS withdrawal of the dead origin’s A record (needs R859-F1’s rendering).”) @yah:next(“The health signal for withdrawal must NOT come from raft health (W267 §‘Where liveness lives’ — reachability is observer-relative); use the supervisor-level fact only: a machine leaving the fleet / its yubaba unreachable from quorum, not a per-proxy probe.”) @yah:next(“cloud.mesh_failover (W271) is the existing manual verb — keep it as the operator path; this ticket automates the effector both paths share.”) @yah:next(“Tier: Wizard — touches live-fleet failover semantics; wrong wiring here turns a leadership flap into a public outage. Design the guard rails (hysteresis, refuse-on-degraded-quorum per yubaba-failover.md) before the effector.”)
Structs§
- Floating
IpAssign Outcome - Outcome of
reconcile_assignment/on_ingress_owner_changed. - Floating
IpState - Current provider-side state of a floating/reserved IP.
- Floating
IpTarget - A resolved reassign target: provider-native attach id + the mobility zone it lives in.
Traits§
- Floating
IpProvider - One provider’s floating/reserved-IP transport + mobility policy.
Functions§
- on_
ingress_ owner_ changed - Callable entry point: react to the raft
ingress_ownerseam namingmachineas the box that now owns public ingress, by commandingip_idto follow it. - reconcile_
assignment - Idempotent, zone-checked core shared by every provider adapter and by
on_ingress_owner_changed.