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 }