Skip to content

feat(gateway): reconcile tenant VRF egress default routes - #385

Open
privateip wants to merge 1 commit into
feat/865-egress-phase-cfrom
feat/865-egress-phase-d
Open

feat(gateway): reconcile tenant VRF egress default routes#385
privateip wants to merge 1 commit into
feat/865-egress-phase-cfrom
feat/865-egress-phase-d

Conversation

@privateip

Copy link
Copy Markdown
Contributor

Summary

Phase D of #865, per docs/plans/865-edge-gateway-nat66-egress.md
§4.4/§4.5. Resolves #384 (candidate 2: new controller). Stacked on
#383 (Phase C). Depends on datum-cloud/network#15 (now includes the
Phase D CRD field additions too).

  • internal/egressroute: per-node reconcile logic -- enumerate local
    VRFs, resolve VPC, check NetworkEgressPolicy enablement + gateway
    assignment, resolve the destination uSID, install/remove the ::/0
    SEG6 encap route. Mirrors internal/gc's split of real-logic-package
    plus thin-controller-wrapper.
  • internal/controller/egressroute_controller.go: ticker-driven
    wrapper (mirrors GCReconciler), registered in galactic-router --
    runs on every compute node, not just gateway nodes.
  • internal/controller/networkegresspolicy_controller.go: new
    NetworkEgressPolicyReconciler, one-time gateway-node assignment
    mirroring NetworkRuleReconciler.assignPrimaryNode. Registered in
    galactic-gateway.
  • internal/plumbing/vrf.ResolveVPC: new exported VPC-from-VRF-name
    resolver.
  • NetworkGatewayReconciler.publishEgressAddresses: extended to
    also publish EgressSID; refactored to share a new
    publishHostRouteAdvertisement helper across all three
    self-address advertisements instead of triplicating the
    get-or-create-or-update block.
  • Config: GALACTIC_ROUTER_EGRESS_ROUTE_INTERVAL, separate from GC's
    own interval.

Two inferences beyond the plan's text

Neither is spelled out in the design plan; both were necessary to
make this phase actually work (full reasoning in
internal/egressroute's package doc comment):

  • tenant_arg reuses each VPC's own BGPVRFInstance.Spec.VRFID
    (already allocated per-node for ingress SRv6 decap, and already
    unique per-node -- exactly the isolation property egress needs).
  • NetworkGatewayStatus.EgressSID must be published into BGP the
    same way SRv6Address is, or no compute node has a kernel route to
    encapsulate toward at all.

Bug found and fixed along the way

srv6.RouteEgressDel has been broken since it was written -- it set
an empty SEG6Encap{} on the delete request, which netlink
unconditionally rejects ("No Segment in srh"). Already called in
production by internal/runtime/gobgp/monitor.go's BGP
path-withdrawal handler, so every SEG6 route withdrawal in this
codebase was likely failing, unrelated to egress. Fixed, with the
first test coverage RouteEgressDel has ever had.

Testing

  • New tests: internal/egressroute (unit + root-gated e2e proving
    the full install/remove lifecycle against a real kernel VRF),
    internal/plumbing/vrf, internal/plumbing/srv6 (the bug-fix
    regression test), internal/controller (both new reconcilers),
    internal/config.
  • go build ./..., go vet ./..., task lint (0 issues) -- clean.
  • Full task test:unit under root: everything this PR touches
    passes. The only failure present (internal/cni/tap) is the same
    pre-existing, unrelated environment issue confirmed on earlier PRs
    in this stack.

Closes #384.

🤖 Generated with Claude Code

@privateip
privateip requested a review from a team as a code owner August 13, 2026 20:43
@privateip
privateip requested review from gaghan430 and removed request for a team August 13, 2026 20:43
Comment on lines +330 to +348
go func() {
ticker := time.NewTicker(cfg.EgressRouteInterval)
defer ticker.Stop()

if !mgr.GetCache().WaitForCacheSync(ctx) {
log.Printf("EgressRoute: cache sync failed, skipping initial pass")
return
}
rec.RunOnce(ctx)

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
rec.RunOnce(ctx)
}
}
}()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why's this use a separate ticker process for reconciling things instead of leveraging the normal controller runtime behavior?

Comment on lines +401 to +403
cmd.Flags().DurationP("egress-route-interval", "",
config.DefaultRouterEgressRouteInterval,
"Egress default-route reconcile interval (datum-cloud/enhancements#865)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link to an enhancement here is a little odd.

Phase D of #865, per docs/plans/865-edge-gateway-nat66-egress.md
§4.4/§4.5, resolved toward the new-controller candidate.

- internal/egressroute: per-node reconcile logic -- enumerate local
  VRFs, resolve VPC, check NetworkEgressPolicy enablement + gateway
  assignment, resolve the destination uSID, install/remove the ::/0
  SEG6 encap route. Mirrors internal/gc's split of real-logic-package
  plus thin-controller-wrapper.
- internal/controller/egressroute_controller.go: ticker-driven
  wrapper (mirrors GCReconciler), registered in galactic-router --
  runs on every compute node, not just gateway nodes, since that's
  where tenant VRFs actually live.
- internal/controller/networkegresspolicy_controller.go: new
  NetworkEgressPolicyReconciler, one-time gateway-node assignment
  mirroring NetworkRuleReconciler.assignPrimaryNode. Registered in
  galactic-gateway alongside NetworkRuleReconciler.
- internal/plumbing/vrf.ResolveVPC: new exported VPC-from-VRF-name
  resolver.
- NetworkGatewayReconciler.publishEgressAddresses: extended to also
  publish EgressSID, refactored to share a new
  publishHostRouteAdvertisement helper across all three self-address
  advertisements (SRv6Address, EgressAddress, EgressSID) instead of
  triplicating the get-or-create-or-update block.
- config: GALACTIC_ROUTER_EGRESS_ROUTE_INTERVAL, separate from GC's
  own interval.

Two inferences the plan's own text doesn't spell out, made necessary
by actually implementing this phase (see network repo's companion PR
and internal/egressroute's package doc comment for the full
reasoning):

- tenant_arg reuses each VPC's own BGPVRFInstance.Spec.VRFID (already
  allocated per-node for ingress SRv6 decap, and already unique
  per-node -- exactly the isolation property egress needs).
- NetworkGatewayStatus.EgressSID must be published into BGP the same
  way SRv6Address is, or no compute node has a kernel route to
  encapsulate toward at all.

Bug found and fixed along the way: srv6.RouteEgressDel has been
broken since it was written -- it set an empty SEG6Encap{} on the
delete request, which netlink unconditionally rejects. Already called
in production by internal/runtime/gobgp/monitor.go's BGP
path-withdrawal handler, so every SEG6 route withdrawal in this
codebase was likely failing, unrelated to egress. Fixed and added the
first test coverage RouteEgressDel has ever had.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design: tenant VRF egress default-route reconciler (865 Phase D)

2 participants