-
Notifications
You must be signed in to change notification settings - Fork 277
fix(operations): withdraw failed reconfigure intent from ComponentParameter desired #10552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,9 +76,62 @@ func (r *reconfigureAction) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli c | |
| if phase == opsv1alpha1.OpsSucceedPhase { | ||
| return r.syncReconfigureForOps(reqCtx, cli, resource, opsDeepCopy, opsv1alpha1.OpsSucceedPhase) | ||
| } | ||
| // The merge failed, so the assignments this ops wrote will never be applied, | ||
| // yet they would stay in the ComponentParameter desired spec and keep failing | ||
| // the projection for every later reconfigure. Withdraw this ops's own writes | ||
| // (and only them) so the failed intent does not outlive the failed ops. | ||
| if err := r.withdrawReconfigureFromParameters(reqCtx, cli, resource); err != nil { | ||
| return "", noRequeueAfter, err | ||
| } | ||
| return opsv1alpha1.OpsFailedPhase, 0, intctrlutil.NewFatalError(fmt.Sprintf("reconfigure failed: %s", msg)) | ||
| } | ||
|
|
||
| // withdrawReconfigureFromParameters removes the desired assignments written by | ||
| // this ops from the ComponentParameter, guarded by value equality so that a | ||
| // newer ops that re-set the same key with a different value is not clobbered. | ||
| // It is the failure-path counterpart of applyReconfigureToParameters: the ops | ||
| // only withdraws its own write, it does not do any schema validation. | ||
| func (r *reconfigureAction) withdrawReconfigureFromParameters(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) error { | ||
| sameValue := func(a, b *string) bool { | ||
| if a == nil || b == nil { | ||
| return a == b | ||
| } | ||
| return *a == *b | ||
| } | ||
| for _, reconfigure := range resource.OpsRequest.Spec.Reconfigures { | ||
| compNames, err := r.resolveReconfigureComponents(reqCtx.Ctx, cli, resource.Cluster, reconfigure.ComponentName) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| for _, compName := range compNames { | ||
| compParam, err := r.getRunningComponentParameter(reqCtx.Ctx, cli, resource.Cluster.Namespace, resource.Cluster.Name, compName) | ||
| if err != nil { | ||
| return client.IgnoreNotFound(err) | ||
| } | ||
| if compParam.Spec.Desired == nil || len(compParam.Spec.Desired.Assignments) == 0 { | ||
| continue | ||
| } | ||
| patch := client.MergeFrom(compParam.DeepCopy()) | ||
| changed := false | ||
| for _, param := range reconfigure.Parameters { | ||
| current, ok := compParam.Spec.Desired.Assignments[param.Key] | ||
| if !ok || !sameValue(current, param.Value) { | ||
| continue | ||
| } | ||
| delete(compParam.Spec.Desired.Assignments, param.Key) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value-equality guard does not prove ownership. If the ComponentParameter already had
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in fdfb93e — you're right that value equality cannot prove ownership, and the fix now anchors ownership on a prior-state snapshot instead:
Tests added for all four behaviors: previously-accepted same-value kept; overwritten key restored to prior value; fresh key deleted; no-snapshot no-op. Existing fixtures (unrelated-key survival, newer-writer guard, mixed two-ops assignments) updated to carry the snapshot and still pass. |
||
| changed = true | ||
| } | ||
| if !changed { | ||
| continue | ||
| } | ||
| if err := cli.Patch(reqCtx.Ctx, compParam, patch); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (r *reconfigureAction) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) (err error) { | ||
| if len(resource.OpsRequest.Spec.Reconfigures) == 0 { | ||
| return intctrlutil.NewErrorf(intctrlutil.ErrorTypeFatal, `invalid reconfigure request: %s`, resource.OpsRequest.GetName()) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
aggregatePhaseobserves one failed ComponentParameter, this loop withdraws the intent from every component in the Ops, including components that have already reachedFinishedand applied their configuration. That silently introduces an all-or-nothing compensation transaction across the entire Reconfigure list. The Reconfigure API currently only defines a list of per-component updates; it does not define atomicity or rollback semantics. Is the whole Ops intended to be atomic? If not, this reverts successful component changes because another component failed. If it is intended to be atomic, that behavior needs to be defined by the API/status contract rather than introduced only by this failure-path implementation.