Skip to content

Commit f9c7e14

Browse files
Add BeforeClusterDelete to runtimeSDK e2e tests
Signed-off-by: killianmuldoon <[email protected]>
1 parent 82d823b commit f9c7e14

File tree

3 files changed

+92
-11
lines changed

3 files changed

+92
-11
lines changed

test/e2e/cluster_upgrade_runtimesdk.go

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
. "github.com/onsi/gomega"
2929
"github.com/pkg/errors"
3030
corev1 "k8s.io/api/core/v1"
31+
apierrors "k8s.io/apimachinery/pkg/api/errors"
3132
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
"k8s.io/apimachinery/pkg/types"
3334
"k8s.io/utils/pointer"
@@ -125,7 +126,7 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
125126
clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult)
126127
})
127128

128-
It("Should create and upgrade a workload cluster", func() {
129+
It("Should create, upgrade and delete a workload cluster", func() {
129130
clusterName := fmt.Sprintf("%s-%s", specName, util.RandomString(6))
130131
By("Deploy Test Extension")
131132
testExtensionDeploymentTemplate, err := os.ReadFile(testExtensionPath) //nolint:gosec
@@ -228,11 +229,17 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
228229
WaitForNodesReady: input.E2EConfig.GetIntervals(specName, "wait-nodes-ready"),
229230
})
230231

232+
By("Backing up and deleting the workload cluster")
233+
backupAndDeleteCluster(ctx, input.BootstrapClusterProxy, namespace.Name, clusterName, input.ArtifactFolder)
234+
235+
beforeClusterDeleteHandler(ctx, input.BootstrapClusterProxy.GetClient(), namespace.Name, clusterName, input.E2EConfig.GetIntervals(specName, "wait-delete-cluster"))
236+
231237
By("Checking all lifecycle hooks have been called")
232238
// Assert that each hook has been called and returned "Success" during the test.
233239
err = checkLifecycleHookResponses(ctx, input.BootstrapClusterProxy.GetClient(), namespace.Name, clusterName, map[string]string{
234240
"BeforeClusterCreate": "Success",
235241
"BeforeClusterUpgrade": "Success",
242+
"BeforeClusterDelete": "Success",
236243
"AfterControlPlaneInitialized": "Success",
237244
"AfterControlPlaneUpgrade": "Success",
238245
"AfterClusterUpgrade": "Success",
@@ -300,6 +307,7 @@ func responsesConfigMap(name string, namespace *corev1.Namespace) *corev1.Config
300307
"BeforeClusterCreate-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
301308
"BeforeClusterUpgrade-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
302309
"AfterControlPlaneUpgrade-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
310+
"BeforeClusterDelete-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
303311

304312
// Non-blocking hooks are set to Status:Success.
305313
"AfterControlPlaneInitialized-preloadedResponse": `{"Status": "Success"}`,
@@ -347,9 +355,8 @@ func getLifecycleHookResponsesFromConfigMap(ctx context.Context, c client.Client
347355
// beforeClusterCreateTestHandler calls runtimeHookTestHandler with a blockedCondition function which returns false if
348356
// the Cluster has entered ClusterPhaseProvisioned.
349357
func beforeClusterCreateTestHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
350-
log.Logf("Blocking with BeforeClusterCreate hook")
351358
hookName := "BeforeClusterCreate"
352-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
359+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
353360
blocked := true
354361
// This hook should block the Cluster from entering the "Provisioned" state.
355362
cluster := &clusterv1.Cluster{}
@@ -371,9 +378,8 @@ func beforeClusterCreateTestHandler(ctx context.Context, c client.Client, namesp
371378
// beforeClusterUpgradeTestHandler calls runtimeHookTestHandler with a blocking function which returns false if the
372379
// Cluster has controlplanev1.RollingUpdateInProgressReason in its ReadyCondition.
373380
func beforeClusterUpgradeTestHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
374-
log.Logf("Blocking with BeforeClusterUpgrade hook")
375381
hookName := "BeforeClusterUpgrade"
376-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
382+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
377383
var blocked = true
378384

379385
cluster := &clusterv1.Cluster{}
@@ -397,9 +403,8 @@ func beforeClusterUpgradeTestHandler(ctx context.Context, c client.Client, names
397403
// afterControlPlaneUpgradeTestHandler calls runtimeHookTestHandler with a blocking function which returns false if any
398404
// MachineDeployment in the Cluster has upgraded to the target Kubernetes version.
399405
func afterControlPlaneUpgradeTestHandler(ctx context.Context, c client.Client, namespace, clusterName, version string, intervals []interface{}) {
400-
log.Logf("Blocking with AfterControlPlaneUpgrade hook")
401406
hookName := "AfterControlPlaneUpgrade"
402-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
407+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
403408
var blocked = true
404409
cluster := &clusterv1.Cluster{}
405410
Eventually(func() error {
@@ -429,15 +434,32 @@ func afterControlPlaneUpgradeTestHandler(ctx context.Context, c client.Client, n
429434
}, intervals)
430435
}
431436

437+
// beforeClusterDeleteHandler calls runtimeHookTestHandler with a blocking function which returns false if any of the
438+
// MachineDeployments associated with the Cluster are in a deleting state.
439+
func beforeClusterDeleteHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
440+
hookName := "BeforeClusterDelete"
441+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, false, func() bool {
442+
var blocked = true
443+
444+
// If the Cluster is not found it has been deleted and the hook is unblocked.
445+
if apierrors.IsNotFound(c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, &clusterv1.Cluster{})) {
446+
blocked = false
447+
}
448+
return blocked
449+
}, intervals)
450+
}
451+
432452
// runtimeHookTestHandler runs a series of tests in sequence to check if the runtimeHook passed to it succeeds.
433-
// 1) Checks that the hook has been called at least once the TopologyReconciled condition is a Failure.
453+
// 1) Checks that the hook has been called at least once, and if set by withTopologyReconciledCondition, that the TopologyReconciled condition is a Failure.
434454
// 2) Check that the hook's blockingCondition is consistently true.
435455
// - At this point the function sets the hook's response to be non-blocking.
436456
// 3) Check that the hook's blocking condition becomes false.
437457
// Note: runtimeHookTestHandler assumes that the hook passed to it is currently returning a blocking response.
438458
// Updating the response to be non-blocking happens inline in the function.
439-
func runtimeHookTestHandler(ctx context.Context, c client.Client, namespace, clusterName, hookName string, blockingCondition func() bool, intervals []interface{}) {
440-
// Check that the LifecycleHook has been called at least once and the TopologyReconciled condition is a Failure.
459+
func runtimeHookTestHandler(ctx context.Context, c client.Client, namespace, clusterName, hookName string, withTopologyReconciledCondition bool, blockingCondition func() bool, intervals []interface{}) {
460+
log.Logf("Blocking with %s hook", hookName)
461+
462+
// Check that the LifecycleHook has been called at least once and - when required - that the TopologyReconciled condition is a Failure.
441463
Eventually(func() error {
442464
if err := checkLifecycleHooksCalledAtLeastOnce(ctx, c, namespace, clusterName, []string{hookName}); err != nil {
443465
return err
@@ -446,7 +468,10 @@ func runtimeHookTestHandler(ctx context.Context, c client.Client, namespace, clu
446468
if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, cluster); err != nil {
447469
return err
448470
}
449-
if !(conditions.GetReason(cluster, clusterv1.TopologyReconciledCondition) == clusterv1.TopologyReconcileFailedReason) {
471+
472+
// Check for the existence of the condition if withTopologyReconciledCondition is true.
473+
if withTopologyReconciledCondition &&
474+
(conditions.GetReason(cluster, clusterv1.TopologyReconciledCondition) != clusterv1.TopologyReconcileFailedReason) {
450475
return errors.New("Condition not found on Cluster object")
451476
}
452477
return nil
@@ -484,3 +509,28 @@ func clusterConditionShowsHookFailed(cluster *clusterv1.Cluster, hookName string
484509
strings.Contains(conditions.GetMessage(cluster, clusterv1.TopologyReconciledCondition), hookFailedMessage) &&
485510
strings.Contains(conditions.GetMessage(cluster, clusterv1.TopologyReconciledCondition), hookName)
486511
}
512+
513+
func backupAndDeleteCluster(ctx context.Context, proxy framework.ClusterProxy, namespace, clusterName, artifactFolder string) {
514+
By("Deleting the workload cluster")
515+
cluster := &clusterv1.Cluster{}
516+
Eventually(func() error {
517+
return proxy.GetClient().Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, cluster)
518+
}).Should(Succeed())
519+
520+
// Dump all the logs from the workload cluster before deleting them.
521+
proxy.CollectWorkloadClusterLogs(ctx, cluster.Namespace, cluster.Name, filepath.Join(artifactFolder, "clusters-beforeClusterDelete", cluster.Name))
522+
523+
// Dump all Cluster API related resources to artifacts before deleting them.
524+
framework.DumpAllResources(ctx, framework.DumpAllResourcesInput{
525+
Lister: proxy.GetClient(),
526+
Namespace: namespace,
527+
LogPath: filepath.Join(artifactFolder, "clusters-beforeClusterDelete", proxy.GetName(), "resources"),
528+
})
529+
530+
By("Deleting the workload cluster")
531+
// Delete the workload cluster and call the before ClusterDeleteTestHandler.
532+
framework.DeleteCluster(ctx, framework.DeleteClusterInput{
533+
Deleter: proxy.GetClient(),
534+
Cluster: cluster,
535+
})
536+
}

test/extension/handlers/lifecycle/handlers.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ func (h *Handler) DoAfterClusterUpgrade(ctx context.Context, request *runtimehoo
126126
}
127127
}
128128

129+
// DoBeforeClusterDelete implements the BeforeClusterDelete hook.
130+
func (h *Handler) DoBeforeClusterDelete(ctx context.Context, request *runtimehooksv1.BeforeClusterDeleteRequest, response *runtimehooksv1.BeforeClusterDeleteResponse) {
131+
log := ctrl.LoggerFrom(ctx)
132+
log.Info("BeforeClusterDelete is called")
133+
cluster := request.Cluster
134+
135+
if err := h.readResponseFromConfigMap(ctx, cluster.Name, cluster.Namespace, runtimehooksv1.BeforeClusterDelete, response); err != nil {
136+
response.Status = runtimehooksv1.ResponseStatusFailure
137+
response.Message = err.Error()
138+
return
139+
}
140+
if err := h.recordCallInConfigMap(ctx, cluster.Name, cluster.Namespace, runtimehooksv1.BeforeClusterDelete, response); err != nil {
141+
response.Status = runtimehooksv1.ResponseStatusFailure
142+
response.Message = err.Error()
143+
}
144+
}
145+
129146
func (h *Handler) readResponseFromConfigMap(ctx context.Context, name, namespace string, hook runtimecatalog.Hook, response runtimehooksv1.ResponseObject) error {
130147
hookName := runtimecatalog.HookName(hook)
131148
configMap := &corev1.ConfigMap{}

test/extension/main.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ func main() {
155155
setupLog.Error(err, "error adding handler")
156156
os.Exit(1)
157157
}
158+
158159
if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{
159160
Hook: runtimehooksv1.AfterControlPlaneInitialized,
160161
Name: "after-control-plane-initialized",
@@ -165,6 +166,7 @@ func main() {
165166
setupLog.Error(err, "error adding handler")
166167
os.Exit(1)
167168
}
169+
168170
if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{
169171
Hook: runtimehooksv1.BeforeClusterUpgrade,
170172
Name: "before-cluster-upgrade",
@@ -186,6 +188,7 @@ func main() {
186188
setupLog.Error(err, "error adding handler")
187189
os.Exit(1)
188190
}
191+
189192
if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{
190193
Hook: runtimehooksv1.AfterClusterUpgrade,
191194
Name: "after-cluster-upgrade",
@@ -197,6 +200,17 @@ func main() {
197200
os.Exit(1)
198201
}
199202

203+
if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{
204+
Hook: runtimehooksv1.BeforeClusterDelete,
205+
Name: "before-cluster-delete",
206+
HandlerFunc: lifecycleHandler.DoBeforeClusterDelete,
207+
TimeoutSeconds: pointer.Int32(5),
208+
FailurePolicy: toPtr(runtimehooksv1.FailurePolicyFail),
209+
}); err != nil {
210+
setupLog.Error(err, "error adding handler")
211+
os.Exit(1)
212+
}
213+
200214
setupLog.Info("starting RuntimeExtension", "version", version.Get().String())
201215
if err := webhookServer.Start(ctx); err != nil {
202216
setupLog.Error(err, "error running webhook server")

0 commit comments

Comments
 (0)