@@ -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,204 @@ func listAttachedFcdsForVM(ctx context.Context,
506508 return attachedFCDs , nil
507509}
508510
511+ // convertDiskTypeToBackingType converts the diskType returned by QueryVirtualDiskInfo
512+ // to the appropriate backing type string used for attach operations.
513+ // The diskType values are based on vSphere virtual disk provisioning types.
514+ func convertDiskTypeToBackingType (diskType string ) string {
515+ switch diskType {
516+ case "thin" :
517+ // thin -> FlatVer2BackingInfo (thinProvisioned=true)
518+ return "thin"
519+ case "preallocated" , "thick" :
520+ // preallocated/thick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=false)
521+ return "thick"
522+ case "eagerZeroedThick" :
523+ // eagerZeroedThick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=true)
524+ return "eagerZeroedThick"
525+ case "sparse2Gb" , "sparseMonolithic" , "delta" , "vmfsSparse" :
526+ // sparse types -> SparseVer2BackingInfo
527+ return "sparse"
528+ case "thick2Gb" , "flatMonolithic" :
529+ // thick2Gb/flatMonolithic -> FlatVer2BackingInfo (split variations)
530+ return "thick"
531+ case "seSparse" :
532+ // seSparse -> SeSparseBackingInfo
533+ return "seSparse"
534+ case "rdm" :
535+ // rdm -> RawDiskMappingVer1BackingInfo (compatibilityMode="virtualMode")
536+ return "rdm"
537+ case "rdmp" :
538+ // rdmp -> RawDiskMappingVer1BackingInfo (compatibilityMode="physicalMode")
539+ return "rdmp"
540+ default :
541+ // Unknown disk type, return empty string
542+ return ""
543+ }
544+ }
545+
546+ // queryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
547+ // VirtualDiskManager's QueryVirtualDiskInfo API.
548+ // It first queries the volume to get the DatastoreUrl, then uses that URL to call
549+ // QueryVirtualDiskInfo which returns the disk type information.
550+ func queryBackingTypeFromVirtualDiskInfo (ctx context.Context ,
551+ volumeManager volumes.Manager ,
552+ configInfo config.ConfigurationInfo ,
553+ volumeID string ) (string , error ) {
554+ log := logger .GetLogger (ctx )
555+
556+ // Step 1: Query the volume to get the DatastoreUrl
557+ queryFilter := cnstypes.CnsQueryFilter {
558+ VolumeIds : []cnstypes.CnsVolumeId {
559+ {
560+ Id : volumeID ,
561+ },
562+ },
563+ }
564+
565+ querySelection := cnstypes.CnsQuerySelection {
566+ Names : []string {string (cnstypes .QuerySelectionNameTypeDataStoreUrl )},
567+ }
568+
569+ queryResult , err := volumeManager .QueryVolumeAsync (ctx , queryFilter , & querySelection )
570+ if err != nil {
571+ // Fall back to QueryVolume if QueryVolumeAsync is not supported
572+ if err .Error () == cnsvsphere .ErrNotSupported .Error () {
573+ log .Warn ("QueryVolumeAsync is not supported. Invoking QueryVolume API" )
574+ queryResult , err = volumeManager .QueryVolume (ctx , queryFilter )
575+ if err != nil {
576+ return "" , fmt .Errorf ("failed to query volume %s: %w" , volumeID , err )
577+ }
578+ } else {
579+ return "" , fmt .Errorf ("failed to query volume async %s: %w" , volumeID , err )
580+ }
581+ }
582+
583+ if queryResult == nil || len (queryResult .Volumes ) == 0 {
584+ return "" , fmt .Errorf ("no volume found for volumeID %s" , volumeID )
585+ }
586+
587+ datastoreUrl := queryResult .Volumes [0 ].DatastoreUrl
588+ if datastoreUrl == "" {
589+ return "" , fmt .Errorf ("datastore URL not found for volumeID %s" , volumeID )
590+ }
591+
592+ log .Debugf ("Retrieved DatastoreUrl %s for volumeID %s" , datastoreUrl , volumeID )
593+
594+ // Step 2: Get VirtualCenter instance
595+ vc , err := cnsvsphere .GetVirtualCenterInstance (ctx , & configInfo , false )
596+ if err != nil {
597+ return "" , fmt .Errorf ("failed to get VirtualCenter instance: %w" , err )
598+ }
599+
600+ err = vc .Connect (ctx )
601+ if err != nil {
602+ return "" , fmt .Errorf ("failed to connect to VirtualCenter: %w" , err )
603+ }
604+
605+ // Step 3: Get the datacenter
606+ dcs , err := vc .GetDatacenters (ctx )
607+ if err != nil {
608+ return "" , fmt .Errorf ("failed to get datacenters: %w" , err )
609+ }
610+
611+ if len (dcs ) == 0 {
612+ return "" , fmt .Errorf ("no datacenters found" )
613+ }
614+
615+ // Step 4: Create VirtualDiskManager and query disk info
616+ virtualDiskManager := object .NewVirtualDiskManager (vc .Client .Client )
617+
618+ // The name parameter for QueryVirtualDiskInfo should be the datastore path
619+ // Format: [datastoreName] path/to/disk.vmdk
620+ // Since we have the DatastoreUrl, we need to construct the disk path
621+ // The DatastoreUrl format is: ds:///vmfs/volumes/<datastore-uuid>/
622+ // We need to query the backing object details to get the file path
623+ queryFilterForPath := cnstypes.CnsQueryFilter {
624+ VolumeIds : []cnstypes.CnsVolumeId {
625+ {
626+ Id : volumeID ,
627+ },
628+ },
629+ }
630+
631+ querySelectionForPath := cnstypes.CnsQuerySelection {
632+ Names : []string {string (cnstypes .QuerySelectionNameTypeBackingObjectDetails )},
633+ }
634+
635+ queryResultForPath , err := volumeManager .QueryVolumeAsync (ctx , queryFilterForPath , & querySelectionForPath )
636+ if err != nil {
637+ if err .Error () == cnsvsphere .ErrNotSupported .Error () {
638+ queryResultForPath , err = volumeManager .QueryVolume (ctx , queryFilterForPath )
639+ if err != nil {
640+ return "" , fmt .Errorf ("failed to query volume for backing details %s: %w" , volumeID , err )
641+ }
642+ } else {
643+ return "" , fmt .Errorf ("failed to query volume async for backing details %s: %w" , volumeID , err )
644+ }
645+ }
646+
647+ if queryResultForPath == nil || len (queryResultForPath .Volumes ) == 0 {
648+ return "" , fmt .Errorf ("no volume found for volumeID %s when querying backing details" , volumeID )
649+ }
650+
651+ backingObjectDetails := queryResultForPath .Volumes [0 ].BackingObjectDetails
652+ if backingObjectDetails == nil {
653+ return "" , fmt .Errorf ("backing object details not found for volumeID %s" , volumeID )
654+ }
655+
656+ // Get the backing file path from BackingObjectDetails
657+ // BackingDiskPath is available on CnsBlockBackingDetails
658+ blockBackingDetails , ok := backingObjectDetails .(* cnstypes.CnsBlockBackingDetails )
659+ if ! ok {
660+ return "" , fmt .Errorf ("backing object details is not of type CnsBlockBackingDetails for volumeID %s" , volumeID )
661+ }
662+
663+ backingFilePath := blockBackingDetails .BackingDiskPath
664+ if backingFilePath == "" {
665+ return "" , fmt .Errorf ("backing disk path not found for volumeID %s" , volumeID )
666+ }
667+
668+ log .Debugf ("Retrieved backing disk path %s for volumeID %s" , backingFilePath , volumeID )
669+
670+ // Step 5: Call QueryVirtualDiskInfo
671+ // Use the first datacenter for the query
672+ var dc * object.Datacenter
673+ if len (dcs ) > 0 {
674+ dc = dcs [0 ].Datacenter
675+ }
676+
677+ diskInfoList , err := virtualDiskManager .QueryVirtualDiskInfo (ctx , backingFilePath , dc , false )
678+ if err != nil {
679+ return "" , fmt .Errorf ("failed to query virtual disk info for %s: %w" , backingFilePath , err )
680+ }
681+
682+ if len (diskInfoList ) == 0 {
683+ return "" , fmt .Errorf ("no disk info returned for %s" , backingFilePath )
684+ }
685+
686+ diskType := diskInfoList [0 ].DiskType
687+ log .Infof ("Retrieved diskType %s for volumeID %s from VirtualDiskManager" , diskType , volumeID )
688+
689+ // Step 6: Convert diskType to backing type
690+ backingType := convertDiskTypeToBackingType (diskType )
691+ if backingType == "" {
692+ log .Warnf ("Unknown diskType %s for volumeID %s, using diskType as backingType" , diskType , volumeID )
693+ backingType = diskType
694+ }
695+
696+ log .Infof ("Converted diskType %s to backingType %s for volumeID %s" , diskType , backingType , volumeID )
697+ return backingType , nil
698+ }
699+
509700// constructBatchAttachRequest goes through all volumes in instance spec and
510701// constructs the batchAttach request for each of them.
511702// It also validates each of the requests to make sure user input is correct.
512703func constructBatchAttachRequest (ctx context.Context ,
513704 volumesToAttach map [string ]string ,
514- instance * v1alpha1.CnsNodeVMBatchAttachment ) (pvcsInSpec map [string ]string ,
705+ instance * v1alpha1.CnsNodeVMBatchAttachment ,
706+ volumeManager volumes.Manager ,
707+ configInfo config.ConfigurationInfo ,
708+ k8sClient kubernetes.Interface ) (pvcsInSpec map [string ]string ,
515709 volumeIdsInSpec map [string ]string ,
516710 batchAttachRequest []volumes.BatchAttachRequest , err error ) {
517711 log := logger .GetLogger (ctx )
@@ -546,14 +740,39 @@ func constructBatchAttachRequest(ctx context.Context,
546740 isPvcEncrypted := isPvcEncrypted (pvcObj .Annotations )
547741 log .Infof ("PVC %s has encryption enabled: %t" , pvcName , isPvcEncrypted )
548742
743+ // Get BackingType from PVC annotation, if not available query from VirtualDiskManager
744+ backingType := pvcObj .GetAnnotations ()[common .AnnKeyBackingDiskType ]
745+ if backingType == "" {
746+ log .Infof ("BackingType annotation not found on PVC %s, querying from VirtualDiskManager" , pvcName )
747+ queriedBackingType , queryErr := queryBackingTypeFromVirtualDiskInfo (ctx , volumeManager , configInfo , volumeID )
748+ if queryErr != nil {
749+ log .Warnf ("Failed to query BackingType from VirtualDiskManager for volumeID %s: %v. " +
750+ "Proceeding with empty BackingType" , volumeID , queryErr )
751+ } else {
752+ backingType = queriedBackingType
753+ log .Infof ("Successfully retrieved BackingType %s for PVC %s from VirtualDiskManager" ,
754+ backingType , pvcName )
755+
756+ // Update the PVC annotation with the queried BackingType so it can be reused in future attach operations
757+ patchErr := patchPVCBackingTypeAnnotation (ctx , k8sClient , pvcObj , backingType )
758+ if patchErr != nil {
759+ log .Warnf ("Failed to update BackingType annotation on PVC %s: %v. " +
760+ "The BackingType will be queried again on next attach" , pvcName , patchErr )
761+ } else {
762+ log .Infof ("Successfully updated BackingType annotation %s on PVC %s" ,
763+ backingType , pvcName )
764+ }
765+ }
766+ }
767+
549768 // Populate values for attach request.
550769 currentBatchAttachRequest := volumes.BatchAttachRequest {
551770 VolumeID : volumeID ,
552771 SharingMode : string (volume .PersistentVolumeClaim .SharingMode ),
553772 DiskMode : string (volume .PersistentVolumeClaim .DiskMode ),
554773 ControllerKey : volume .PersistentVolumeClaim .ControllerKey ,
555774 UnitNumber : volume .PersistentVolumeClaim .UnitNumber ,
556- BackingType : pvcObj . GetAnnotations ()[ common . AnnKeyBackingDiskType ] ,
775+ BackingType : backingType ,
557776 VolumeEncrypted : & isPvcEncrypted ,
558777 }
559778 batchAttachRequest = append (batchAttachRequest , currentBatchAttachRequest )
@@ -755,6 +974,53 @@ func patchPVCAnnotations(ctx context.Context, k8sClient kubernetes.Interface,
755974 return nil
756975}
757976
977+ // patchPVCBackingTypeAnnotation updates the BackingType annotation on the PVC.
978+ // This is used to cache the backing type so it doesn't need to be queried again on future attach operations.
979+ func patchPVCBackingTypeAnnotation (ctx context.Context , k8sClient kubernetes.Interface ,
980+ pvc * v1.PersistentVolumeClaim , backingType string ) error {
981+ log := logger .GetLogger (ctx )
982+
983+ patchAnnotations := make (map [string ]interface {})
984+ if pvc .Annotations != nil {
985+ for k , v := range pvc .Annotations {
986+ patchAnnotations [k ] = v
987+ }
988+ }
989+
990+ log .Infof ("Setting BackingType annotation %s=%s on PVC %s" , common .AnnKeyBackingDiskType , backingType , pvc .Name )
991+ patchAnnotations [common .AnnKeyBackingDiskType ] = backingType
992+
993+ // Build patch structure
994+ patch := map [string ]interface {}{
995+ "metadata" : map [string ]interface {}{
996+ "annotations" : patchAnnotations ,
997+ },
998+ }
999+
1000+ patchBytes , err := json .Marshal (patch )
1001+ if err != nil {
1002+ log .Errorf ("failed to marshal BackingType annotation for PVC %s. Err: %s" , pvc .Name , err )
1003+ return fmt .Errorf ("failed to marshal patch: %v" , err )
1004+ }
1005+
1006+ log .Infof ("Patching PVC %s with BackingType annotation" , pvc .Name )
1007+
1008+ // Apply the patch
1009+ updatedpvc , err := k8sClient .CoreV1 ().PersistentVolumeClaims (pvc .Namespace ).Patch (
1010+ ctx ,
1011+ pvc .Name ,
1012+ types .MergePatchType ,
1013+ patchBytes ,
1014+ metav1.PatchOptions {},
1015+ )
1016+ if err != nil {
1017+ log .Errorf ("failed to patch PVC %s with BackingType annotation. Err: %s" , pvc .Name , err )
1018+ return fmt .Errorf ("failed to patch PVC %s: %v" , pvc .Name , err )
1019+ }
1020+ log .Debugf ("Successfully patched PVC: %s with BackingType annotation %+v" , pvc .Name , updatedpvc .Annotations )
1021+ return nil
1022+ }
1023+
7581024// pvcHasUsedByAnnotaion goes through all annotations on the PVC to find out if the PVC is used by any VM or not.
7591025func pvcHasUsedByAnnotaion (ctx context.Context , pvc * v1.PersistentVolumeClaim ) bool {
7601026 log := logger .GetLogger (ctx )
0 commit comments