sigs.k8s.io/cluster-api@v1.6.3/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/pointer"
    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: pointer.String("my-data-secret"),
    56  					},
    57  					Version: pointer.String("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: pointer.String("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  				Kind: "Machine",
   188  			},
   189  			ObjectMeta: metav1.ObjectMeta{
   190  				Name:      "machine-name",
   191  				Namespace: metav1.NamespaceDefault,
   192  			},
   193  		}
   194  		content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machine)
   195  		if err != nil {
   196  			g.Fail(err.Error())
   197  		}
   198  		unstructuredOwner := unstructured.Unstructured{}
   199  		unstructuredOwner.SetUnstructuredContent(content)
   200  		co := ConfigOwner{&unstructuredOwner}
   201  		result := co.HasNodeRefs()
   202  		g.Expect(result).To(BeFalse())
   203  	})
   204  	t.Run("should return true if there is a nodeRef for Machine", func(t *testing.T) {
   205  		g := NewWithT(t)
   206  		machine := &clusterv1.Machine{
   207  			TypeMeta: metav1.TypeMeta{
   208  				Kind: "Machine",
   209  			},
   210  			ObjectMeta: metav1.ObjectMeta{
   211  				Name:      "machine-name",
   212  				Namespace: metav1.NamespaceDefault,
   213  			},
   214  			Status: clusterv1.MachineStatus{
   215  				InfrastructureReady: true,
   216  				NodeRef: &corev1.ObjectReference{
   217  					Kind:      "Node",
   218  					Namespace: metav1.NamespaceDefault,
   219  					Name:      "node-0",
   220  				},
   221  			},
   222  		}
   223  		content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machine)
   224  		if err != nil {
   225  			g.Fail(err.Error())
   226  		}
   227  		unstructuredOwner := unstructured.Unstructured{}
   228  		unstructuredOwner.SetUnstructuredContent(content)
   229  		co := ConfigOwner{&unstructuredOwner}
   230  
   231  		result := co.HasNodeRefs()
   232  		g.Expect(result).To(BeTrue())
   233  	})
   234  	t.Run("should return false if nodes are missing from MachinePool", func(t *testing.T) {
   235  		g := NewWithT(t)
   236  		machinePools := []expv1.MachinePool{
   237  			{
   238  				// No replicas specified (default is 1). No nodeRefs either.
   239  				TypeMeta: metav1.TypeMeta{
   240  					Kind: "MachinePool",
   241  				},
   242  				ObjectMeta: metav1.ObjectMeta{
   243  					Namespace: metav1.NamespaceDefault,
   244  					Name:      "machine-pool-name",
   245  				},
   246  			},
   247  			{
   248  				// 1 replica but no nodeRefs
   249  				TypeMeta: metav1.TypeMeta{
   250  					Kind: "MachinePool",
   251  				},
   252  				ObjectMeta: metav1.ObjectMeta{
   253  					Namespace: metav1.NamespaceDefault,
   254  					Name:      "machine-pool-name",
   255  				},
   256  				Spec: expv1.MachinePoolSpec{
   257  					Replicas: pointer.Int32(1),
   258  				},
   259  			},
   260  			{
   261  				// 2 replicas but only 1 nodeRef
   262  				TypeMeta: metav1.TypeMeta{
   263  					Kind: "MachinePool",
   264  				},
   265  				ObjectMeta: metav1.ObjectMeta{
   266  					Namespace: metav1.NamespaceDefault,
   267  					Name:      "machine-pool-name",
   268  				},
   269  				Spec: expv1.MachinePoolSpec{
   270  					Replicas: pointer.Int32(2),
   271  				},
   272  				Status: expv1.MachinePoolStatus{
   273  					NodeRefs: []corev1.ObjectReference{
   274  						{
   275  							Kind:      "Node",
   276  							Namespace: metav1.NamespaceDefault,
   277  							Name:      "node-0",
   278  						},
   279  					},
   280  				},
   281  			},
   282  		}
   283  
   284  		for i := range machinePools {
   285  			content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machinePools[i])
   286  			if err != nil {
   287  				g.Fail(err.Error())
   288  			}
   289  			unstructuredOwner := unstructured.Unstructured{}
   290  			unstructuredOwner.SetUnstructuredContent(content)
   291  			co := ConfigOwner{&unstructuredOwner}
   292  
   293  			result := co.HasNodeRefs()
   294  			g.Expect(result).To(BeFalse())
   295  		}
   296  	})
   297  	t.Run("should return true if MachinePool has nodeRefs for all replicas", func(t *testing.T) {
   298  		g := NewWithT(t)
   299  		machinePools := []expv1.MachinePool{
   300  			{
   301  				// 1 replica (default) and 1 nodeRef
   302  				TypeMeta: metav1.TypeMeta{
   303  					Kind: "MachinePool",
   304  				},
   305  				ObjectMeta: metav1.ObjectMeta{
   306  					Namespace: metav1.NamespaceDefault,
   307  					Name:      "machine-pool-name",
   308  				},
   309  				Status: expv1.MachinePoolStatus{
   310  					NodeRefs: []corev1.ObjectReference{
   311  						{
   312  							Kind:      "Node",
   313  							Namespace: metav1.NamespaceDefault,
   314  							Name:      "node-0",
   315  						},
   316  					},
   317  				},
   318  			},
   319  			{
   320  				// 2 replicas and nodeRefs
   321  				TypeMeta: metav1.TypeMeta{
   322  					Kind: "MachinePool",
   323  				},
   324  				ObjectMeta: metav1.ObjectMeta{
   325  					Namespace: metav1.NamespaceDefault,
   326  					Name:      "machine-pool-name",
   327  				},
   328  				Spec: expv1.MachinePoolSpec{
   329  					Replicas: pointer.Int32(2),
   330  				},
   331  				Status: expv1.MachinePoolStatus{
   332  					NodeRefs: []corev1.ObjectReference{
   333  						{
   334  							Kind:      "Node",
   335  							Namespace: metav1.NamespaceDefault,
   336  							Name:      "node-0",
   337  						},
   338  						{
   339  							Kind:      "Node",
   340  							Namespace: metav1.NamespaceDefault,
   341  							Name:      "node-1",
   342  						},
   343  					},
   344  				},
   345  			},
   346  			{
   347  				// 0 replicas and 0 nodeRef
   348  				TypeMeta: metav1.TypeMeta{
   349  					Kind: "MachinePool",
   350  				},
   351  				ObjectMeta: metav1.ObjectMeta{
   352  					Namespace: metav1.NamespaceDefault,
   353  					Name:      "machine-pool-name",
   354  				},
   355  				Spec: expv1.MachinePoolSpec{
   356  					Replicas: pointer.Int32(0),
   357  				},
   358  			},
   359  		}
   360  
   361  		for i := range machinePools {
   362  			content, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&machinePools[i])
   363  			if err != nil {
   364  				g.Fail(err.Error())
   365  			}
   366  			unstructuredOwner := unstructured.Unstructured{}
   367  			unstructuredOwner.SetUnstructuredContent(content)
   368  			co := ConfigOwner{&unstructuredOwner}
   369  
   370  			result := co.HasNodeRefs()
   371  			g.Expect(result).To(BeTrue())
   372  		}
   373  	})
   374  }