@@ -29,9 +29,12 @@ 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"
@@ -113,7 +116,7 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
113
116
workerMachineCount = * input .WorkerMachineCount
114
117
}
115
118
116
- // Setup a Namespace where to host objects for this spec and create a watcher for the Namespace events.
119
+ // Set up a Namespace where to host objects for this spec and create a watcher for the Namespace events.
117
120
namespace , cancelWatches = setupSpecNamespace (ctx , specName , input .BootstrapClusterProxy , input .ArtifactFolder )
118
121
clusterResources = new (clusterctl.ApplyClusterTemplateAndWaitResult )
119
122
})
@@ -156,6 +159,12 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
156
159
ControlPlaneMachineCount : pointer .Int64Ptr (controlPlaneMachineCount ),
157
160
WorkerMachineCount : pointer .Int64Ptr (workerMachineCount ),
158
161
},
162
+ RuntimeHookTestHandlers : framework.RuntimeHookTestHandlers {
163
+ BeforeClusterCreate : func () {
164
+ beforeClusterCreateTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetIntervals (specName , "wait-cluster" ))
165
+ },
166
+ },
167
+
159
168
WaitForClusterIntervals : input .E2EConfig .GetIntervals (specName , "wait-cluster" ),
160
169
WaitForControlPlaneIntervals : input .E2EConfig .GetIntervals (specName , "wait-control-plane" ),
161
170
WaitForMachineDeployments : input .E2EConfig .GetIntervals (specName , "wait-worker-nodes" ),
@@ -176,6 +185,12 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
176
185
WaitForKubeProxyUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
177
186
WaitForDNSUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
178
187
WaitForEtcdUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
188
+ RuntimeHookTestHandlers : framework.RuntimeHookTestHandlers {
189
+ BeforeClusterUpgrade : func () {
190
+ beforeClusterUpgradeTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ), input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ))
191
+ afterControlPlaneUpgradeTestHandler (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ), input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ))
192
+ },
193
+ },
179
194
})
180
195
181
196
// Only attempt to upgrade MachinePools if they were provided in the template.
@@ -201,13 +216,13 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
201
216
})
202
217
203
218
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
219
+ // Assert that each hook has been called and returned "Success" during the test.
205
220
err = checkLifecycleHooks (ctx , input .BootstrapClusterProxy .GetClient (), namespace .Name , clusterName , map [string ]string {
206
- "BeforeClusterCreate" : "" ,
207
- "BeforeClusterUpgrade" : "" ,
208
- "AfterControlPlaneInitialized" : "" ,
209
- "AfterControlPlaneUpgrade" : "" ,
210
- "AfterClusterUpgrade" : "" ,
221
+ "BeforeClusterCreate" : "Success " ,
222
+ "BeforeClusterUpgrade" : "Success " ,
223
+ "AfterControlPlaneInitialized" : "Success " ,
224
+ "AfterControlPlaneUpgrade" : "Success " ,
225
+ "AfterClusterUpgrade" : "Success " ,
211
226
})
212
227
Expect (err ).ToNot (HaveOccurred (), "Lifecycle hook calls were not as expected" )
213
228
@@ -266,13 +281,16 @@ func responsesConfigMap(name string, namespace *corev1.Namespace) *corev1.Config
266
281
Name : fmt .Sprintf ("%s-hookresponses" , name ),
267
282
Namespace : namespace .Name ,
268
283
},
269
- // Every response contain only Status:Success. The test checks whether each handler has been called at least once .
284
+ // Non-blocking hooks are set to Status:Success initially .
270
285
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"}` ,
286
+ // Blocking hooks are set to Status:Failure initially. These will be changed during the test.
287
+ "BeforeClusterCreate-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
288
+ "BeforeClusterUpgrade-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
289
+ "AfterControlPlaneUpgrade-preloadedResponse" : `{"Status": "Failure", "Message": "hook failed"}` ,
290
+
291
+ // Non-blocking hooks are set to Status:Success.
292
+ "AfterControlPlaneInitialized-preloadedResponse" : `{"Status": "Success"}` ,
293
+ "AfterClusterUpgrade-preloadedResponse" : `{"Status": "Success"}` ,
276
294
},
277
295
}
278
296
}
@@ -282,10 +300,94 @@ func checkLifecycleHooks(ctx context.Context, c client.Client, namespace string,
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 != "Success" {
309
+ return errors .Errorf ("hook %s was expected to be show \" Success\" in configMap got %s" , hook , v )
310
+ }
289
311
}
290
312
return nil
291
313
}
314
+
315
+ func beforeClusterCreateTestHandler (ctx context.Context , c client.Client , namespace , clusterName string , intervals []interface {}) {
316
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "BeforeClusterCreate" , func () bool {
317
+ // This hook should block the Cluster from entering the "Provisioned" state.
318
+ cluster := & clusterv1.Cluster {}
319
+ Expect (c .Get (ctx , client.ObjectKey {Namespace : namespace , Name : clusterName }, cluster )).To (Succeed ())
320
+ return cluster .Status .Phase == string (clusterv1 .ClusterPhaseProvisioned )
321
+ }, intervals )
322
+ }
323
+
324
+ func beforeClusterUpgradeTestHandler (ctx context.Context , c client.Client , namespace , clusterName , version string , intervals []interface {}) {
325
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "BeforeClusterUpgrade" , func () bool {
326
+ cluster := & clusterv1.Cluster {}
327
+ var unblocked bool
328
+
329
+ // First ensure the Cluster topology has been updated to the target Kubernetes Version.
330
+ Eventually (func () bool {
331
+ Expect (c .Get (ctx , client.ObjectKey {Namespace : namespace , Name : clusterName }, cluster )).To (Succeed ())
332
+ return cluster .Spec .Topology .Version == version
333
+ }).Should (BeTrue (), "BeforeClusterUpgrade blocking condition false: Cluster topology has not been updated to the target Kubernetes Version" )
334
+
335
+ // Check if the Cluster is showing the RollingUpdateInProgress condition reason. If it has the process is unblocked.
336
+ for _ , condition := range cluster .GetConditions () {
337
+ if condition .Type == clusterv1 .ReadyCondition {
338
+ if condition .Status == corev1 .ConditionFalse && condition .Reason == controlplanev1 .RollingUpdateInProgressReason {
339
+ unblocked = true
340
+ }
341
+ }
342
+ }
343
+ return unblocked
344
+ }, intervals )
345
+ }
346
+
347
+ func afterControlPlaneUpgradeTestHandler (ctx context.Context , c client.Client , namespace , clusterName , version string , intervals []interface {}) {
348
+ runtimeHookTestHandler (ctx , c , namespace , clusterName , "AfterControlPlaneUpgrade" , func () bool {
349
+ var unblocked bool
350
+ mds := & clusterv1.MachineDeploymentList {}
351
+ Expect (c .List (ctx , mds , client.MatchingLabels {
352
+ clusterv1 .ClusterLabelName : clusterName ,
353
+ clusterv1 .ClusterTopologyOwnedLabel : "" ,
354
+ })).To (Succeed ())
355
+
356
+ // If any of the MachineDeployments have the target Kubernetes Version, the hook is unblocked.
357
+ for _ , md := range mds .Items {
358
+ if * md .Spec .Template .Spec .Version == version {
359
+ unblocked = true
360
+ }
361
+ }
362
+ return unblocked
363
+ }, intervals )
364
+ }
365
+
366
+ func runtimeHookTestHandler (ctx context.Context , c client.Client , namespace , clusterName , hookName string , blockingCondition func () bool , intervals []interface {}) {
367
+ // First check that the LifecycleHook has been called at least once.
368
+ Eventually (func () bool {
369
+ err := checkLifecycleHooks (ctx , c , namespace , clusterName , map [string ]string {hookName : "" })
370
+ return err != nil
371
+ }, 10 * time .Second ).Should (BeTrue (), "%s has not been called" , hookName )
372
+
373
+ // Blocking condition should consistently be false as the Runtime hook is returning "Failure".
374
+ Consistently (func () bool {
375
+ return blockingCondition ()
376
+ }, 10 * time .Second ).Should (BeFalse (), fmt .Sprintf ("%s hook blocking condition succeeded before unblocking" , hookName ))
377
+
378
+ // Patch the ConfigMap to set the hook response to "Success".
379
+ By (fmt .Sprintf ("Setting %s response to Status:Success" , hookName ))
380
+
381
+ configMap := & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Name : clusterName + "-hookresponses" , Namespace : namespace }}
382
+ Expect (c .Get (ctx , util .ObjectKey (configMap ), configMap )).To (Succeed ())
383
+ patch := client .RawPatch (types .MergePatchType ,
384
+ []byte (fmt .Sprintf (`{"data":{"%s-preloadedResponse":%s}}` , hookName , "\" {\\ \" Status\\ \" : \\ \" Success\\ \" }\" " )))
385
+ err := c .Patch (ctx , configMap , patch )
386
+ Expect (err ).ToNot (HaveOccurred ())
387
+
388
+ // Expect the Hook to pass, setting the blocking condition to true before the timeout ends
389
+ Eventually (func () bool {
390
+ return blockingCondition ()
391
+ }, intervals ... ).Should (BeTrue (),
392
+ fmt .Sprintf ("%s hook blocking condition did not succeed after unblocking" , hookName ))
393
+ }
0 commit comments