sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machineset/machineset_preflight_test.go (about) 1 /* 2 Copyright 2023 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 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 utilfeature "k8s.io/component-base/featuregate/testing" 27 "k8s.io/utils/ptr" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 31 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 32 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 33 "sigs.k8s.io/cluster-api/feature" 34 "sigs.k8s.io/cluster-api/internal/contract" 35 "sigs.k8s.io/cluster-api/internal/test/builder" 36 ) 37 38 func TestMachineSetReconciler_runPreflightChecks(t *testing.T) { 39 ns := "ns1" 40 41 controlPlaneWithNoVersion := builder.ControlPlane(ns, "cp1").Build() 42 43 controlPlaneWithInvalidVersion := builder.ControlPlane(ns, "cp1"). 44 WithVersion("v1.25.6.0").Build() 45 46 controlPlaneProvisioning := builder.ControlPlane(ns, "cp1"). 47 WithVersion("v1.25.6").Build() 48 49 controlPlaneUpgrading := builder.ControlPlane(ns, "cp1"). 50 WithVersion("v1.26.2"). 51 WithStatusFields(map[string]interface{}{ 52 "status.version": "v1.25.2", 53 }). 54 Build() 55 56 controlPlaneStable := builder.ControlPlane(ns, "cp1"). 57 WithVersion("v1.26.2"). 58 WithStatusFields(map[string]interface{}{ 59 "status.version": "v1.26.2", 60 }). 61 Build() 62 63 controlPlaneStable128 := builder.ControlPlane(ns, "cp1"). 64 WithVersion("v1.28.0"). 65 WithStatusFields(map[string]interface{}{ 66 "status.version": "v1.28.0", 67 }). 68 Build() 69 70 t.Run("should run preflight checks if the feature gate is enabled", func(t *testing.T) { 71 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachineSetPreflightChecks, true)() 72 73 tests := []struct { 74 name string 75 cluster *clusterv1.Cluster 76 controlPlane *unstructured.Unstructured 77 machineSet *clusterv1.MachineSet 78 wantPass bool 79 wantErr bool 80 }{ 81 { 82 name: "should pass if cluster has no control plane", 83 cluster: &clusterv1.Cluster{}, 84 machineSet: &clusterv1.MachineSet{}, 85 wantPass: true, 86 }, 87 { 88 name: "should pass if the control plane version is not defined", 89 cluster: &clusterv1.Cluster{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Namespace: ns, 92 }, 93 Spec: clusterv1.ClusterSpec{ 94 ControlPlaneRef: contract.ObjToRef(controlPlaneWithNoVersion), 95 }, 96 }, 97 controlPlane: controlPlaneWithNoVersion, 98 machineSet: &clusterv1.MachineSet{}, 99 wantPass: true, 100 }, 101 { 102 name: "should error if the control plane version is invalid", 103 cluster: &clusterv1.Cluster{ 104 ObjectMeta: metav1.ObjectMeta{ 105 Namespace: ns, 106 }, 107 Spec: clusterv1.ClusterSpec{ 108 ControlPlaneRef: contract.ObjToRef(controlPlaneWithInvalidVersion), 109 }, 110 }, 111 controlPlane: controlPlaneWithInvalidVersion, 112 machineSet: &clusterv1.MachineSet{}, 113 wantErr: true, 114 }, 115 { 116 name: "should pass if all preflight checks are skipped", 117 cluster: &clusterv1.Cluster{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Namespace: ns, 120 }, 121 Spec: clusterv1.ClusterSpec{ 122 ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading), 123 }, 124 }, 125 controlPlane: controlPlaneUpgrading, 126 machineSet: &clusterv1.MachineSet{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Namespace: ns, 129 Annotations: map[string]string{ 130 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckAll), 131 }, 132 }, 133 }, 134 wantPass: true, 135 }, 136 { 137 name: "control plane preflight check: should fail if the control plane is provisioning", 138 cluster: &clusterv1.Cluster{ 139 ObjectMeta: metav1.ObjectMeta{ 140 Namespace: ns, 141 }, 142 Spec: clusterv1.ClusterSpec{ 143 ControlPlaneRef: contract.ObjToRef(controlPlaneProvisioning), 144 }, 145 }, 146 controlPlane: controlPlaneProvisioning, 147 machineSet: &clusterv1.MachineSet{}, 148 wantPass: false, 149 }, 150 { 151 name: "control plane preflight check: should fail if the control plane is upgrading", 152 cluster: &clusterv1.Cluster{ 153 ObjectMeta: metav1.ObjectMeta{ 154 Namespace: ns, 155 }, 156 Spec: clusterv1.ClusterSpec{ 157 ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading), 158 }, 159 }, 160 controlPlane: controlPlaneUpgrading, 161 machineSet: &clusterv1.MachineSet{}, 162 wantPass: false, 163 }, 164 { 165 name: "control plane preflight check: should pass if the control plane is upgrading but the preflight check is skipped", 166 cluster: &clusterv1.Cluster{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Namespace: ns, 169 }, 170 Spec: clusterv1.ClusterSpec{ 171 ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading), 172 }, 173 }, 174 controlPlane: controlPlaneUpgrading, 175 machineSet: &clusterv1.MachineSet{ 176 ObjectMeta: metav1.ObjectMeta{ 177 Namespace: ns, 178 Annotations: map[string]string{ 179 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckControlPlaneIsStable), 180 }, 181 }, 182 Spec: clusterv1.MachineSetSpec{ 183 Template: clusterv1.MachineTemplateSpec{ 184 Spec: clusterv1.MachineSpec{ 185 Version: ptr.To("v1.26.2"), 186 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{Kind: "KubeadmConfigTemplate"}}, 187 }, 188 }, 189 }, 190 }, 191 wantPass: true, 192 }, 193 { 194 name: "control plane preflight check: should pass if the control plane is stable", 195 cluster: &clusterv1.Cluster{ 196 ObjectMeta: metav1.ObjectMeta{ 197 Namespace: ns, 198 }, 199 Spec: clusterv1.ClusterSpec{ 200 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 201 }, 202 }, 203 controlPlane: controlPlaneStable, 204 machineSet: &clusterv1.MachineSet{}, 205 wantPass: true, 206 }, 207 { 208 name: "should pass if the machine set version is not defined", 209 cluster: &clusterv1.Cluster{ 210 ObjectMeta: metav1.ObjectMeta{ 211 Namespace: ns, 212 }, 213 Spec: clusterv1.ClusterSpec{ 214 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 215 }, 216 }, 217 controlPlane: controlPlaneStable, 218 machineSet: &clusterv1.MachineSet{ 219 ObjectMeta: metav1.ObjectMeta{ 220 Namespace: ns, 221 }, 222 Spec: clusterv1.MachineSetSpec{}, 223 }, 224 wantPass: true, 225 }, 226 { 227 name: "should error if the machine set version is invalid", 228 cluster: &clusterv1.Cluster{ 229 ObjectMeta: metav1.ObjectMeta{ 230 Namespace: ns, 231 }, 232 Spec: clusterv1.ClusterSpec{ 233 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 234 }, 235 }, 236 controlPlane: controlPlaneStable, 237 machineSet: &clusterv1.MachineSet{ 238 ObjectMeta: metav1.ObjectMeta{ 239 Namespace: ns, 240 }, 241 Spec: clusterv1.MachineSetSpec{ 242 Template: clusterv1.MachineTemplateSpec{ 243 Spec: clusterv1.MachineSpec{ 244 Version: ptr.To("v1.27.0.0"), 245 }, 246 }, 247 }, 248 }, 249 wantErr: true, 250 }, 251 { 252 name: "kubernetes version preflight check: should fail if the machine set minor version is greater than control plane minor version", 253 cluster: &clusterv1.Cluster{ 254 ObjectMeta: metav1.ObjectMeta{ 255 Namespace: ns, 256 }, 257 Spec: clusterv1.ClusterSpec{ 258 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 259 }, 260 }, 261 controlPlane: controlPlaneStable, 262 machineSet: &clusterv1.MachineSet{ 263 ObjectMeta: metav1.ObjectMeta{ 264 Namespace: ns, 265 }, 266 Spec: clusterv1.MachineSetSpec{ 267 Template: clusterv1.MachineTemplateSpec{ 268 Spec: clusterv1.MachineSpec{ 269 Version: ptr.To("v1.27.0"), 270 }, 271 }, 272 }, 273 }, 274 wantPass: false, 275 }, 276 { 277 name: "kubernetes version preflight check: should fail if the machine set minor version is 4 older than control plane minor version for >= v1.28", 278 cluster: &clusterv1.Cluster{ 279 ObjectMeta: metav1.ObjectMeta{ 280 Namespace: ns, 281 }, 282 Spec: clusterv1.ClusterSpec{ 283 ControlPlaneRef: contract.ObjToRef(controlPlaneStable128), 284 }, 285 }, 286 controlPlane: controlPlaneStable128, 287 machineSet: &clusterv1.MachineSet{ 288 ObjectMeta: metav1.ObjectMeta{ 289 Namespace: ns, 290 }, 291 Spec: clusterv1.MachineSetSpec{ 292 Template: clusterv1.MachineTemplateSpec{ 293 Spec: clusterv1.MachineSpec{ 294 Version: ptr.To("v1.24.0"), 295 }, 296 }, 297 }, 298 }, 299 wantPass: false, 300 }, 301 { 302 name: "kubernetes version preflight check: should fail if the machine set minor version is 3 older than control plane minor version for < v1.28", 303 cluster: &clusterv1.Cluster{ 304 ObjectMeta: metav1.ObjectMeta{ 305 Namespace: ns, 306 }, 307 Spec: clusterv1.ClusterSpec{ 308 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 309 }, 310 }, 311 controlPlane: controlPlaneStable, 312 machineSet: &clusterv1.MachineSet{ 313 ObjectMeta: metav1.ObjectMeta{ 314 Namespace: ns, 315 }, 316 Spec: clusterv1.MachineSetSpec{ 317 Template: clusterv1.MachineTemplateSpec{ 318 Spec: clusterv1.MachineSpec{ 319 Version: ptr.To("v1.23.0"), 320 }, 321 }, 322 }, 323 }, 324 wantPass: false, 325 }, 326 { 327 name: "kubernetes version preflight check: should pass if the machine set minor version is greater than control plane minor version but the preflight check is skipped", 328 cluster: &clusterv1.Cluster{ 329 ObjectMeta: metav1.ObjectMeta{ 330 Namespace: ns, 331 }, 332 Spec: clusterv1.ClusterSpec{ 333 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 334 }, 335 }, 336 controlPlane: controlPlaneStable, 337 machineSet: &clusterv1.MachineSet{ 338 ObjectMeta: metav1.ObjectMeta{ 339 Namespace: ns, 340 Annotations: map[string]string{ 341 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckKubernetesVersionSkew), 342 }, 343 }, 344 Spec: clusterv1.MachineSetSpec{ 345 Template: clusterv1.MachineTemplateSpec{ 346 Spec: clusterv1.MachineSpec{ 347 Version: ptr.To("v1.27.0"), 348 }, 349 }, 350 }, 351 }, 352 wantPass: true, 353 }, 354 { 355 name: "kubernetes version preflight check: should pass if the machine set minor version and control plane version conform to kubernetes version skew policy >= v1.28", 356 cluster: &clusterv1.Cluster{ 357 ObjectMeta: metav1.ObjectMeta{ 358 Namespace: ns, 359 }, 360 Spec: clusterv1.ClusterSpec{ 361 ControlPlaneRef: contract.ObjToRef(controlPlaneStable128), 362 }, 363 }, 364 controlPlane: controlPlaneStable128, 365 machineSet: &clusterv1.MachineSet{ 366 ObjectMeta: metav1.ObjectMeta{ 367 Namespace: ns, 368 }, 369 Spec: clusterv1.MachineSetSpec{ 370 Template: clusterv1.MachineTemplateSpec{ 371 Spec: clusterv1.MachineSpec{ 372 Version: ptr.To("v1.25.0"), 373 }, 374 }, 375 }, 376 }, 377 wantPass: true, 378 }, 379 { 380 name: "kubernetes version preflight check: should pass if the machine set minor version and control plane version conform to kubernetes version skew policy < v1.28", 381 cluster: &clusterv1.Cluster{ 382 ObjectMeta: metav1.ObjectMeta{ 383 Namespace: ns, 384 }, 385 Spec: clusterv1.ClusterSpec{ 386 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 387 }, 388 }, 389 controlPlane: controlPlaneStable, 390 machineSet: &clusterv1.MachineSet{ 391 ObjectMeta: metav1.ObjectMeta{ 392 Namespace: ns, 393 }, 394 Spec: clusterv1.MachineSetSpec{ 395 Template: clusterv1.MachineTemplateSpec{ 396 Spec: clusterv1.MachineSpec{ 397 Version: ptr.To("v1.24.0"), 398 }, 399 }, 400 }, 401 }, 402 wantPass: true, 403 }, 404 { 405 name: "kubeadm version preflight check: should fail if the machine set version is not equal (major+minor) to control plane version when using kubeadm bootstrap provider", 406 cluster: &clusterv1.Cluster{ 407 ObjectMeta: metav1.ObjectMeta{ 408 Namespace: ns, 409 }, 410 Spec: clusterv1.ClusterSpec{ 411 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 412 }, 413 }, 414 controlPlane: controlPlaneStable, 415 machineSet: &clusterv1.MachineSet{ 416 ObjectMeta: metav1.ObjectMeta{ 417 Namespace: ns, 418 }, 419 Spec: clusterv1.MachineSetSpec{ 420 Template: clusterv1.MachineTemplateSpec{ 421 Spec: clusterv1.MachineSpec{ 422 Version: ptr.To("v1.25.5"), 423 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{ 424 APIVersion: bootstrapv1.GroupVersion.String(), 425 Kind: "KubeadmConfigTemplate", 426 }}, 427 }, 428 }, 429 }, 430 }, 431 wantPass: false, 432 }, 433 { 434 name: "kubeadm version preflight check: should pass if the machine set is not using kubeadm bootstrap provider", 435 cluster: &clusterv1.Cluster{ 436 ObjectMeta: metav1.ObjectMeta{ 437 Namespace: ns, 438 }, 439 Spec: clusterv1.ClusterSpec{ 440 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 441 }, 442 }, 443 controlPlane: controlPlaneStable, 444 machineSet: &clusterv1.MachineSet{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Namespace: ns, 447 }, 448 Spec: clusterv1.MachineSetSpec{ 449 Template: clusterv1.MachineTemplateSpec{ 450 Spec: clusterv1.MachineSpec{ 451 Version: ptr.To("v1.25.0"), 452 }, 453 }, 454 }, 455 }, 456 wantPass: true, 457 }, 458 { 459 name: "kubeadm version preflight check: should pass if the machine set version and control plane version do not conform to kubeadm version skew policy but the preflight check is skipped", 460 cluster: &clusterv1.Cluster{ 461 ObjectMeta: metav1.ObjectMeta{ 462 Namespace: ns, 463 }, 464 Spec: clusterv1.ClusterSpec{ 465 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 466 }, 467 }, 468 controlPlane: controlPlaneStable, 469 machineSet: &clusterv1.MachineSet{ 470 ObjectMeta: metav1.ObjectMeta{ 471 Namespace: ns, 472 Annotations: map[string]string{ 473 clusterv1.MachineSetSkipPreflightChecksAnnotation: "foobar," + string(clusterv1.MachineSetPreflightCheckKubeadmVersionSkew), 474 }, 475 }, 476 Spec: clusterv1.MachineSetSpec{ 477 Template: clusterv1.MachineTemplateSpec{ 478 Spec: clusterv1.MachineSpec{ 479 Version: ptr.To("v1.25.0"), 480 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{ 481 APIVersion: bootstrapv1.GroupVersion.String(), 482 Kind: "KubeadmConfigTemplate", 483 }}, 484 }, 485 }, 486 }, 487 }, 488 wantPass: true, 489 }, 490 { 491 name: "kubeadm version preflight check: should pass if the machine set version and control plane version conform to kubeadm version skew when using kubeadm bootstrap provider", 492 cluster: &clusterv1.Cluster{ 493 ObjectMeta: metav1.ObjectMeta{ 494 Namespace: ns, 495 }, 496 Spec: clusterv1.ClusterSpec{ 497 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 498 }, 499 }, 500 controlPlane: controlPlaneStable, 501 machineSet: &clusterv1.MachineSet{ 502 ObjectMeta: metav1.ObjectMeta{ 503 Namespace: ns, 504 }, 505 Spec: clusterv1.MachineSetSpec{ 506 Template: clusterv1.MachineTemplateSpec{ 507 Spec: clusterv1.MachineSpec{ 508 Version: ptr.To("v1.26.2"), 509 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{ 510 APIVersion: bootstrapv1.GroupVersion.String(), 511 Kind: "KubeadmConfigTemplate", 512 }}, 513 }, 514 }, 515 }, 516 }, 517 wantPass: true, 518 }, 519 { 520 name: "kubeadm version preflight check: should error if the bootstrap ref APIVersion is invalid", 521 cluster: &clusterv1.Cluster{ 522 ObjectMeta: metav1.ObjectMeta{ 523 Namespace: ns, 524 }, 525 Spec: clusterv1.ClusterSpec{ 526 ControlPlaneRef: contract.ObjToRef(controlPlaneStable), 527 }, 528 }, 529 controlPlane: controlPlaneStable, 530 machineSet: &clusterv1.MachineSet{ 531 ObjectMeta: metav1.ObjectMeta{ 532 Namespace: ns, 533 }, 534 Spec: clusterv1.MachineSetSpec{ 535 Template: clusterv1.MachineTemplateSpec{ 536 Spec: clusterv1.MachineSpec{ 537 Version: ptr.To("v1.26.2"), 538 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{ 539 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1/invalid", 540 Kind: "KubeadmConfigTemplate", 541 }}, 542 }, 543 }, 544 }, 545 }, 546 wantErr: true, 547 }, 548 } 549 550 for _, tt := range tests { 551 t.Run(tt.name, func(t *testing.T) { 552 g := NewWithT(t) 553 objs := []client.Object{} 554 if tt.controlPlane != nil { 555 objs = append(objs, tt.controlPlane) 556 } 557 fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build() 558 r := &Reconciler{ 559 Client: fakeClient, 560 UnstructuredCachingClient: fakeClient, 561 } 562 result, _, err := r.runPreflightChecks(ctx, tt.cluster, tt.machineSet, "") 563 if tt.wantErr { 564 g.Expect(err).To(HaveOccurred()) 565 } else { 566 g.Expect(err).ToNot(HaveOccurred()) 567 g.Expect(result.IsZero()).To(Equal(tt.wantPass)) 568 } 569 }) 570 } 571 }) 572 573 t.Run("should not run the preflight checks if the feature gate is disabled", func(t *testing.T) { 574 defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachineSetPreflightChecks, false)() 575 576 g := NewWithT(t) 577 cluster := &clusterv1.Cluster{ 578 ObjectMeta: metav1.ObjectMeta{ 579 Namespace: ns, 580 }, 581 Spec: clusterv1.ClusterSpec{ 582 ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading), 583 }, 584 } 585 controlPlane := controlPlaneUpgrading 586 machineSet := &clusterv1.MachineSet{ 587 ObjectMeta: metav1.ObjectMeta{ 588 Namespace: ns, 589 }, 590 Spec: clusterv1.MachineSetSpec{ 591 Template: clusterv1.MachineTemplateSpec{ 592 Spec: clusterv1.MachineSpec{ 593 Version: ptr.To("v1.26.0"), 594 Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{ 595 APIVersion: bootstrapv1.GroupVersion.String(), 596 Kind: "KubeadmConfigTemplate", 597 }}, 598 }, 599 }, 600 }, 601 } 602 fakeClient := fake.NewClientBuilder().WithObjects(controlPlane).Build() 603 r := &Reconciler{Client: fakeClient} 604 result, _, err := r.runPreflightChecks(ctx, cluster, machineSet, "") 605 g.Expect(err).ToNot(HaveOccurred()) 606 g.Expect(result.IsZero()).To(BeTrue()) 607 }) 608 }