Skip to content

Commit 1569ede

Browse files
committed
Consider volumes in status also while removing finalizers from PVC
1 parent c059af9 commit 1569ede

File tree

4 files changed

+224
-43
lines changed

4 files changed

+224
-43
lines changed

pkg/common/unittestcommon/utils.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ func (c *FakeK8SOrchestrator) GetPVCNameFromCSIVolumeID(volumeID string) (string
469469
return "with-used-by-annotation", "test-ns", true
470470
}
471471

472+
if strings.Contains(volumeID, "vol-1") {
473+
return "mock-pvc-1", "mock-namespace", true
474+
}
475+
472476
// Simulate a case where the volumeID corresponds to a PVC.
473477
return "mock-pvc", "mock-namespace", true
474478
}

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,18 +295,21 @@ func (r *Reconciler) Reconcile(ctx context.Context,
295295
if instance.DeletionTimestamp != nil && vm == nil {
296296
log.Infof("Instance %s is being deleted and VM object is also deleted from VC", request.NamespacedName.String())
297297

298-
// For every PVC mentioned in instance.Spec, remove finalizer from its PVC.
299-
for _, volume := range instance.Spec.Volumes {
300-
err := removePvcFinalizer(ctx, r.client, k8sClient, volume.PersistentVolumeClaim.ClaimName, instance.Namespace,
298+
pvcsInSpecAndStatus := getPvcsFromSpecAndStatus(ctx, instance)
299+
300+
// For every PVC mentioned in instance.Spec and in instance.Status, remove finalizer from it.
301+
// It is important to remove the finalizer from PVCs in instance.Status also as it is possible
302+
// that someone removes the PVC from spec after trriggering deletion of VM.
303+
for pvcName, volumeName := range pvcsInSpecAndStatus {
304+
err := removePvcFinalizer(ctx, r.client, k8sClient, pvcName, instance.Namespace,
301305
instance.Spec.InstanceUUID)
302306
if err != nil {
303-
updateInstanceVolumeStatus(ctx, instance, volume.Name, volume.PersistentVolumeClaim.ClaimName, "", "", err,
307+
updateInstanceVolumeStatus(ctx, instance, volumeName, pvcName, "", "", err,
304308
v1alpha1.ConditionDetached, v1alpha1.ReasonDetachFailed)
305-
log.Errorf("failed to remove finalizer from PVC %s. Err: %s", volume.PersistentVolumeClaim.ClaimName,
306-
err)
309+
log.Errorf("failed to remove finalizer from PVC %s. Err: %s", pvcName, err)
307310
return r.completeReconciliationWithError(batchAttachCtx, instance, request.NamespacedName, timeout, err)
308311
} else {
309-
updateInstanceVolumeStatus(ctx, instance, volume.Name, volume.PersistentVolumeClaim.ClaimName, "", "", err,
312+
updateInstanceVolumeStatus(ctx, instance, volumeName, pvcName, "", "", err,
310313
v1alpha1.ConditionDetached, "")
311314
}
312315
}

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

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -424,33 +424,23 @@ func getVolumeMetadataMaps(ctx context.Context,
424424
return
425425
}
426426

427-
// getPvcsInSpec returns map of PVCs and their volumeIDs.
428-
func getPvcsInSpec(ctx context.Context, instance *v1alpha1.CnsNodeVMBatchAttachment,
429-
k8sClient kubernetes.Interface) (map[string]string, error) {
427+
// getAllPvcsAttachedToVM returns PVC name to volumeID map for all the FCDs which are attached to the VM.
428+
func getAllPvcsAttachedToVM(ctx context.Context,
429+
attachedFCDList map[string]FCDBackingDetails) map[string]string {
430430
log := logger.GetLogger(ctx)
431+
pvcsAttachedToVM := make(map[string]string)
431432

432-
pvcsInSpec := make(map[string]string)
433-
for _, volume := range instance.Spec.Volumes {
434-
volumeId, ok := commonco.ContainerOrchestratorUtility.GetVolumeIDFromPVCName(
435-
instance.Namespace, volume.PersistentVolumeClaim.ClaimName)
436-
if !ok {
437-
pvcName := volume.PersistentVolumeClaim.ClaimName
438-
_, err := k8sClient.CoreV1().PersistentVolumeClaims(instance.Namespace).Get(ctx,
439-
pvcName, metav1.GetOptions{})
440-
if err != nil {
441-
if apierrors.IsNotFound(err) {
442-
log.Infof("PVC %s has already been deleted. No action to be taken", pvcName)
443-
continue
444-
}
445-
return pvcsInSpec, fmt.Errorf("failed to find volumeID for PVC %s", volume.PersistentVolumeClaim.ClaimName)
446-
}
447-
return pvcsInSpec, fmt.Errorf("failed to find volumeID for PVC %s", volume.PersistentVolumeClaim.ClaimName)
433+
for volumeID := range attachedFCDList {
434+
pvcName, _, found := commonco.ContainerOrchestratorUtility.GetPVCNameFromCSIVolumeID(volumeID)
435+
if !found {
436+
// Do not fail if volumeID is not found to avoid cases where cache is not refreshed.
437+
// Clean up routine in syncer will take care of the cases where finalizer was not removed from such PVCs.
438+
log.Warnf("failed to find PVC name for volumeID %s", volumeID)
439+
continue
448440
}
449-
pvcsInSpec[volume.PersistentVolumeClaim.ClaimName] = volumeId
441+
pvcsAttachedToVM[pvcName] = volumeID
450442
}
451-
452-
return pvcsInSpec, nil
453-
443+
return pvcsAttachedToVM
454444
}
455445

456446
// listAttachedFcdsForVM returns list of FCDs (present in the K8s cluster)
@@ -504,7 +494,7 @@ func listAttachedFcdsForVM(ctx context.Context,
504494
log.Debugf("failed to get diskMode and sharingMode for virtual disk")
505495
}
506496

507-
log.Infof("Adding volume with ID %s to attachedFCDs list", virtualDisk.VDiskId.Id)
497+
log.Debugf("Adding volume with ID %s to attachedFCDs list", virtualDisk.VDiskId.Id)
508498
attachedFCDs[virtualDisk.VDiskId.Id] = FCDBackingDetails{
509499
ControllerKey: controllerKey,
510500
UnitNumber: unitNumber,
@@ -572,6 +562,21 @@ func constructBatchAttachRequest(ctx context.Context,
572562
return pvcsInSpec, volumeIdsInSpec, batchAttachRequest, nil
573563
}
574564

565+
// getPvcsFromSpecAndStatus returns all the PVCs in spec as well as in status of the given instance.
566+
func getPvcsFromSpecAndStatus(ctx context.Context,
567+
instance *v1alpha1.CnsNodeVMBatchAttachment) map[string]string {
568+
569+
listOfPvcsToRemoveFinalzer := make(map[string]string, 0)
570+
571+
for _, volume := range instance.Spec.Volumes {
572+
listOfPvcsToRemoveFinalzer[volume.PersistentVolumeClaim.ClaimName] = volume.Name
573+
}
574+
for _, volume := range instance.Status.VolumeStatus {
575+
listOfPvcsToRemoveFinalzer[volume.PersistentVolumeClaim.ClaimName] = volume.Name
576+
}
577+
return listOfPvcsToRemoveFinalzer
578+
}
579+
575580
// isPvcEncrypted returns true if annotation csi.vsphere.encryption-class
576581
// is present on the PVC.
577582
func isPvcEncrypted(pvcAnnotations map[string]string) bool {
@@ -615,17 +620,6 @@ func getVolumesToAttachAndDetach(ctx context.Context, instance *v1alpha1.CnsNode
615620
pvcsToAttach := make(map[string]string, 0)
616621
pvcsToDetach := make(map[string]string, 0)
617622

618-
if instance.DeletionTimestamp != nil {
619-
log.Debugf("Instance %s is being deleted, adding all volumes in spec to volumesToDetach list.", instance.Name)
620-
volumesToDetach, err := getPvcsInSpec(ctx, instance, k8sClient)
621-
if err != nil {
622-
log.Errorf("failed to get volumes to detach from instance spec. Err: %s", err)
623-
return pvcsToAttach, volumesToDetach, err
624-
}
625-
log.Debugf("Volumes to detach list %+v for instance %s", volumesToDetach, instance.Name)
626-
return pvcsToAttach, volumesToDetach, nil
627-
}
628-
629623
// Query vCenter to find the list of FCDs which are attached to the VM.
630624
attachedFcdList, err := listAttachedFcdsForVM(ctx, vm)
631625
if err != nil {
@@ -634,6 +628,14 @@ func getVolumesToAttachAndDetach(ctx context.Context, instance *v1alpha1.CnsNode
634628
}
635629
log.Infof("List of attached FCDs %+v to VM %s", attachedFcdList, instance.Spec.InstanceUUID)
636630

631+
if instance.DeletionTimestamp != nil {
632+
log.Debugf("Instance %s is being deleted, adding all volumes attached to the VM to volumesToDetach list.",
633+
instance.Name)
634+
volumesToDetach := getAllPvcsAttachedToVM(ctx, attachedFcdList)
635+
log.Debugf("Volumes to detach list %+v for instance %s", volumesToDetach, instance.Name)
636+
return pvcsToAttach, volumesToDetach, nil
637+
}
638+
637639
// Get all PVCs and their corresponding volumeID mapping from instance spec.
638640
volumeIdsInSpec, volumeNamesInSpec, pvcNameToVolumeIDInSpec, err := getVolumeMetadataMaps(ctx, instance)
639641
if err != nil {
@@ -893,7 +895,7 @@ func removePvcFinalizer(ctx context.Context, client client.Client,
893895
return nil
894896
}
895897

896-
log.Infof("VM %s was the last attached VM for the PVC %s. Finalizer %s can be safely removed fromt the PVC",
898+
log.Infof("VM %s was the last attached VM for the PVC %s. Finalizer %s can be safely removed from the PVC",
897899
vmInstanceUUID, pvcName, cnsoperatortypes.CNSPvcFinalizer)
898900

899901
if !controllerutil.ContainsFinalizer(pvc, cnsoperatortypes.CNSPvcFinalizer) {

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

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,178 @@ func TestIsPvcEncrypted(t *testing.T) {
12541254
}
12551255
}
12561256

1257+
func TestGetPvcsFromSpecAndStatus(t *testing.T) {
1258+
tests := []struct {
1259+
name string
1260+
instance *v1alpha1.CnsNodeVMBatchAttachment
1261+
want map[string]string
1262+
}{
1263+
{
1264+
name: "PVCs only in spec",
1265+
instance: &v1alpha1.CnsNodeVMBatchAttachment{
1266+
Spec: v1alpha1.CnsNodeVMBatchAttachmentSpec{
1267+
Volumes: []v1alpha1.VolumeSpec{
1268+
{
1269+
Name: "vol-1",
1270+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimSpec{
1271+
ClaimName: "pvc-a",
1272+
},
1273+
},
1274+
{
1275+
Name: "vol-2",
1276+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimSpec{
1277+
ClaimName: "pvc-b",
1278+
},
1279+
},
1280+
},
1281+
},
1282+
},
1283+
want: map[string]string{
1284+
"pvc-a": "vol-1",
1285+
"pvc-b": "vol-2",
1286+
},
1287+
},
1288+
{
1289+
name: "PVCs only in status",
1290+
instance: &v1alpha1.CnsNodeVMBatchAttachment{
1291+
Status: v1alpha1.CnsNodeVMBatchAttachmentStatus{
1292+
VolumeStatus: []v1alpha1.VolumeStatus{
1293+
{
1294+
Name: "vol-1",
1295+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimStatus{
1296+
ClaimName: "pvc-x",
1297+
},
1298+
},
1299+
{
1300+
Name: "vol-2",
1301+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimStatus{
1302+
ClaimName: "pvc-y",
1303+
},
1304+
},
1305+
},
1306+
},
1307+
},
1308+
want: map[string]string{
1309+
"pvc-x": "vol-1",
1310+
"pvc-y": "vol-2",
1311+
},
1312+
},
1313+
{
1314+
name: "PVCs in both spec and status",
1315+
instance: &v1alpha1.CnsNodeVMBatchAttachment{
1316+
Spec: v1alpha1.CnsNodeVMBatchAttachmentSpec{
1317+
Volumes: []v1alpha1.VolumeSpec{
1318+
{
1319+
Name: "vol-1",
1320+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimSpec{
1321+
ClaimName: "pvc-a",
1322+
},
1323+
},
1324+
},
1325+
},
1326+
Status: v1alpha1.CnsNodeVMBatchAttachmentStatus{
1327+
VolumeStatus: []v1alpha1.VolumeStatus{
1328+
{
1329+
Name: "vol-2",
1330+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimStatus{
1331+
ClaimName: "pvc-b",
1332+
},
1333+
},
1334+
},
1335+
},
1336+
},
1337+
want: map[string]string{
1338+
"pvc-a": "vol-1",
1339+
"pvc-b": "vol-2",
1340+
},
1341+
},
1342+
{
1343+
name: "Duplicate PVCs across spec and status",
1344+
instance: &v1alpha1.CnsNodeVMBatchAttachment{
1345+
Spec: v1alpha1.CnsNodeVMBatchAttachmentSpec{
1346+
Volumes: []v1alpha1.VolumeSpec{
1347+
{
1348+
Name: "vol-1",
1349+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimSpec{
1350+
ClaimName: "pvc-dup",
1351+
},
1352+
},
1353+
},
1354+
},
1355+
Status: v1alpha1.CnsNodeVMBatchAttachmentStatus{
1356+
VolumeStatus: []v1alpha1.VolumeStatus{
1357+
{
1358+
Name: "vol-1",
1359+
PersistentVolumeClaim: v1alpha1.PersistentVolumeClaimStatus{
1360+
ClaimName: "pvc-dup",
1361+
},
1362+
},
1363+
},
1364+
},
1365+
},
1366+
want: map[string]string{
1367+
"pvc-dup": "vol-1",
1368+
},
1369+
},
1370+
{
1371+
name: "Empty spec and status",
1372+
instance: &v1alpha1.CnsNodeVMBatchAttachment{},
1373+
want: map[string]string{},
1374+
},
1375+
}
1376+
1377+
for _, tt := range tests {
1378+
t.Run(tt.name, func(t *testing.T) {
1379+
got := getPvcsFromSpecAndStatus(context.Background(), tt.instance)
1380+
if !reflect.DeepEqual(got, tt.want) {
1381+
t.Errorf("getPvcsFromSpecAndStatus() = %v, want %v", got, tt.want)
1382+
}
1383+
})
1384+
}
1385+
}
1386+
1387+
func TestGetAllPvcsAttachedToVMSuccess(t *testing.T) {
1388+
commonco.ContainerOrchestratorUtility = &unittestcommon.FakeK8SOrchestrator{}
1389+
1390+
attachedFCD := map[string]FCDBackingDetails{
1391+
"vol-1": {},
1392+
"vol-2": {},
1393+
}
1394+
1395+
got := getAllPvcsAttachedToVM(context.Background(), attachedFCD)
1396+
want := map[string]string{
1397+
"mock-pvc-1": "vol-1",
1398+
"mock-pvc": "vol-2",
1399+
}
1400+
1401+
assert.Equal(t, want, got)
1402+
}
1403+
1404+
func TestGetAllPvcsAttachedToVM_EmptyInput(t *testing.T) {
1405+
1406+
commonco.ContainerOrchestratorUtility = &unittestcommon.FakeK8SOrchestrator{}
1407+
1408+
attachedFCD := map[string]FCDBackingDetails{}
1409+
1410+
got := getAllPvcsAttachedToVM(context.Background(), attachedFCD)
1411+
want := map[string]string{}
1412+
1413+
assert.Equal(t, want, got)
1414+
}
1415+
1416+
func TestGetAllPvcsAttachedToVM_PVCNotFound(t *testing.T) {
1417+
1418+
commonco.ContainerOrchestratorUtility = &unittestcommon.FakeK8SOrchestrator{}
1419+
1420+
attachedFCD := map[string]FCDBackingDetails{
1421+
"invalid": {},
1422+
}
1423+
got := getAllPvcsAttachedToVM(context.Background(), attachedFCD)
1424+
want := map[string]string{}
1425+
1426+
assert.Equal(t, want, got)
1427+
}
1428+
12571429
func MockGetVMFromVcenter(ctx context.Context, nodeUUID string,
12581430
configInfo config.ConfigurationInfo) (*cnsvsphere.VirtualMachine, error) {
12591431
var vm *cnsvsphere.VirtualMachine

0 commit comments

Comments
 (0)