@@ -23,19 +23,23 @@ import (
2323
2424 . "github.com/onsi/gomega"
2525 corev1 "k8s.io/api/core/v1"
26+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2728 "k8s.io/apimachinery/pkg/runtime/schema"
2829 utilfeature "k8s.io/component-base/featuregate/testing"
2930 ctrl "sigs.k8s.io/controller-runtime"
3031 "sigs.k8s.io/controller-runtime/pkg/client"
32+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
3133 "sigs.k8s.io/controller-runtime/pkg/reconcile"
3234
3335 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
36+ runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
3437 runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog"
3538 runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
3639 "sigs.k8s.io/cluster-api/feature"
3740 "sigs.k8s.io/cluster-api/internal/contract"
3841 "sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/scope"
42+ "sigs.k8s.io/cluster-api/internal/hooks"
3943 fakeruntimeclient "sigs.k8s.io/cluster-api/internal/runtime/client/fake"
4044 "sigs.k8s.io/cluster-api/internal/test/builder"
4145 "sigs.k8s.io/cluster-api/util/conditions"
@@ -389,6 +393,157 @@ func TestClusterReconciler_reconcileClusterClassRebase(t *testing.T) {
389393 }, timeout ).Should (Succeed ())
390394}
391395
396+ func TestClusterReconciler_reconcileDelete (t * testing.T ) {
397+ defer utilfeature .SetFeatureGateDuringTest (t , feature .Gates , feature .RuntimeSDK , true )()
398+
399+ catalog := runtimecatalog .New ()
400+ _ = runtimehooksv1 .AddToCatalog (catalog )
401+
402+ beforeClusterDeleteGVH , err := catalog .GroupVersionHook (runtimehooksv1 .BeforeClusterDelete )
403+ if err != nil {
404+ panic (err )
405+ }
406+
407+ blockingResponse := & runtimehooksv1.BeforeClusterDeleteResponse {
408+ CommonRetryResponse : runtimehooksv1.CommonRetryResponse {
409+ RetryAfterSeconds : int32 (10 ),
410+ CommonResponse : runtimehooksv1.CommonResponse {
411+ Status : runtimehooksv1 .ResponseStatusSuccess ,
412+ },
413+ },
414+ }
415+ nonBlockingResponse := & runtimehooksv1.BeforeClusterDeleteResponse {
416+ CommonRetryResponse : runtimehooksv1.CommonRetryResponse {
417+ RetryAfterSeconds : int32 (0 ),
418+ CommonResponse : runtimehooksv1.CommonResponse {
419+ Status : runtimehooksv1 .ResponseStatusSuccess ,
420+ },
421+ },
422+ }
423+ failureResponse := & runtimehooksv1.BeforeClusterDeleteResponse {
424+ CommonRetryResponse : runtimehooksv1.CommonRetryResponse {
425+ CommonResponse : runtimehooksv1.CommonResponse {
426+ Status : runtimehooksv1 .ResponseStatusFailure ,
427+ },
428+ },
429+ }
430+
431+ tests := []struct {
432+ name string
433+ cluster * clusterv1.Cluster
434+ hookResponse * runtimehooksv1.BeforeClusterDeleteResponse
435+ wantHookToBeCalled bool
436+ wantResult ctrl.Result
437+ wantOkToDelete bool
438+ wantErr bool
439+ }{
440+ {
441+ name : "should apply the ok-to-delete annotation if the BeforeClusterDelete hook returns a non-blocking response" ,
442+ cluster : & clusterv1.Cluster {
443+ ObjectMeta : metav1.ObjectMeta {
444+ Name : "test-cluster" ,
445+ Namespace : "test-ns" ,
446+ },
447+ Spec : clusterv1.ClusterSpec {
448+ Topology : & clusterv1.Topology {},
449+ },
450+ },
451+ hookResponse : nonBlockingResponse ,
452+ wantResult : ctrl.Result {},
453+ wantHookToBeCalled : true ,
454+ wantOkToDelete : true ,
455+ wantErr : false ,
456+ },
457+ {
458+ name : "should requeue if the BeforeClusterDelete hook returns a blocking response" ,
459+ cluster : & clusterv1.Cluster {
460+ ObjectMeta : metav1.ObjectMeta {
461+ Name : "test-cluster" ,
462+ Namespace : "test-ns" ,
463+ },
464+ Spec : clusterv1.ClusterSpec {
465+ Topology : & clusterv1.Topology {},
466+ },
467+ },
468+ hookResponse : blockingResponse ,
469+ wantResult : ctrl.Result {RequeueAfter : time .Duration (10 ) * time .Second },
470+ wantHookToBeCalled : true ,
471+ wantOkToDelete : false ,
472+ wantErr : false ,
473+ },
474+ {
475+ name : "should fail if the BeforeClusterDelete hook returns a failure response" ,
476+ cluster : & clusterv1.Cluster {
477+ ObjectMeta : metav1.ObjectMeta {
478+ Name : "test-cluster" ,
479+ Namespace : "test-ns" ,
480+ },
481+ Spec : clusterv1.ClusterSpec {
482+ Topology : & clusterv1.Topology {},
483+ },
484+ },
485+ hookResponse : failureResponse ,
486+ wantResult : ctrl.Result {},
487+ wantHookToBeCalled : true ,
488+ wantOkToDelete : false ,
489+ wantErr : true ,
490+ },
491+ {
492+ name : "should succeed if the ok-to-delete annotation is already present" ,
493+ cluster : & clusterv1.Cluster {
494+ ObjectMeta : metav1.ObjectMeta {
495+ Name : "test-cluster" ,
496+ Namespace : "test-ns" ,
497+ Annotations : map [string ]string {
498+ // If the hook is already marked the hook should not be called during cluster delete.
499+ runtimev1 .OkToDeleteAnnotation : "" ,
500+ },
501+ },
502+ Spec : clusterv1.ClusterSpec {
503+ Topology : & clusterv1.Topology {},
504+ },
505+ },
506+ // Using a blocking response here should not matter as the hook should never be called.
507+ // Using a blocking response to enforce the point.
508+ hookResponse : blockingResponse ,
509+ wantResult : ctrl.Result {},
510+ wantHookToBeCalled : false ,
511+ wantOkToDelete : true ,
512+ wantErr : false ,
513+ },
514+ }
515+
516+ for _ , tt := range tests {
517+ t .Run (tt .name , func (t * testing.T ) {
518+ g := NewWithT (t )
519+
520+ fakeClient := fake .NewClientBuilder ().WithObjects (tt .cluster ).Build ()
521+ fakeRuntimeClient := fakeruntimeclient .NewRuntimeClientBuilder ().
522+ WithCallAllExtensionResponses (map [runtimecatalog.GroupVersionHook ]runtimehooksv1.ResponseObject {
523+ beforeClusterDeleteGVH : tt .hookResponse ,
524+ }).
525+ WithCatalog (catalog ).
526+ Build ()
527+
528+ r := & Reconciler {
529+ Client : fakeClient ,
530+ APIReader : fakeClient ,
531+ RuntimeClient : fakeRuntimeClient ,
532+ }
533+
534+ res , err := r .reconcileDelete (ctx , tt .cluster )
535+ if tt .wantErr {
536+ g .Expect (err ).NotTo (BeNil ())
537+ } else {
538+ g .Expect (err ).To (BeNil ())
539+ g .Expect (res ).To (Equal (tt .wantResult ))
540+ g .Expect (hooks .IsOkToDelete (tt .cluster )).To (Equal (tt .wantOkToDelete ))
541+ g .Expect (fakeRuntimeClient .CallAllCount (runtimehooksv1 .BeforeClusterDelete ) == 1 ).To (Equal (tt .wantHookToBeCalled ))
542+ }
543+ })
544+ }
545+ }
546+
392547// TestClusterReconciler_deleteClusterClass tests the correct deletion behaviour for a ClusterClass with references in existing Clusters.
393548// In this case deletion of the ClusterClass should be blocked by the webhook.
394549func TestClusterReconciler_deleteClusterClass (t * testing.T ) {
0 commit comments