Skip to content

Commit 28da214

Browse files
committed
Enhancement - Introduce pvcName input param in CNSUnregisterVolume spec
Adds validation rules using x-kubernetes-validations to ensure that only one of the volumeID or pvcName is specified to avoid ambiguity. Updates the reconciler to use the informer cache to find out the PV and VolumeID when the PVC Name is supplied. Optimises the reconciler logic to ignore all such events that do not increment the generation. Optimises the usage calculation logic by failing fast when usages are detected. Updates the reconciler to add a finalizer to the CR before reconciling to have control over deletion process Updates the reconciler to process the delete events and remove the finalizer for graceful deletion. Updates the reconciler to protect the PVC using a finalizer to gracefully reconciler in case of retries Removes the code that retains the persistent volume as it's no longer required Updates the reconciler to remove the finalizer on the PVC once unregistration is successful for graceful deletion of the PVC. Adds/updates unit tests wherever necessary and applicable.
1 parent 1a0b8f2 commit 28da214

File tree

14 files changed

+1190
-520
lines changed

14 files changed

+1190
-520
lines changed

pkg/apis/cnsoperator/cnsunregistervolume/v1alpha1/cnsunregistervolume_types.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import (
2424
// +k8s:openapi-gen=true
2525
type CnsUnregisterVolumeSpec struct {
2626
// VolumeID indicates the volume handle of CNS volume to be unregistered
27-
VolumeID string `json:"volumeID"`
27+
VolumeID string `json:"volumeID,omitempty"`
28+
29+
// PVCName indicates the name of the PVC to be unregistered.
30+
PVCName string `json:"pvcName,omitempty"`
2831

2932
// RetainFCD indicates if the volume should be retained as an FCD.
3033
// If set to false or not specified, the volume will be retained as a VMDK.
Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
@@ -16,66 +15,73 @@ spec:
1615
singular: cnsunregistervolume
1716
scope: Namespaced
1817
versions:
19-
- name: v1alpha1
20-
schema:
21-
openAPIV3Schema:
22-
description: CnsUnregisterVolume is the Schema for the cnsunregistervolumes API
23-
properties:
24-
apiVersion:
25-
description: 'APIVersion defines the versioned schema of this representation
18+
- name: v1alpha1
19+
schema:
20+
openAPIV3Schema:
21+
description: CnsUnregisterVolume is the Schema for the cnsunregistervolumes API
22+
properties:
23+
apiVersion:
24+
description: 'APIVersion defines the versioned schema of this representation
2625
of an object. Servers should convert recognized schemas to the latest
2726
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
28-
type: string
29-
kind:
30-
description: 'Kind is a string value representing the REST resource this
27+
type: string
28+
kind:
29+
description: 'Kind is a string value representing the REST resource this
3130
object represents. Servers may infer this from the endpoint the client
3231
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
33-
type: string
34-
metadata:
35-
type: object
36-
spec:
37-
description: CnsUnregisterVolumeSpec defines the desired state of CnsUnregisterVolume
38-
properties:
39-
volumeID:
40-
description: VolumeID indicates the volume handle of CNS volume to be unregistered.
41-
type: string
42-
pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
43-
retainFCD:
44-
description: RetainFCD indicates if the volume should be retained as an FCD.
45-
If set to false or not specified, the volume will be retained as a VMDK.
46-
type: boolean
47-
forceUnregister:
48-
description: ForceUnregister indicates if the volume should be forcefully unregistered.
49-
If set to true, the volume will be unregistered even if it is still in use by any VM.
50-
This should be used with caution as it may lead to data loss.
51-
type: boolean
52-
required:
53-
- volumeID
54-
type: object
55-
status:
56-
description: CnsUnregisterVolumeStatus defines the observed state of CnsUnregisterVolume
57-
properties:
58-
error:
59-
description: The last error encountered during export operation, if
60-
any. This field must only be set by the entity completing the export
61-
operation, i.e. the CNS Operator.
62-
type: string
63-
unregistered:
64-
description: Indicates the volume is successfully unregistered.
65-
This field must only be set by the entity completing the unregister
66-
operation, i.e. the CNS Operator.
67-
type: boolean
68-
required:
69-
- unregistered
70-
type: object
71-
type: object
72-
served: true
73-
storage: true
74-
subresources:
75-
status: {}
32+
type: string
33+
metadata:
34+
type: object
35+
spec:
36+
description: CnsUnregisterVolumeSpec defines the desired state of CnsUnregisterVolume
37+
properties:
38+
volumeID:
39+
description: VolumeID indicates the volume handle of CNS volume to be unregistered.
40+
type: string
41+
pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
42+
pvcName:
43+
description: PVCName indicates the name of the PVC to be unregistered.
44+
type: string
45+
pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
46+
retainFCD:
47+
description: RetainFCD indicates if the volume should be retained as an FCD.
48+
If set to false or not specified, the volume will be retained as a VMDK.
49+
type: boolean
50+
forceUnregister:
51+
description: ForceUnregister indicates if the volume should be forcefully unregistered.
52+
If set to true, the volume will be unregistered even if it is still in use by any VM.
53+
This should be used with caution as it may lead to data loss.
54+
type: boolean
55+
type: object
56+
x-kubernetes-validations:
57+
- rule: "has(self.volumeID) || has(self.pvcName)"
58+
message: "Either 'volumeID' or 'pvcName' must be specified, but not both"
59+
- rule: "!(has(self.volumeID) && has(self.pvcName))"
60+
message: "Cannot specify both 'volumeID' and 'pvcName' at the same time. Please specify only one of them"
61+
status:
62+
description: CnsUnregisterVolumeStatus defines the observed state of CnsUnregisterVolume
63+
properties:
64+
error:
65+
description: The last error encountered during export operation, if
66+
any. This field must only be set by the entity completing the export
67+
operation, i.e. the CNS Operator.
68+
type: string
69+
unregistered:
70+
description: Indicates the volume is successfully unregistered.
71+
This field must only be set by the entity completing the unregister
72+
operation, i.e. the CNS Operator.
73+
type: boolean
74+
required:
75+
- unregistered
76+
type: object
77+
type: object
78+
served: true
79+
storage: true
80+
subresources:
81+
status: { }
7682
status:
7783
acceptedNames:
7884
kind: ""
7985
plural: ""
80-
conditions: []
81-
storedVersions: []
86+
conditions: [ ]
87+
storedVersions: [ ]

pkg/common/cns-lib/volume/manager.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3766,6 +3766,11 @@ func (m *defaultManager) UnregisterVolume(ctx context.Context, volumeID string,
37663766
func (m *defaultManager) unregisterVolume(ctx context.Context, volumeID string, unregisterDisk bool) error {
37673767
log := logger.GetLogger(ctx)
37683768

3769+
if volumeID == "" {
3770+
log.Debugf("UnregisterVolume: volumeID is empty, nothing to unregister")
3771+
return nil
3772+
}
3773+
37693774
if m.virtualCenter == nil {
37703775
return errors.New("invalid manager instance")
37713776
}

pkg/common/unittestcommon/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ func (c *FakeK8SOrchestrator) GetPVCNameFromCSIVolumeID(volumeID string) (string
431431
}
432432

433433
// GetVolumeIDFromPVCName simlates an invalid case when pvcName contains "invalid".
434-
func (c *FakeK8SOrchestrator) GetVolumeIDFromPVCName(pvcName string) (string, bool) {
434+
func (c *FakeK8SOrchestrator) GetVolumeIDFromPVCName(namespace string, pvcName string) (string, bool) {
435435
if strings.Contains(pvcName, "invalid") {
436436
// Simulate a case where the volumeID is invalid and does not correspond to any PVC.
437437
return "", false

pkg/csi/service/common/commonco/coagnostic.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,8 @@ type COCommonInterface interface {
9898
GetPVNameFromCSIVolumeID(volumeID string) (string, bool)
9999
// GetPVCNameFromCSIVolumeID returns `pvc name` and `pvc namespace` for the given volumeID using volumeIDToPvcMap.
100100
GetPVCNameFromCSIVolumeID(volumeID string) (string, string, bool)
101-
// GetVolumeIDFromPVCName returns volumeID the given pvcName using pvcToVolumeIDMap.
102-
// PVC name is its namespaced name.
103-
GetVolumeIDFromPVCName(pvcName string) (string, bool)
101+
// GetVolumeIDFromPVCName returns volumeID for the given pvc name and namespace.
102+
GetVolumeIDFromPVCName(namespace string, pvcName string) (string, bool)
104103
// InitializeCSINodes creates CSINode instances for each K8s node with the appropriate topology keys.
105104
InitializeCSINodes(ctx context.Context) error
106105
// StartZonesInformer starts a dynamic informer which listens on Zones CR in

pkg/csi/service/common/commonco/k8sorchestrator/k8sorchestrator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,8 +2016,8 @@ func (c *K8sOrchestrator) GetPVCNameFromCSIVolumeID(volumeID string) (
20162016

20172017
// GetVolumeIDFromPVCName returns volumeID the given pvcName using pvcToVolumeIDMap.
20182018
// PVC name is its namespaced name.
2019-
func (c *K8sOrchestrator) GetVolumeIDFromPVCName(pvcName string) (string, bool) {
2020-
return c.pvcToVolumeIDMap.get(pvcName)
2019+
func (c *K8sOrchestrator) GetVolumeIDFromPVCName(namespace string, pvcName string) (string, bool) {
2020+
return c.pvcToVolumeIDMap.get(fmt.Sprintf("%s/%s", namespace, pvcName))
20212021
}
20222022

20232023
// IsLinkedCloneRequest checks if the pvc is a linked clone request

pkg/kubernetes/kubernetes.go

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -713,85 +713,52 @@ func PatchFinalizers(ctx context.Context, c client.Client, obj client.Object, fi
713713
return c.Patch(ctx, obj, patch)
714714
}
715715

716-
// RetainPersistentVolume updates the PersistentVolume's ReclaimPolicy to Retain.
717-
// This is useful to preserve the PersistentVolume even if the associated PersistentVolumeClaim is deleted.
718-
func RetainPersistentVolume(ctx context.Context, k8sClient clientset.Interface, pvName string) error {
719-
log := logger.GetLogger(ctx)
720-
721-
if pvName == "" {
722-
log.Debugf("PersistentVolume name is empty. Exiting...")
723-
return nil
724-
}
725-
726-
log.Debugf("Retaining PersistentVolume %q", pvName)
727-
pv, err := k8sClient.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{})
728-
if err != nil {
729-
if apierrors.IsNotFound(err) {
730-
log.Debugf("PersistentVolume %q not found. Exiting...", pvName)
731-
return nil
732-
}
733-
734-
return logger.LogNewErrorf(log, "Failed to get PersistentVolume %q. Error: %s", pvName, err.Error())
735-
}
736-
737-
pv.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain
738-
_, err = k8sClient.CoreV1().PersistentVolumes().Update(ctx, pv, metav1.UpdateOptions{})
739-
if err != nil {
740-
return logger.LogNewErrorf(log, "Failed to update PersistentVolume %q to retain policy. Error: %s",
741-
pvName, err.Error())
742-
}
743-
744-
log.Debugf("Successfully retained PersistentVolume %q", pvName)
745-
return nil
746-
}
747-
748716
// DeletePersistentVolumeClaim deletes the PersistentVolumeClaim with the given name and namespace.
749717
func DeletePersistentVolumeClaim(ctx context.Context, k8sClient clientset.Interface,
750718
pvcName, pvcNamespace string) error {
751-
log := logger.GetLogger(ctx)
719+
log := logger.GetLogger(ctx).With("name", fmt.Sprintf("%s/%s", pvcNamespace, pvcName))
752720

753721
if pvcName == "" {
754-
log.Debugf("PVC name is empty. Exiting...")
722+
log.Debug("PVC name is empty. Exiting...")
755723
return nil
756724
}
757725

758-
log.Debugf("Deleting PersistentVolumeClaim %q in namespace %q", pvcName, pvcNamespace)
726+
log.Debug("Deleting PVC")
759727
err := k8sClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Delete(ctx, pvcName, metav1.DeleteOptions{})
760728
if err != nil {
761729
if apierrors.IsNotFound(err) {
762-
log.Debugf("PersistentVolumeClaim %q in namespace %q not found. Exiting...", pvcName, pvcNamespace)
730+
log.Debug("PVC not found. Exiting...")
763731
return nil
764732
}
765733

766-
return logger.LogNewErrorf(log, "Failed to delete PersistentVolumeClaim %q in namespace %q. Error: %s",
767-
pvcName, pvcNamespace, err.Error())
734+
return logger.LogNewErrorf(log, "Failed to delete PVC. Error: %s", err.Error())
768735
}
769736

770-
log.Debugf("Successfully deleted PersistentVolumeClaim %q in namespace %q", pvcName, pvcNamespace)
737+
log.Debug("Successfully deleted PVC")
771738
return nil
772739
}
773740

774741
// DeletePersistentVolume deletes the PersistentVolume with the given name.
775742
func DeletePersistentVolume(ctx context.Context, k8sClient clientset.Interface, pvName string) error {
776-
log := logger.GetLogger(ctx)
743+
log := logger.GetLogger(ctx).With("name", pvName)
777744

778745
if pvName == "" {
779-
log.Debugf("PersistentVolume name is empty. Exiting...")
746+
log.Debug("PersistentVolume name is empty. Exiting...")
780747
return nil
781748
}
782749

783-
log.Debugf("Deleting PersistentVolume %q", pvName)
750+
log.Debug("Deleting PV")
784751
err := k8sClient.CoreV1().PersistentVolumes().Delete(ctx, pvName, metav1.DeleteOptions{})
785752
if err != nil {
786753
if apierrors.IsNotFound(err) {
787-
log.Debugf("PersistentVolume %q not found. Exiting...", pvName)
754+
log.Debug("PV not found. Exiting...")
788755
return nil
789756
}
790757

791-
return logger.LogNewErrorf(log, "Failed to delete PersistentVolume %q. Error: %s", pvName, err.Error())
758+
return logger.LogNewErrorf(log, "Failed to delete PV. Error: %s", err.Error())
792759
}
793760

794-
log.Debugf("Successfully deleted PersistentVolume %q", pvName)
761+
log.Debug("Successfully deleted PV")
795762
return nil
796763
}
797764

@@ -811,6 +778,80 @@ func UpdateStatus(ctx context.Context, c client.Client, obj client.Object) error
811778
return nil
812779
}
813780

781+
// AddFinalizerOnPVC adds the specified finalizer to the PersistentVolumeClaim (PVC)
782+
// if it is not already present.
783+
func AddFinalizerOnPVC(ctx context.Context, k8sClient clientset.Interface, pvcName, pvcNamespace,
784+
finalizer string) error {
785+
log := logger.GetLogger(ctx).With("name", fmt.Sprintf("%s/%s", pvcNamespace, pvcName))
786+
787+
if pvcName == "" {
788+
log.Debug("PVC name is empty. Exiting...")
789+
return nil
790+
}
791+
792+
pvc, err := k8sClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Get(ctx, pvcName, metav1.GetOptions{})
793+
if err != nil {
794+
if apierrors.IsNotFound(err) {
795+
log.Debug("PVC not found. Exiting...", pvcName, pvcNamespace)
796+
return nil
797+
}
798+
799+
return logger.LogNewErrorf(log, "Failed to get PVC. Error: %s", err.Error())
800+
}
801+
802+
// If the finalizer is already present, no action is needed
803+
if !controllerutil.AddFinalizer(pvc, finalizer) {
804+
log.Debugf("Finalizer %s already present on PVC. No action needed.", finalizer)
805+
return nil
806+
}
807+
808+
// Update the PVC with the new finalizer
809+
_, err = k8sClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Update(ctx, pvc, metav1.UpdateOptions{})
810+
if err != nil {
811+
return logger.LogNewErrorf(log, "Failed to add finalizer %s to PVC. Error: %s", finalizer, err.Error())
812+
}
813+
814+
log.Debugf("Successfully added finalizer %s to PVC", finalizer)
815+
return nil
816+
}
817+
818+
// RemoveFinalizerFromPVC removes the specified finalizer from the PersistentVolumeClaim (PVC)
819+
// if it is present.
820+
func RemoveFinalizerFromPVC(ctx context.Context, k8sClient clientset.Interface, pvcName, pvcNamespace,
821+
finalizer string) error {
822+
log := logger.GetLogger(ctx).With("name", fmt.Sprintf("%s/%s", pvcNamespace, pvcName))
823+
824+
if pvcName == "" {
825+
log.Debug("PVC name is empty. Exiting...")
826+
return nil
827+
}
828+
829+
pvc, err := k8sClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Get(ctx, pvcName, metav1.GetOptions{})
830+
if err != nil {
831+
if apierrors.IsNotFound(err) {
832+
log.Debug("PVC not found. Exiting...", pvcName, pvcNamespace)
833+
return nil
834+
}
835+
836+
return logger.LogNewErrorf(log, "Failed to get PVC. Error: %s", err.Error())
837+
}
838+
839+
// If the finalizer is not present, no action is needed
840+
if !controllerutil.RemoveFinalizer(pvc, finalizer) {
841+
log.Debugf("Finalizer %s not present on PVC. No action needed.", finalizer)
842+
return nil
843+
}
844+
845+
// Update the PVC to remove the finalizer
846+
_, err = k8sClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Update(ctx, pvc, metav1.UpdateOptions{})
847+
if err != nil {
848+
return logger.LogNewErrorf(log, "Failed to remove finalizer %s from PVC. Error: %s", finalizer, err.Error())
849+
}
850+
851+
log.Debugf("Successfully removed finalizer %s from PVC", finalizer)
852+
return nil
853+
}
854+
814855
// AddFinalizer adds the specified finalizer to the given Kubernetes object if it is not already present.
815856
// It updates the object in the Kubernetes cluster to persist the change.
816857
func AddFinalizer(ctx context.Context, c client.Client, obj client.Object, finalizer string) error {

pkg/syncer/admissionhandler/cnscsi_admissionhandler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (m *MockCOCommonInterface) GetPVCNameFromCSIVolumeID(volumeID string) (stri
188188
return args.String(0), args.String(1), args.Bool(2)
189189
}
190190

191-
func (m *MockCOCommonInterface) GetVolumeIDFromPVCName(pvcName string) (string, bool) {
191+
func (m *MockCOCommonInterface) GetVolumeIDFromPVCName(namespace, pvcName string) (string, bool) {
192192
args := m.Called(pvcName)
193193
return args.String(0), args.Bool(1)
194194
}

0 commit comments

Comments
 (0)