8000 feat(components): send RequestSource from bridge and record status.componentGeneration by VLukiantsev · Pull Request #1237 · google/xmc · GitHub
[go: up one dir, main page]

Skip to content

feat(components): send RequestSource from bridge and record status.componentGeneration - #1237

Merged
VLukiantsev merged 6 commits into
feat/components-bridge-cluster-fence-b548436300from
feat/components-bridge-request-source-b548436300
Oct 19, 2026
Merged

VLukiantsev merged 6 commits into
feat/components-bridge-cluster-fence-b548436300from
feat/components-bridge-request-source-b548436300

Conversation

@VLukiantsev
@VLukiantsev VLukiantsev commented Oct 13, 2026 •
Copy link
Copy Markdown
Collaborator

Summary

The bridge now sends RequestSource on its CMS intent RPC (SetComponentIntent), records the CMS generation in status.componentGeneration, and projects ownership rejections as a SourceConflict condition. Same-cluster recreation takeover is the layer above.

Motivation & Context

CMS can enforce ownership (#1232–#1235) only for sourced requests. The bridge must send its source, re-declare when CMS has no fence for it, and keep the CMS generation so the sync tracker and the actuator can compare against it.

Key Changes

  • RequestSource built from the CR (uid, generation, namespace) and the declared ClusterIdentity; callWithOwnership attaches it, records fence rejections on the ClusterFence (RecordConflict), and on CLUSTER_NOT_DECLARED re-declares the epoch and retries once.
  • Ownership rejections map to SourceConflict=True (SourceIncarnationMismatch, SourceNamespaceConflict, ClusterFenced, ClusterEpochRegressed) with Ready=False/SourceConflict and Stalled=True. Policy blocks return RequeueAfter 15m without an error log.
  • STALE_GENERATION is a stale informer snapshot, not a conflict: no status write, requeue after ~1s.
  • DEPENDENCY_CYCLE and DEPENDENCY_NOT_REGISTERED are matched by ErrorInfo reason, never by message text.
  • CRD: status.componentGeneration (the CMS generation), used by the sync tracker and observed-generation updates. The actuator drift monitor compares it with the locked generation with no fallback; componentGeneration == 0 means not synced and blocks placement.
  • Tests: TestApplyIntentResponse (full-struct cmp.Diff), BuildStatusMap checked against a full expected map, TestCallWithOwnership_WithoutFence, TestCallWithOwnership_Retries (re-declare and stale-generation paths), sourced intent / namespace conflict / stale generation against an in-process CMS, reconciler and actuator tests.

Stack Position

Assumptions

  • Only SetComponentIntent is sourced in this layer, so CMS ownership and fencing do not yet cover observation, deactivation or deletion from a fenced cluster or a stale incarnation — feat(components): enforce source ownership on observation and deactivation RPCs #1238 sources UpdateObservedGeneration, PrepareDeactivation and CompleteDeactivation, and feat(components): enforce deletion ownership and fenced CR cleanup #1240 sources ExecuteDeletion — if those layers do not land with this one, a fenced cluster can still deactivate or delete records it does not own.
  • Until feat(components): adopt a same-cluster recreated Component via expected_owner_uid #1274 lands, a CR recreated in the same cluster while CMS still holds the old record reports SourceConflict=SourceIncarnationMismatch — acceptable while Components are unlaunched.
  • Components have not launched, so no deployed bridge predates componentGeneration; the actuator therefore has no observedGeneration fallback — impact if wrong: placement is blocked (retryable) until the bridge writes componentGeneration.
  • The live GKE evidence still applies to this revision — it was collected before the last rebases onto main and before the split — if wrong, the checkpoint must be re-run before merge.

Verification & Testing

  • Commands Executed: bazel test //pkg/components/... //cmd/xmc_bridge/... //cmd/experiment_state_server/... //cmd/actuator/... //test/e2e:kind_3p_harness_unit_test and the //test/helm py tests at this layer's tip; golangci-lint run (v1.64.6); gofmt -l.
  • Coverage & Test Status: all pass.
  • KinD E2E: //test/e2e:components_lifecycle_test passed with the whole stack applied.
  • Diff size (tools/analyze_diff_size.py): 305 prod / 593 test / 898 total / 5 doc lines (was 424 / 670 before the split).

Live Deployment & Manual Testing

Scenario Result Observation
RequestSource and status.componentGeneration ✅ PASS Profile CRs and mt-cp3-recreate (1 → 2 → 3) persist the CMS-assigned status.componentGeneration, with matching Source* columns in Spanner.
SourceConflict projection on ownership rejection ✅ PASS Creating mt-cp3-del2 in xmc while CMS recorded other-ns as its owner projected SourceConflict=True (SourceNamespaceConflict), Ready=False (SourceConflict) and Stalled=True.

Related Issues / Bugs

Bug: 548436300

TAG=agy
CONV=d833b0e6-4821-48f6-8576-776d53f55a5c


Stack created with GitHub Stacks CLI • Give Feedback 💬

@VLukiantsev
VLukiantsev added this pull request to stack #1245 October 13, 2026 07:27
This was referenced Oct 13, 2026
@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces componentGeneration to track the CMS component generation in the Component status, independent of the Kubernetes custom resource's observedGeneration. It also implements single-writer ownership checks using RequestSource (with UID and Cluster fields) to safely handle same-cluster custom resource recreations and prevent stale incarnations from overwriting newer statuses. Feedback on the changes highlights a bug in sourceChanged where requiring entry.componentGeneration > 0 prevents syncing the transition from an unknown/zero generation to a valid positive generation, which can be resolved by simplifying the condition.

Comment thread pkg/components/bridge/cms_client.go Resolved
@VLukiantsev
Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces tracking for componentGeneration in the Component status, allowing CMS component generations to be tracked independently of the Kubernetes custom resource observedGeneration. It also implements single-writer ownership checks and same-cluster recreation takeovers using a new LiveUIDReader. The review feedback focuses on defensive programming, suggesting explicit nil checks for info (returned by extractErrorInfo), setResp, r.fence, and t.entries to prevent potential nil pointer dereferences and improve overall code robustness.

Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/cms_client.go Resolved
@VLukiantsev
Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces 'componentGeneration' to track CMS component generation independently of 'observedGeneration', updating the CRD and reconciliation logic accordingly. It enhances CMS intent handling by implementing ownership verification via 'RequestSource' and 'liveUIDReader', allowing for safer same-cluster custom resource recreation. Additionally, it adds conflict handling for ownership mismatches and stale incarnations. The review feedback correctly identifies potential safety issues, recommending the addition of defensive nil checks when accessing 'ErrorInfo' fields and 'SetComponentIntent' response structures to prevent runtime panics.

Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
Comment thread pkg/components/bridge/reconciler.go Resolved
@VLukiantsev
Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a robust ownership and synchronization mechanism for XMC Components by adding componentGeneration to track CMS state independently of Kubernetes metadata generation. It includes logic to handle source conflicts (e.g., incarnation mismatches, stale generations, and cluster fencing) using google.rpc.ErrorInfo for machine-readable error handling. The changes involve updating the Reconciler to verify live component UIDs, enhancing the cmsSyncTracker to detect source changes, and updating the CRD and status handling to support the new generation tracking. Several tests were added to verify the new adoption and conflict resolution logic.

@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch from ca35c8f to a35295c Compare October 16, 2026 09:54
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch from a35295c to a3137cf Compare October 16, 2026 11:27
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch 2 times, most recently from 2c11ac7 to 09b57ba Compare October 16, 2026 12:18
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch 3 times, most recently from e2afa75 to a4e95f0 Compare October 16, 2026 13:45
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch from a4e95f0 to ab0dac6 Compare October 16, 2026 14:39
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch 2 times, most recently from 40d751d to fcf9ec3 Compare October 16, 2026 15:27
Comment thread pkg/components/bridge/reconciler.go Resolved
@VLukiantsev
VLukiantsev marked this pull request as ready for review October 19, 2026 07:24
@github-actions
github-actions Bot requested a review from janzak-dev October 19, 2026 07:24
@VLukiantsev
VLukiantsev force-pushed the feat/components-bridge-request-source-b548436300 branch from ffee966 to 2b6ffb6 Compare October 19, 2026 08:05
…mponentGeneration

Every bridge intent and lifecycle RPC now carries a RequestSource (CR
UID, generation, namespace and the declared cluster identity), so CMS
enforces single-writer ownership and the cluster fence on it:

- callWithOwnership attaches the source, records fence rejections on
  the ClusterFence (RecordConflict), and on CLUSTER_NOT_DECLARED
  re-declares the cluster epoch and retries once. CMS ownership and
  fence rejections (SOURCE_INCARNATION_MISMATCH,
  SOURCE_NAMESPACE_CONFLICT, CLUSTER_FENCED, CLUSTER_EPOCH_REGRESSED)
  report SourceConflict=True without Helm side effects.
- STALE_GENERATION means the informer snapshot is older than a
  generation CMS already applied: the pass writes no status
  (StaleIncarnation) and requeues after about 1s, never reporting a
  SourceConflict.
- The bridge matches DEPENDENCY_NOT_REGISTERED and DEPENDENCY_CYCLE by
  ErrorInfo reason instead of by message prefix.
- The CMS component generation returned by SetComponentIntent is kept
  in status.componentGeneration (new CRD field) and fed back into the
  sync tracker and observed-generation updates; the actuator checks it.

A Component recreated in the same cluster while CMS still holds the
old incarnation's record reports SourceConflict=SourceIncarnationMismatch
in this layer; the next layer adopts it via expected_owner_uid.

Folds this layer's review fixes (DEPENDENCY_NOT_REGISTERED by reason,
first-known component generation, nil-safe RecordConflict, explicit nil
guards, staleComponentRetryDelay, envtest intents at the overriding
fence epoch, skipping sync records at unknown generation) and the CMS
authorizer in the tests this layer adds.

Bug: 548436300
TAG=agy
CONV=b2ebecbe-c04a-4fb7-a16c-d23de82ed3e5
…test file

Bug: 548436300

TAG=agy

CONV=d833b0e6-4821-48f6-8576-776d53f55a5c
…h ErrorInfo

The CMS sync-condition case added for observedGeneration gating built a plain FailedPrecondition status, which the ErrorInfo-reason matcher introduced here no longer recognizes. Use the dependencyNotRegisteredErr helper like the other gating tests.

Bug: 548436300
TAG=agy
CONV=d833b0e6-4821-48f6-8576-776d53f55a5c
…tusInt table-driven

Run the team-b enabled/disabled namespace-conflict cases in
TestReconcileComponent_SourceNamespaceConflictAndStaleGeneration as named
subtests, keeping the shared reconciler state and case order. Run each
TestExtractStatusInt case as a subtest and fold the nil-object check into
the table. Inputs and assertions are unchanged.

Bug: 548436300
TAG=agy
CONV=d833b0e6-4821-48f6-8576-776d53f55a5c
…alls

The test file targets reconciler.go, and this layer has no live-UID
takeover, so the header no longer mentions it. sourceCallRecorder counts
calls instead of recording ExpectedOwnerUid, which this layer never
sets; the owner-UID recording arrives with the takeover.

Comment link: #1237 (comment)

Bug: 548436300
TAG=agy
CONV=d833b0e6-4821-48f6-8576-776d53f55a5c
…vely

TestApplyIntentResponse now compares the whole ReconcileResult with
cmp.Diff instead of checking State and ComponentGeneration with chained
inequalities, so stray field writes also fail the test.

The populated TestBuildStatusAndBuildStatusMap subtest now declares the
full expected status map (generations, state, access env vars and the
condition with its fixed lastTransitionTime) and compares it with
cmp.Diff, like the nil subtest.

Comment link: #1237 (comment)
Comment link: #1237 (comment)

Bug: 548436300
TAG=agy
CONV=d833b0e6-4821-48f6-8576-776d53f55a5c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority::2-high top of mind. should not wait.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0