sigs.k8s.io/cluster-api@v1.7.1/bootstrap/util/configowner_test.go (about)

     1  /*
     2  Copyright 2019 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 util
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/utils/ptr"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    31  
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    34  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api/feature"
    36  )
    37  
    38  func TestGetConfigOwner(t *testing.T) {
    39  	doTests := func(t *testing.T, getFn func(context.Context, client.Client, metav1.Object) (*ConfigOwner, error)) {
    40  		t.Helper()
    41  
    42  		t.Run("should get the owner when present (Machine)", func(t *testing.T) {
    43  			g := NewWithT(t)
    44  			myMachine := &clusterv1.Machine{
    45  				ObjectMeta: metav1.ObjectMeta{
    46  					Name:      "my-machine",
    47  					Namespace: metav1.NamespaceDefault,
    48  					Labels: map[string]string{
    49  						clusterv1.MachineControlPlaneLabel: "",
    50  					},
    51  				},
    52  				Spec: clusterv1.MachineSpec{
    53  					ClusterName: "my-cluster",
    54  					Bootstrap: clusterv1.Bootstrap{
    55  						DataSecretName: ptr.To("my-data-secret"),
    56  					},
    57  					Version: ptr.To("v1.19.6"),
    58  				},
    59  				Status: clusterv1.MachineStatus{
    60  					InfrastructureReady: true,
    61  				},
    62  			}
    63  
    64  			c := fake.NewClientBuilder().WithObjects(myMachine).Build()
    65  			obj := &bootstrapv1.KubeadmConfig{
    66  				ObjectMeta: metav1.ObjectMeta{
    67  					OwnerReferences: []metav1.OwnerReference{
    68  						{
    69  							Kind:       "Machine",
    70  							APIVersion: clusterv1.GroupVersion.String(),
    71  							Name:       "my-machine",
    72  						},
    73  					},
    74  					Namespace: metav1.NamespaceDefault,
    75  					Name:      "my-resource-owned-by-machine",
    76  				},
    77  			}
    78  			configOwner, err := getFn(ctx, c, obj)
    79  			g.Expect(err).ToNot(HaveOccurred())
    80  			g.Expect(configOwner).ToNot(BeNil())
    81  			g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
    82  			g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
    83  			g.Expect(configOwner.IsControlPlaneMachine()).To(BeTrue())
    84  			g.Expect(configOwner.IsMachinePool()).To(BeFalse())
    85  			g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
    86  			g.Expect(*configOwner.DataSecretName()).To(BeEquivalentTo("my-data-secret"))
    87  		})
    88  
    89  		t.Run("should get the owner when present (MachinePool)", func(t *testing.T) {
    90  			_ = feature.MutableGates.Set("MachinePool=true")
    91  
    92  			g := NewWithT(t)
    93  			myPool := &expv1.MachinePool{
    94  				ObjectMeta: metav1.ObjectMeta{
    95  					Name:      "my-machine-pool",
    96  					Namespace: metav1.NamespaceDefault,
    97  					Labels: map[string]string{
    98  						clusterv1.MachineControlPlaneLabel: "",
    99  					},
   100  				},
   101  				Spec: expv1.MachinePoolSpec{
   102  					ClusterName: "my-cluster",
   103  					Template: clusterv1.MachineTemplateSpec{
   104  						Spec: clusterv1.MachineSpec{
   105  							Version: ptr.To("v1.19.6"),
   106  						},
   107  					},
   108  				},
   109  				Status: expv1.MachinePoolStatus{
   110  					InfrastructureReady: true,
   111  				},
   112  			}
   113  
   114  			c := fake.NewClientBuilder().WithObjects(myPool).Build()
   115  			obj := &bootstrapv1.KubeadmConfig{
   116  				ObjectMeta: metav1.ObjectMeta{
   117  					OwnerReferences: []metav1.OwnerReference{
   118  						{
   119  							Kind:       "MachinePool",
   120  							APIVersion: expv1.GroupVersion.String(),
   121  							Name:       "my-machine-pool",
   122  						},
   123  					},
   124  					Namespace: metav1.NamespaceDefault,
   125  					Name:      "my-resource-owned-by-machine-pool",
   126  				},
   127  			}
   128  			configOwner, err := getFn(ctx, c, obj)
   129  			g.Expect(err).ToNot(HaveOccurred())
   130  			g.Expect(configOwner).ToNot(BeNil())
   131  			g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster"))
   132  			g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue())
   133  			g.Expect(configOwner.IsControlPlaneMachine()).To(BeFalse())
   134  			g.Expect(configOwner.IsMachinePool()).To(BeTrue())
   135  			g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6"))
   136  			g.Expect(configOwner.DataSecretName()).To(BeNil())
   137  		})
   138  
   139  		t.Run("return an error when not found", func(t *testing.T) {
   140  			g := NewWithT(t)
   141  			c := fake.NewClientBuilder().Build()
   142  			obj := &bootstrapv1.KubeadmConfig{
   143  				ObjectMeta: metav1.ObjectMeta{
   144  					OwnerReferences: []metav1.OwnerReference{
   145  						{
   146  							Kind:       "Machine",
   147  							APIVersion: clusterv1.GroupVersion.String(),
   148  							Name:       "my-machine",
   149  						},
   150  					},
   151  					Namespace: metav1.NamespaceDefault,
   152  					Name:      "my-resource-owned-by-machine",
   153  				},
   154  			}
   155  			_, err := getFn(ctx, c, obj)
   156  			g.Expect(err).To(HaveOccurred())
   157  		})
   158  
   159  		t.Run("return nothing when there is no owner", func(t *testing.T) {
   160  			g := NewWithT(t)
   161  			c := fake.NewClientBuilder().Build()
   162  			obj := &bootstrapv1.KubeadmConfig{
   163  				ObjectMeta: metav1.ObjectMeta{
   164  					OwnerReferences: []metav1.OwnerReference{},
   165  					Namespace:       metav1.NamespaceDefault,
   166  					Name:            "my-resource-owned-by-machine",
   167  				},
   168  			}
   169  			configOwner, err := getFn(ctx, c, obj)
   170  			g.Expect(err).ToNot(HaveOccurred())
   171  			g.Expect(configOwner).To(BeNil())
   172  		})
   173  	}
   174  	t.Run("uncached", func(t *testing.T) {
   175  		doTests(t, GetConfigOwner)
   176  	})
   177  	t.Run("cached", func(t *testing.T) {
   178  		doTests(t, GetTypedConfigOwner)
   179  	})
   180  }
   181  
   182  func TestHasNodeRefs(t *testing.T) {
   183  	t.Run("should return false if there is no nodeRef", func(t *testing.T) {
   184  		g := NewWithT(t)
   185  		machine := &clusterv1.Machine{
   186  			TypeMeta: metav1.TypeMeta{
   187  				APIVersion: clusterv1.GroupVersion.String(),
   188  				Kind:       "Machine",
   189  			},
   190  			ObjectMeta: metav1.ObjectMeta{
   191  				Name:      "machine-name",
   192  				Namespace: metav1.NamespaceDefault,
   193  			},
   194  		}
   195  		content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machine)
   196  		if err != nil {
   197  			g.Fail(err.Error())
   198  		}
   199  		unstructuredOwner := unstructured.Unstructured{}
   200  		unstructuredOwner.SetUnstructuredContent(content)
   201  		co := ConfigOwner{&unstructuredOwner}
   202  		result := co.HasNodeRefs()
   203  		g.Expect(result).To(BeFalse())
   204  	})
   205  	t.Run("should return true if there is a nodeRef for Machine", func(t *testing.T) {
   206  		g := NewWithT(t)
   207  		machine := &clusterv1.Machine{
   208  			TypeMeta: metav1.TypeMeta{
   209  				APIVersion: clusterv1.GroupVersion.String(),
   210  				Kind:       "Machine",
   211  			},
   212  			ObjectMeta: metav1.ObjectMeta{
   213  				Name:      "machine-name",
   214  				Namespace: metav1.NamespaceDefault,
   215  			},
   216  			Status: clusterv1.MachineStatus{
   217  				InfrastructureReady: true,
   218  				NodeRef: &corev1.ObjectReference{
   219  					Kind:      "Node",
   220  					Namespace: metav1.NamespaceDefault,
   221  					Name:      "node-0",
   222  				},
   223  			},
   224  		}
   225  		content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machine)
   226  		if err != nil {
   227  			g.Fail(err.Error())
   228  		}
   229  		unstructuredOwner := unstructured.Unstructured{}
   230  		unstructuredOwner.SetUnstructuredContent(content)
   231  		co := ConfigOwner{&unstructuredOwner}
   232  
   233  		result := co.HasNodeRefs()
   234  		g.Expect(result).To(BeTrue())
   235  	})
   236  	t.Run("should return false if nodes are missing from MachinePool", func(t *testing.T) {
   237  		g := NewWithT(t)
   238  		machinePools := []expv1.MachinePool{
   239  			{
   240  				// No replicas specified (default is 1). No nodeRefs either.
   241  				TypeMeta: metav1.TypeMeta{
   242  					APIVersion: expv1.GroupVersion.String(),
   243  					Kind:       "MachinePool",
   244  				},
   245  				ObjectMeta: metav1.ObjectMeta{
   246  					Namespace: metav1.NamespaceDefault,
   247  					Name:      "machine-pool-name",
   248  				},
   249  			},
   250  			{
   251  				// 1 replica but no nodeRefs
   252  				TypeMeta: metav1.TypeMeta{
   253  					APIVersion: expv1.GroupVersion.String(),
   254  					Kind:       "MachinePool",
   255  				},
   256  				ObjectMeta: metav1.ObjectMeta{
   257  					Namespace: metav1.NamespaceDefault,
   258  					Name:      "machine-pool-name",
   259  				},
   260  				Spec: expv1.MachinePoolSpec{
   261  					Replicas: ptr.To[int32](1),
   262  				},
   263  			},
   264  			{
   265  				// 2 replicas but only 1 nodeRef
   266  				TypeMeta: metav1.TypeMeta{
   267  					APIVersion: expv1.GroupVersion.String(),
   268  					Kind:       "MachinePool",
   269  				},
   270  				ObjectMeta: metav1.ObjectMeta{
   271  					Namespace: metav1.NamespaceDefault,
   272  					Name:      "machine-pool-name",
   273  				},
   274  				Spec: expv1.MachinePoolSpec{
   275  					Replicas: ptr.To[int32](2),
   276  				},
   277  				Status: expv1.MachinePoolStatus{
   278  					NodeRefs: []corev1.ObjectReference{
   279  						{
   280  							Kind:      "Node",
   281  							Namespace: metav1.NamespaceDefault,
   282  							Name:      "node-0",
   283  						},
   284  					},
   285  				},
   286  			},
   287  		}
   288  
   289  		for i := range machinePools {
   290  			content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machinePools[i])
   291  			if err != nil {
   292  				g.Fail(err.Error())
   293  			}
   294  			unstructuredOwner := unstructured.Unstructured{}
   295  			unstructuredOwner.SetUnstructuredContent(content)
   296  			co := ConfigOwner{&unstructuredOwner}
   297  
   298  			result := co.HasNodeRefs()
   299  			g.Expect(result).To(BeFalse())
   300  		}
   301  	})
   302  	t.Run("should return true if MachinePool has nodeRefs for all replicas", func(t *testing.T) {
   303  		g := NewWithT(t)
   304  		machinePools := []expv1.MachinePool{
   305  			{
   306  				// 1 replica (default) and 1 nodeRef
   307  				TypeMeta: metav1.TypeMeta{
   308  					APIVersion: expv1.GroupVersion.String(),
   309  					Kind:       "MachinePool",
   310  				},
   311  				ObjectMeta: metav1.ObjectMeta{
   312  					Namespace: metav1.NamespaceDefault,
   313  					Name:      "machine-pool-name",
   314  				},
   315  				Status: expv1.MachinePoolStatus{
   316  					NodeRefs: []corev1.ObjectReference{
   317  						{
   318  							Kind:      "Node",
   319  							Namespace: metav1.NamespaceDefault,
   320  							Name:      "node-0",
   321  						},
   322  					},
   323  				},
   324  			},
   325  			{
   326  				// 2 replicas and nodeRefs
   327  				TypeMeta: metav1.TypeMeta{
   328  					APIVersion: expv1.GroupVersion.String(),
   329  					Kind:       "MachinePool",
   330  				},
   331  				ObjectMeta: metav1.ObjectMeta{
   332  					Namespace: metav1.NamespaceDefault,
   333  					Name:      "machine-pool-name",
   334  				},
   335  				Spec: expv1.MachinePoolSpec{
   336  					Replicas: ptr.To[int32](2),
   337  				},
   338  				Status: expv1.MachinePoolStatus{
   339  					NodeRefs: []corev1.ObjectReference{
   340  						{
   341  							Kind:      "Node",
   342  							Namespace: metav1.NamespaceDefault,
   343  							Name:      "node-0",
   344  						},
   345  						{
   346  							Kind:      "Node",
   347  							Namespace: metav1.NamespaceDefault,
   348  							Name:      "node-1",
   349  						},
   350  					},
   351  				},
   352  			},
   353  			{
   354  				// 0 replicas and 0 nodeRef
   355  				TypeMeta: metav1.TypeMeta{
   356  					APIVersion: expv1.GroupVersion.String(),
   357  					Kind:       "MachinePool",
   358  				},
   359  				ObjectMeta: metav1.ObjectMeta{
   360  					Namespace: metav1.NamespaceDefault,
   361  					Name:      "machine-pool-name",
   362  				},
   363  				Spec: expv1.MachinePoolSpec{
   364  					Replicas: ptr.To[int32](0),
   365  				},
   366  			},
   367  		}
   368  
   369  		for i := range machinePools {
   370  			content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machinePools[i])
   371  			if err != nil {
   372  				g.Fail(err.Error())
   373  			}
   374  			unstructuredOwner := unstructured.Unstructured{}
   375  			unstructuredOwner.SetUnstructuredContent(content)
   376  			co := ConfigOwner{&unstructuredOwner}
   377  
   378  			result := co.HasNodeRefs()
   379  			g.Expect(result).To(BeTrue())
   380  		}
   381  	})
   382  }