sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/topology/machineset/machineset_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 machineset 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 TestMachineSetTopologyFinalizer(t *testing.T) { 38 mdName := "md" 39 40 msBT := builder.BootstrapTemplate(metav1.NamespaceDefault, "msBT").Build() 41 msIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "msIMT").Build() 42 43 cluster := builder.Cluster(metav1.NamespaceDefault, "fake-cluster").Build() 44 msBuilder := builder.MachineSet(metav1.NamespaceDefault, "ms"). 45 WithBootstrapTemplate(msBT). 46 WithInfrastructureTemplate(msIMT). 47 WithClusterName(cluster.Name). 48 WithOwnerReferences([]metav1.OwnerReference{ 49 { 50 Kind: "MachineDeployment", 51 APIVersion: clusterv1.GroupVersion.String(), 52 Name: "md", 53 }, 54 }). 55 WithLabels(map[string]string{ 56 clusterv1.MachineDeploymentNameLabel: mdName, 57 clusterv1.ClusterTopologyOwnedLabel: "", 58 }) 59 60 ms := msBuilder.Build() 61 msWithFinalizer := msBuilder.Build() 62 msWithFinalizer.Finalizers = []string{clusterv1.MachineSetTopologyFinalizer} 63 64 testCases := []struct { 65 name string 66 ms *clusterv1.MachineSet 67 expectFinalizer bool 68 }{ 69 // Note: We are not testing the case of a MS with deletionTimestamp and no finalizer. 70 // This case is impossible to reproduce in fake client without deleting the object. 71 { 72 name: "should add ClusterTopology finalizer to a MachineSet with no finalizer", 73 ms: ms, 74 expectFinalizer: true, 75 }, 76 { 77 name: "should retain ClusterTopology finalizer on MachineSet with finalizer", 78 ms: msWithFinalizer, 79 expectFinalizer: true, 80 }, 81 } 82 83 for _, tc := range testCases { 84 t.Run(tc.name, func(t *testing.T) { 85 g := NewWithT(t) 86 87 fakeClient := fake.NewClientBuilder(). 88 WithScheme(fakeScheme). 89 WithObjects(tc.ms, msBT, msIMT, cluster). 90 Build() 91 92 msr := &Reconciler{ 93 Client: fakeClient, 94 APIReader: fakeClient, 95 } 96 97 _, err := msr.Reconcile(ctx, reconcile.Request{ 98 NamespacedName: util.ObjectKey(tc.ms), 99 }) 100 g.Expect(err).ToNot(HaveOccurred()) 101 102 key := client.ObjectKey{Namespace: tc.ms.Namespace, Name: tc.ms.Name} 103 var actual clusterv1.MachineSet 104 g.Expect(msr.Client.Get(ctx, key, &actual)).To(Succeed()) 105 if tc.expectFinalizer { 106 g.Expect(actual.Finalizers).To(ConsistOf(clusterv1.MachineSetTopologyFinalizer)) 107 } else { 108 g.Expect(actual.Finalizers).To(BeEmpty()) 109 } 110 }) 111 } 112 } 113 114 func TestMachineSetReconciler_ReconcileDelete(t *testing.T) { 115 deletionTimeStamp := metav1.Now() 116 117 mdName := "md" 118 119 msBT := builder.BootstrapTemplate(metav1.NamespaceDefault, "msBT").Build() 120 msIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "msIMT").Build() 121 ms := builder.MachineSet(metav1.NamespaceDefault, "ms"). 122 WithBootstrapTemplate(msBT). 123 WithInfrastructureTemplate(msIMT). 124 WithLabels(map[string]string{ 125 clusterv1.MachineDeploymentNameLabel: mdName, 126 }). 127 Build() 128 ms.SetDeletionTimestamp(&deletionTimeStamp) 129 ms.SetFinalizers([]string{clusterv1.MachineSetTopologyFinalizer}) 130 ms.SetOwnerReferences([]metav1.OwnerReference{ 131 { 132 Kind: "MachineDeployment", 133 APIVersion: clusterv1.GroupVersion.String(), 134 Name: "md", 135 }, 136 }) 137 138 t.Run("Should delete templates of a MachineSet", func(t *testing.T) { 139 g := NewWithT(t) 140 141 // Copying the MS so changes made by reconcileDelete do not affect other tests. 142 ms := ms.DeepCopy() 143 144 fakeClient := fake.NewClientBuilder(). 145 WithScheme(fakeScheme). 146 WithObjects(ms, msBT, msIMT). 147 Build() 148 149 r := &Reconciler{ 150 Client: fakeClient, 151 APIReader: fakeClient, 152 } 153 err := r.reconcileDelete(ctx, ms) 154 g.Expect(err).ToNot(HaveOccurred()) 155 156 g.Expect(controllerutil.ContainsFinalizer(ms, clusterv1.MachineSetTopologyFinalizer)).To(BeFalse()) 157 g.Expect(templateExists(fakeClient, msBT)).To(BeFalse()) 158 g.Expect(templateExists(fakeClient, msIMT)).To(BeFalse()) 159 }) 160 161 t.Run("Should delete infra template of a MachineSet without a bootstrap template", func(t *testing.T) { 162 g := NewWithT(t) 163 164 msWithoutBootstrapTemplateIMT := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "msWithoutBootstrapTemplateIMT").Build() 165 msWithoutBootstrapTemplate := builder.MachineSet(metav1.NamespaceDefault, "msWithoutBootstrapTemplate"). 166 WithInfrastructureTemplate(msWithoutBootstrapTemplateIMT). 167 WithLabels(map[string]string{ 168 clusterv1.MachineDeploymentNameLabel: mdName, 169 }). 170 Build() 171 msWithoutBootstrapTemplate.SetDeletionTimestamp(&deletionTimeStamp) 172 msWithoutBootstrapTemplate.SetFinalizers([]string{clusterv1.MachineSetTopologyFinalizer}) 173 msWithoutBootstrapTemplate.SetOwnerReferences([]metav1.OwnerReference{ 174 { 175 Kind: "MachineDeployment", 176 APIVersion: clusterv1.GroupVersion.String(), 177 Name: "md", 178 }, 179 }) 180 181 fakeClient := fake.NewClientBuilder(). 182 WithScheme(fakeScheme). 183 WithObjects(msWithoutBootstrapTemplate, msWithoutBootstrapTemplateIMT). 184 Build() 185 186 r := &Reconciler{ 187 Client: fakeClient, 188 APIReader: fakeClient, 189 } 190 err := r.reconcileDelete(ctx, msWithoutBootstrapTemplate) 191 g.Expect(err).ToNot(HaveOccurred()) 192 193 g.Expect(controllerutil.ContainsFinalizer(msWithoutBootstrapTemplate, clusterv1.MachineSetTopologyFinalizer)).To(BeFalse()) 194 g.Expect(templateExists(fakeClient, msWithoutBootstrapTemplateIMT)).To(BeFalse()) 195 }) 196 197 t.Run("Should not delete templates of a MachineSet when they are still in use in a MachineDeployment", func(t *testing.T) { 198 g := NewWithT(t) 199 200 // Copying the MS so changes made by reconcileDelete do not affect other tests. 201 ms := ms.DeepCopy() 202 203 md := builder.MachineDeployment(metav1.NamespaceDefault, "md"). 204 WithBootstrapTemplate(msBT). 205 WithInfrastructureTemplate(msIMT). 206 Build() 207 208 fakeClient := fake.NewClientBuilder(). 209 WithScheme(fakeScheme). 210 WithObjects(md, ms, msBT, msIMT). 211 Build() 212 213 r := &Reconciler{ 214 Client: fakeClient, 215 APIReader: fakeClient, 216 } 217 err := r.reconcileDelete(ctx, ms) 218 g.Expect(err).ToNot(HaveOccurred()) 219 220 g.Expect(controllerutil.ContainsFinalizer(ms, clusterv1.MachineSetTopologyFinalizer)).To(BeFalse()) 221 g.Expect(templateExists(fakeClient, msBT)).To(BeTrue()) 222 g.Expect(templateExists(fakeClient, msIMT)).To(BeTrue()) 223 }) 224 225 t.Run("Should not delete templates of a MachineSet when they are still in use in another MachineSet", func(t *testing.T) { 226 g := NewWithT(t) 227 228 md := builder.MachineDeployment(metav1.NamespaceDefault, "md"). 229 WithBootstrapTemplate(msBT). 230 WithInfrastructureTemplate(msIMT). 231 Build() 232 md.SetDeletionTimestamp(&deletionTimeStamp) 233 md.SetFinalizers([]string{clusterv1.MachineDeploymentTopologyFinalizer}) 234 235 // anotherMS is another MachineSet of the same MachineDeployment using the same templates. 236 // Because anotherMS is not in deleting, reconcileDelete should not delete the templates. 237 anotherMS := builder.MachineSet(metav1.NamespaceDefault, "anotherMS"). 238 WithBootstrapTemplate(msBT). 239 WithInfrastructureTemplate(msIMT). 240 WithLabels(map[string]string{ 241 clusterv1.MachineDeploymentNameLabel: mdName, 242 }). 243 Build() 244 anotherMS.SetOwnerReferences([]metav1.OwnerReference{ 245 { 246 Kind: "MachineDeployment", 247 APIVersion: clusterv1.GroupVersion.String(), 248 Name: "md", 249 }, 250 }) 251 252 fakeClient := fake.NewClientBuilder(). 253 WithScheme(fakeScheme). 254 WithObjects(md, anotherMS, ms, msBT, msIMT). 255 Build() 256 257 r := &Reconciler{ 258 Client: fakeClient, 259 APIReader: fakeClient, 260 } 261 err := r.reconcileDelete(ctx, ms) 262 g.Expect(err).ToNot(HaveOccurred()) 263 264 g.Expect(controllerutil.ContainsFinalizer(ms, clusterv1.MachineSetTopologyFinalizer)).To(BeFalse()) 265 g.Expect(templateExists(fakeClient, msBT)).To(BeTrue()) 266 g.Expect(templateExists(fakeClient, msIMT)).To(BeTrue()) 267 }) 268 } 269 270 func templateExists(fakeClient client.Reader, template *unstructured.Unstructured) bool { 271 obj := &unstructured.Unstructured{} 272 obj.SetKind(template.GetKind()) 273 obj.SetAPIVersion(template.GetAPIVersion()) 274 275 err := fakeClient.Get(ctx, client.ObjectKeyFromObject(template), obj) 276 if err != nil && !apierrors.IsNotFound(err) { 277 panic(errors.Wrapf(err, "failed to get %s/%s", template.GetKind(), template.GetName())) 278 } 279 return err == nil 280 }