Skip to content

Commit 76f5ab0

Browse files
WIP: Implement pausing for machine controller
1 parent ccec792 commit 76f5ab0

File tree

2 files changed

+181
-6
lines changed

2 files changed

+181
-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: 170 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,163 @@ 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, machine)
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+
key := client.ObjectKey{Name: machine.Name, Namespace: machine.Namespace}
2517+
2518+
// Wait for reconciliation to happen when infra and bootstrap objects are not ready.
2519+
g.Eventually(func() bool {
2520+
if err := env.Get(ctx, key, machine); err != nil {
2521+
return false
2522+
}
2523+
return len(machine.Finalizers) > 0
2524+
}, timeout).Should(BeTrue())
2525+
2526+
// Set bootstrap ready.
2527+
bootstrapPatch := client.MergeFrom(defaultBootstrap.DeepCopy())
2528+
g.Expect(unstructured.SetNestedField(defaultBootstrap.Object, true, "status", "ready")).ToNot(HaveOccurred())
2529+
g.Expect(env.Status().Patch(ctx, defaultBootstrap, bootstrapPatch)).To(Succeed())
2530+
2531+
// Set infrastructure ready.
2532+
infraMachinePatch := client.MergeFrom(infraMachine.DeepCopy())
2533+
g.Expect(unstructured.SetNestedField(infraMachine.Object, true, "status", "ready")).To(Succeed())
2534+
g.Expect(env.Status().Patch(ctx, infraMachine, infraMachinePatch)).To(Succeed())
2535+
2536+
// Wait for Machine Ready Condition to become True.
2537+
g.Eventually(func() bool {
2538+
if err := env.Get(ctx, key, machine); err != nil {
2539+
return false
2540+
}
2541+
if !conditions.Has(machine, clusterv1.InfrastructureReadyCondition) {
2542+
return false
2543+
}
2544+
readyCondition := conditions.Get(machine, clusterv1.ReadyCondition)
2545+
return readyCondition.Status == corev1.ConditionTrue
2546+
}, timeout).Should(BeTrue())
2547+
2548+
// The cluster starts unpaused, so the machine machine has a Paused: false
2549+
// condition
2550+
g.Eventually(func() bool {
2551+
if err := env.Get(ctx, key, machine); err != nil {
2552+
return false
2553+
}
2554+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2555+
return false
2556+
}
2557+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2558+
return pausedCondition.Status == corev1.ConditionFalse
2559+
}, timeout).Should(BeTrue())
2560+
2561+
// Case: We pause the machine indirectly by pausing the cluster, eventually
2562+
// the machine has a Paused: true condition
2563+
2564+
testCluster.Spec.Paused = true
2565+
g.Expect(env.Update(ctx, testCluster)).To(Succeed())
2566+
2567+
// Eventually, machine has condition paused: true
2568+
g.Eventually(func() bool {
2569+
if err := env.Get(ctx, key, machine); err != nil {
2570+
return false
2571+
}
2572+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2573+
return false
2574+
}
2575+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2576+
return pausedCondition.Status == corev1.ConditionTrue
2577+
}, timeout).Should(BeTrue())
2578+
2579+
// Case: Then we unpause, eventually machine has paused:false
2580+
testCluster.Spec.Paused = false
2581+
g.Expect(env.Update(ctx, testCluster)).To(Succeed())
2582+
2583+
// Eventually, machine has condition paused: true
2584+
g.Eventually(func() bool {
2585+
if err := env.Get(ctx, key, machine); err != nil {
2586+
return false
2587+
}
2588+
if !conditions.Has(machine, clusterv1.PausedCondition) {
2589+
return false
2590+
}
2591+
pausedCondition := conditions.Get(machine, clusterv1.PausedCondition)
2592+
return pausedCondition.Status == corev1.ConditionFalse
2593+
}, timeout).Should(BeTrue())
2594+
}
2595+
24262596
// adds a condition list to an external object.
24272597
func addConditionsToExternal(u *unstructured.Unstructured, newConditions clusterv1.Conditions) {
24282598
existingConditions := clusterv1.Conditions{}

0 commit comments

Comments
 (0)