@@ -23,19 +23,23 @@ import (
23
23
24
24
. "github.com/onsi/gomega"
25
25
corev1 "k8s.io/api/core/v1"
26
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
27
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27
28
"k8s.io/apimachinery/pkg/runtime/schema"
28
29
utilfeature "k8s.io/component-base/featuregate/testing"
29
30
ctrl "sigs.k8s.io/controller-runtime"
30
31
"sigs.k8s.io/controller-runtime/pkg/client"
32
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
31
33
"sigs.k8s.io/controller-runtime/pkg/reconcile"
32
34
33
35
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
36
+ runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
34
37
runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog"
35
38
runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
36
39
"sigs.k8s.io/cluster-api/feature"
37
40
"sigs.k8s.io/cluster-api/internal/contract"
38
41
"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/scope"
42
+ "sigs.k8s.io/cluster-api/internal/hooks"
39
43
fakeruntimeclient "sigs.k8s.io/cluster-api/internal/runtime/client/fake"
40
44
"sigs.k8s.io/cluster-api/internal/test/builder"
41
45
"sigs.k8s.io/cluster-api/util/conditions"
@@ -389,6 +393,157 @@ func TestClusterReconciler_reconcileClusterClassRebase(t *testing.T) {
389
393
}, timeout ).Should (Succeed ())
390
394
}
391
395
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
+
392
547
// TestClusterReconciler_deleteClusterClass tests the correct deletion behaviour for a ClusterClass with references in existing Clusters.
393
548
// In this case deletion of the ClusterClass should be blocked by the webhook.
394
549
func TestClusterReconciler_deleteClusterClass (t * testing.T ) {
0 commit comments