Skip to content

Commit f040e82

Browse files
Implement pausing for machine controller
This change updates the machine controller to check if the machine resource is paused, and if it is sets the condition Paused: True, otherwise it sets Paused: False.
1 parent ccec792 commit f040e82

File tree

2 files changed

+185
-6
lines changed

2 files changed

+185
-6
lines changed

internal/controllers/machine/machine_controller.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,6 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
183183
m.Spec.ClusterName, m.Name, m.Namespace)
184184
}
185185

186-
// Return early if the object or Cluster is paused.
187-
if annotations.IsPaused(cluster, m) {
188-
log.Info("Reconciliation is paused for this object")
189-
return ctrl.Result{}, nil
190-
}
191-
192186
// Initialize the patch helper
193187
patchHelper, err := patch.NewHelper(m, r.Client)
194188
if err != nil {
@@ -209,6 +203,16 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re
209203
}
210204
}()
211205

206+
// Return early and set the paused condition to True if the object or Cluster
207+
// is paused.
208+
if annotations.IsPaused(cluster, m) {
209+
log.Info("Reconciliation is paused for this object")
210+
conditions.MarkTrue(m, clusterv1.PausedCondition)
211+
return ctrl.Result{}, nil
212+
}
213+
214+
conditions.MarkFalse(m, clusterv1.PausedCondition, clusterv1.ResourceNotPausedReason, clusterv1.ConditionSeverityInfo, "Resource is operating as expected")
215+
212216
// Reconcile labels.
213217
if m.Labels == nil {
214218
m.Labels = make(map[string]string)
@@ -275,6 +279,7 @@ func patchMachine(ctx context.Context, patchHelper *patch.Helper, machine *clust
275279
clusterv1.ReadyCondition,
276280
clusterv1.BootstrapReadyCondition,
277281
clusterv1.InfrastructureReadyCondition,
282+
clusterv1.PausedCondition,
278283
clusterv1.DrainingSucceededCondition,
279284
clusterv1.MachineHealthCheckSucceededCondition,
280285
clusterv1.MachineOwnerRemediatedCondition,

internal/controllers/machine/machine_controller_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,19 @@ func TestMachineConditions(t *testing.T) {
10501050
conditions.TrueCondition(clusterv1.ReadyCondition),
10511051
},
10521052
},
1053+
{
1054+
name: "paused initialises false",
1055+
infraReady: true,
1056+
bootstrapReady: true,
1057+
beforeFunc: func(_, _ *unstructured.Unstructured, m *clusterv1.Machine) {
1058+
// since these conditions are set by an external controller
1059+
conditions.MarkTrue(m, clusterv1.MachineHealthCheckSucceededCondition)
1060+
conditions.MarkTrue(m, clusterv1.MachineOwnerRemediatedCondition)
1061+
},
1062+
conditionsToAssert: []*clusterv1.Condition{
1063+
conditions.FalseCondition(clusterv1.PausedCondition, "ResourceNotPaused", clusterv1.ConditionSeverityInfo, "Resource is operating as expected"),
1064+
},
1065+
},
10531066
{
10541067
name: "infra condition consumes reason from the infra config",
10551068
infraReady: false,
@@ -2423,6 +2436,167 @@ func TestNodeDeletion(t *testing.T) {
24232436
}
24242437
}
24252438

2439+
func TestPauseConditionReconcile(t *testing.T) {
2440+
g := NewWithT(t)
2441+
2442+
ns, err := env.CreateNamespace(ctx, "test-paused-condition-reconcile")
2443+
g.Expect(err).ToNot(HaveOccurred())
2444+
2445+
infraMachine := &unstructured.Unstructured{
2446+
Object: map[string]interface{}{
2447+
"kind": "GenericInfrastructureMachine",
2448+
"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
2449+
"metadata": map[string]interface{}{
2450+
"name": "infra-config1",
2451+
"namespace": ns.Name,
2452+
},
2453+
"spec": map[string]interface{}{
2454+
"providerID": "test://id-1",
2455+
},
2456+
},
2457+
}
2458+
2459+
defaultBootstrap := &unstructured.Unstructured{
2460+
Object: map[string]interface{}{
2461+
"kind": "GenericBootstrapConfig",
2462+
"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
2463+
"metadata": map[string]interface{}{
2464+
"name": "bootstrap-config-pausereconcile",
2465+
"namespace": ns.Name,
2466+
},
2467+
"spec": map[string]interface{}{},
2468+
"status": map[string]interface{}{},
2469+
},
2470+
}
2471+
2472+
testCluster := &clusterv1.Cluster{
2473+
ObjectMeta: metav1.ObjectMeta{
2474+
GenerateName: "pause-condition-reconcile-",
2475+
Namespace: ns.Name,
2476+
},
2477+
}
2478+
2479+
g.Expect(env.Create(ctx, testCluster)).To(Succeed())
2480+
g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
2481+
g.Expect(env.Create(ctx, defaultBootstrap)).To(Succeed())
2482+
2483+
defer func(do ...client.Object) {
2484+
g.Expect(env.Cleanup(ctx, do...)).To(Succeed())
2485+
}(ns, testCluster, defaultBootstrap, infraMachine)
2486+
2487+
machine := &clusterv1.Machine{
2488+
ObjectMeta: metav1.ObjectMeta{
2489+
GenerateName: "machine-created-",
2490+
Namespace: ns.Name,
2491+
Finalizers: []string{clusterv1.MachineFinalizer},
2492+
},
2493+
Spec: clusterv1.MachineSpec{
2494+
ClusterName: testCluster.Name,
2495+
InfrastructureRef: corev1.ObjectReference{
2496+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
2497+
Kind: "GenericInfrastructureMachine",
2498+
Name: "infra-config1",
2499+
},
2500+
Bootstrap: clusterv1.Bootstrap{
2501+
ConfigRef: &corev1.ObjectReference{
2502+
APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
2503+
Kind: "GenericBootstrapConfig",
2504+
Name: "bootstrap-config-machinereconcile",
2505+
},
2506+
},
2507+
},
2508+
Status: clusterv1.MachineStatus{
2509+
NodeRef: &corev1.ObjectReference{
2510+
Name: "test",
2511+
},
2512+
},
2513+
}
2514+
g.Expect(env.Create(ctx, machine)).To(Succeed())
2515+
2516+
defer func(do ...client.Object) {
2517+
g.Expect(env.Cleanup(ctx, do...)).To(Succeed())
2518+
}(machine)
2519+
2520+
key := client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}
2521+
2522+
// Wait for reconciliation to happen when infra and bootstrap objects are not ready.
2523+
g.Eventually(func() bool {
2524+
if err := env.Get(ctx, key, machine); err != nil {
2525+
return false
2526+
}
2527+
return len(machine.Finalizers) > 0
2528+
}, timeout).Should(BeTrue())
2529+
2530+
// Set bootstrap ready.
2531+
bootstrapPatch := client.MergeFrom(defaultBootstrap.DeepCopy())
2532+
g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).ToNot(HaveOccurred())
2533+
g.Expect(env.Status().Patch(ctx, defaultBootstrap, bootstrapPatch)).To(Succeed())
2534+
2535+
// Set infrastructure ready.
2536+
infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy())
2537+
g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed())
2538+
g.Expect(env.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed())
2539+
2540+
// Wait for Machine Ready Condition to become True.
2541+
g.Eventually(func() bool {
2542+
if err := env.Get(ctx, key, machine); err != nil {
2543+
return false
2544+
}
2545+
if !conditions.Has(machine, clusterv1.InfrastructureReadyCondition) {
2546+
return false
2547+
}
2548+
readyCondition := conditions.Get(machine, clusterv1.ReadyCondition)
2549+
return readyCondition.Status == corev1.ConditionTrue
2550+
}, timeout).Should(BeTrue())
2551+
2552+
// The cluster starts unpaused, so the machine machine has a Paused: false
2553+
// condition
2554+
g.Eventually(func() bool {
2555+
if err := env.Get(ctx, key, machine); err != nil {
2556+
return false
2557+
}
2558+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2559+
return false
2560+
}
2561+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2562+
return pausedCondition.Status == corev1.ConditionFalse
2563+
}, timeout).Should(BeTrue())
2564+
2565+
// Case: We pause the machine indirectly by pausing the cluster, eventually
2566+
// the machine has a Paused: true condition
2567+
2568+
testCluster.Spec.Paused = true
2569+
g.Expect(env.Update(ctx, testCluster)).To(Succeed())
2570+
2571+
// Eventually, machine has condition paused: true
2572+
g.Eventually(func() bool {
2573+
if err := env.Get(ctx, key, machine); err != nil {
2574+
return false
2575+
}
2576+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2577+
return false
2578+
}
2579+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2580+
return pausedCondition.Status == corev1.ConditionTrue
2581+
}, timeout).Should(BeTrue())
2582+
2583+
// Case: Then we unpause, eventually machine has paused:false
2584+
testCluster.Spec.Paused = false
2585+
g.Expect(env.Update(ctx, testCluster)).To(Succeed())
2586+
2587+
// Eventually, machine has condition paused: true
2588+
g.Eventually(func() bool {
2589+
if err := env.Get(ctx, key, machine); err != nil {
2590+
return false
2591+
}
2592+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2593+
return false
2594+
}
2595+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2596+
return pausedCondition.Status == corev1.ConditionFalse
2597+
}, timeout).Should(BeTrue())
2598+
}
2599+
24262600
// adds a condition list to an external object.
24272601
func addConditionsToExternal(u *unstructured.Unstructured, newConditions clusterv1.Conditions) {
24282602
existingConditions := clusterv1.Conditions{}

0 commit comments

Comments
 (0)