Skip to content

Commit 950117d

Browse files
authored
Fix nil pointer exception when PVC is already deleted while removing finalizer. (#3757)
1 parent 69fb822 commit 950117d

File tree

3 files changed

+121
-4
lines changed

3 files changed

+121
-4
lines changed

pkg/common/unittestcommon/utils.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ func (c *FakeK8SOrchestrator) GetPvcObjectByName(ctx context.Context,
162162
return pvc, nil
163163
}
164164

165+
if pvcName == "not-found-error" {
166+
return nil, apierrors.NewNotFound(v1.Resource("persistentvolumeclaim"), pvcName)
167+
}
168+
165169
pvc := &v1.PersistentVolumeClaim{
166170
ObjectMeta: metav1.ObjectMeta{
167171
Name: pvcName, // Name of the PVC

pkg/syncer/cnsoperator/controller/cnsnodevmbatchattachment/cnsnodevmbatchattachment_helper.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,6 @@ func addPvcFinalizer(ctx context.Context, client client.Client,
672672
}
673673

674674
// Add annotation indicating that the PVC is being used by this VM.
675-
log.Infof("PVC %s is shared", pvc.Name)
676675
err = addPvcAnnotation(ctx, k8sClient, vmInstanceUUID, pvc)
677676
if err != nil {
678677
log.Errorf("failed to add annotation %s to PVC %s in namespace %s for VM %s", cnsoperatortypes.CNSPvcFinalizer,
@@ -732,7 +731,7 @@ func removePvcFinalizer(ctx context.Context, client client.Client,
732731
}
733732

734733
// Verify if the PVC object itself has been deleted by querying the API server in case the cache is old.
735-
pvc, err = k8sClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvcName, metav1.GetOptions{})
734+
pvc, err = k8sClient.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{})
736735
if err != nil {
737736
if apierrors.IsNotFound(err) {
738737
log.Infof("PVC %s has already been deleted. No action to be taken", pvcName)
@@ -744,7 +743,6 @@ func removePvcFinalizer(ctx context.Context, client client.Client,
744743
}
745744

746745
// Remove usedby annotation
747-
log.Infof("PVC %s is shared", pvc.Name)
748746
err = removePvcAnnotation(ctx, k8sClient, vmInstanceUUID, pvc)
749747
if err != nil {
750748
return err

pkg/syncer/cnsoperator/controller/cnsnodevmbatchattachment/cnsnodevmbatchattachment_test.go

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 {
114119
func 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+
783898
func MockGetVMFromVcenter(ctx context.Context, nodeUUID string,
784899
configInfo config.ConfigurationInfo) (*cnsvsphere.VirtualMachine, error) {
785900
var vm *cnsvsphere.VirtualMachine

0 commit comments

Comments
 (0)