Skip to content

Commit 84c894a

Browse files
authored
Merge pull request #3767 from xing-yang/volumemode
Validate volumeMode in CnsRegisterVolume
2 parents 3b10cae + 30e1ca8 commit 84c894a

File tree

3 files changed

+768
-6
lines changed

3 files changed

+768
-6
lines changed

pkg/syncer/cnsoperator/controller/cnsregistervolume/cnsregistervolume_controller.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3434
"k8s.io/apimachinery/pkg/runtime"
3535
apitypes "k8s.io/apimachinery/pkg/types"
36+
"k8s.io/apimachinery/pkg/util/wait"
3637
"k8s.io/client-go/kubernetes/scheme"
3738
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
3839
"k8s.io/client-go/tools/record"
@@ -534,6 +535,40 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
534535
return reconcile.Result{RequeueAfter: timeout}, nil
535536
}
536537

538+
// If existing PVC has DataSourceRef and volumeMode set, handle volumeMode validation and inheritance
539+
volumeModeInherited := false
540+
if pvc != nil && pvc.Spec.DataSourceRef != nil && pvc.Spec.VolumeMode != nil {
541+
if instance.Spec.VolumeMode == "" {
542+
// Inherit the volumeMode from the existing PVC
543+
log.Infof("Existing PVC %s in namespace %s has DataSourceRef and volumeMode set to %s. "+
544+
"CnsRegisterVolume does not have volumeMode set. Inheriting volumeMode from existing PVC.",
545+
pvc.Name, pvc.Namespace, *pvc.Spec.VolumeMode)
546+
instance.Spec.VolumeMode = *pvc.Spec.VolumeMode
547+
volumeModeInherited = true
548+
} else if instance.Spec.VolumeMode != *pvc.Spec.VolumeMode {
549+
// Both are set but don't match - this is an error
550+
msg := fmt.Sprintf("VolumeMode mismatch: existing PVC %s in namespace %s has volumeMode %s, "+
551+
"but CnsRegisterVolume specifies volumeMode %s",
552+
pvc.Name, pvc.Namespace, *pvc.Spec.VolumeMode, instance.Spec.VolumeMode)
553+
log.Error(msg)
554+
setInstanceError(ctx, r, instance, msg)
555+
return reconcile.Result{RequeueAfter: timeout}, nil
556+
}
557+
}
558+
559+
// Persist the inherited volumeMode to the CnsRegisterVolume CR
560+
if volumeModeInherited {
561+
err = updateCnsRegisterVolume(ctx, r.client, instance)
562+
if err != nil {
563+
msg := fmt.Sprintf("Failed to update CnsRegisterVolume with inherited volumeMode. Error: %+v", err)
564+
log.Error(msg)
565+
setInstanceError(ctx, r, instance, msg)
566+
return reconcile.Result{RequeueAfter: timeout}, nil
567+
}
568+
log.Infof("Successfully updated CnsRegisterVolume %s with inherited volumeMode: %s",
569+
instance.Name, instance.Spec.VolumeMode)
570+
}
571+
537572
// Do this check before creating a PV. Otherwise, PVC will be bound to PV after PV
538573
// is created even if validation fails
539574
if pvc != nil {
@@ -618,6 +653,13 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
618653
setInstanceError(ctx, r, instance, msg)
619654
return reconcile.Result{RequeueAfter: timeout}, nil
620655
}
656+
} else {
657+
// PV exists - check if volumeMode needs correction
658+
pv, err = validateAndFixPVVolumeMode(ctx, k8sclient, r, instance, pv, pvName, volumeID,
659+
capacityInMb, accessMode, storageClassName, pvNodeAffinity, timeout)
660+
if err != nil {
661+
return reconcile.Result{RequeueAfter: timeout}, nil
662+
}
621663
}
622664
// If PV is already bound to a different PVC at this point, then its a
623665
// duplicate request.
@@ -1029,6 +1071,103 @@ func isBlockVolumeRegisterRequest(ctx context.Context, instance *cnsregistervolu
10291071
return false
10301072
}
10311073

1074+
// validateAndFixPVVolumeMode checks if an existing PV has the correct volumeMode.
1075+
// If the volumeMode doesn't match what's expected, it untags the CNS volume, deletes
1076+
// and recreates the PV with the correct volumeMode since volumeMode is immutable on PVs.
1077+
func validateAndFixPVVolumeMode(ctx context.Context, k8sclient clientset.Interface,
1078+
r *ReconcileCnsRegisterVolume, instance *cnsregistervolumev1alpha1.CnsRegisterVolume,
1079+
pv *v1.PersistentVolume, pvName, volumeID string, capacityInMb int64,
1080+
accessMode v1.PersistentVolumeAccessMode, storageClassName string,
1081+
pvNodeAffinity *v1.VolumeNodeAffinity, timeout time.Duration) (*v1.PersistentVolume, error) {
1082+
log := logger.GetLogger(ctx)
1083+
1084+
// Determine expected volumeMode
1085+
expectedVolumeMode := instance.Spec.VolumeMode
1086+
if expectedVolumeMode == "" {
1087+
expectedVolumeMode = v1.PersistentVolumeFilesystem
1088+
}
1089+
1090+
// Get actual volumeMode from PV
1091+
pvVolumeMode := v1.PersistentVolumeFilesystem
1092+
if pv.Spec.VolumeMode != nil {
1093+
pvVolumeMode = *pv.Spec.VolumeMode
1094+
}
1095+
1096+
// Check if volumeMode matches
1097+
if expectedVolumeMode != pvVolumeMode {
1098+
log.Warnf("PV %s exists but has incorrect volumeMode. Expected: %s, Actual: %s. "+
1099+
"Untagging CNS volume and recreating PV with correct volumeMode.", pvName, expectedVolumeMode, pvVolumeMode)
1100+
1101+
// Untag the CNS volume before deleting PV to prevent underlying volume deletion.
1102+
// deleteDisk=false ensures the underlying vSphere disk is preserved.
1103+
log.Infof("Untagging CNS volume %s to preserve underlying disk before PV deletion", volumeID)
1104+
_, err := common.DeleteVolumeUtil(ctx, r.volumeManager, volumeID, false)
1105+
if err != nil {
1106+
msg := fmt.Sprintf("Failed to untag CNS volume %s. Error: %+v", volumeID, err)
1107+
log.Error(msg)
1108+
setInstanceError(ctx, r, instance, msg)
1109+
return nil, err
1110+
}
1111+
log.Infof("Successfully untagged CNS volume %s", volumeID)
1112+
1113+
// Delete the existing PV (underlying volume is safe due to CNS untag with deleteDisk=false)
1114+
err = k8sclient.CoreV1().PersistentVolumes().Delete(ctx, pvName, *metav1.NewDeleteOptions(0))
1115+
if err != nil {
1116+
msg := fmt.Sprintf("Failed to delete PV %s with incorrect volumeMode. Error: %+v", pvName, err)
1117+
log.Error(msg)
1118+
setInstanceError(ctx, r, instance, msg)
1119+
return nil, err
1120+
}
1121+
log.Infof("Successfully deleted PV %s with incorrect volumeMode", pvName)
1122+
1123+
// Wait for PV to be fully deleted before recreating
1124+
log.Infof("Waiting for PV %s to be fully deleted", pvName)
1125+
waitErr := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
1126+
_, err := k8sclient.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{})
1127+
if err != nil {
1128+
if apierrors.IsNotFound(err) {
1129+
// PV is fully deleted
1130+
return true, nil
1131+
}
1132+
// Unexpected error
1133+
log.Warnf("Error checking PV deletion status: %+v", err)
1134+
return false, err
1135+
}
1136+
// PV still exists, continue waiting
1137+
return false, nil
1138+
})
1139+
if waitErr != nil {
1140+
msg := fmt.Sprintf("Timeout waiting for PV %s to be deleted. Error: %+v", pvName, waitErr)
1141+
log.Error(msg)
1142+
setInstanceError(ctx, r, instance, msg)
1143+
return nil, waitErr
1144+
}
1145+
log.Infof("PV %s has been fully deleted", pvName)
1146+
1147+
// Recreate PV with correct volumeMode
1148+
claimRef := &v1.ObjectReference{
1149+
Kind: "PersistentVolumeClaim",
1150+
APIVersion: "v1",
1151+
Namespace: instance.Namespace,
1152+
Name: instance.Spec.PvcName,
1153+
}
1154+
pvSpec := getPersistentVolumeSpec(pvName, volumeID, capacityInMb,
1155+
accessMode, instance.Spec.VolumeMode, storageClassName, claimRef)
1156+
pvSpec.Spec.NodeAffinity = pvNodeAffinity
1157+
log.Debugf("Recreating PV with spec: %+v", pvSpec)
1158+
pv, err = k8sclient.CoreV1().PersistentVolumes().Create(ctx, pvSpec, metav1.CreateOptions{})
1159+
if err != nil {
1160+
log.Errorf("Failed to recreate PV with spec: %+v. Error: %+v", pvSpec, err)
1161+
setInstanceError(ctx, r, instance,
1162+
fmt.Sprintf("Failed to recreate PV: %s with correct volumeMode. Error: %+v", pvName, err))
1163+
return nil, err
1164+
}
1165+
log.Infof("Successfully recreated PV %s with correct volumeMode: %s", pvName, expectedVolumeMode)
1166+
}
1167+
1168+
return pv, nil
1169+
}
1170+
10321171
// setInstanceError sets error and records an event on the CnsRegisterVolume
10331172
// instance.
10341173
func setInstanceError(ctx context.Context, r *ReconcileCnsRegisterVolume,

0 commit comments

Comments
 (0)