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  }