Skip to content

feat(ha): prune-sync drift backstop + HAMirrorSyncFailed event (Part of #295) - #1

Open
sumanthd032 wants to merge 2 commits into
feat/295-remote-syncerfrom
feat/295-prune-sync-events
Open

feat(ha): prune-sync drift backstop + HAMirrorSyncFailed event (Part of #295)#1
sumanthd032 wants to merge 2 commits into
feat/295-remote-syncerfrom
feat/295-prune-sync-events

Conversation

@sumanthd032

Copy link
Copy Markdown
Owner

Description

Part of kubeslice#295 — PR2, stacked on PR1 (feat/295-remote-syncer). Adds two RemoteSyncer resilience backstops:

  • Prune-only resync — periodically diffs Standby objects labeled ha.kubeslice.io/synced-from against the remote cache and enqueues orphaned mirrors (Active-side deletes missed while not watching) onto the existing workqueue. Waits for cache sync first; a failed list skips that kind and bumps ha_sync_errors_total{kind,"prune"} instead of pruning on partial info. Configurable via --ha-sync-interval (default 60s).
  • HAMirrorSyncFailed event — mirror failures surface as a Kubernetes event on the failing object, once per failure episode. Ships with its make generate-events output; a regression test pins the generated-map entry.

Base is feat/295-remote-syncer, not master, so the diff is exactly these 2 commits. Review after PR1.

How Has This Been Tested?

go test -race ./pkg/ha/... (52 tests), go build ./..., go vet, and gofmt — all clean.

Checklist:

  • The title of the PR states what changed and the related issue number.
  • Does this PR requires documentation updates?
  • I have performed a self-review of my own code.
  • I have added all the required unit test cases.

Does this PR introduce a breaking change for other components like worker-operator?

No.


Informers self-heal missed updates via periodic resync and the workqueue
owns retry-on-failure, but neither can remove a mirror whose Active-side
original was deleted while the Standby wasn't watching (e.g. between two
Standby runs): cold-start informers only deliver what currently exists,
so such an orphan would survive forever.

Add a prune loop to RemoteSyncer that periodically lists Standby objects
carrying the ha.kubeslice.io/synced-from label per mirrored type, diffs
them against the remote cache, and enqueues anything no longer present
on the Active hub onto the ordinary mirror workqueue. The worker re-reads
Active at dequeue time, so the existing conflict guard, NotFound->delete
semantics, and rate-limited retry apply unchanged, and no second write
path races the workers.

Fail-safe choices:
- The first pass waits for the remote cache to sync; an unsynced cache
  lists empty, which would otherwise read as "everything was deleted"
  and prune every mirror on the Standby.
- A failed list (remote or local) skips that kind for the round and
  increments ha_sync_errors_total{kind,"prune"} instead of pruning on
  partial information.

Configurable via --ha-sync-interval (default 60s).

Part of kubeslice#295

Signed-off-by: Sumanth D <sumanthd032@gmail.com>
…lure

Surface mirror failures as Kubernetes events on the Standby, attached to
the object that failed to sync, using the EventRecorder main.go already
builds for the reconcilers. The entry lives in
config/events/controller.yaml with the generated map and config-map
output from make generate-events committed alongside — RecordEvent
hard-fails for any EventName missing from the generated EventsMap, so
skipping that step would silently no-op the whole feature. A regression
test pins the entry's presence in the generated map (and that a missing
entry errors loudly), so an accidental revert of the generated code
fails in go test rather than at runtime.

One event per failure episode, not per retry attempt: NumRequeues is 0
only on the first failure since the last success, and early workqueue
backoff retries arrive milliseconds apart — although the recorder
aggregates repeats into one Event's Count, every call is still an
API-server write. ha_sync_errors_total continues to count every attempt.

The recorder is called directly rather than through util.RecordEvent:
that helper logs via util.CtxLogger, which panics on any context that
didn't pass through a reconciler's request-context setup — true for the
syncer's own context (main.go's signal-handler context). Caught by the
new event-emission test before it could crash a live Standby.

Part of kubeslice#295

Signed-off-by: Sumanth D <sumanthd032@gmail.com>
Copilot AI review requested due to automatic review settings July 24, 2026 12:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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