@@ -55,6 +55,7 @@ import (
5555 cbtconfigv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cbtconfig/v1alpha1"
5656 cnsoperatorapis "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator"
5757 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/cnsnodevmbatchattachment/v1alpha1"
58+ csivolumeinfo "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/csivolumeinfo"
5859 volumes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/volume"
5960 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
6061 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common"
@@ -165,21 +166,38 @@ func Add(mgr manager.Manager, clusterFlavor cnstypes.CnsClusterFlavor,
165166 }
166167
167168 recorder := eventBroadcaster .NewRecorder (scheme .Scheme , v1.EventSource {Component : cnsoperatorapis .GroupName })
169+
170+ // Initialise the CsiVolumeInfo service when the VM-owned volumes FSS is
171+ // enabled. The service is a package-level singleton; errors are non-fatal
172+ // here — the reconciler will fall back to the legacy attach path.
173+ var cviSvc csivolumeinfo.CsiVolumeInfoService
174+ if commonco .ContainerOrchestratorUtility .IsFSSEnabled (ctx , common .VMOwnedVolumes ) {
175+ var cviErr error
176+ cviSvc , cviErr = csivolumeinfo .InitCsiVolumeInfoService (ctx )
177+ if cviErr != nil {
178+ log .Warnf ("CsiVolumeInfo service initialisation failed; VMOwnedVolumes attach path will be skipped. Err: %v" ,
179+ cviErr )
180+ cviSvc = nil
181+ }
182+ }
183+
168184 return add (mgr , newReconciler (mgr , configInfo , volumeManager , vmOperatorClient , cnsOperatorClient ,
169- recorder , cbtClient ))
185+ recorder , cbtClient , cviSvc ))
170186}
171187
172188func newReconciler (mgr manager.Manager , configInfo * config.ConfigurationInfo ,
173189 volumeManager volumes.Manager , vmOperatorClient client.Client ,
174190 cnsOperatorClient client.Client ,
175- recorder record.EventRecorder , cbtClient client.Client ) reconcile.Reconciler {
191+ recorder record.EventRecorder , cbtClient client.Client ,
192+ cviSvc csivolumeinfo.CsiVolumeInfoService ) reconcile.Reconciler {
176193 return & Reconciler {client : mgr .GetClient (),
177194 scheme : mgr .GetScheme (),
178195 configInfo : * configInfo , volumeManager : volumeManager ,
179196 vmOperatorClient : vmOperatorClient ,
180197 cnsOperatorClient : cnsOperatorClient ,
181198 recorder : recorder ,
182199 cbtClient : cbtClient ,
200+ cviService : cviSvc ,
183201 instanceLock : sync.Map {}}
184202}
185203
@@ -222,6 +240,10 @@ type Reconciler struct {
222240 // cbtClient is set when isCSIBackupAPIEnabled (CSI_Backup_API FSS on); used for
223241 // namespace-scoped CBTConfig lookups before batch attach.
224242 cbtClient client.Client
243+ // cviService provides CRUD operations for CsiVolumeInfo CRs.
244+ // It is set when the VMOwnedVolumes FSS is enabled and is used by the
245+ // ownership-transfer attach and detach paths.
246+ cviService csivolumeinfo.CsiVolumeInfoService
225247 // instanceLock to ensure that for an instance we have only
226248 // one reconciliation at a time.
227249 instanceLock sync.Map
@@ -470,10 +492,52 @@ func (r *Reconciler) detachVolumes(ctx context.Context,
470492
471493 volumesThatFailedToDetach := make ([]string , 0 )
472494
495+ // When the VMOwnedVolumes FSS is enabled, resolve the VM name once and
496+ // check whether the VM uses the ownership-transfer detach path.
497+ var vmName string
498+ var isVMOwnedVolumesVM bool
499+ if commonco .ContainerOrchestratorUtility .IsFSSEnabled (ctx , common .VMOwnedVolumes ) &&
500+ r .cviService != nil {
501+ var vmNameErr error
502+ vmName , vmNameErr = getVMNameByInstanceUUID (ctx , r .vmOperatorClient ,
503+ instance .Spec .InstanceUUID , instance .Namespace )
504+ if vmNameErr != nil {
505+ log .Warnf ("detachVolumes: failed to resolve VM name for instanceUUID %q; " +
506+ "using legacy detach path for all volumes. Err: %v" ,
507+ instance .Spec .InstanceUUID , vmNameErr )
508+ } else if vmName != "" {
509+ var checkErr error
510+ isVMOwnedVolumesVM , checkErr = IsVMOwnedVolumesVM (ctx , r .vmOperatorClient ,
511+ instance .Namespace , vmName )
512+ if checkErr != nil {
513+ log .Warnf ("detachVolumes: IsVMOwnedVolumesVM check failed for %s/%s; " +
514+ "falling back to legacy detach. Err: %v" ,
515+ instance .Namespace , vmName , checkErr )
516+ isVMOwnedVolumesVM = false
517+ }
518+ }
519+ }
520+
473521 for pvc , volumeId := range volumesToDetach {
474522 log .Infof ("Detach call started for PVC %s with volumeID %s in namespace %s for instance %s" ,
475523 pvc , volumeId , instance .Namespace , instance .Name )
476524
525+ // For VMs with the VMOwnedVolumes annotation all volumes use the
526+ // ownership-transfer detach path — the legacy CNS DetachVolume is
527+ // never called for these VMs.
528+ if isVMOwnedVolumesVM {
529+ if detachErr := reconcileVMOwnedVolumesDetach (ctx ,
530+ r .cviService , r .volumeManager , r .client ,
531+ vm , instance , pvc , volumeId , & r .configInfo ); detachErr != nil {
532+ log .Errorf ("detachVolumes: VMOwnedVolumes detach failed for PVC %s: %v" ,
533+ pvc , detachErr )
534+ updateInstanceVolumeStatus (ctx , instance , "" , pvc , "" , "" , detachErr ,
535+ v1alpha1 .ConditionDetached , v1alpha1 .ReasonDetachFailed )
536+ volumesThatFailedToDetach = append (volumesThatFailedToDetach , pvc )
537+ }
538+ continue
539+ }
540+
477541 // Call CNS DetachVolume
478542 faulttype , detachErr := r .volumeManager .DetachVolume (ctx , vm , volumeId )
479543 if detachErr != nil {
@@ -496,7 +560,7 @@ func (r *Reconciler) detachVolumes(ctx context.Context,
496560 // Remove finalizer from the PVC as the detach was successful.
497561 volumesThatFailedToDetach = removeFinalizerAndStatusEntry (ctx , r .client , k8sClient , r .cnsOperatorClient ,
498562 instance , pvc , volumesThatFailedToDetach )
499- // Attempt lazy CVI creation for brownfield volumes when preconditions are met .
563+ // Attempt lazy CVI creation for volumes provisioned before the VMOwnedVolumes FSS was enabled .
500564 go MaybeLazilyCreateCVI (ctx , vm , r .volumeManager ,
501565 instance .Namespace ,
502566 volumeId , pvc , "" , "" ,
@@ -573,12 +637,62 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete
573637 instance .Namespace )
574638 }
575639
640+ // When the VMOwnedVolumes FSS is enabled, check once whether this VM
641+ // carries the annotation. If it does, all volumes use the
642+ // ownership-transfer attach path and BatchAttachVolumes is not called.
643+ if commonco .ContainerOrchestratorUtility .IsFSSEnabled (ctx , common .VMOwnedVolumes ) &&
644+ r .cviService != nil {
645+ vmName , vmNameErr := getVMNameByInstanceUUID (ctx , r .vmOperatorClient ,
646+ instance .Spec .InstanceUUID , instance .Namespace )
647+ if vmNameErr != nil {
648+ log .Warnf ("processBatchAttach: failed to resolve VM name for instanceUUID %q; " +
649+ "falling back to legacy attach path. Err: %v" ,
650+ instance .Spec .InstanceUUID , vmNameErr )
651+ } else if vmName != "" {
652+ isVMOwned , checkErr := IsVMOwnedVolumesVM (ctx , r .vmOperatorClient ,
653+ instance .Namespace , vmName )
654+ if checkErr != nil {
655+ log .Warnf ("processBatchAttach: IsVMOwnedVolumesVM check failed for %s/%s; " +
656+ "falling back to legacy attach path. Err: %v" ,
657+ instance .Namespace , vmName , checkErr )
658+ } else if isVMOwned {
659+ log .Infof ("processBatchAttach: VM %s/%s uses ownership-transfer attach path" ,
660+ instance .Namespace , vmName )
661+ for pvcName , volumeID := range volumesToAttach {
662+ // Derive volumeName for status updates from the spec.
663+ var volumeName string
664+ for _ , v := range instance .Spec .Volumes {
665+ if v .PersistentVolumeClaim .ClaimName == pvcName {
666+ volumeName = v .Name
667+ break
668+ }
669+ }
670+ if attachErr := processVMOwnedVolumesAttach (ctx ,
671+ r .cviService , r .vmOperatorClient , r .volumeManager ,
672+ instance , vmName , instance .Spec .InstanceUUID ,
673+ pvcName , volumeID , volumeName ); attachErr != nil {
674+ log .Errorf ("processBatchAttach: VMOwnedVolumes attach failed for PVC %s: %v" ,
675+ pvcName , attachErr )
676+ err = errors .Join (err , attachErr )
677+ }
678+ }
679+ // Persist BA status changes and return — this path never
680+ // falls through to BatchAttachVolumes.
681+ if patchErr := patchBAStatus (ctx , r .client , instance ); patchErr != nil {
682+ log .Errorf ("processBatchAttach: failed to patch BA status: %v" , patchErr )
683+ err = errors .Join (err , patchErr )
684+ }
685+ return err
686+ }
687+ }
688+ }
689+
576690 // Construct batch attach request
577- pvcsInAttachList , volumeIdsInAttachList , batchAttachRequest , err := constructBatchAttachRequest (ctx ,
691+ pvcsInAttachList , volumeIdsInAttachList , batchAttachRequest , batchErr := constructBatchAttachRequest (ctx ,
578692 volumesToAttach , instance )
579- if err != nil {
580- log .Errorf ("failed to construct batch attach request. Err: %s" , err )
581- return err
693+ if batchErr != nil {
694+ log .Errorf ("failed to construct batch attach request. Err: %s" , batchErr )
695+ return errors . Join ( err , batchErr )
582696 }
583697
584698 // Manage CBT for each volume before batch attach.
@@ -619,10 +733,11 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete
619733 if result .Error == nil {
620734 reason = ""
621735 // Add finalizer on PVC as attach was successful.
622- err = addPvcFinalizer (ctx , r .client , k8sClient , pvcName , instance .Namespace , instance .Spec .InstanceUUID )
623- if err != nil {
736+ finalizerErr := addPvcFinalizer (ctx , r .client , k8sClient , pvcName ,
737+ instance .Namespace , instance .Spec .InstanceUUID )
738+ if finalizerErr != nil {
624739 log .Errorf ("failed to add finalizer %s on PVC %s" , cnsoperatortypes .CNSPvcFinalizer , pvcName )
625- result .Error = err
740+ result .Error = finalizerErr
626741 attachErr = errors .Join (attachErr ,
627742 fmt .Errorf ("failure during attach of PVC %s" , pvcName ))
628743 }
@@ -632,7 +747,7 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete
632747 v1alpha1 .ConditionAttached , reason )
633748
634749 }
635- return attachErr
750+ return errors . Join ( err , attachErr )
636751}
637752
638753// recordEvent records the event, sets the backOffDuration for the instance
0 commit comments