sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/machinedeployment/machinedeployment_controller_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package machinedeployment 18 19 import ( 20 "testing" 21 22 . "github.com/onsi/gomega" 23 "github.com/pkg/errors" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 30 "sigs.k8s.io/controller-runtime/pkg/reconcile" 31 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 "sigs.k8s.io/cluster-api/internal/test/builder" 34 "sigs.k8s.io/cluster-api/util" 35 ) 36 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 50 testCases := []struct { 51 name string 52 md *clusterv1.MachineDeployment 53 expectFinalizer bool 54 }{ 55 // Note: We are not testing the case of a MD with deletionTimestamp and no finalizer. 56 // This case is impossible to reproduce in fake client without deleting the object. 57 { 58 name: "should add ClusterTopology finalizer to a MachineDeployment with no finalizer", 59 md: md, 60 expectFinalizer: true, 61 }, 62 { 63 name: "should retain ClusterTopology finalizer on MachineDeployment with finalizer", 64 md: mdWithFinalizer, 65 expectFinalizer: true, 66 }, 67 } 68 69 for _, tc := range testCases { 70 t.Run(tc.name, func(t *testing.T) { 71 g := NewWithT(t) 72 73 fakeClient := fake.NewClientBuilder(). 74 WithScheme(fakeScheme). 75 WithObjects(tc.md, mdBT, mdIMT, cluster). 76 Build() 77 78 mdr := &Reconciler{ 79 Client: fakeClient, 80 APIReader: fakeClient, 81 } 82 83 _, err := mdr.Reconcile(ctx, reconcile.Request{ 84 NamespacedName: util.ObjectKey(tc.md), 85 }) 86 g.Expect(err).ToNot(HaveOccurred()) 87 88 key := client.ObjectKey{Namespace: tc.md.Namespace, Name: tc.md.Name} 89 var actual clusterv1.MachineDeployment 90 g.Expect(mdr.Client.Get(ctx, key, &actual)).To(Succeed()) 91 if tc.expectFinalizer { 92 g.Expect(actual.Finalizers).To(ConsistOf(clusterv1.MachineDeploymentTopologyFinalizer)) 93 } else { 94 g.Expect(actual.Finalizers).To(BeEmpty()) 95 } 96 }) 97 } 98 } 99 100 func TestMachineDeploymentReconciler_ReconcileDelete(t *testing.T) { 101 deletionTimeStamp := metav1.Now() 102 103 mdBT := builder.BootstrapTemplate(metav1.NamespaceDefault, "mdBT").Build() 104 mdIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "mdIMT").Build() 105 md := builder.MachineDeployment(metav1.NamespaceDefault, "md"). 106 WithBootstrapTemplate(mdBT). 107 WithInfrastructureTemplate(mdIMT). 108 Build() 109 mhc := builder.MachineHealthCheck(metav1.NamespaceDefault, "md").Build() 110 md.SetDeletionTimestamp(&deletionTimeStamp) 111 md.SetFinalizers([]string{clusterv1.MachineDeploymentTopologyFinalizer}) 112 113 t.Run("Should delete templates of a MachineDeployment", func(t *testing.T) { 114 g := NewWithT(t) 115 116 // Copying the MD so changes made by reconcileDelete do not affect other tests. 117 md := md.DeepCopy() 118 119 fakeClient := fake.NewClientBuilder(). 120 WithScheme(fakeScheme). 121 WithObjects(md, mdBT, mdIMT). 122 Build() 123 124 r := &Reconciler{ 125 Client: fakeClient, 126 APIReader: fakeClient, 127 } 128 err := r.reconcileDelete(ctx, md) 129 g.Expect(err).ToNot(HaveOccurred()) 130 131 g.Expect(controllerutil.ContainsFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeFalse()) 132 g.Expect(templateExists(fakeClient, mdBT)).To(BeFalse()) 133 g.Expect(templateExists(fakeClient, mdIMT)).To(BeFalse()) 134 }) 135 136 t.Run("Should delete infra template of a MachineDeployment without a bootstrap template", func(t *testing.T) { 137 g := NewWithT(t) 138 139 mdWithoutBootstrapTemplateIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "mdWithoutBootstrapTemplateIMT").Build() 140 mdWithoutBootstrapTemplate := builder.MachineDeployment(metav1.NamespaceDefault, "mdWithoutBootstrapTemplate"). 141 WithInfrastructureTemplate(mdWithoutBootstrapTemplateIMT). 142 Build() 143 mdWithoutBootstrapTemplate.SetDeletionTimestamp(&deletionTimeStamp) 144 mdWithoutBootstrapTemplate.SetFinalizers([]string{clusterv1.MachineDeploymentTopologyFinalizer}) 145 146 fakeClient := fake.NewClientBuilder(). 147 WithScheme(fakeScheme). 148 WithObjects(mdWithoutBootstrapTemplate, mdWithoutBootstrapTemplateIMT). 149 Build() 150 151 r := &Reconciler{ 152 Client: fakeClient, 153 APIReader: fakeClient, 154 } 155 err := r.reconcileDelete(ctx, mdWithoutBootstrapTemplate) 156 g.Expect(err).ToNot(HaveOccurred()) 157 158 g.Expect(controllerutil.ContainsFinalizer(mdWithoutBootstrapTemplate, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeFalse()) 159 g.Expect(templateExists(fakeClient, mdWithoutBootstrapTemplateIMT)).To(BeFalse()) 160 }) 161 162 t.Run("Should not delete templates of a MachineDeployment when they are still in use in a MachineSet", func(t *testing.T) { 163 g := NewWithT(t) 164 165 // Copying the MD so changes made by reconcileDelete do not affect other tests. 166 md := md.DeepCopy() 167 168 ms := builder.MachineSet(md.Namespace, "md"). 169 WithBootstrapTemplate(mdBT). 170 WithInfrastructureTemplate(mdIMT). 171 WithLabels(map[string]string{ 172 clusterv1.MachineDeploymentNameLabel: md.Name, 173 }). 174 Build() 175 176 fakeClient := fake.NewClientBuilder(). 177 WithScheme(fakeScheme). 178 WithObjects(md, ms, mdBT, mdIMT). 179 Build() 180 181 r := &Reconciler{ 182 Client: fakeClient, 183 APIReader: fakeClient, 184 } 185 err := r.reconcileDelete(ctx, md) 186 g.Expect(err).ToNot(HaveOccurred()) 187 188 g.Expect(controllerutil.ContainsFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeFalse()) 189 g.Expect(templateExists(fakeClient, mdBT)).To(BeTrue()) 190 g.Expect(templateExists(fakeClient, mdIMT)).To(BeTrue()) 191 }) 192 t.Run("Should delete a MachineHealthCheck when its linked MachineDeployment is deleted", func(t *testing.T) { 193 g := NewWithT(t) 194 195 // Copying the MD so changes made by reconcileDelete do not affect other tests. 196 md := md.DeepCopy() 197 198 fakeClient := fake.NewClientBuilder(). 199 WithScheme(fakeScheme). 200 WithObjects(md, mhc). 201 Build() 202 203 r := &Reconciler{ 204 Client: fakeClient, 205 APIReader: fakeClient, 206 } 207 err := r.reconcileDelete(ctx, md) 208 g.Expect(err).ToNot(HaveOccurred()) 209 210 g.Expect(controllerutil.ContainsFinalizer(md, clusterv1.MachineDeploymentTopologyFinalizer)).To(BeFalse()) 211 212 gotMHC := clusterv1.MachineHealthCheck{} 213 err = fakeClient.Get(ctx, client.ObjectKeyFromObject(mhc), &gotMHC) 214 g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) 215 }) 216 } 217 218 func templateExists(fakeClient client.Reader, template *unstructured.Unstructured) bool { 219 obj := &unstructured.Unstructured{} 220 obj.SetKind(template.GetKind()) 221 obj.SetAPIVersion(template.GetAPIVersion()) 222 223 err := fakeClient.Get(ctx, client.ObjectKeyFromObject(template), obj) 224 if err != nil && !apierrors.IsNotFound(err) { 225 panic(errors.Wrapf(err, "failed to get %s/%s", template.GetKind(), template.GetName())) 226 } 227 return err == nil 228 }