@@ -34,6 +34,7 @@ import (
34
34
35
35
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
36
36
"sigs.k8s.io/cluster-api/controllers/external"
37
+ expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
37
38
"sigs.k8s.io/cluster-api/internal/contract"
38
39
"sigs.k8s.io/cluster-api/test/e2e/internal/log"
39
40
"sigs.k8s.io/cluster-api/test/framework"
@@ -426,6 +427,131 @@ func modifyMachineDeploymentViaClusterClassAndWait(ctx context.Context, input mo
426
427
}
427
428
}
428
429
430
+ // modifyMachinePoolViaClusterClassAndWaitInput is the input type for modifyMachinePoolViaClusterClassAndWait.
431
+ type modifyMachinePoolViaClusterClassAndWaitInput struct {
432
+ ClusterProxy framework.ClusterProxy
433
+ ClusterClass * clusterv1.ClusterClass
434
+ Cluster * clusterv1.Cluster
435
+ ModifyBootstrapConfigTemplateFields map [string ]interface {}
436
+ ModifyInfrastructureMachinePoolTemplateFields map [string ]interface {}
437
+ WaitForMachinePools []interface {}
438
+ }
439
+
440
+ // modifyMachinePoolViaClusterClassAndWait modifies the BootstrapConfigTemplate of MachinePoolClasses of a ClusterClass
441
+ // by setting ModifyBootstrapConfigTemplateFields and waits until the changes are rolled out to the MachinePools of the Cluster.
442
+ // NOTE: This helper is really specific to this test, so we are keeping this private vs. adding it to the framework.
443
+ func modifyMachinePoolViaClusterClassAndWait (ctx context.Context , input modifyMachinePoolViaClusterClassAndWaitInput ) {
444
+ Expect (ctx ).NotTo (BeNil (), "ctx is required for modifyMachinePoolViaClusterClassAndWait" )
445
+ Expect (input .ClusterProxy ).ToNot (BeNil (), "Invalid argument. input.ClusterProxy can't be nil when calling modifyMachinePoolViaClusterClassAndWait" )
446
+ Expect (input .ClusterClass ).ToNot (BeNil (), "Invalid argument. input.ClusterClass can't be nil when calling modifyMachinePoolViaClusterClassAndWait" )
447
+ Expect (input .Cluster ).ToNot (BeNil (), "Invalid argument. input.Cluster can't be nil when calling modifyMachinePoolViaClusterClassAndWait" )
448
+
449
+ mgmtClient := input .ClusterProxy .GetClient ()
450
+
451
+ for _ , mpClass := range input .ClusterClass .Spec .Workers .MachinePools {
452
+ // Only try to modify the BootstrapConfigTemplate if the MachinePoolClass is using a BootstrapConfigTemplate.
453
+ var bootstrapConfigTemplateRef * corev1.ObjectReference
454
+ var newBootstrapConfigTemplateName string
455
+ if mpClass .Template .Bootstrap .Ref != nil {
456
+ log .Logf ("Modifying the BootstrapConfigTemplate of MachinePoolClass %q of ClusterClass %s" , mpClass .Class , klog .KObj (input .ClusterClass ))
457
+
458
+ // Retrieve BootstrapConfigTemplate object.
459
+ bootstrapConfigTemplateRef = mpClass .Template .Bootstrap .Ref
460
+ bootstrapConfigTemplate , err := external .Get (ctx , mgmtClient , bootstrapConfigTemplateRef , input .Cluster .Namespace )
461
+ Expect (err ).ToNot (HaveOccurred ())
462
+ // Create a new BootstrapConfigTemplate object with a new name and ModifyBootstrapConfigTemplateFields set.
463
+ newBootstrapConfigTemplate := bootstrapConfigTemplate .DeepCopy ()
464
+ newBootstrapConfigTemplateName = fmt .Sprintf ("%s-%s" , bootstrapConfigTemplateRef .Name , util .RandomString (6 ))
465
+ newBootstrapConfigTemplate .SetName (newBootstrapConfigTemplateName )
466
+ newBootstrapConfigTemplate .SetResourceVersion ("" )
467
+ for fieldPath , value := range input .ModifyBootstrapConfigTemplateFields {
468
+ Expect (unstructured .SetNestedField (newBootstrapConfigTemplate .Object , value , strings .Split (fieldPath , "." )... )).To (Succeed ())
469
+ }
470
+ Expect (mgmtClient .Create (ctx , newBootstrapConfigTemplate )).To (Succeed ())
471
+ }
472
+
473
+ log .Logf ("Modifying the InfrastructureMachinePoolTemplate of MachinePoolClass %q of ClusterClass %s" , mpClass .Class , klog .KObj (input .ClusterClass ))
474
+
475
+ // Retrieve InfrastructureMachineTemplate object.
476
+ infrastructureMachinePoolTemplateRef := mpClass .Template .Infrastructure .Ref
477
+ infrastructureMachinePoolTemplate , err := external .Get (ctx , mgmtClient , infrastructureMachinePoolTemplateRef , input .Cluster .Namespace )
478
+ Expect (err ).ToNot (HaveOccurred ())
479
+ // Create a new InfrastructureMachinePoolTemplate object with a new name and ModifyInfrastructureMachinePoolTemplateFields set.
480
+ newInfrastructureMachinePoolTemplate := infrastructureMachinePoolTemplate .DeepCopy ()
481
+ newInfrastructureMachinePoolTemplateName := fmt .Sprintf ("%s-%s" , infrastructureMachinePoolTemplateRef .Name , util .RandomString (6 ))
482
+ newInfrastructureMachinePoolTemplate .SetName (newInfrastructureMachinePoolTemplateName )
483
+ newInfrastructureMachinePoolTemplate .SetResourceVersion ("" )
484
+ for fieldPath , value := range input .ModifyInfrastructureMachinePoolTemplateFields {
485
+ Expect (unstructured .SetNestedField (newInfrastructureMachinePoolTemplate .Object , value , strings .Split (fieldPath , "." )... )).To (Succeed ())
486
+ }
487
+ Expect (mgmtClient .Create (ctx , newInfrastructureMachinePoolTemplate )).To (Succeed ())
488
+
489
+ // Patch the refs of the MachinePoolClass to reference the new templates.
490
+ patchHelper , err := patch .NewHelper (input .ClusterClass , mgmtClient )
491
+ Expect (err ).ToNot (HaveOccurred ())
492
+ if mpClass .Template .Bootstrap .Ref != nil {
493
+ bootstrapConfigTemplateRef .Name = newBootstrapConfigTemplateName
494
+ }
495
+ infrastructureMachinePoolTemplateRef .Name = newInfrastructureMachinePoolTemplateName
496
+ Expect (patchHelper .Patch (ctx , input .ClusterClass )).To (Succeed ())
497
+
498
+ log .Logf ("Waiting for MachinePool rollout for MachinePoolClass %q to complete." , mpClass .Class )
499
+ for _ , mpTopology := range input .Cluster .Spec .Topology .Workers .MachinePools {
500
+ // Continue if the MachinePoolTopology belongs to another MachinePoolClass.
501
+ if mpTopology .Class != mpClass .Class {
502
+ continue
503
+ }
504
+
505
+ // NOTE: We only wait until the change is rolled out to the MachinePool objects and not to the worker machines
506
+ // to speed up the test and focus the test on the ClusterClass feature.
507
+ log .Logf ("Waiting for MachinePool rollout for MachinePoolTopology %q (class %q) to complete." , mpTopology .Name , mpTopology .Class )
508
+ Eventually (func (g Gomega ) error {
509
+ // Get MachinePool for the current MachinePoolTopology.
510
+ mpList := & expv1.MachinePoolList {}
511
+ g .Expect (mgmtClient .List (ctx , mpList , client .InNamespace (input .Cluster .Namespace ), client.MatchingLabels {
512
+ clusterv1 .ClusterTopologyMachinePoolNameLabel : mpTopology .Name ,
513
+ })).To (Succeed ())
514
+ g .Expect (mpList .Items ).To (HaveLen (1 ), fmt .Sprintf ("expected one MachinePool for topology %q, but got %d" , mpTopology .Name , len (mpList .Items )))
515
+ mp := mpList .Items [0 ]
516
+
517
+ // Verify that the fields from Cluster topology are set on the MachinePool.
518
+ assertMachinePoolTopologyFields (g , mp , mpTopology )
519
+
520
+ if mpClass .Template .Bootstrap .Ref != nil {
521
+ // Get the corresponding BootstrapConfig object.
522
+ bootstrapConfigObjectRef := mp .Spec .Template .Spec .Bootstrap .ConfigRef
523
+ bootstrapConfigObject , err := external .Get (ctx , mgmtClient , bootstrapConfigObjectRef , input .Cluster .Namespace )
524
+ g .Expect (err ).ToNot (HaveOccurred ())
525
+
526
+ // Verify that ModifyBootstrapConfigTemplateFields have been set and propagates to the BootstrapConfig.
527
+ for fieldPath , expectedValue := range input .ModifyBootstrapConfigTemplateFields {
528
+ // MachinePools have a BootstrapConfig, not a BootstrapConfigTemplate, so we need to convert the fieldPath so it can find it on the object.
529
+ fieldPath = strings .TrimPrefix (fieldPath , "spec.template." )
530
+ currentValue , ok , err := unstructured .NestedFieldNoCopy (bootstrapConfigObject .Object , strings .Split (fieldPath , "." )... )
531
+ g .Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("failed to get field %q" , fieldPath ))
532
+ g .Expect (ok ).To (BeTrue (), fmt .Sprintf ("failed to get field %q" , fieldPath ))
533
+ g .Expect (currentValue ).To (Equal (expectedValue ), fmt .Sprintf ("field %q should be equal" , fieldPath ))
534
+ }
535
+ }
536
+
537
+ // Get the corresponding InfrastructureMachinePoolTemplate.
538
+ infrastructureMachinePoolTemplateRef := mp .Spec .Template .Spec .InfrastructureRef
539
+ infrastructureMachinePoolTemplate , err := external .Get (ctx , mgmtClient , & infrastructureMachinePoolTemplateRef , input .Cluster .Namespace )
540
+ g .Expect (err ).ToNot (HaveOccurred ())
541
+
542
+ // Verify that ModifyInfrastructureMachinePoolTemplateFields have been set.
543
+ for fieldPath , expectedValue := range input .ModifyInfrastructureMachinePoolTemplateFields {
544
+ currentValue , ok , err := unstructured .NestedFieldNoCopy (infrastructureMachinePoolTemplate .Object , strings .Split (fieldPath , "." )... )
545
+ g .Expect (err ).ToNot (HaveOccurred (), fmt .Sprintf ("failed to get field %q" , fieldPath ))
546
+ g .Expect (ok ).To (BeTrue (), fmt .Sprintf ("failed to get field %q" , fieldPath ))
547
+ g .Expect (currentValue ).To (Equal (expectedValue ), fmt .Sprintf ("field %q should be equal" , fieldPath ))
548
+ }
549
+ return nil
550
+ }, input .WaitForMachinePools ... ).Should (BeNil ())
551
+ }
552
+ }
553
+ }
554
+
429
555
// assertMachineDeploymentTopologyFields asserts that all fields set in the MachineDeploymentTopology have been set on the MachineDeployment.
430
556
// Note: We intentionally focus on the fields set in the MachineDeploymentTopology and ignore the ones set through ClusterClass
431
557
// as we want to validate that the fields of the MachineDeploymentTopology have been propagated correctly.
@@ -464,6 +590,40 @@ func assertMachineDeploymentTopologyFields(g Gomega, md clusterv1.MachineDeploym
464
590
}
465
591
}
466
592
593
+ // assertMachinePoolTopologyFields asserts that all fields set in the MachinePoolTopology have been set on the MachinePool.
594
+ // Note: We intentionally focus on the fields set in the MachinePoolTopology and ignore the ones set through ClusterClass
595
+ // as we want to validate that the fields of the MachinePoolTopology have been propagated correctly.
596
+ func assertMachinePoolTopologyFields (g Gomega , mp expv1.MachinePool , mpTopology clusterv1.MachinePoolTopology ) {
597
+ // Note: We only verify that all labels and annotations from the Cluster topology exist to keep it simple here.
598
+ // This is fully covered by the ClusterClass rollout test.
599
+ for k , v := range mpTopology .Metadata .Labels {
600
+ g .Expect (mp .Labels ).To (HaveKeyWithValue (k , v ))
601
+ }
602
+ for k , v := range mpTopology .Metadata .Annotations {
603
+ g .Expect (mp .Annotations ).To (HaveKeyWithValue (k , v ))
604
+ }
605
+
606
+ if mpTopology .NodeDrainTimeout != nil {
607
+ g .Expect (mp .Spec .Template .Spec .NodeDrainTimeout ).To (Equal (mpTopology .NodeDrainTimeout ))
608
+ }
609
+
610
+ if mpTopology .NodeDeletionTimeout != nil {
611
+ g .Expect (mp .Spec .Template .Spec .NodeDeletionTimeout ).To (Equal (mpTopology .NodeDeletionTimeout ))
612
+ }
613
+
614
+ if mpTopology .NodeVolumeDetachTimeout != nil {
615
+ g .Expect (mp .Spec .Template .Spec .NodeVolumeDetachTimeout ).To (Equal (mpTopology .NodeVolumeDetachTimeout ))
616
+ }
617
+
618
+ if mpTopology .MinReadySeconds != nil {
619
+ g .Expect (mp .Spec .MinReadySeconds ).To (Equal (mpTopology .MinReadySeconds ))
620
+ }
621
+
622
+ if mpTopology .FailureDomains != nil && mp .Spec .Template .Spec .FailureDomain != nil {
623
+ g .Expect (mpTopology .FailureDomains ).To (ContainElement (mp .Spec .Template .Spec .FailureDomain ))
624
+ }
625
+ }
626
+
467
627
// rebaseClusterClassAndWaitInput is the input type for rebaseClusterClassAndWait.
468
628
type rebaseClusterClassAndWaitInput struct {
469
629
ClusterProxy framework.ClusterProxy
0 commit comments