@@ -42,6 +42,7 @@ import (
4242 "sigs.k8s.io/cluster-api/test/framework/clusterctl"
4343 "sigs.k8s.io/cluster-api/util"
4444 "sigs.k8s.io/cluster-api/util/conditions"
45+ "sigs.k8s.io/cluster-api/util/patch"
4546)
4647
4748// The Cluster API test extension uses a ConfigMap named cluster-name + suffix to determine answers to the lifecycle hook calls;
@@ -221,6 +222,10 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
221222 input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ))
222223 },
223224 PreWaitForMachineDeploymentToBeUpgraded : func () {
225+ machineSetPreflightChecksTestHandler (ctx ,
226+ input .BootstrapClusterProxy .GetClient (),
227+ clusterRef )
228+
224229 afterControlPlaneUpgradeTestHandler (ctx ,
225230 input .BootstrapClusterProxy .GetClient (),
226231 clusterRef ,
@@ -281,6 +286,104 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
281286 })
282287}
283288
289+ // machineSetPreflightChecksTestHandler verifies the MachineSet preflight checks.
290+ // At this point in the test the ControlPlane is upgraded to the new version and the upgrade to the MachineDeployments
291+ // should be blocked by the AfterControlPlaneUpgrade hook.
292+ // Test the MachineSet preflight checks by scaling up the MachineDeployment. The creation on the new Machine
293+ // should be blocked because the preflight checks should not pass (kubeadm version skew preflight check should fail).
294+ func machineSetPreflightChecksTestHandler (ctx context.Context , c client.Client , clusterRef types.NamespacedName ) {
295+ // Verify that the hook is called and the topology reconciliation is blocked.
296+ hookName := "AfterControlPlaneUpgrade"
297+ Eventually (func () error {
298+ if err := checkLifecycleHooksCalledAtLeastOnce (ctx , c , clusterRef , []string {hookName }); err != nil {
299+ return err
300+ }
301+
302+ cluster := framework .GetClusterByName (ctx , framework.GetClusterByNameInput {
303+ Name : clusterRef .Name , Namespace : clusterRef .Namespace , Getter : c })
304+
305+ if ! clusterConditionShowsHookBlocking (cluster , hookName ) {
306+ return errors .Errorf ("Blocking condition for %s not found on Cluster object" , hookName )
307+ }
308+
309+ return nil
310+ }, 30 * time .Second ).Should (Succeed (), "%s has not been called" , hookName )
311+
312+ // Scale up the MachineDeployment
313+ machineDeployments := framework .GetMachineDeploymentsByCluster (ctx , framework.GetMachineDeploymentsByClusterInput {
314+ Lister : c ,
315+ ClusterName : clusterRef .Name ,
316+ Namespace : clusterRef .Namespace ,
317+ })
318+ md := machineDeployments [0 ]
319+
320+ // Note: It is fair to assume that the Cluster is ClusterClass based since RuntimeSDK
321+ // is only supported for ClusterClass based Clusters.
322+ patchHelper , err := patch .NewHelper (md , c )
323+ Expect (err ).To (BeNil ())
324+
325+ // Scale up the MachineDeployment.
326+ // IMPORTANT: Since the MachineDeployment is pending an upgrade at this point the topology controller will not push any changes
327+ // to the MachineDeployment. Therefore, the changes made to the MachineDeployment here will not be replaced
328+ // until the AfterControlPlaneUpgrade hook unblocks the upgrade.
329+ * md .Spec .Replicas ++
330+ Eventually (func () error {
331+ return patchHelper .Patch (ctx , md )
332+ }).Should (Succeed (), "Failed to scale up the MachineDeployment %s" , klog .KObj (md ))
333+ // Verify the MachineDeployment updated replicas are not overridden by the topology controller.
334+ // Note: This verifies that the topology controller in fact holds any reconciliation of this MachineDeployment.
335+ Consistently (func (g Gomega ) {
336+ // Get the updated MachineDeployment.
337+ targetMD := & clusterv1.MachineDeployment {}
338+ // Wrap in an Eventually block for additional safety. Since all of this is in a Consistently block it
339+ // will fail if we hit a transient error like a network flake.
340+ g .Eventually (func () error {
341+ return c .Get (ctx , client .ObjectKeyFromObject (md ), targetMD )
342+ }).Should (Succeed (), "Failed to get MachineDeployment %s" , klog .KObj (md ))
343+ // Verify replicas are not overridden.
344+ g .Expect (targetMD .Spec .Replicas ).To (Equal (md .Spec .Replicas ))
345+ }, 10 * time .Second , 1 * time .Second )
346+
347+ // Since the MachineDeployment is scaled up (overriding the topology controller) at this point the MachineSet would
348+ // also scale up. However, a new Machine creation would be blocked by one of the MachineSet preflight checks (KubeadmVersionSkew).
349+ // Verify the MachineSet is blocking new Machine creation.
350+ Eventually (func (g Gomega ) {
351+ machineSets := framework .GetMachineSetsByDeployment (ctx , framework.GetMachineSetsByDeploymentInput {
352+ Lister : c ,
353+ MDName : md .Name ,
354+ Namespace : md .Namespace ,
355+ })
356+ g .Expect (conditions .IsFalse (machineSets [0 ], clusterv1 .MachinesCreatedCondition )).To (BeTrue ())
357+ machinesCreatedCondition := conditions .Get (machineSets [0 ], clusterv1 .MachinesCreatedCondition )
358+ g .Expect (machinesCreatedCondition ).NotTo (BeNil ())
359+ g .Expect (machinesCreatedCondition .Reason ).To (Equal (clusterv1 .PreflightCheckFailedReason ))
360+ g .Expect (machineSets [0 ].Spec .Replicas ).To (Equal (md .Spec .Replicas ))
361+ }).Should (Succeed (), "New Machine creation not blocked by MachineSet preflight checks" )
362+
363+ // Verify that the MachineSet is not creating the new Machine.
364+ // No new machines should be created for this MachineDeployment even though it is scaled up.
365+ // Creation of new Machines will be blocked by MachineSet preflight checks (KubeadmVersionSkew).
366+ Consistently (func (g Gomega ) {
367+ originalReplicas := int (* md .Spec .Replicas - 1 )
368+ machines := framework .GetMachinesByMachineDeployments (ctx , framework.GetMachinesByMachineDeploymentsInput {
369+ Lister : c ,
370+ ClusterName : clusterRef .Name ,
371+ Namespace : clusterRef .Namespace ,
372+ MachineDeployment : * md ,
373+ })
374+ g .Expect (machines ).To (HaveLen (originalReplicas ), "New Machines should not be created" )
375+ }, 10 * time .Second , time .Second )
376+
377+ // Scale down the MachineDeployment to the original replicas to restore to the state of the MachineDeployment
378+ // it existed in before this test block.
379+ patchHelper , err = patch .NewHelper (md , c )
380+ Expect (err ).To (BeNil ())
381+ * md .Spec .Replicas --
382+ Eventually (func () error {
383+ return patchHelper .Patch (ctx , md )
384+ }).Should (Succeed (), "Failed to scale down the MachineDeployment %s" , klog .KObj (md ))
385+ }
386+
284387// extensionConfig generates an ExtensionConfig.
285388// We make sure this cluster-wide object does not conflict with others by using a random generated
286389// name and a NamespaceSelector selecting on the namespace of the current test.
0 commit comments