@@ -34,14 +34,19 @@ import (
3434 k8sFake "k8s.io/client-go/kubernetes/fake"
3535 "k8s.io/client-go/kubernetes/scheme"
3636 "k8s.io/client-go/tools/record"
37+ crclient "sigs.k8s.io/controller-runtime/pkg/client"
3738 "sigs.k8s.io/controller-runtime/pkg/client/fake"
3839 "sigs.k8s.io/controller-runtime/pkg/reconcile"
3940
4041 cnsvsphere "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/vsphere"
4142 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
4243 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/unittestcommon"
4344 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco"
45+ k8s "sigs.k8s.io/vsphere-csi-driver/v3/pkg/kubernetes"
46+ cnsoperatortypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/types"
4447
48+ "github.com/agiledragon/gomonkey/v2"
49+ "k8s.io/apimachinery/pkg/runtime"
4550 v1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/cnsnodevmbatchattachment/v1alpha1"
4651)
4752
@@ -114,7 +119,6 @@ func setupTestCnsNodeVMBatchAttachment() v1alpha1.CnsNodeVMBatchAttachment {
114119func setTestEnvironment (testCnsNodeVMBatchAttachment * v1alpha1.CnsNodeVMBatchAttachment ,
115120 setDeletionTimestamp bool ) * Reconciler {
116121 cnsNodeVmBatchAttachment := testCnsNodeVMBatchAttachment .DeepCopy ()
117- //objs := []runtime.Object{cnsNodeVmBatchAttachment}
118122
119123 if setDeletionTimestamp {
120124 currentTime := time .Now ()
@@ -780,6 +784,117 @@ func TestUpdatePvcStatusEntryName_SkipsNonTargetPVC(t *testing.T) {
780784 }
781785}
782786
787+ func TestRemovePvcFinalizer_WhenPVCIsAlreadyDeleted (t * testing.T ) {
788+ ctx := context .Background ()
789+
790+ namespace := "test-ns"
791+ pvcName := "not-found-error"
792+ vmInstanceUUID := "vm-111"
793+
794+ scheme := runtime .NewScheme ()
795+ _ = v1 .AddToScheme (scheme )
796+
797+ // fake controller-runtime client (used by PatchFinalizers)
798+ crClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects ().Build ()
799+
800+ // fake k8s clientset
801+ clientset := k8sFake .NewSimpleClientset ()
802+
803+ // ---- Monkey patches ----
804+
805+ patches := gomonkey .NewPatches ()
806+ defer patches .Reset ()
807+
808+ commonco .ContainerOrchestratorUtility = & unittestcommon.FakeK8SOrchestrator {}
809+
810+ VolumeLock = & sync.Map {}
811+ // ---------------------------------------
812+ // Run function
813+ err := removePvcFinalizer (ctx , crClient , clientset , pvcName , namespace , vmInstanceUUID )
814+ // ---------------------------------------
815+
816+ if err != nil {
817+ t .Fatalf ("expected no error, got %v" , err )
818+ }
819+ }
820+
821+ func TestRemovePvcFinalizer_WhenPVCIsPresent (t * testing.T ) {
822+ ctx := context .Background ()
823+
824+ namespace := "test-ns"
825+ pvcName := "mypvc"
826+ vmInstanceUUID := "vm-111"
827+
828+ // Prepare pvc with finalizer and shared annotation
829+ pvc := & v1.PersistentVolumeClaim {
830+ ObjectMeta : metav1.ObjectMeta {
831+ Name : pvcName ,
832+ Namespace : namespace ,
833+ Finalizers : []string {cnsoperatortypes .CNSPvcFinalizer },
834+ Annotations : map [string ]string {
835+ "cns.vmware.com/used-by" : vmInstanceUUID ,
836+ },
837+ },
838+ }
839+
840+ scheme := runtime .NewScheme ()
841+ _ = v1 .AddToScheme (scheme )
842+
843+ // fake controller-runtime client (used by PatchFinalizers)
844+ crClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (pvc ).Build ()
845+
846+ // fake k8s clientset
847+ clientset := k8sFake .NewSimpleClientset (pvc )
848+
849+ // ---- Monkey patches ----
850+
851+ patches := gomonkey .NewPatches ()
852+ defer patches .Reset ()
853+
854+ commonco .ContainerOrchestratorUtility = & unittestcommon.FakeK8SOrchestrator {}
855+
856+ // removePvcAnnotation is expected to succeed
857+ patches .ApplyFunc (
858+ removePvcAnnotation ,
859+ func (ctx context.Context , k8sClient interface {}, vmID string , pvcObj * v1.PersistentVolumeClaim ) error {
860+ return nil
861+ })
862+
863+ // pvcHasUsedByAnnotaion → no: this is last VM
864+ patches .ApplyFunc (
865+ pvcHasUsedByAnnotaion ,
866+ func (ctx context.Context , p * v1.PersistentVolumeClaim ) bool {
867+ return false
868+ })
869+
870+ // PatchFinalizers: expect correct finalizers passed
871+ patches .ApplyFunc (
872+ k8s .PatchFinalizers ,
873+ func (ctx context.Context , c crclient.Client , obj crclient.Object , finals []string ) error {
874+ _ , ok := obj .(* v1.PersistentVolumeClaim )
875+ if ! ok {
876+ t .Fatalf ("expected PVC object, got %T" , obj )
877+ }
878+
879+ if len (finals ) != 0 {
880+ t .Errorf ("expected finalizers to be removed, got %v" , finals )
881+ }
882+
883+ return nil
884+ },
885+ )
886+
887+ VolumeLock = & sync.Map {}
888+ // ---------------------------------------
889+ // Run function
890+ err := removePvcFinalizer (ctx , crClient , clientset , pvcName , namespace , vmInstanceUUID )
891+ // ---------------------------------------
892+
893+ if err != nil {
894+ t .Fatalf ("expected no error, got %v" , err )
895+ }
896+ }
897+
783898func MockGetVMFromVcenter (ctx context.Context , nodeUUID string ,
784899 configInfo config.ConfigurationInfo ) (* cnsvsphere.VirtualMachine , error ) {
785900 var vm * cnsvsphere.VirtualMachine
0 commit comments