Skip to content

Commit 6eac2cc

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

File tree

3 files changed

+97
-14
lines changed

3 files changed

+97
-14
lines changed

test/e2e/cluster_upgrade_runtimesdk.go

Lines changed: 66 additions & 14 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,18 +229,36 @@ func clusterUpgradeWithRuntimeSDKSpec(ctx context.Context, inputGetter func() cl
228229
WaitForNodesReady: input.E2EConfig.GetIntervals(specName, "wait-nodes-ready"),
229230
})
230231

232+
Expect(err).ToNot(HaveOccurred(), "Lifecycle hook calls were not as expected")
233+
234+
By("Deleting the workload cluster")
235+
cluster := &clusterv1.Cluster{}
236+
Eventually(func() error {
237+
return input.BootstrapClusterProxy.GetClient().Get(ctx, client.ObjectKey{Namespace: namespace.Name, Name: clusterName}, cluster)
238+
}).Should(Succeed())
239+
240+
// Dump all the logs from the workload cluster before deleting them.
241+
input.BootstrapClusterProxy.CollectWorkloadClusterLogs(ctx, cluster.Namespace, cluster.Name, filepath.Join(input.ArtifactFolder, "clusters", cluster.Name))
242+
243+
framework.DeleteCluster(ctx, framework.DeleteClusterInput{
244+
Deleter: input.BootstrapClusterProxy.GetClient(),
245+
Cluster: cluster,
246+
})
247+
248+
beforeClusterDeleteHandler(ctx, input.BootstrapClusterProxy.GetClient(), namespace.Name, clusterName, input.E2EConfig.GetIntervals(specName, "wait-machine-upgrade"))
249+
231250
By("Checking all lifecycle hooks have been called")
232251
// Assert that each hook has been called and returned "Success" during the test.
233252
err = checkLifecycleHookResponses(ctx, input.BootstrapClusterProxy.GetClient(), namespace.Name, clusterName, map[string]string{
234253
"BeforeClusterCreate": "Success",
235254
"BeforeClusterUpgrade": "Success",
255+
"BeforeClusterDelete": "Success",
236256
"AfterControlPlaneInitialized": "Success",
237257
"AfterControlPlaneUpgrade": "Success",
238258
"AfterClusterUpgrade": "Success",
239259
})
240-
Expect(err).ToNot(HaveOccurred(), "Lifecycle hook calls were not as expected")
241-
242260
By("PASSED!")
261+
243262
})
244263

245264
AfterEach(func() {
@@ -300,6 +319,7 @@ func responsesConfigMap(name string, namespace *corev1.Namespace) *corev1.Config
300319
"BeforeClusterCreate-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
301320
"BeforeClusterUpgrade-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
302321
"AfterControlPlaneUpgrade-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
322+
"BeforeClusterDelete-preloadedResponse": fmt.Sprintf(`{"Status": "Failure", "Message": %q}`, hookFailedMessage),
303323

304324
// Non-blocking hooks are set to Status:Success.
305325
"AfterControlPlaneInitialized-preloadedResponse": `{"Status": "Success"}`,
@@ -347,9 +367,8 @@ func getLifecycleHookResponsesFromConfigMap(ctx context.Context, c client.Client
347367
// beforeClusterCreateTestHandler calls runtimeHookTestHandler with a blockedCondition function which returns false if
348368
// the Cluster has entered ClusterPhaseProvisioned.
349369
func beforeClusterCreateTestHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
350-
log.Logf("Blocking with BeforeClusterCreate hook")
351370
hookName := "BeforeClusterCreate"
352-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
371+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
353372
blocked := true
354373
// This hook should block the Cluster from entering the "Provisioned" state.
355374
cluster := &clusterv1.Cluster{}
@@ -371,9 +390,8 @@ func beforeClusterCreateTestHandler(ctx context.Context, c client.Client, namesp
371390
// beforeClusterUpgradeTestHandler calls runtimeHookTestHandler with a blocking function which returns false if the
372391
// Cluster has controlplanev1.RollingUpdateInProgressReason in its ReadyCondition.
373392
func beforeClusterUpgradeTestHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
374-
log.Logf("Blocking with BeforeClusterUpgrade hook")
375393
hookName := "BeforeClusterUpgrade"
376-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
394+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
377395
var blocked = true
378396

379397
cluster := &clusterv1.Cluster{}
@@ -397,9 +415,8 @@ func beforeClusterUpgradeTestHandler(ctx context.Context, c client.Client, names
397415
// afterControlPlaneUpgradeTestHandler calls runtimeHookTestHandler with a blocking function which returns false if any
398416
// MachineDeployment in the Cluster has upgraded to the target Kubernetes version.
399417
func afterControlPlaneUpgradeTestHandler(ctx context.Context, c client.Client, namespace, clusterName, version string, intervals []interface{}) {
400-
log.Logf("Blocking with AfterControlPlaneUpgrade hook")
401418
hookName := "AfterControlPlaneUpgrade"
402-
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, func() bool {
419+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, true, func() bool {
403420
var blocked = true
404421
cluster := &clusterv1.Cluster{}
405422
Eventually(func() error {
@@ -429,15 +446,47 @@ func afterControlPlaneUpgradeTestHandler(ctx context.Context, c client.Client, n
429446
}, intervals)
430447
}
431448

449+
// beforeClusterDeleteHandler calls runtimeHookTestHandler with a blocking function which returns false if any of the
450+
// MachineDeployments associated with the Cluster are in a deleting state.
451+
func beforeClusterDeleteHandler(ctx context.Context, c client.Client, namespace, clusterName string, intervals []interface{}) {
452+
hookName := "BeforeClusterDelete"
453+
runtimeHookTestHandler(ctx, c, namespace, clusterName, hookName, false, func() bool {
454+
var blocked = true
455+
mds := &clusterv1.MachineDeploymentList{}
456+
Eventually(func() error {
457+
return c.List(ctx, mds, client.MatchingLabels{
458+
clusterv1.ClusterLabelName: clusterName,
459+
clusterv1.ClusterTopologyOwnedLabel: "",
460+
})
461+
}).Should(Succeed())
462+
463+
// If any of the MachineDeployments has a deletion timestamp the delete process is unblocked.
464+
for _, md := range mds.Items {
465+
if md.ObjectMeta.DeletionTimestamp != nil {
466+
blocked = false
467+
}
468+
}
469+
470+
// If the Cluster is not found it has been deleted and the hook is unblocked.
471+
if apierrors.IsNotFound(c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, &clusterv1.Cluster{})) {
472+
blocked = false
473+
}
474+
475+
return blocked
476+
}, intervals)
477+
}
478+
432479
// 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.
480+
// 1) Checks that the hook has been called at least once, and if set by withTopologyReconciledCondition, that the TopologyReconciled condition is a Failure.
434481
// 2) Check that the hook's blockingCondition is consistently true.
435482
// - At this point the function sets the hook's response to be non-blocking.
436483
// 3) Check that the hook's blocking condition becomes false.
437484
// Note: runtimeHookTestHandler assumes that the hook passed to it is currently returning a blocking response.
438485
// 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.
486+
func runtimeHookTestHandler(ctx context.Context, c client.Client, namespace, clusterName, hookName string, withTopologyReconciledCondition bool, blockingCondition func() bool, intervals []interface{}) {
487+
log.Logf("Blocking with %s hook", hookName)
488+
489+
// Check that the LifecycleHook has been called at least once and - when required - that the TopologyReconciled condition is a Failure.
441490
Eventually(func() error {
442491
if err := checkLifecycleHooksCalledAtLeastOnce(ctx, c, namespace, clusterName, []string{hookName}); err != nil {
443492
return err
@@ -446,11 +495,14 @@ func runtimeHookTestHandler(ctx context.Context, c client.Client, namespace, clu
446495
if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: clusterName}, cluster); err != nil {
447496
return err
448497
}
449-
if !(conditions.GetReason(cluster, clusterv1.TopologyReconciledCondition) == clusterv1.TopologyReconcileFailedReason) {
498+
499+
// Check for the existence of the condition if withTopologyReconciledCondition is true.
500+
if withTopologyReconciledCondition &&
501+
!(conditions.GetReason(cluster, clusterv1.TopologyReconciledCondition) == clusterv1.TopologyReconcileFailedReason) {
450502
return errors.New("Condition not found on Cluster object")
451503
}
452504
return nil
453-
}, 60*time.Second).Should(Succeed(), "%s has not been called", hookName)
505+
}, intervals...).Should(Succeed(), "%s has not been called", hookName)
454506

455507
// blockingCondition should consistently be true as the Runtime hook is returning "Failure".
456508
Consistently(func() bool {

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 BeforeClusterCreate 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)