Skip to content

fix(router): hold consumers whose interface is not ready yet - #1379

Merged
scotwells merged 4 commits into
mainfrom
fix/serviceroute-missing-consumer-link
Nov 10, 2026
Merged

scotwells merged 4 commits into
mainfrom
fix/serviceroute-missing-consumer-link

Conversation

@scotwells

@scotwells scotwells commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Scot Schuchert-Wells

Related: datum-cloud/enhancements#921, datum-cloud/compute#630.

Problem

When an instance is deleted and recreated under the same name, its new attachment can be selected by a service route policy before the new host interface exists, or before the CNI has registered its attachment identity. The router treated that as a programming error. It also stopped at the first error, so other new consumers of the same policy on that node weren't programmed until a retry succeeded. The router logged "resolve consumer interface ...: Link not found" or "lookup attachment identity ...: key does not exist" until the interface appeared, which can trip the service route programming alert.

Measured in a test cell: every same-name redeploy produced 10 to 14 such errors in the 0.3 to 0.7 s before the new interface came up. First deploys of a name produced none.

Change

  • Hold, don't fail: EBPFRouteProgrammer.Apply holds a consumer whose interface is missing, or has no attachment identity, with nothing installed. This is what repair already does for an interface that disappears. The next repair installs it once the interface is ready. serviceroutemap.IdentityToken now wraps ErrNoAttachmentIdentity so that case can be told apart.
  • Event-based install: galactic-router watches the node's interfaces with the existing linkWatch. On a change, it requeues the policies whose applied intents use that interface.
  • Fallback for a missing identity: the CNI registers an interface's attachment identity after the interface appears, and that write sends no event. So a policy that holds a consumer is also rechecked after 1 s, doubling to the 30 s resync while the hold lasts. The router logs when a policy starts holding. Policies that hold nothing keep their normal requeue.
  • Don't stop at one failure: replacePolicyLocked tries every intent of a policy and returns the failures together.

A held consumer counts as applied. For a frontend policy, the node's programming acknowledgment can then report ready while that one consumer's interface isn't there yet. Until the interface exists, that consumer has nothing to serve.

Testing

  • New tests cover:
    • holding a consumer for a missing interface and for a missing identity, then installing it on repair
    • applying past a failing consumer
    • the in-place LocalSet replacement when every producer is gone
    • the per-policy recheck backing off from 1 s to 30 s, and not affecting policies that hold nothing
    • interface events requeueing only the policies that use the interface
  • go build ./..., golangci-lint (0 issues) and go test -race pass for ./internal/serviceroute/, ./internal/controller/, ./internal/plumbing/ebpf/serviceroutemap/ and ./cmd/galactic-router/ in a Linux build container.
  • Not yet run in a lab. After rollout, the check is that same-name redeploys log no "Link not found" programming errors, and that their DNS lookups still answer on the first try.

A recreated instance can be selected before its new host interface
exists or is registered. Programming it failed, and the first failure
stopped the rest of the policy, so other new consumers on the node
waited for a retry and the router kept reporting errors until the
interface appeared.

Hold such a consumer with nothing installed, as repair already does for
an interface that disappears, and install it when the interface comes
up: the router now watches the node's interfaces and requeues the
policies that use one. Apply every intent of a policy even when one
fails, and return the failures together.
The CNI registers an interface's attachment identity after the interface
appears, and that write sends no interface event, so a consumer held for
a missing identity waited for the 30 second resync. While the programmer
holds any consumer, recheck after one second and log that it is holding.
Exercise the in-place LocalSet replacement when every producer is gone.
Rechecking every policy each second while any consumer was held would
keep the single worker busy for as long as one interface never returns.
Recheck only a policy that holds one of its own consumers, starting at
one second and doubling to the 30 second resync, and log when a policy
starts holding.
@scotwells
scotwells marked this pull request as ready for review November 10, 2026 14:19
@scotwells
scotwells requested a review from a team as a code owner November 10, 2026 14:19
@scotwells
scotwells requested review from ecv and privateip November 10, 2026 14:19

@ecv ecv left a comment

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.

Approve. A failed Apply rolls back its own entries and only successes enter current, so going past a failing intent leaves nothing half-installed, and real failures still reach the programming alert. ErrNoAttachmentIdentity classifies through recoverableEntries, which apply, rebuild and repair all share.

Follow-ups:

  • A held consumer counts as a successful apply, sets Ready=true and adds to the programmed gauge, while GalacticServiceRouteProgrammingFailing fires only on result="error". A consumer whose identity never arrives is held at the 30 s recheck forever with only the one "holding" log. Add a held-intents gauge with an alert for a hold longer than a few minutes, and leave held intents out of the programmed gauge.
  • When Cleanup fails, replacePolicyLocked still returns early and skips the remaining intents, contrary to its new comment.

@scotwells
scotwells merged commit e053943 into main Nov 10, 2026
28 checks passed
@scotwells
scotwells deleted the fix/serviceroute-missing-consumer-link branch November 10, 2026 14:52
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.

3 participants