Skip to content

Commit 01d3c1a

Browse files
weicaoapecloud-bot
authored andcommitted
chore: patch metadata-only pod updates (#10401)
(cherry picked from commit e296fcd)
1 parent 2535caa commit 01d3c1a

6 files changed

Lines changed: 82 additions & 20 deletions

File tree

pkg/controller/instance/reconciler_update.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,14 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder
149149
return kubebuilderx.Continue, err
150150
}
151151

152-
// if already updating using subresource, don't update it again, because without subresource, those fields are considered immutable.
153-
// Another reconciliation will be triggered since pod status will be updated.
154-
if !equalResourcesInPlaceFields(pod, newPod) && supportResizeSubResource {
152+
switch {
153+
case !equalResourcesInPlaceFields(pod, newPod) && supportResizeSubResource:
155154
err = tree.Update(newMergedPod, kubebuilderx.WithSubResource("resize"))
156-
} else {
157-
if !safeMetadataOnlyInPlaceUpdate(pod, newPod) {
158-
if err = r.switchover(tree, inst, newMergedPod.(*corev1.Pod)); err != nil {
159-
return kubebuilderx.Continue, err
160-
}
155+
case safeMetadataOnlyInPlaceUpdate(pod, newPod):
156+
err = tree.Update(newMergedPod, kubebuilderx.WithPatch(true))
157+
default:
158+
if err = r.switchover(tree, inst, newMergedPod.(*corev1.Pod)); err != nil {
159+
return kubebuilderx.Continue, err
161160
}
162161
err = tree.Update(newMergedPod)
163162
}

pkg/controller/instanceset/reconciler_update.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,14 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder
177177
return kubebuilderx.Continue, err
178178
}
179179

180-
// if already updating using subresource, don't update it again, because without subresource, those fields are considered immutable.
181-
// Another reconciliation will be triggered since pod status will be updated.
182-
if !equalResourcesInPlaceFields(pod, newPod) && supportResizeSubResource {
180+
switch {
181+
case !equalResourcesInPlaceFields(pod, newPod) && supportResizeSubResource:
183182
err = tree.Update(newMergedPod, kubebuilderx.WithSubResource("resize"))
184-
} else {
185-
if !safeMetadataOnlyInPlaceUpdate(pod, newPod) {
186-
if err = r.switchover(tree, its, newMergedPod.(*corev1.Pod)); err != nil {
187-
return kubebuilderx.Continue, err
188-
}
183+
case safeMetadataOnlyInPlaceUpdate(pod, newPod):
184+
err = tree.Update(newMergedPod, kubebuilderx.WithPatch(true))
185+
default:
186+
if err = r.switchover(tree, its, newMergedPod.(*corev1.Pod)); err != nil {
187+
return kubebuilderx.Continue, err
189188
}
190189
err = tree.Update(newMergedPod)
191190
}

pkg/controller/instanceset/reconciler_update_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,10 @@ var _ = Describe("update reconciler test", func() {
482482
updatedPod := postPods[0].(*corev1.Pod)
483483
Expect(updatedPod.Annotations).Should(HaveKeyWithValue(constant.CMInsConfigurationHashLabelKey, "new-hash"),
484484
"pod should be patched with the new config-hash annotation even though switchover is skipped")
485+
_, option, err := tree.GetWithOption(updatedPod)
486+
Expect(err).NotTo(HaveOccurred())
487+
Expect(option.Patch).Should(BeTrue(),
488+
"metadata-only pod updates should use a patch to avoid stale full-update conflicts")
485489
Expect(spy.switchoverCalls).Should(Equal(0),
486490
"switchover must not be invoked when only the config-hash annotation differs")
487491
})

pkg/controller/kubebuilderx/plan_builder.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,18 @@ func buildOrderedVertices(transCtx *transformContext, currentTree *ObjectTree, d
187187
transCtx.logger.Error(err, "can't get GVKName from object", "object", newObj.GetName())
188188
return
189189
}
190-
var v *model.ObjectVertex
191-
subResource := desiredTree.childrenOptions[*name].SubResource
190+
var (
191+
v *model.ObjectVertex
192+
subResource = desiredTree.childrenOptions[*name].SubResource
193+
action = model.ActionUpdatePtr()
194+
)
195+
if desiredTree.childrenOptions[*name].Patch {
196+
action = model.ActionPatchPtr()
197+
}
192198
if subResource != "" {
193-
v = model.NewObjectVertex(oldObj, newObj, model.ActionUpdatePtr(), inDataContext4G(), model.WithSubResource(subResource))
199+
v = model.NewObjectVertex(oldObj, newObj, action, inDataContext4G(), model.WithSubResource(subResource))
194200
} else {
195-
v = model.NewObjectVertex(oldObj, newObj, model.ActionUpdatePtr(), inDataContext4G())
201+
v = model.NewObjectVertex(oldObj, newObj, action, inDataContext4G())
196202
}
197203
findAndAppend(v)
198204
}

pkg/controller/kubebuilderx/plan_builder_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ var _ = Describe("plan builder test", func() {
9898
Expect(planBuilder.defaultWalkFunc(v)).Should(Succeed())
9999
})
100100

101+
It("should patch object", func() {
102+
svcOrig := builder.NewServiceBuilder(namespace, name).GetObject()
103+
svc := svcOrig.DeepCopy()
104+
svc.Labels = map[string]string{"patched": "true"}
105+
v := &model.ObjectVertex{
106+
OriObj: svcOrig,
107+
Obj: svc,
108+
Action: model.ActionPatchPtr(),
109+
}
110+
k8sMock.EXPECT().
111+
Patch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
112+
DoAndReturn(func(_ context.Context, obj *corev1.Service, _ client.Patch, _ ...client.PatchOption) error {
113+
Expect(obj).ShouldNot(BeNil())
114+
Expect(obj.Namespace).Should(Equal(svc.Namespace))
115+
Expect(obj.Name).Should(Equal(svc.Name))
116+
Expect(obj.Labels).Should(Equal(svc.Labels))
117+
return nil
118+
}).Times(1)
119+
Expect(planBuilder.defaultWalkFunc(v)).Should(Succeed())
120+
})
121+
101122
It("should update pvc object", func() {
102123
pvcOrig := builder.NewPVCBuilder(namespace, name).GetObject()
103124
pvc := pvcOrig.DeepCopy()
@@ -286,6 +307,30 @@ var _ = Describe("plan builder test", func() {
286307
}
287308
Expect(found).To(BeTrue())
288309
})
310+
311+
It("should append a patch vertex when requested by object options", func() {
312+
oldPod := builder.NewPodBuilder("ns", "pod").GetObject()
313+
newPod := oldPod.DeepCopy()
314+
newPod.Labels = map[string]string{"patched": "true"}
315+
316+
currentTree := NewObjectTree()
317+
desiredTree := NewObjectTree()
318+
currentTree.SetRoot(builder.NewInstanceSetBuilder("ns", "root").GetObject())
319+
desiredTree.SetRoot(currentTree.GetRoot())
320+
Expect(currentTree.Add(oldPod)).Should(Succeed())
321+
Expect(desiredTree.Update(newPod, WithPatch(true))).Should(Succeed())
322+
323+
vertices := buildOrderedVertices(transCtx, currentTree, desiredTree)
324+
325+
found := false
326+
for _, v := range vertices {
327+
if pod, ok := v.Obj.(*corev1.Pod); ok && pod.Name == "pod" {
328+
found = true
329+
Expect(*v.Action).To(Equal(model.PATCH))
330+
}
331+
}
332+
Expect(found).To(BeTrue())
333+
})
289334
})
290335
})
291336
})

pkg/controller/kubebuilderx/reconciler.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type ObjectOptions struct {
4141
// if not empty, action should be done on the specified subresource
4242
SubResource string
4343

44+
// if true, the object should be patched instead of fully updated.
45+
Patch bool
46+
4447
// if true, the object should not be reconciled
4548
SkipToReconcile bool
4649
}
@@ -51,6 +54,12 @@ func (w WithSubResource) ApplyToObject(opts *ObjectOptions) {
5154
opts.SubResource = string(w)
5255
}
5356

57+
type WithPatch bool
58+
59+
func (w WithPatch) ApplyToObject(opts *ObjectOptions) {
60+
opts.Patch = bool(w)
61+
}
62+
5463
type SkipToReconcile bool
5564

5665
func (o SkipToReconcile) ApplyToObject(opts *ObjectOptions) {

0 commit comments

Comments
 (0)