@@ -29,13 +29,17 @@ import (
29
29
"github.com/pkg/errors"
30
30
corev1 "k8s.io/api/core/v1"
31
31
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32
+ "k8s.io/apimachinery/pkg/types"
32
33
"k8s.io/utils/pointer"
33
34
"sigs.k8s.io/controller-runtime/pkg/client"
34
35
36
+ clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
37
+ controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
35
38
runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
36
39
"sigs.k8s.io/cluster-api/test/framework"
37
40
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
38
41
"sigs.k8s.io/cluster-api/util"
42
+ "sigs.k8s.io/cluster-api/util/conditions"
39
43
)
40
44
41
45
// clusterUpgradeWithRuntimeSDKSpecInput is the input for clusterUpgradeWithRuntimeSDKSpec.
@@ -113,7 +117,7 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
113
117
workerMachineCount = * input .WorkerMachineCount
114
118
}
115
119
116
- // Setup a Namespace where to host objects for this spec and create a watcher for the Namespace events.
120
+ // Set up a Namespace where to host objects for this spec and create a watcher for the Namespace events.
117
121
namespace , cancelWatches = setupSpecNamespace (ctx , specName , input .BootstrapClusterProxy , input .ArtifactFolder )
118
122
clusterResources = new (clusterctl.ApplyClusterTemplateAndWaitResult )
119
123
})
@@ -156,6 +160,9 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
156
160
ControlPlaneMachineCount : pointer .Int64Ptr (controlPlaneMachineCount ),
157
161
WorkerMachineCount : pointer .Int64Ptr (workerMachineCount ),
158
162
},
163
+ PreWaitForCluster : func () {
164
+ beforeClusterCreateTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetIntervals (specName , "wait-cluster" ))
165
+ },
159
166
WaitForClusterIntervals : input .E2EConfig .GetIntervals (specName , "wait-cluster" ),
160
167
WaitForControlPlaneIntervals : input .E2EConfig .GetIntervals (specName , "wait-control-plane" ),
161
168
WaitForMachineDeployments : input .E2EConfig .GetIntervals (specName , "wait-worker-nodes" ),
@@ -176,6 +183,12 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
176
183
WaitForKubeProxyUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
177
184
WaitForDNSUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
178
185
WaitForEtcdUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
186
+ PreWaitForControlPlaneToBeUpgraded : func () {
187
+ beforeClusterUpgradeTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ), input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ))
188
+ },
189
+ PreWaitForMachineDeploymentToBeUpgraded : func () {
190
+ afterControlPlaneUpgradeTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ), input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ))
191
+ },
179
192
})
180
193
181
194
// Only attempt to upgrade MachinePools if they were provided in the template.
@@ -201,13 +214,13 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
201
214
})
202
215
203
216
By ("Checking all lifecycle hooks have been called" )
204
- // Assert that each hook passed to this function is marked as "true" in the response configmap
217
+ // Assert that each hook has been called and returned "Success" during the test.
205
218
err = checkLifecycleHooks (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , map [string ]string {
206
- "BeforeClusterCreate" : "" ,
207
- "BeforeClusterUpgrade" : "" ,
208
- "AfterControlPlaneInitialized" : "" ,
209
- "AfterControlPlaneUpgrade" : "" ,
210
- "AfterClusterUpgrade" : "" ,
219
+ "BeforeClusterCreate" : "Success " ,
220
+ "BeforeClusterUpgrade" : "Success " ,
221
+ "AfterControlPlaneInitialized" : "Success " ,
222
+ "AfterControlPlaneUpgrade" : "Success " ,
223
+ "AfterClusterUpgrade" : "Success " ,
211
224
})
212
225
Expect (err ).ToNot (HaveOccurred (), "Lifecycle hook calls were not as expected" )
213
226
@@ -266,26 +279,117 @@ func responsesConfigMap(name string, namespace *corev1.Namespace) *corev1.Config
266
279
Name : fmt .Sprintf ("%s-hookresponses" , name ),
267
280
Namespace : namespace .Name ,
268
281
},
269
- // Every response contain only Status:Success. The test checks whether each handler has been called at least once .
282
+ // Set the initial preloadedResponses for each of the tested hooks .
270
283
Data : map [string ]string {
271
- "BeforeClusterCreate-response" : `{"Status": "Success"}` ,
272
- "BeforeClusterUpgrade-response" : `{"Status": "Success"}` ,
273
- "AfterControlPlaneInitialized-response" : `{"Status": "Success"}` ,
274
- "AfterControlPlaneUpgrade-response" : `{"Status": "Success"}` ,
275
- "AfterClusterUpgrade-response" : `{"Status": "Success"}` ,
284
+ // Blocking hooks are set to Status:Failure initially. These will be changed during the test.
285
+ "BeforeClusterCreate-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
286
+ "BeforeClusterUpgrade-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
287
+ "AfterControlPlaneUpgrade-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
288
+
289
+ // Non-blocking hooks are set to Status:Success.
290
+ "AfterControlPlaneInitialized-preloadedResponse" : `{"Status": "Success"}` ,
291
+ "AfterClusterUpgrade-preloadedResponse" : `{"Status": "Success"}` ,
276
292
},
277
293
}
278
294
}
279
295
296
+ // Check that each hook in hooks has been called at least once by checking if its actualResponseStatus is in the hook response configmap.
297
+ // If the provided hooks have both keys and values check that the values match those in the hook response configmap.
280
298
func checkLifecycleHooks (ctx context.Context , c client.Client , namespace string , clusterName string , hooks map [string ]string ) error {
281
299
configMap := & corev1.ConfigMap {}
282
300
configMapName := clusterName + "-hookresponses"
283
301
err := c .Get (ctx , client.ObjectKey {Namespace : namespace , Name : configMapName }, configMap )
284
302
Expect (err ).ToNot (HaveOccurred (), "Failed to get the hook response configmap" )
285
- for hook := range hooks {
286
- if _ , ok := configMap .Data [hook + "-called" ]; ! ok {
303
+ for hook , expected := range hooks {
304
+ v , ok := configMap .Data [hook + "-actualResponseStatus" ]
305
+ if ! ok {
287
306
return errors .Errorf ("hook %s call not recorded in configMap %s/%s" , hook , namespace , configMapName )
288
307
}
308
+ if expected != "" && expected != v {
309
+ return errors .Errorf ("hook %s was expected to be %s in configMap got %s" , expected , hook , v )
310
+ }
289
311
}
290
312
return nil
291
313
}
314
+
315
+ // beforeClusterCreateTestHandler provides an unblocked function which returns true if the Cluster has entered ClusterPhaseProvisioned.
316
+ func beforeClusterCreateTestHandler (ctx context.Context , c client.Client , namespace , clusterName string , intervals []interface {}) {
317
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "BeforeClusterCreate" , func () bool {
318
+ // This hook should block the Cluster from entering the "Provisioned" state.
319
+ cluster := & clusterv1.Cluster {}
320
+ Expect (c .Get (ctx , client.ObjectKey {Namespace : namespace , Name : clusterName }, cluster )).To (Succeed ())
321
+ return cluster .Status .Phase == string (clusterv1 .ClusterPhaseProvisioned )
322
+ }, intervals )
323
+ }
324
+
325
+ // beforeClusterUpgradeTestHandler provides an unblocked function which returns true if the Cluster has controlplanev1.RollingUpdateInProgressReason in its
326
+ // ReadyCondition.
327
+ func beforeClusterUpgradeTestHandler (ctx context.Context , c client.Client , namespace , clusterName , version string , intervals []interface {}) {
328
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "BeforeClusterUpgrade" , func () bool {
329
+ cluster := & clusterv1.Cluster {}
330
+ var unblocked bool
331
+
332
+ // First ensure the Cluster topology has been updated to the target Kubernetes Version.
333
+ Eventually (func () bool {
334
+ Expect (c .Get (ctx , client.ObjectKey {Namespace : namespace , Name : clusterName }, cluster )).To (Succeed ())
335
+ return cluster .Spec .Topology .Version == version
336
+ }).Should (BeTrue (), "BeforeClusterUpgrade blocking condition false: Cluster topology has not been updated to the target Kubernetes Version" )
337
+
338
+ // Check if the Cluster is showing the RollingUpdateInProgress condition reason. If it has the update process is unblocked.
339
+ if conditions .IsFalse (cluster , clusterv1 .ReadyCondition ) &&
340
+ conditions .GetReason (cluster , clusterv1 .ReadyCondition ) == controlplanev1 .RollingUpdateInProgressReason {
341
+ unblocked = true
342
+ }
343
+ return unblocked
344
+ }, intervals )
345
+ }
346
+
347
+ // afterControlPlaneUpgradeTestHandler provides an unblocked function which returns true if any MachineDeployment in the Cluster
348
+ // has upgraded to the target Kubernetes version.
349
+ func afterControlPlaneUpgradeTestHandler (ctx context.Context , c client.Client , namespace , clusterName , version string , intervals []interface {}) {
350
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "AfterControlPlaneUpgrade" , func () bool {
351
+ var unblocked bool
352
+ mds := & clusterv1.MachineDeploymentList {}
353
+ Expect (c .List (ctx , mds , client.MatchingLabels {
354
+ clusterv1 .ClusterLabelName : clusterName ,
355
+ clusterv1 .ClusterTopologyOwnedLabel : "" ,
356
+ })).To (Succeed ())
357
+
358
+ // If any of the MachineDeployments have the target Kubernetes Version, the hook is unblocked.
359
+ for _ , md := range mds .Items {
360
+ if * md .Spec .Template .Spec .Version == version {
361
+ unblocked = true
362
+ }
363
+ }
364
+ return unblocked
365
+ }, intervals )
366
+ }
367
+
368
+ func runtimeHookTestHandler (ctx context.Context , c client.Client , namespace , clusterName , hookName string , condition func () bool , intervals []interface {}) {
369
+ // First check that the LifecycleHook has been called at least once.
370
+ Eventually (func () bool {
371
+ err := checkLifecycleHooks (ctx , c , namespace , clusterName , map [string ]string {hookName : "" })
372
+ return err != nil
373
+ }, intervals ... ).Should (BeTrue (), "%s has not been called" , hookName )
374
+
375
+ // condition should consistently be false as the Runtime hook is returning "Failure".
376
+ Consistently (func () bool {
377
+ return condition ()
378
+ }, intervals ... ).Should (BeFalse (), fmt .Sprintf ("%s hook blocking condition succeeded before unblocking" , hookName ))
379
+
380
+ // Patch the ConfigMap to set the hook response to "Success".
381
+ By (fmt .Sprintf ("Setting %s response to Status:Success" , hookName ))
382
+
383
+ configMap := & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Name : clusterName + "-hookresponses" , Namespace : namespace }}
384
+ Expect (c .Get (ctx , util .ObjectKey (configMap ), configMap )).To (Succeed ())
385
+ patch := client .RawPatch (types .MergePatchType ,
386
+ []byte (fmt .Sprintf (`{"data":{"%s-preloadedResponse":%s}}` , hookName , "\" {\\ \" Status\\ \" : \\ \" Success\\ \" }\" " )))
387
+ err := c .Patch (ctx , configMap , patch )
388
+ Expect (err ).ToNot (HaveOccurred ())
389
+
390
+ // Expect the Hook to pass, setting the condition to true before the timeout ends.
391
+ Eventually (func () bool {
392
+ return condition ()
393
+ }, intervals ... ).Should (BeTrue (),
394
+ fmt .Sprintf ("%s hook blocking condition did not succeed after unblocking" , hookName ))
395
+ }
0 commit comments