Skip to content

Commit 4421b2c

Browse files
committed
fix: controllers: guard on empty .status.authoritativeAPI, take 2
1 parent db257cd commit 4421b2c

File tree

4 files changed

+65
-90
lines changed

4 files changed

+65
-90
lines changed

pkg/controller/machine/controller.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,21 +179,21 @@ func (r *ReconcileMachine) Reconcile(ctx context.Context, request reconcile.Requ
179179
if r.gate.Enabled(featuregate.Feature(openshiftfeatures.FeatureGateMachineAPIMigration)) {
180180
switch m.Status.AuthoritativeAPI {
181181
case "":
182-
// An empty .status.authoritativeAPI normally means the resource has not yet been reconciled
183-
// by the migration controller, which has the responsibility for propagating .spec.authoritativeAPI to the status.
184-
// Pause the resource and take no further action but return until that field is populated.
185-
desiredCondition := conditions.TrueConditionWithReason(
186-
PausedCondition, PausedConditionReason,
187-
"The AuthoritativeAPI status is not yet set",
188-
)
189-
190-
if _, err := r.ensureUpdatedPausedCondition(ctx, m, desiredCondition,
191-
fmt.Sprintf("%v: machine .status.authoritativeAPI is not yet set, ensuring machine is paused", machineName)); err != nil {
192-
return reconcile.Result{}, fmt.Errorf("failed to ensure paused condition: %w", err)
182+
// An empty .status.authoritativeAPI normally means the resource has not yet been reconciled.
183+
// and that the value in .spec.authoritativeAPI has not been propagated to .status.authoritativeAPI yet.
184+
// This value can be set by two separate controllers, depending which one of them is running at that time,
185+
// or in case they are both running, which one gets to set it first (the operation is idempotent so there is no harm in racing).
186+
// - the cluster-capi-operator machine-api-migration's migration controller
187+
// - this controller
188+
189+
klog.Infof("%v: machine .status.authoritativeAPI is not yet set, setting it to .spec.authoritativeAPI", m.Name)
190+
191+
if err := r.patchStatusAuthoritativeAPI(ctx, m, m.Spec.AuthoritativeAPI); err != nil {
192+
klog.Errorf("%v: error patching status to set .status.authoritativeAPI for machine: %v", m.Name, err)
193+
return reconcile.Result{}, fmt.Errorf("error patching status to set .status.authoritativeAPI for machine %s: %w", m.Name, err)
193194
}
194195

195-
klog.Infof("%v: machine is paused, taking no further action", machineName)
196-
196+
// Return to give a chance to the changes to get propagated.
197197
return reconcile.Result{}, nil
198198

199199
case machinev1.MachineAuthorityClusterAPI, machinev1.MachineAuthorityMigrating:
@@ -572,6 +572,17 @@ func (r *ReconcileMachine) updateStatus(ctx context.Context, machine *machinev1.
572572
return nil
573573
}
574574

575+
func (r *ReconcileMachine) patchStatusAuthoritativeAPI(ctx context.Context, machine *machinev1.Machine, authoritativeAPI machinev1.MachineAuthority) error {
576+
baseToPatch := client.MergeFrom(machine.DeepCopy())
577+
machine.Status.AuthoritativeAPI = authoritativeAPI
578+
579+
if err := r.Client.Status().Patch(ctx, machine, baseToPatch); err != nil {
580+
return fmt.Errorf("error patching machine status: %w", err)
581+
}
582+
583+
return nil
584+
}
585+
575586
func (r *ReconcileMachine) patchFailedMachineInstanceAnnotation(ctx context.Context, machine *machinev1.Machine) error {
576587
baseToPatch := client.MergeFrom(machine.DeepCopy())
577588
if machine.Annotations == nil {

pkg/controller/machine/machine_controller_test.go

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,15 @@ var _ = Describe("Machine Reconciler", func() {
104104
testutils.CleanupResources(Default, ctx, cfg, k8sClient, namespace.GetName(),
105105
&machinev1.Machine{},
106106
)
107-
108107
})
109108

110109
It("Should reconcile a Machine", func() {
111-
instance := machineBuilder.Build()
110+
instance := machineBuilder.WithAuthoritativeAPI(machinev1.MachineAuthorityMachineAPI).Build()
112111

113112
By("Creating the Machine")
114113
Expect(k8sClient.Create(ctx, instance)).To(Succeed())
115114

116-
By("Setting the AuthoritativeAPI to MachineAPI")
117-
Eventually(k.UpdateStatus(instance, func() {
118-
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMachineAPI
119-
})).Should(Succeed())
120-
115+
By("Verying the Machine has been reconciled (status set)")
121116
Eventually(k.Object(instance), timeout).Should(HaveField("Status", Not(Equal(machinev1.MachineStatus{}))))
122117

123118
// // Expect platform status is not empty. This (check) means we've called the
@@ -127,23 +122,17 @@ var _ = Describe("Machine Reconciler", func() {
127122

128123
})
129124

130-
Describe("Paused condition", func() {
125+
Describe("Paused Condition", func() {
131126
var instance *machinev1.Machine
132127
BeforeEach(func() {
133-
instance = machineBuilder.Build()
128+
instance = machineBuilder.WithAuthoritativeAPI(machinev1.MachineAuthorityClusterAPI).Build()
134129
})
135130

136131
It("Should set the Paused condition appropriately", func() {
137-
138-
By("Creating the Machine (empty status.authoritativeAPI)")
132+
By("Creating the Machine (spec.authoritativeAPI=ClusterAPI)")
139133
Expect(k8sClient.Create(ctx, instance)).To(Succeed())
140134

141-
By("Setting the AuthoritativeAPI to ClusterAPI")
142-
Eventually(k.UpdateStatus(instance, func() {
143-
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityClusterAPI
144-
})).Should(Succeed())
145-
146-
By("Verifying the paused condition is appropriately set to true")
135+
By("Verifying the status.authoritativeAPI has been set to ClusterAPI and the paused condition is appropriately set to true")
147136
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
148137
HaveField("Status.Conditions", ContainElement(SatisfyAll(
149138
HaveField("Type", Equal(PausedCondition)),
@@ -195,7 +184,7 @@ var _ = Describe("Machine Reconciler", func() {
195184
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMigrating
196185
})).Should(Succeed())
197186

198-
By("Verifying the paused condition is appropriately set to true")
187+
By("Verifying the paused condition is appropriately set to true while in Migrating")
199188
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
200189
HaveField("Status.Conditions", ContainElement(SatisfyAll(
201190
HaveField("Type", Equal(PausedCondition)),
@@ -210,7 +199,7 @@ var _ = Describe("Machine Reconciler", func() {
210199
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMachineAPI
211200
})).Should(Succeed())
212201

213-
By("Verifying the paused condition is appropriately set to false")
202+
By("Verifying the paused condition is appropriately set to false when in MachineAPI")
214203
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
215204
HaveField("Status.Conditions", ContainElement(SatisfyAll(
216205
HaveField("Type", Equal(PausedCondition)),
@@ -219,21 +208,6 @@ var _ = Describe("Machine Reconciler", func() {
219208
))),
220209
HaveField("Status.AuthoritativeAPI", Equal(machinev1.MachineAuthorityMachineAPI)),
221210
))
222-
223-
By("Changing status.authoritativeAPI from MachineAPI to empty")
224-
Eventually(k.UpdateStatus(instance, func() {
225-
instance.Status.AuthoritativeAPI = ""
226-
})).Should(Succeed())
227-
228-
By("Verifying the paused condition is still appropriately set to true when empty status.authoritativeAPI")
229-
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
230-
HaveField("Status.Conditions", ContainElement(SatisfyAll(
231-
HaveField("Type", Equal(PausedCondition)),
232-
HaveField("Status", Equal(corev1.ConditionTrue)),
233-
HaveField("Message", Equal("The AuthoritativeAPI status is not yet set")),
234-
))),
235-
HaveField("Status.AuthoritativeAPI", BeEquivalentTo("")),
236-
))
237211
})
238212
})
239213
})

pkg/controller/machineset/controller.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,20 +176,21 @@ func (r *ReconcileMachineSet) Reconcile(ctx context.Context, request reconcile.R
176176
if r.gate.Enabled(featuregate.Feature(openshiftfeatures.FeatureGateMachineAPIMigration)) {
177177
switch machineSet.Status.AuthoritativeAPI {
178178
case "":
179-
// An empty .status.authoritativeAPI normally means the resource has not yet been reconciled
180-
// by the migration controller, which has the responsibility for propagating .spec.authoritativeAPI to the status.
181-
// Pause the resource and take no further action but return until that field is populated.
182-
desiredCondition := conditions.TrueConditionWithReason(
183-
machine.PausedCondition, machine.PausedConditionReason,
184-
"The AuthoritativeAPI status is not yet set",
185-
)
186-
187-
if _, err := r.ensureUpdatedPausedCondition(machineSet, desiredCondition,
188-
fmt.Sprintf("%v: machine set .status.authoritativeAPI is not yet set, ensuring machine set is paused", machineSet.Name)); err != nil {
189-
return reconcile.Result{}, fmt.Errorf("failed to ensure paused condition: %w", err)
179+
// An empty .status.authoritativeAPI normally means the resource has not yet been reconciled.
180+
// and that the value in .spec.authoritativeAPI has not been propagated to .status.authoritativeAPI yet.
181+
// This value can be set by two separate controllers, depending which one of them is running at that time,
182+
// or in case they are both running, which one gets to set it first (the operation is idempotent so there is no harm in racing).
183+
// - the cluster-capi-operator machine-api-migration's migration controller
184+
// - this controller
185+
186+
klog.Infof("%v: machine set .status.authoritativeAPI is not yet set, setting it to .spec.authoritativeAPI", machineSet.Name)
187+
188+
if err := r.patchStatusAuthoritativeAPI(ctx, machineSet, machineSet.Spec.AuthoritativeAPI); err != nil {
189+
klog.Errorf("%v: error patching status to set .status.authoritativeAPI for machine set: %v", machineSet.Name, err)
190+
return reconcile.Result{}, fmt.Errorf("error patching status to set .status.authoritativeAPI for machine set %s: %w", machineSet.Name, err)
190191
}
191-
klog.Infof("%v: machine set is paused, taking no further action", machineSet.Name)
192192

193+
// Return to give a chance to the changes to get propagated.
193194
return reconcile.Result{}, nil
194195

195196
case machinev1.MachineAuthorityClusterAPI, machinev1.MachineAuthorityMigrating:
@@ -518,6 +519,17 @@ func (r *ReconcileMachineSet) waitForMachineDeletion(machineList []*machinev1.Ma
518519
return nil
519520
}
520521

522+
func (r *ReconcileMachineSet) patchStatusAuthoritativeAPI(ctx context.Context, machineSet *machinev1.MachineSet, authoritativeAPI machinev1.MachineAuthority) error {
523+
baseToPatch := client.MergeFrom(machineSet.DeepCopy())
524+
machineSet.Status.AuthoritativeAPI = authoritativeAPI
525+
526+
if err := r.Client.Status().Patch(ctx, machineSet, baseToPatch); err != nil {
527+
return fmt.Errorf("error patching machine set status: %w", err)
528+
}
529+
530+
return nil
531+
}
532+
521533
func validateMachineset(m *machinev1.MachineSet) field.ErrorList {
522534
errors := field.ErrorList{}
523535

pkg/controller/machineset/machineset_controller_test.go

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,13 @@ var _ = Describe("MachineSet Reconciler", func() {
108108
})
109109

110110
It("Should reconcile a MachineSet", func() {
111-
instance := machineSetBuilder.Build()
111+
instance := machineSetBuilder.WithAuthoritativeAPI(machinev1.MachineAuthorityMachineAPI).Build()
112112

113-
By("Creating the MachineSet (empty status.authoritativeAPI)")
113+
By("Creating the MachineSet")
114114
Expect(k8sClient.Create(ctx, instance)).To(Succeed())
115115

116-
By("Setting status.authoritativeAPI to MachineAPI")
117-
Eventually(k.UpdateStatus(instance, func() {
118-
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMachineAPI
119-
})).Should(Succeed())
116+
By("Verying the MachineSet has been reconciled (status set)")
117+
Eventually(k.Object(instance), timeout).Should(HaveField("Status", Not(Equal(machinev1.MachineStatus{}))))
120118

121119
machines := &machinev1.MachineList{}
122120
By("Verifying that we have 2 replicas")
@@ -153,19 +151,14 @@ var _ = Describe("MachineSet Reconciler", func() {
153151
instance = machineSetBuilder.
154152
WithGenerateName("baz-").
155153
WithMachineTemplateLabels(map[string]string{"baz": "bar"}).
154+
WithAuthoritativeAPI(machinev1.MachineAuthorityClusterAPI).
156155
Build()
157156
})
158157
It("Should set the Paused condition appropriately", func() {
159-
160-
By("Creating the MachineSet (empty status.authoritativeAPI)")
158+
By("Creating the MachineSet (spec.authoritativeAPI=ClusterAPI)")
161159
Expect(k8sClient.Create(ctx, instance)).To(Succeed())
162160

163-
By("Setting status.authoritativeAPI to ClusterAPI")
164-
Eventually(k.UpdateStatus(instance, func() {
165-
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityClusterAPI
166-
})).Should(Succeed())
167-
168-
By("Verifying the paused condition is appropriately set to true")
161+
By("Verifying the status.authoritativeAPI has been set to ClusterAPI and paused condition is appropriately set to true")
169162
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
170163
HaveField("Status.Conditions", ContainElement(SatisfyAll(
171164
HaveField("Type", Equal(machine.PausedCondition)),
@@ -217,7 +210,7 @@ var _ = Describe("MachineSet Reconciler", func() {
217210
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMigrating
218211
})).Should(Succeed())
219212

220-
By("Verifying the paused condition is appropriately set to true")
213+
By("Verifying the paused condition is appropriately set to true while in Migrating")
221214
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
222215
HaveField("Status.Conditions", ContainElement(SatisfyAll(
223216
HaveField("Type", Equal(machine.PausedCondition)),
@@ -232,7 +225,7 @@ var _ = Describe("MachineSet Reconciler", func() {
232225
instance.Status.AuthoritativeAPI = machinev1.MachineAuthorityMachineAPI
233226
})).Should(Succeed())
234227

235-
By("Verifying the paused condition is appropriately set to false")
228+
By("Verifying the paused condition is appropriately set to false when in MachineAPI")
236229
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
237230
HaveField("Status.Conditions", ContainElement(SatisfyAll(
238231
HaveField("Type", Equal(machine.PausedCondition)),
@@ -241,21 +234,6 @@ var _ = Describe("MachineSet Reconciler", func() {
241234
))),
242235
HaveField("Status.AuthoritativeAPI", Equal(machinev1.MachineAuthorityMachineAPI)),
243236
))
244-
245-
By("Changing status.authoritativeAPI from MachineAPI to empty")
246-
Eventually(k.UpdateStatus(instance, func() {
247-
instance.Status.AuthoritativeAPI = ""
248-
})).Should(Succeed())
249-
250-
By("Verifying the paused condition is still appropriately set to true when empty status.authoritativeAPI")
251-
Eventually(k.Object(instance), timeout).Should(SatisfyAll(
252-
HaveField("Status.Conditions", ContainElement(SatisfyAll(
253-
HaveField("Type", Equal(machine.PausedCondition)),
254-
HaveField("Status", Equal(corev1.ConditionTrue)),
255-
HaveField("Message", Equal("The AuthoritativeAPI status is not yet set")),
256-
))),
257-
HaveField("Status.AuthoritativeAPI", BeEquivalentTo("")),
258-
))
259237
})
260238
})
261239
})

0 commit comments

Comments
 (0)