Skip to content

fix: Withdraw BGP routes gracefully on router shutdown - #414

Merged
privateip merged 1 commit into
mainfrom
fix/router-graceful-shutdown
Aug 16, 2026
Merged

fix: Withdraw BGP routes gracefully on router shutdown#414
privateip merged 1 commit into
mainfrom
fix/router-graceful-shutdown

Conversation

@privateip

Copy link
Copy Markdown
Contributor

Summary

galactic-router had no graceful shutdown story: on termination it exited immediately while its embedded BGP server still held every peer session open. Peers only noticed via connection reset or a hold-timer expiry, not an explicit withdrawal, so a rolling update or node drain could blackhole traffic until that timer fired. The routine to withdraw routes cleanly already existed but nothing ever called it. Shutdown now drives that routine directly, so peers get an immediate, explicit withdrawal instead of waiting on a timeout.

Test plan

  • On termination, galactic-router withdraws its BGP routes and exits cleanly within the termination grace period
  • Unit tests, lint, and build all pass

galactic-router had no graceful-shutdown story: on SIGTERM the process just exited while its embedded GoBGP server still held every peer session open. Peers only noticed via TCP RST or hold-timer expiry (default 90s minus keepalive slop) instead of an immediate withdrawal, so a rolling update or node drain could blackhole traffic for up to that long before routes cleared.

The machinery to do this correctly already existed and was already correct: RuntimeManager.StopAll drives each RouterRuntime.Stop, which for GoBGP cancels its server context and lets the embedded BgpServer's own StopBgp run — which does send a Cease NOTIFICATION to every neighbor before closing the session, an immediate and explicit withdrawal rather than a timeout. The gap was that nothing ever called StopAll: the GoBGP server runs in a goroutine started independently of the manager's context and never registered as a Runnable, so controller-runtime's own graceful shutdown had no path to reach it, and StopAll had zero callers anywhere in the repo.

runCmd now calls runtimeMgr.StopAll with a fresh, bounded-10s context right after mgr.Start returns (mgr.Start returning nil means the process's own ctx is already Done, from either a signal or the health server's fatal Serve error, so ctx itself can't be reused). No manifest change needed — the default 30s terminationGracePeriodSeconds already comfortably covers a 10s-bounded stop.

Added internal/runtime/manager_test.go, covering StopAll for the first time (0% coverage before this on internal/runtime): confirms it stops every live runtime, returns the first error across all of them, and is a no-op with an empty manager.

Verified on a live Kind cluster: applied a real BGPRouter CR to bring up an actual GoBGP runtime under the hardened container from the previous commit, confirmed Ready/RuntimeReady status, then deleted the pod and watched controller-runtime's shutdown sequence complete cleanly well inside the termination grace period.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@privateip
privateip requested a review from a team as a code owner August 16, 2026 13:10
@privateip
privateip merged commit 788c805 into main Aug 16, 2026
11 checks passed
@privateip
privateip deleted the fix/router-graceful-shutdown branch August 16, 2026 14:18
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.

2 participants