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  }