Skip to content

Commit 3ea223a

Browse files
Add finalizer reconcile for Topology MachineDeployments and MachineSets
Signed-off-by: killianmuldoon <[email protected]>
1 parent ebbd333 commit 3ea223a

File tree

10 files changed

+227
-64
lines changed

10 files changed

+227
-64
lines changed

internal/controllers/machinedeployment/machinedeployment_sync.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ import (
3535
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3636

3737
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
38-
"sigs.k8s.io/cluster-api/feature"
3938
"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
4039
"sigs.k8s.io/cluster-api/util"
4140
"sigs.k8s.io/cluster-api/util/conditions"
42-
"sigs.k8s.io/cluster-api/util/labels"
4341
"sigs.k8s.io/cluster-api/util/patch"
4442
)
4543

@@ -178,17 +176,6 @@ func (r *Reconciler) getNewMachineSet(ctx context.Context, d *clusterv1.MachineD
178176
},
179177
}
180178

181-
if feature.Gates.Enabled(feature.ClusterTopology) {
182-
// If the MachineDeployment is owned by a Cluster Topology,
183-
// add the finalizer to allow the topology controller to
184-
// clean up resources when the MachineSet is deleted.
185-
// MachineSets are deleted during rollout (e.g. template rotation) and
186-
// after MachineDeployment deletion.
187-
if labels.IsTopologyOwned(d) {
188-
controllerutil.AddFinalizer(&newMS, clusterv1.MachineSetTopologyFinalizer)
189-
}
190-
}
191-
192179
if d.Spec.Strategy.RollingUpdate.DeletePolicy != nil {
193180
newMS.Spec.DeletePolicy = *d.Spec.Strategy.RollingUpdate.DeletePolicy
194181
}

internal/controllers/topology/cluster/cluster_controller_test.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -911,9 +911,8 @@ func assertControlPlaneReconcile(cluster *clusterv1.Cluster) error {
911911
// assertMachineDeploymentsReconcile checks if the MachineDeployments:
912912
// 1) Are created in the correct number.
913913
// 2) Have the correct labels (TopologyOwned, ClusterName, MachineDeploymentName).
914-
// 3) Have the correct finalizer applied.
915-
// 4) Have the correct replicas and version.
916-
// 6) Have the correct Kind/APIVersion and Labels/Annotations for BoostrapRef and InfrastructureRef templates.
914+
// 3) Have the correct replicas and version.
915+
// 4) Have the correct Kind/APIVersion and Labels/Annotations for BoostrapRef and InfrastructureRef templates.
917916
func assertMachineDeploymentsReconcile(cluster *clusterv1.Cluster) error {
918917
// List all created machine deployments to assert the expected numbers are created.
919918
machineDeployments := &clusterv1.MachineDeploymentList{}
@@ -950,16 +949,6 @@ func assertMachineDeploymentsReconcile(cluster *clusterv1.Cluster) error {
950949
continue
951950
}
952951

953-
// Assert that the correct Finalizer has been added to the MachineDeployment.
954-
for _, f := range md.Finalizers {
955-
// Break as soon as we find a matching finalizer.
956-
if f == clusterv1.MachineDeploymentTopologyFinalizer {
957-
break
958-
}
959-
// False if the finalizer is not present on the MachineDeployment.
960-
return fmt.Errorf("finalizer %v not found on MachineDeployment", clusterv1.MachineDeploymentTopologyFinalizer)
961-
}
962-
963952
// Check if the ClusterTopologyLabelName and ClusterTopologyOwnedLabel are set correctly.
964953
if err := assertClusterTopologyOwnedLabel(&md); err != nil {
965954
return err

internal/controllers/topology/cluster/desired_state.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"k8s.io/apiserver/pkg/storage/names"
2929
"k8s.io/utils/pointer"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
31-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3231

3332
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3433
"sigs.k8s.io/cluster-api/controllers/external"
@@ -623,13 +622,6 @@ func computeMachineDeployment(_ context.Context, s *scope.Scope, desiredControlP
623622
},
624623
}
625624

626-
// If it's a new MachineDeployment, set the finalizer.
627-
// Note: we only add it on creation to avoid race conditions later on when
628-
// the MachineDeployment topology controller removes the finalizer.
629-
if currentMachineDeployment == nil {
630-
controllerutil.AddFinalizer(desiredMachineDeploymentObj, clusterv1.MachineDeploymentTopologyFinalizer)
631-
}
632-
633625
// If an existing MachineDeployment is present, override the MachineDeployment generate name
634626
// re-using the existing name (this will help in reconcile).
635627
if currentMachineDeployment != nil && currentMachineDeployment.Object != nil {

internal/controllers/topology/cluster/desired_state_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
utilfeature "k8s.io/component-base/featuregate/testing"
3131
"k8s.io/utils/pointer"
3232
"sigs.k8s.io/controller-runtime/pkg/client/fake"
33-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3433

3534
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3635
runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
@@ -1382,7 +1381,6 @@ func TestComputeMachineDeployment(t *testing.T) {
13821381

13831382
g.Expect(actualMd.Labels).To(HaveKeyWithValue(clusterv1.ClusterTopologyMachineDeploymentLabelName, "big-pool-of-machines"))
13841383
g.Expect(actualMd.Labels).To(HaveKey(clusterv1.ClusterTopologyOwnedLabel))
1385-
g.Expect(controllerutil.ContainsFinalizer(actualMd, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeTrue())
13861384

13871385
g.Expect(actualMd.Spec.Selector.MatchLabels).To(HaveKey(clusterv1.ClusterTopologyOwnedLabel))
13881386
g.Expect(actualMd.Spec.Selector.MatchLabels).To(HaveKeyWithValue(clusterv1.ClusterTopologyMachineDeploymentLabelName, "big-pool-of-machines"))
@@ -1436,7 +1434,6 @@ func TestComputeMachineDeployment(t *testing.T) {
14361434

14371435
g.Expect(actualMd.Labels).To(HaveKeyWithValue(clusterv1.ClusterTopologyMachineDeploymentLabelName, "big-pool-of-machines"))
14381436
g.Expect(actualMd.Labels).To(HaveKey(clusterv1.ClusterTopologyOwnedLabel))
1439-
g.Expect(controllerutil.ContainsFinalizer(actualMd, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeFalse())
14401437

14411438
g.Expect(actualMd.Spec.Template.ObjectMeta.Labels).To(HaveKeyWithValue("foo", "baz"))
14421439
g.Expect(actualMd.Spec.Template.ObjectMeta.Labels).To(HaveKeyWithValue("fizz", "buzz"))

internal/controllers/topology/machinedeployment/machinedeployment_controller.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/pkg/errors"
2323
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
kerrors "k8s.io/apimachinery/pkg/util/errors"
2425
"k8s.io/klog/v2"
2526
ctrl "sigs.k8s.io/controller-runtime"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -84,7 +85,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
8485
//
8586
// We don't have to set the finalizer, as it's already set during MachineDeployment creation
8687
// in the cluster topology controller.
87-
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
88+
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
8889
log := ctrl.LoggerFrom(ctx)
8990

9091
// Fetch the MachineDeployment instance.
@@ -112,12 +113,25 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
112113
return ctrl.Result{}, nil
113114
}
114115

116+
// Create a patch helper to add or remove the finalizer from the MachineDeployment.
117+
patchHelper, err := patch.NewHelper(md, r.Client)
118+
if err != nil {
119+
return ctrl.Result{}, errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: md})
120+
}
121+
defer func() {
122+
if err := patchHelper.Patch(ctx, md); err != nil {
123+
reterr = kerrors.NewAggregate([]error{reterr, errors.Wrapf(err, "failed to patch %s", tlog.KObj{Obj: md})})
124+
}
125+
}()
126+
115127
// Handle deletion reconciliation loop.
116128
if !md.ObjectMeta.DeletionTimestamp.IsZero() {
117129
return r.reconcileDelete(ctx, md)
118130
}
119131

120-
// Nothing to do.
132+
// If the MachineDeployment is not being deleted ensure the finalizer is set.
133+
controllerutil.AddFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer)
134+
121135
return ctrl.Result{}, nil
122136
}
123137

@@ -151,16 +165,8 @@ func (r *Reconciler) reconcileDelete(ctx context.Context, md *clusterv1.MachineD
151165
return ctrl.Result{}, err
152166
}
153167

154-
// Remove the finalizer so the MachineDeployment can be garbage collected by Kubernetes.
155-
patchHelper, err := patch.NewHelper(md, r.Client)
156-
if err != nil {
157-
return ctrl.Result{}, errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: md})
158-
}
159-
160168
controllerutil.RemoveFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer)
161-
if err := patchHelper.Patch(ctx, md); err != nil {
162-
return ctrl.Result{}, errors.Wrapf(err, "failed to patch %s", tlog.KObj{Obj: md})
163-
}
169+
164170
return ctrl.Result{}, nil
165171
}
166172

internal/controllers/topology/machinedeployment/machinedeployment_controller_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,85 @@ import (
2727
"sigs.k8s.io/controller-runtime/pkg/client"
2828
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2929
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
30+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3031

3132
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3233
"sigs.k8s.io/cluster-api/internal/test/builder"
34+
"sigs.k8s.io/cluster-api/util"
3335
)
3436

37+
func TestMachineDeploymentTopologyFinalizer(t *testing.T) {
38+
cluster := builder.Cluster(metav1.NamespaceDefault, "fake-cluster").Build()
39+
mdBT := builder.BootstrapTemplate(metav1.NamespaceDefault, "mdBT").Build()
40+
mdIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "mdIMT").Build()
41+
mdBuilder := builder.MachineDeployment(metav1.NamespaceDefault, "md").
42+
WithClusterName("fake-cluster").
43+
WithBootstrapTemplate(mdBT).
44+
WithInfrastructureTemplate(mdIMT)
45+
46+
md := mdBuilder.Build()
47+
mdWithFinalizer := mdBuilder.Build()
48+
mdWithFinalizer.Finalizers = []string{clusterv1.MachineDeploymentTopologyFinalizer}
49+
mdWithDeletionTimestamp := mdBuilder.Build()
50+
deletionTimestamp := metav1.Now()
51+
mdWithDeletionTimestamp.DeletionTimestamp = &deletionTimestamp
52+
53+
mdWithDeletionTimestampAndFinalizer := mdWithDeletionTimestamp.DeepCopy()
54+
mdWithDeletionTimestampAndFinalizer.Finalizers = []string{clusterv1.MachineDeploymentTopologyFinalizer}
55+
56+
testCases := []struct {
57+
name string
58+
md *clusterv1.MachineDeployment
59+
expectFinalizer bool
60+
}{
61+
{
62+
name: "should add ClusterTopology finalizer to a MachineDeployment with no finalizer",
63+
md: md,
64+
expectFinalizer: true,
65+
},
66+
{
67+
name: "should retain ClusterTopology finalizer on MachineDeployment with finalizer",
68+
md: mdWithFinalizer,
69+
expectFinalizer: true,
70+
},
71+
{
72+
name: "should not add ClusterTopology finalizer on MachineDeployment with Deletion Timestamp and no finalizer ",
73+
md: mdWithDeletionTimestamp,
74+
expectFinalizer: false,
75+
},
76+
}
77+
78+
for _, tc := range testCases {
79+
t.Run(tc.name, func(t *testing.T) {
80+
g := NewWithT(t)
81+
82+
fakeClient := fake.NewClientBuilder().
83+
WithScheme(fakeScheme).
84+
WithObjects(tc.md, mdBT, mdIMT, cluster).
85+
Build()
86+
87+
mdr := &Reconciler{
88+
Client: fakeClient,
89+
APIReader: fakeClient,
90+
}
91+
92+
_, err := mdr.Reconcile(ctx, reconcile.Request{
93+
NamespacedName: util.ObjectKey(tc.md),
94+
})
95+
g.Expect(err).NotTo(HaveOccurred())
96+
97+
key := client.ObjectKey{Namespace: tc.md.Namespace, Name: tc.md.Name}
98+
var actual clusterv1.MachineDeployment
99+
g.Expect(mdr.Client.Get(ctx, key, &actual)).To(Succeed())
100+
if tc.expectFinalizer {
101+
g.Expect(actual.Finalizers).To(ConsistOf(clusterv1.MachineDeploymentTopologyFinalizer))
102+
} else {
103+
g.Expect(actual.Finalizers).To(BeEmpty())
104+
}
105+
})
106+
}
107+
}
108+
35109
func TestMachineDeploymentReconciler_ReconcileDelete(t *testing.T) {
36110
deletionTimeStamp := metav1.Now()
37111

@@ -98,7 +172,7 @@ func TestMachineDeploymentReconciler_ReconcileDelete(t *testing.T) {
98172
t.Run("Should not delete templates of a MachineDeployment when they are still in use in a MachineSet", func(t *testing.T) {
99173
g := NewWithT(t)
100174

101-
ms := builder.MachineSet(md.Namespace, "ms").
175+
ms := builder.MachineSet(md.Namespace, "md").
102176
WithBootstrapTemplate(mdBT).
103177
WithInfrastructureTemplate(mdIMT).
104178
WithLabels(map[string]string{

internal/controllers/topology/machineset/machineset_controller.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
apierrors "k8s.io/apimachinery/pkg/api/errors"
2424
"k8s.io/apimachinery/pkg/runtime/schema"
2525
"k8s.io/apimachinery/pkg/types"
26+
kerrors "k8s.io/apimachinery/pkg/util/errors"
2627
"k8s.io/klog/v2"
2728
ctrl "sigs.k8s.io/controller-runtime"
2829
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -86,7 +87,7 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opt
8687
//
8788
// We don't have to set the finalizer, as it's already set during MachineSet creation
8889
// in the MachineSet controller.
89-
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
90+
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
9091
// Fetch the MachineSet instance.
9192
ms := &clusterv1.MachineSet{}
9293
if err := r.Client.Get(ctx, req.NamespacedName, ms); err != nil {
@@ -119,12 +120,25 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
119120
return ctrl.Result{}, nil
120121
}
121122

123+
// Create a patch helper to add or remove the finalizer from the MachineSet.
124+
patchHelper, err := patch.NewHelper(ms, r.Client)
125+
if err != nil {
126+
return ctrl.Result{}, errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: ms})
127+
}
128+
defer func() {
129+
if err := patchHelper.Patch(ctx, ms); err != nil {
130+
reterr = kerrors.NewAggregate([]error{reterr, errors.Wrapf(err, "failed to patch %s", tlog.KObj{Obj: ms})})
131+
}
132+
}()
133+
122134
// Handle deletion reconciliation loop.
123135
if !ms.ObjectMeta.DeletionTimestamp.IsZero() {
124136
return r.reconcileDelete(ctx, ms)
125137
}
126138

127-
// Nothing to do.
139+
// If the MachineSet is not being deleted ensure the finalizer is set.
140+
controllerutil.AddFinalizer(ms, clusterv1.MachineSetTopologyFinalizer)
141+
128142
return ctrl.Result{}, nil
129143
}
130144

@@ -172,14 +186,7 @@ func (r *Reconciler) reconcileDelete(ctx context.Context, ms *clusterv1.MachineS
172186
}
173187

174188
// Remove the finalizer so the MachineSet can be garbage collected by Kubernetes.
175-
patchHelper, err := patch.NewHelper(ms, r.Client)
176-
if err != nil {
177-
return ctrl.Result{}, errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: ms})
178-
}
179189
controllerutil.RemoveFinalizer(ms, clusterv1.MachineSetTopologyFinalizer)
180-
if err := patchHelper.Patch(ctx, ms); err != nil {
181-
return ctrl.Result{}, errors.Wrapf(err, "failed to patch %s", tlog.KObj{Obj: ms})
182-
}
183190

184191
return ctrl.Result{}, nil
185192
}

0 commit comments

Comments
 (0)