@@ -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.
10341173func setInstanceError (ctx context.Context , r * ReconcileCnsRegisterVolume ,
0 commit comments