@@ -32,6 +32,8 @@ import (
3232 "k8s.io/client-go/kubernetes"
3333 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common"
3434
35+ cnstypes "github.com/vmware/govmomi/cns/types"
36+ "github.com/vmware/govmomi/object"
3537 vimtypes "github.com/vmware/govmomi/vim25/types"
3638 "sigs.k8s.io/controller-runtime/pkg/client"
3739 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -506,12 +508,198 @@ func listAttachedFcdsForVM(ctx context.Context,
506508 return attachedFCDs , nil
507509}
508510
511+ // convertDiskTypeToBackingType converts the diskType returned by QueryVirtualDiskInfo
512+ // to the appropriate CnsVolumeBackingType string used for batch attach operations.
513+ // The returned values correspond to VirtualDevice.FileBackingInfo subclasses as defined in
514+ // github.com/vmware/govmomi/cns/types (CnsVolumeBackingType constants).
515+ func convertDiskTypeToBackingType (diskType string ) string {
516+ switch diskType {
517+ case "thin" , "preallocated" , "thick" , "eagerZeroedThick" , "thick2Gb" , "flatMonolithic" :
518+ // All flat/thick disk types use FlatVer2BackingInfo
519+ // thin -> FlatVer2BackingInfo (thinProvisioned=true)
520+ // preallocated/thick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=false)
521+ // eagerZeroedThick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=true)
522+ // thick2Gb/flatMonolithic -> FlatVer2BackingInfo (split variations)
523+ return string (cnstypes .CnsVolumeBackingTypeFlatVer2BackingInfo )
524+ case "sparse2Gb" , "sparseMonolithic" , "delta" , "vmfsSparse" :
525+ // sparse types -> SparseVer2BackingInfo
526+ return string (cnstypes .CnsVolumeBackingTypeSparseVer2BackingInfo )
527+ case "seSparse" :
528+ // seSparse -> SeSparseBackingInfo
529+ return string (cnstypes .CnsVolumeBackingTypeSeSparseBackingInfo )
530+ case "rdm" , "rdmp" :
531+ // rdm -> RawDiskMappingVer1BackingInfo (compatibilityMode="virtualMode")
532+ // rdmp -> RawDiskMappingVer1BackingInfo (compatibilityMode="physicalMode")
533+ return string (cnstypes .CnsVolumeBackingTypeRawDiskMappingVer1BackingInfo )
534+ default :
535+ // Unknown disk type, return empty string
536+ return ""
537+ }
538+ }
539+
540+ // queryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
541+ // VirtualDiskManager's QueryVirtualDiskInfo API.
542+ // It first queries the volume to get the DatastoreUrl, then uses that URL to call
543+ // QueryVirtualDiskInfo which returns the disk type information.
544+ func queryBackingTypeFromVirtualDiskInfo (ctx context.Context ,
545+ volumeManager volumes.Manager ,
546+ configInfo config.ConfigurationInfo ,
547+ volumeID string ) (string , error ) {
548+ log := logger .GetLogger (ctx )
549+
550+ // Step 1: Query the volume to get the DatastoreUrl
551+ queryFilter := cnstypes.CnsQueryFilter {
552+ VolumeIds : []cnstypes.CnsVolumeId {
553+ {
554+ Id : volumeID ,
555+ },
556+ },
557+ }
558+
559+ querySelection := cnstypes.CnsQuerySelection {
560+ Names : []string {string (cnstypes .QuerySelectionNameTypeDataStoreUrl )},
561+ }
562+
563+ queryResult , err := volumeManager .QueryVolumeAsync (ctx , queryFilter , & querySelection )
564+ if err != nil {
565+ // Fall back to QueryVolume if QueryVolumeAsync is not supported
566+ if err .Error () == cnsvsphere .ErrNotSupported .Error () {
567+ log .Warn ("QueryVolumeAsync is not supported. Invoking QueryVolume API" )
568+ queryResult , err = volumeManager .QueryVolume (ctx , queryFilter )
569+ if err != nil {
570+ return "" , fmt .Errorf ("failed to query volume %s: %w" , volumeID , err )
571+ }
572+ } else {
573+ return "" , fmt .Errorf ("failed to query volume async %s: %w" , volumeID , err )
574+ }
575+ }
576+
577+ if queryResult == nil || len (queryResult .Volumes ) == 0 {
578+ return "" , fmt .Errorf ("no volume found for volumeID %s" , volumeID )
579+ }
580+
581+ datastoreUrl := queryResult .Volumes [0 ].DatastoreUrl
582+ if datastoreUrl == "" {
583+ return "" , fmt .Errorf ("datastore URL not found for volumeID %s" , volumeID )
584+ }
585+
586+ log .Debugf ("Retrieved DatastoreUrl %s for volumeID %s" , datastoreUrl , volumeID )
587+
588+ // Step 2: Get VirtualCenter instance
589+ vc , err := cnsvsphere .GetVirtualCenterInstance (ctx , & configInfo , false )
590+ if err != nil {
591+ return "" , fmt .Errorf ("failed to get VirtualCenter instance: %w" , err )
592+ }
593+
594+ err = vc .Connect (ctx )
595+ if err != nil {
596+ return "" , fmt .Errorf ("failed to connect to VirtualCenter: %w" , err )
597+ }
598+
599+ // Step 3: Get the datacenter
600+ dcs , err := vc .GetDatacenters (ctx )
601+ if err != nil {
602+ return "" , fmt .Errorf ("failed to get datacenters: %w" , err )
603+ }
604+
605+ if len (dcs ) == 0 {
606+ return "" , fmt .Errorf ("no datacenters found" )
607+ }
608+
609+ // Step 4: Create VirtualDiskManager and query disk info
610+ virtualDiskManager := object .NewVirtualDiskManager (vc .Client .Client )
611+
612+ // The name parameter for QueryVirtualDiskInfo should be the datastore path
613+ // Format: [datastoreName] path/to/disk.vmdk
614+ // Since we have the DatastoreUrl, we need to construct the disk path
615+ // The DatastoreUrl format is: ds:///vmfs/volumes/<datastore-uuid>/
616+ // We need to query the backing object details to get the file path
617+ queryFilterForPath := cnstypes.CnsQueryFilter {
618+ VolumeIds : []cnstypes.CnsVolumeId {
619+ {
620+ Id : volumeID ,
621+ },
622+ },
623+ }
624+
625+ querySelectionForPath := cnstypes.CnsQuerySelection {
626+ Names : []string {string (cnstypes .QuerySelectionNameTypeBackingObjectDetails )},
627+ }
628+
629+ queryResultForPath , err := volumeManager .QueryVolumeAsync (ctx , queryFilterForPath , & querySelectionForPath )
630+ if err != nil {
631+ if err .Error () == cnsvsphere .ErrNotSupported .Error () {
632+ queryResultForPath , err = volumeManager .QueryVolume (ctx , queryFilterForPath )
633+ if err != nil {
634+ return "" , fmt .Errorf ("failed to query volume for backing details %s: %w" , volumeID , err )
635+ }
636+ } else {
637+ return "" , fmt .Errorf ("failed to query volume async for backing details %s: %w" , volumeID , err )
638+ }
639+ }
640+
641+ if queryResultForPath == nil || len (queryResultForPath .Volumes ) == 0 {
642+ return "" , fmt .Errorf ("no volume found for volumeID %s when querying backing details" , volumeID )
643+ }
644+
645+ backingObjectDetails := queryResultForPath .Volumes [0 ].BackingObjectDetails
646+ if backingObjectDetails == nil {
647+ return "" , fmt .Errorf ("backing object details not found for volumeID %s" , volumeID )
648+ }
649+
650+ // Get the backing file path from BackingObjectDetails
651+ // BackingDiskPath is available on CnsBlockBackingDetails
652+ blockBackingDetails , ok := backingObjectDetails .(* cnstypes.CnsBlockBackingDetails )
653+ if ! ok {
654+ return "" , fmt .Errorf ("backing object details is not of type CnsBlockBackingDetails for volumeID %s" , volumeID )
655+ }
656+
657+ backingFilePath := blockBackingDetails .BackingDiskPath
658+ if backingFilePath == "" {
659+ return "" , fmt .Errorf ("backing disk path not found for volumeID %s" , volumeID )
660+ }
661+
662+ log .Debugf ("Retrieved backing disk path %s for volumeID %s" , backingFilePath , volumeID )
663+
664+ // Step 5: Call QueryVirtualDiskInfo
665+ // Use the first datacenter for the query
666+ var dc * object.Datacenter
667+ if len (dcs ) > 0 {
668+ dc = dcs [0 ].Datacenter
669+ }
670+
671+ diskInfoList , err := virtualDiskManager .QueryVirtualDiskInfo (ctx , backingFilePath , dc , false )
672+ if err != nil {
673+ return "" , fmt .Errorf ("failed to query virtual disk info for %s: %w" , backingFilePath , err )
674+ }
675+
676+ if len (diskInfoList ) == 0 {
677+ return "" , fmt .Errorf ("no disk info returned for %s" , backingFilePath )
678+ }
679+
680+ diskType := diskInfoList [0 ].DiskType
681+ log .Infof ("Retrieved diskType %s for volumeID %s from VirtualDiskManager" , diskType , volumeID )
682+
683+ // Step 6: Convert diskType to backing type
684+ backingType := convertDiskTypeToBackingType (diskType )
685+ if backingType == "" {
686+ log .Warnf ("Unknown diskType %s for volumeID %s, using diskType as backingType" , diskType , volumeID )
687+ backingType = diskType
688+ }
689+
690+ log .Infof ("Converted diskType %s to backingType %s for volumeID %s" , diskType , backingType , volumeID )
691+ return backingType , nil
692+ }
693+
509694// constructBatchAttachRequest goes through all volumes in instance spec and
510695// constructs the batchAttach request for each of them.
511696// It also validates each of the requests to make sure user input is correct.
512697func constructBatchAttachRequest (ctx context.Context ,
513698 volumesToAttach map [string ]string ,
514- instance * v1alpha1.CnsNodeVMBatchAttachment ) (pvcsInSpec map [string ]string ,
699+ instance * v1alpha1.CnsNodeVMBatchAttachment ,
700+ volumeManager volumes.Manager ,
701+ configInfo config.ConfigurationInfo ,
702+ k8sClient kubernetes.Interface ) (pvcsInSpec map [string ]string ,
515703 volumeIdsInSpec map [string ]string ,
516704 batchAttachRequest []volumes.BatchAttachRequest , err error ) {
517705 log := logger .GetLogger (ctx )
@@ -546,14 +734,39 @@ func constructBatchAttachRequest(ctx context.Context,
546734 isPvcEncrypted := isPvcEncrypted (pvcObj .Annotations )
547735 log .Infof ("PVC %s has encryption enabled: %t" , pvcName , isPvcEncrypted )
548736
737+ // Get BackingType from PVC annotation, if not available query from VirtualDiskManager
738+ backingType := pvcObj .GetAnnotations ()[common .AnnKeyBackingDiskType ]
739+ if backingType == "" {
740+ log .Infof ("BackingType annotation not found on PVC %s, querying from VirtualDiskManager" , pvcName )
741+ queriedBackingType , queryErr := queryBackingTypeFromVirtualDiskInfo (ctx , volumeManager , configInfo , volumeID )
742+ if queryErr != nil {
743+ log .Warnf ("Failed to query BackingType from VirtualDiskManager for volumeID %s: %v. " +
744+ "Proceeding with empty BackingType" , volumeID , queryErr )
745+ } else {
746+ backingType = queriedBackingType
747+ log .Infof ("Successfully retrieved BackingType %s for PVC %s from VirtualDiskManager" ,
748+ backingType , pvcName )
749+
750+ // Update the PVC annotation with the queried BackingType so it can be reused in future attach operations
751+ patchErr := patchPVCBackingTypeAnnotation (ctx , k8sClient , pvcObj , backingType )
752+ if patchErr != nil {
753+ log .Warnf ("Failed to update BackingType annotation on PVC %s: %v. " +
754+ "The BackingType will be queried again on next attach" , pvcName , patchErr )
755+ } else {
756+ log .Infof ("Successfully updated BackingType annotation %s on PVC %s" ,
757+ backingType , pvcName )
758+ }
759+ }
760+ }
761+
549762 // Populate values for attach request.
550763 currentBatchAttachRequest := volumes.BatchAttachRequest {
551764 VolumeID : volumeID ,
552765 SharingMode : string (volume .PersistentVolumeClaim .SharingMode ),
553766 DiskMode : string (volume .PersistentVolumeClaim .DiskMode ),
554767 ControllerKey : volume .PersistentVolumeClaim .ControllerKey ,
555768 UnitNumber : volume .PersistentVolumeClaim .UnitNumber ,
556- BackingType : pvcObj . GetAnnotations ()[ common . AnnKeyBackingDiskType ] ,
769+ BackingType : backingType ,
557770 VolumeEncrypted : & isPvcEncrypted ,
558771 }
559772 batchAttachRequest = append (batchAttachRequest , currentBatchAttachRequest )
@@ -755,6 +968,53 @@ func patchPVCAnnotations(ctx context.Context, k8sClient kubernetes.Interface,
755968 return nil
756969}
757970
971+ // patchPVCBackingTypeAnnotation updates the BackingType annotation on the PVC.
972+ // This is used to cache the backing type so it doesn't need to be queried again on future attach operations.
973+ func patchPVCBackingTypeAnnotation (ctx context.Context , k8sClient kubernetes.Interface ,
974+ pvc * v1.PersistentVolumeClaim , backingType string ) error {
975+ log := logger .GetLogger (ctx )
976+
977+ patchAnnotations := make (map [string ]interface {})
978+ if pvc .Annotations != nil {
979+ for k , v := range pvc .Annotations {
980+ patchAnnotations [k ] = v
981+ }
982+ }
983+
984+ log .Infof ("Setting BackingType annotation %s=%s on PVC %s" , common .AnnKeyBackingDiskType , backingType , pvc .Name )
985+ patchAnnotations [common .AnnKeyBackingDiskType ] = backingType
986+
987+ // Build patch structure
988+ patch := map [string ]interface {}{
989+ "metadata" : map [string ]interface {}{
990+ "annotations" : patchAnnotations ,
991+ },
992+ }
993+
994+ patchBytes , err := json .Marshal (patch )
995+ if err != nil {
996+ log .Errorf ("failed to marshal BackingType annotation for PVC %s. Err: %s" , pvc .Name , err )
997+ return fmt .Errorf ("failed to marshal patch: %v" , err )
998+ }
999+
1000+ log .Infof ("Patching PVC %s with BackingType annotation" , pvc .Name )
1001+
1002+ // Apply the patch
1003+ updatedpvc , err := k8sClient .CoreV1 ().PersistentVolumeClaims (pvc .Namespace ).Patch (
1004+ ctx ,
1005+ pvc .Name ,
1006+ types .MergePatchType ,
1007+ patchBytes ,
1008+ metav1.PatchOptions {},
1009+ )
1010+ if err != nil {
1011+ log .Errorf ("failed to patch PVC %s with BackingType annotation. Err: %s" , pvc .Name , err )
1012+ return fmt .Errorf ("failed to patch PVC %s: %v" , pvc .Name , err )
1013+ }
1014+ log .Debugf ("Successfully patched PVC: %s with BackingType annotation %+v" , pvc .Name , updatedpvc .Annotations )
1015+ return nil
1016+ }
1017+
7581018// pvcHasUsedByAnnotaion goes through all annotations on the PVC to find out if the PVC is used by any VM or not.
7591019func pvcHasUsedByAnnotaion (ctx context.Context , pvc * v1.PersistentVolumeClaim ) bool {
7601020 log := logger .GetLogger (ctx )
0 commit comments