Skip to content

Commit d94a9bb

Browse files
committed
Complete deletion of CnsFileAccessConfig instance if associated nodeVM instance is not found
1 parent 21521e2 commit d94a9bb

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

pkg/internalapis/cnsoperator/cnsfilevolumeclient/cnsfilevolumeclient.go

+82-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis"
3232
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsoperator/cnsfilevolumeclient/v1alpha1"
3333
k8s "sigs.k8s.io/vsphere-csi-driver/v3/pkg/kubernetes"
34+
cnsoperatortypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/types"
3435
)
3536

3637
// FileVolumeClient exposes an interface to support
@@ -49,6 +50,9 @@ type FileVolumeClient interface {
4950
// clientVMIP for a given file volume. fileVolumeName is used
5051
// to uniquely identify CnsFileVolumeClient instances.
5152
RemoveClientVMFromIPList(ctx context.Context, fileVolumeName, clientVMName, clientVMIP string) error
53+
// GetVMIPFromVMName returns the VMIP associated with a
54+
// given client VM name.
55+
GetVMIPFromVMName(ctx context.Context, fileVolumeName string, clientVMName string) (string, int, error)
5256
}
5357

5458
// fileVolumeClient maintains a client to the API
@@ -186,6 +190,8 @@ func (f *fileVolumeClient) AddClientVMToIPList(ctx context.Context,
186190
ObjectMeta: v1.ObjectMeta{
187191
Name: instanceName,
188192
Namespace: instanceNamespace,
193+
// Add finalizer so that CnsFileVolumeClient instance doesn't get deleted abruptly
194+
Finalizers: []string{cnsoperatortypes.CNSFinalizer},
189195
},
190196
Spec: v1alpha1.CnsFileVolumeClientSpec{
191197
ExternalIPtoClientVms: map[string][]string{
@@ -282,9 +288,16 @@ func (f *fileVolumeClient) RemoveClientVMFromIPList(ctx context.Context,
282288
delete(instance.Spec.ExternalIPtoClientVms, clientVMIP)
283289
}
284290
if len(instance.Spec.ExternalIPtoClientVms) == 0 {
285-
log.Debugf("Deleting cnsfilevolumeclient instance %s from API server", fileVolumeName)
291+
log.Infof("Deleting cnsfilevolumeclient instance %s from API server", fileVolumeName)
292+
// Remove finalizer from CnsFileVolumeClient instance
293+
removeFinalizerFromCRDInstance(ctx, instance)
286294
err = f.client.Delete(ctx, instance)
287295
if err != nil {
296+
if errors.IsNotFound(err) {
297+
log.Infof("cnsfilevolumeclient instance %s seems to be already deleted.", fileVolumeName)
298+
f.volumeLock.Delete(fileVolumeName)
299+
return nil
300+
}
288301
log.Errorf("failed to delete cnsfilevolumeclient instance %s with error: %+v", fileVolumeName, err)
289302
return err
290303
}
@@ -302,3 +315,71 @@ func (f *fileVolumeClient) RemoveClientVMFromIPList(ctx context.Context,
302315
log.Debugf("Could not find VM %s in list. Returning.", clientVMName)
303316
return nil
304317
}
318+
319+
// GetVMIPFromVMName returns the VM IP associated with a
320+
// given client VM name.
321+
// Callers need to specify fileVolumeName as a combination of
322+
// "<SV-namespace>/<SV-PVC-name>". This combination is used to uniquely
323+
// identify CnsFileVolumeClient instances.
324+
// Returns an empty string if the instance doesn't exist OR if the
325+
// input VM name is not present in this instance.
326+
// Returns an error if any operations fails.
327+
func (f *fileVolumeClient) GetVMIPFromVMName(ctx context.Context,
328+
fileVolumeName string, clientVMName string) (string, int, error) {
329+
log := logger.GetLogger(ctx)
330+
331+
log.Infof("Fetching VM IP from cnsfilevolumeclient %s for VM name %s", fileVolumeName, clientVMName)
332+
actual, _ := f.volumeLock.LoadOrStore(fileVolumeName, &sync.Mutex{})
333+
instanceLock, ok := actual.(*sync.Mutex)
334+
if !ok {
335+
return "", 0, fmt.Errorf("failed to cast lock for cnsfilevolumeclient instance: %s", fileVolumeName)
336+
}
337+
instanceLock.Lock()
338+
defer instanceLock.Unlock()
339+
340+
instance := &v1alpha1.CnsFileVolumeClient{}
341+
instanceNamespace, instanceName, err := cache.SplitMetaNamespaceKey(fileVolumeName)
342+
if err != nil {
343+
log.Errorf("failed to split key %s with error: %+v", fileVolumeName, err)
344+
return "", 0, err
345+
}
346+
instanceKey := types.NamespacedName{
347+
Namespace: instanceNamespace,
348+
Name: instanceName,
349+
}
350+
err = f.client.Get(ctx, instanceKey, instance)
351+
if err != nil {
352+
if errors.IsNotFound(err) {
353+
// If the get() on the instance fails, then we return empty string.
354+
log.Infof("Cnsfilevolumeclient instance %s not found. Returning empty string", fileVolumeName)
355+
return "", 0, nil
356+
}
357+
log.Errorf("failed to get cnsfilevolumeclient instance %s with error: %+v", fileVolumeName, err)
358+
return "", 0, err
359+
}
360+
361+
// Verify if input VM name exists in Spec.ExternalIPtoClientVms
362+
log.Debugf("Verifying if ExternalIPtoClientVms list has VM name: %s", clientVMName)
363+
for vmIP, vmNames := range instance.Spec.ExternalIPtoClientVms {
364+
for _, vmName := range vmNames {
365+
if vmName == clientVMName {
366+
return vmIP, len(vmNames), nil
367+
}
368+
}
369+
}
370+
return "", 0, nil
371+
}
372+
373+
// removeFinalizerFromCRDInstance will remove the CNS Finalizer = cns.vmware.com,
374+
// from a given CnsFileVolumeClient instance.
375+
func removeFinalizerFromCRDInstance(ctx context.Context, instance *v1alpha1.CnsFileVolumeClient) {
376+
log := logger.GetLogger(ctx)
377+
for i, finalizer := range instance.Finalizers {
378+
if finalizer == cnsoperatortypes.CNSFinalizer {
379+
log.Debugf("Removing %q finalizer from CnsFileVolumeClient instance with name: %q on namespace: %q",
380+
cnsoperatortypes.CNSFinalizer, instance.Name, instance.Namespace)
381+
instance.Finalizers = append(instance.Finalizers[:i], instance.Finalizers[i+1:]...)
382+
break
383+
}
384+
}
385+
}

pkg/syncer/cnsoperator/controller/cnsfileaccessconfig/cnsfileaccessconfig_controller.go

+86
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,52 @@ func (r *ReconcileCnsFileAccessConfig) Reconcile(ctx context.Context,
246246
msg := fmt.Sprintf("Failed to get virtualmachine instance for the VM with name: %q. Error: %+v",
247247
instance.Spec.VMName, err)
248248
log.Error(msg)
249+
// If virtualmachine instance is NotFound and if deletion timestamp is set on CnsFileAccessConfig instance,
250+
// then proceed with the deletion of CnsFileAccessConfig instance.
251+
if apierrors.IsNotFound(err) && instance.DeletionTimestamp != nil {
252+
log.Infof("CnsFileAccessConfig instance %q has deletion timestamp set, but VM instance with "+
253+
"name %q is not found. Processing the deletion of CnsFileAccessConfig instance.",
254+
instance.Name, instance.Spec.VMName)
255+
// Fetch the PVC and PV instance and get volume ID
256+
skipConfigureVolumeACL := false
257+
volumeID, err := cnsoperatorutil.GetVolumeID(ctx, r.client, instance.Spec.PvcName, instance.Namespace)
258+
if err != nil {
259+
if apierrors.IsNotFound(err) {
260+
// If PVC instance is NotFound (deleted), then there is no need to configure ACL on file volume
261+
skipConfigureVolumeACL = true
262+
} else {
263+
msg := fmt.Sprintf("Failed to get volumeID from pvcName: %q. Error: %+v", instance.Spec.PvcName, err)
264+
log.Error(msg)
265+
setInstanceError(ctx, r, instance, msg)
266+
return reconcile.Result{RequeueAfter: timeout}, nil
267+
}
268+
}
269+
if !skipConfigureVolumeACL {
270+
err = r.removePermissionsForFileVolume(ctx, volumeID, instance)
271+
if err != nil {
272+
msg := fmt.Sprintf("Failed to remove file volume permissions with error: %+v", err)
273+
log.Error(msg)
274+
setInstanceError(ctx, r, instance, msg)
275+
return reconcile.Result{RequeueAfter: timeout}, nil
276+
}
277+
}
278+
279+
// Remove finalizer from CnsFileAccessConfig CRD
280+
removeFinalizerFromCRDInstance(ctx, instance)
281+
err = updateCnsFileAccessConfig(ctx, r.client, instance)
282+
if err != nil {
283+
msg := fmt.Sprintf("failed to update CnsFileAccessConfig instance: %q on namespace: %q. Error: %+v",
284+
instance.Name, instance.Namespace, err)
285+
recordEvent(ctx, r, instance, v1.EventTypeWarning, msg)
286+
return reconcile.Result{RequeueAfter: timeout}, nil
287+
}
288+
// Cleanup instance entry from backOffDuration map.
289+
backOffDurationMapMutex.Lock()
290+
delete(backOffDuration, instance.Name)
291+
backOffDurationMapMutex.Unlock()
292+
return reconcile.Result{}, nil
293+
}
294+
249295
setInstanceError(ctx, r, instance, msg)
250296
return reconcile.Result{RequeueAfter: timeout}, nil
251297
}
@@ -412,6 +458,46 @@ func (r *ReconcileCnsFileAccessConfig) Reconcile(ctx context.Context,
412458
return reconcile.Result{}, nil
413459
}
414460

461+
// removePermissionsForFileVolume helps to remove net permissions for a given file volume.
462+
// This method is used when we don't have VM instance. It fetches the VM IP from CNSFileVolumeClient
463+
// instance for the VM name associated with CnsFileAccessConfig.
464+
func (r *ReconcileCnsFileAccessConfig) removePermissionsForFileVolume(ctx context.Context,
465+
volumeID string, instance *cnsfileaccessconfigv1alpha1.CnsFileAccessConfig) error {
466+
log := logger.GetLogger(ctx)
467+
cnsFileVolumeClientInstance, err := cnsfilevolumeclient.GetFileVolumeClientInstance(ctx)
468+
if err != nil {
469+
return logger.LogNewErrorf(log, "Failed to get CNSFileVolumeClient instance. Error: %+v", err)
470+
}
471+
472+
vmIP, vmsAssociatedWithIP, err := cnsFileVolumeClientInstance.GetVMIPFromVMName(ctx,
473+
instance.Namespace+"/"+instance.Spec.PvcName, instance.Spec.VMName)
474+
if err != nil {
475+
return logger.LogNewErrorf(log, "Failed to get VM IP from VM name in CNSFileVolumeClient instance. "+
476+
"Error: %+v", err)
477+
}
478+
if vmIP == "" {
479+
// vmIP is "" if we can't find given vmName in ExternalIPtoClientVms map of CNSFileVolumeClient instance.
480+
// Assuming that this vmName is already removed, return success.
481+
return nil
482+
}
483+
if vmsAssociatedWithIP == 1 {
484+
err = r.configureVolumeACLs(ctx, volumeID, vmIP, true)
485+
if err != nil {
486+
return logger.LogNewErrorf(log, "Failed to remove net permissions for file volume %q. Error: %+v",
487+
volumeID, err)
488+
}
489+
}
490+
err = cnsFileVolumeClientInstance.RemoveClientVMFromIPList(ctx,
491+
instance.Namespace+"/"+instance.Spec.PvcName, instance.Spec.VMName, vmIP)
492+
if err != nil {
493+
return logger.LogNewErrorf(log, "Failed to remove VM %q with IP %q from IPList. Error: %+v",
494+
instance.Spec.VMName, vmIP, err)
495+
}
496+
log.Infof("Successfully removed VM IP %q from IPList for CnsFileAccessConfig request with name: %q on "+
497+
"namespace: %q", vmIP, instance.Name, instance.Namespace)
498+
return nil
499+
}
500+
415501
// configureNetPermissionsForFileVolume helps to add or remove net permissions
416502
// for a given file volume. The callers of this method can remove or add net
417503
// permissions by setting the parameter removePermission to true or false

pkg/syncer/metadatasyncer.go

+76
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import (
6565
commoncotypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco/types"
6666
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
6767
csitypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/types"
68+
cnsfilevolumeclientv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsoperator/cnsfilevolumeclient/v1alpha1"
6869
triggercsifullsyncv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsoperator/triggercsifullsync/v1alpha1"
6970
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsvolumeinfo"
7071
cnsvolumeinfov1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/cnsvolumeinfo/v1alpha1"
@@ -74,6 +75,7 @@ import (
7475
csinodetopologyv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/csinodetopology/v1alpha1"
7576
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/internalapis/featurestates"
7677
k8s "sigs.k8s.io/vsphere-csi-driver/v3/pkg/kubernetes"
78+
cnsoperatortypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/types"
7779
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/storagepool"
7880
)
7981

@@ -290,6 +292,15 @@ func InitMetadataSyncer(ctx context.Context, clusterFlavor cnstypes.CnsClusterFl
290292
}
291293
}
292294

295+
// Check if finalizer is added on CnsFileVolumeClient CRs, if not then add a finalizer.
296+
// We want to protect CnsFileVolumeClient from getting abruptly deleted, as it is being used
297+
// in CnsFileAccessConfig CR. So, in case of upgrade we will add finalizer if it is missing.
298+
err = addFinalizerOnCnsFileVolumeClientCRs(ctx)
299+
if err != nil {
300+
log.Errorf("Failed to add finalizer on CnsFileVolumeClient CRs. Error: %v", err)
301+
return err
302+
}
303+
293304
parseBool := func(CfgMap *v1.ConfigMap, featureName string, namespace string) bool {
294305
var fssVal bool
295306
if state, ok := CfgMap.Data[featureName]; ok {
@@ -986,6 +997,71 @@ func InitMetadataSyncer(ctx context.Context, clusterFlavor cnstypes.CnsClusterFl
986997
return nil
987998
}
988999

1000+
// addFinalizerOnCnsFileVolumeClientCRs checks and adds finalizer on CnsFileVolumeClient CRs if it is missing.
1001+
func addFinalizerOnCnsFileVolumeClientCRs(ctx context.Context) error {
1002+
log := logger.GetLogger(ctx).WithOptions()
1003+
var stretchedSupervisor bool
1004+
if commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx, common.TKGsHA) {
1005+
clusterComputeResourceMoIds, err := common.GetClusterComputeResourceMoIds(ctx)
1006+
if err != nil {
1007+
log.Errorf("failed to get clusterComputeResourceMoIds. err: %v", err)
1008+
return err
1009+
}
1010+
if len(clusterComputeResourceMoIds) > 1 {
1011+
stretchedSupervisor = true
1012+
}
1013+
}
1014+
if !stretchedSupervisor || (stretchedSupervisor && IsWorkloadDomainIsolationSupported) {
1015+
if commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx, common.FileVolume) {
1016+
// Prepare Config and NewClientForGroup for cnsOperatorClient
1017+
restConfig, err := config.GetConfig()
1018+
if err != nil {
1019+
log.Errorf("failed to get Kubernetes k8sconfig. Err: %+v", err)
1020+
return err
1021+
}
1022+
cnsOperatorClient, err := k8s.NewClientForGroup(ctx, restConfig, cnsoperatorv1alpha1.GroupName)
1023+
if err != nil {
1024+
log.Errorf("failed to create CnsOperator client. Err: %+v", err)
1025+
return err
1026+
}
1027+
// Get the list of all CnsFileVolumeClient CRs from all supervisor namespaces.
1028+
cnsFileVolumeClientList := &cnsfilevolumeclientv1alpha1.CnsFileVolumeClientList{}
1029+
err = cnsOperatorClient.List(ctx, cnsFileVolumeClientList)
1030+
if err != nil {
1031+
log.Errorf("failed to list CnsFileVolumeClient CRs from all supervisor namespaces. Error: %+v",
1032+
err)
1033+
return err
1034+
}
1035+
1036+
for _, cnsFileVolumeClient := range cnsFileVolumeClientList.Items {
1037+
// If cnsFileVolumeClient instance is marked for deletion, then no need to add finalizer
1038+
if cnsFileVolumeClient.DeletionTimestamp == nil {
1039+
cnsFinalizerExists := false
1040+
// Check if finalizer already exists.
1041+
for _, finalizer := range cnsFileVolumeClient.Finalizers {
1042+
if finalizer == cnsoperatortypes.CNSFinalizer {
1043+
cnsFinalizerExists = true
1044+
break
1045+
}
1046+
}
1047+
if !cnsFinalizerExists {
1048+
// Add finalizer.
1049+
cnsFileVolumeClient.Finalizers = append(cnsFileVolumeClient.Finalizers,
1050+
cnsoperatortypes.CNSFinalizer)
1051+
err = cnsOperatorClient.Update(ctx, &cnsFileVolumeClient)
1052+
if err != nil {
1053+
log.Errorf("failed to update CnsFileVolumeClient instance: %q on namespace: %q. Error: %+v",
1054+
cnsFileVolumeClient.Name, cnsFileVolumeClient.Namespace, err)
1055+
return err
1056+
}
1057+
}
1058+
}
1059+
}
1060+
}
1061+
}
1062+
return nil
1063+
}
1064+
9891065
func initStorageQuotaPeriodicSync(ctx context.Context, metadataSyncer *metadataSyncInformer) error {
9901066
log := logger.GetLogger(ctx).WithOptions()
9911067
if int(PeriodicSyncIntervalInMin.Minutes()) == 0 {

0 commit comments

Comments
 (0)