@@ -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,145 @@ 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+ // Get VirtualCenter instance
551+ vc , err := cnsvsphere .GetVirtualCenterInstance (ctx , & configInfo , false )
552+ if err != nil {
553+ return "" , fmt .Errorf ("failed to get VirtualCenter instance: %w" , err )
554+ }
555+ err = vc .Connect (ctx )
556+ if err != nil {
557+ return "" , fmt .Errorf ("failed to connect to VirtualCenter: %w" , err )
558+ }
559+
560+ // Get the datacenter
561+ dcs , err := vc .GetDatacenters (ctx )
562+ if err != nil {
563+ return "" , fmt .Errorf ("failed to get datacenters: %w" , err )
564+ }
565+
566+ if len (dcs ) == 0 {
567+ return "" , fmt .Errorf ("no datacenters found" )
568+ }
569+
570+ // Create VirtualDiskManager and query disk info
571+ virtualDiskManager := object .NewVirtualDiskManager (vc .Client .Client )
572+ queryFilterForPath := cnstypes.CnsQueryFilter {
573+ VolumeIds : []cnstypes.CnsVolumeId {
574+ {
575+ Id : volumeID ,
576+ },
577+ },
578+ }
579+
580+ querySelectionForPath := cnstypes.CnsQuerySelection {
581+ Names : []string {string (cnstypes .QuerySelectionNameTypeBackingObjectDetails )},
582+ }
583+
584+ queryResultForPath , err := volumeManager .QueryVolumeAsync (ctx , queryFilterForPath , & querySelectionForPath )
585+ if err != nil {
586+ return "" , fmt .Errorf ("failed to query volume async for backing details %s: %w" , volumeID , err )
587+ }
588+
589+ if queryResultForPath == nil || len (queryResultForPath .Volumes ) == 0 {
590+ return "" , fmt .Errorf ("no volume found for volumeID %s when querying backing details" , volumeID )
591+ }
592+
593+ backingObjectDetails := queryResultForPath .Volumes [0 ].BackingObjectDetails
594+ if backingObjectDetails == nil {
595+ return "" , fmt .Errorf ("backing object details not found for volumeID %s" , volumeID )
596+ }
597+
598+ // Get the backing file path from BackingObjectDetails
599+ // BackingDiskPath is available on CnsBlockBackingDetails
600+ blockBackingDetails , ok := backingObjectDetails .(* cnstypes.CnsBlockBackingDetails )
601+ if ! ok {
602+ return "" , fmt .Errorf ("backing object details is not of type CnsBlockBackingDetails for volumeID %s" , volumeID )
603+ }
604+
605+ backingFilePath := blockBackingDetails .BackingDiskPath
606+ if backingFilePath == "" {
607+ return "" , fmt .Errorf ("backing disk path not found for volumeID %s" , volumeID )
608+ }
609+
610+ log .Debugf ("Retrieved backing disk path %s for volumeID %s" , backingFilePath , volumeID )
611+
612+ // Call QueryVirtualDiskInfo
613+ // Use the first datacenter for the query
614+ var dc * object.Datacenter
615+ if len (dcs ) > 0 {
616+ dc = dcs [0 ].Datacenter
617+ }
618+
619+ diskInfoList , err := virtualDiskManager .QueryVirtualDiskInfo (ctx , backingFilePath , dc , false )
620+ if err != nil {
621+ return "" , fmt .Errorf ("failed to query virtual disk info for %s: %w" , backingFilePath , err )
622+ }
623+
624+ if len (diskInfoList ) == 0 {
625+ return "" , fmt .Errorf ("no disk info returned for %s" , backingFilePath )
626+ }
627+
628+ diskType := diskInfoList [0 ].DiskType
629+ log .Infof ("Retrieved diskType %s for volumeID %s from VirtualDiskManager" , diskType , volumeID )
630+
631+ // Convert diskType to backing type
632+ backingType := convertDiskTypeToBackingType (diskType )
633+ if backingType == "" {
634+ return "" , fmt .Errorf ("unable to find backingType for diskType:%s for the volume %s" ,
635+ diskType , volumeID )
636+ }
637+ log .Infof ("Converted diskType %s to backingType %s for volumeID %s" , diskType , backingType , volumeID )
638+ return backingType , nil
639+ }
640+
509641// constructBatchAttachRequest goes through all volumes in instance spec and
510642// constructs the batchAttach request for each of them.
511643// It also validates each of the requests to make sure user input is correct.
512644func constructBatchAttachRequest (ctx context.Context ,
513645 volumesToAttach map [string ]string ,
514- instance * v1alpha1.CnsNodeVMBatchAttachment ) (pvcsInSpec map [string ]string ,
646+ instance * v1alpha1.CnsNodeVMBatchAttachment ,
647+ volumeManager volumes.Manager ,
648+ configInfo config.ConfigurationInfo ,
649+ k8sClient kubernetes.Interface ) (pvcsInSpec map [string ]string ,
515650 volumeIdsInSpec map [string ]string ,
516651 batchAttachRequest []volumes.BatchAttachRequest , err error ) {
517652 log := logger .GetLogger (ctx )
@@ -546,14 +681,34 @@ func constructBatchAttachRequest(ctx context.Context,
546681 isPvcEncrypted := isPvcEncrypted (pvcObj .Annotations )
547682 log .Infof ("PVC %s has encryption enabled: %t" , pvcName , isPvcEncrypted )
548683
684+ // Get BackingType from PVC annotation, if not available query from VirtualDiskManager
685+ backingType := pvcObj .GetAnnotations ()[common .AnnKeyBackingDiskType ]
686+ if backingType == "" {
687+ log .Infof ("BackingType annotation not found on PVC %s, querying from VirtualDiskManager" , pvcName )
688+ queriedBackingType , queryErr := queryBackingTypeFromVirtualDiskInfo (ctx , volumeManager , configInfo , volumeID )
689+ if queryErr != nil {
690+ log .With ("pvc" , pvcName ).With ("namespace" , instance .Namespace ).Error (queryErr )
691+ return pvcsInSpec , volumeIdsInSpec , batchAttachRequest , queryErr
692+ }
693+ backingType = queriedBackingType
694+ log .Infof ("Successfully retrieved BackingType %s for PVC %s from VirtualDiskManager" ,
695+ backingType , pvcName )
696+ // Update the PVC annotation with the queried BackingType so it can be reused in future attach operations
697+ patchErr := patchPVCBackingTypeAnnotation (ctx , k8sClient , pvcObj , backingType )
698+ if patchErr != nil {
699+ log .With ("pvc" , pvcName ).With ("namespace" , instance .Namespace ).Error (patchErr )
700+ return pvcsInSpec , volumeIdsInSpec , batchAttachRequest , patchErr
701+ }
702+ }
703+
549704 // Populate values for attach request.
550705 currentBatchAttachRequest := volumes.BatchAttachRequest {
551706 VolumeID : volumeID ,
552707 SharingMode : string (volume .PersistentVolumeClaim .SharingMode ),
553708 DiskMode : string (volume .PersistentVolumeClaim .DiskMode ),
554709 ControllerKey : volume .PersistentVolumeClaim .ControllerKey ,
555710 UnitNumber : volume .PersistentVolumeClaim .UnitNumber ,
556- BackingType : pvcObj . GetAnnotations ()[ common . AnnKeyBackingDiskType ] ,
711+ BackingType : backingType ,
557712 VolumeEncrypted : & isPvcEncrypted ,
558713 }
559714 batchAttachRequest = append (batchAttachRequest , currentBatchAttachRequest )
@@ -755,6 +910,54 @@ func patchPVCAnnotations(ctx context.Context, k8sClient kubernetes.Interface,
755910 return nil
756911}
757912
913+ // patchPVCBackingTypeAnnotation updates the BackingType annotation on the PVC.
914+ // This is used to cache the backing type so it doesn't need to be queried again on future attach operations.
915+ func patchPVCBackingTypeAnnotation (ctx context.Context , k8sClient kubernetes.Interface ,
916+ pvc * v1.PersistentVolumeClaim , backingType string ) error {
917+ log := logger .GetLogger (ctx )
918+
919+ patchAnnotations := make (map [string ]interface {})
920+ if pvc .Annotations != nil {
921+ for k , v := range pvc .Annotations {
922+ patchAnnotations [k ] = v
923+ }
924+ }
925+
926+ log .Infof ("Setting BackingType annotation %s=%s on PVC %s" ,
927+ common .AnnKeyBackingDiskType , backingType , pvc .Name )
928+ patchAnnotations [common .AnnKeyBackingDiskType ] = backingType
929+
930+ // Build patch structure
931+ patch := map [string ]interface {}{
932+ "metadata" : map [string ]interface {}{
933+ "annotations" : patchAnnotations ,
934+ },
935+ }
936+
937+ patchBytes , err := json .Marshal (patch )
938+ if err != nil {
939+ log .Errorf ("failed to marshal BackingType annotation for PVC %s. Err: %s" , pvc .Name , err )
940+ return fmt .Errorf ("failed to marshal patch: %v" , err )
941+ }
942+
943+ log .Infof ("Patching PVC %s with BackingType annotation" , pvc .Name )
944+
945+ // Apply the patch
946+ updatedpvc , err := k8sClient .CoreV1 ().PersistentVolumeClaims (pvc .Namespace ).Patch (
947+ ctx ,
948+ pvc .Name ,
949+ types .MergePatchType ,
950+ patchBytes ,
951+ metav1.PatchOptions {},
952+ )
953+ if err != nil {
954+ log .Errorf ("failed to patch PVC %s with BackingType annotation. Err: %s" , pvc .Name , err )
955+ return fmt .Errorf ("failed to patch PVC %s: %v" , pvc .Name , err )
956+ }
957+ log .Infof ("Successfully patched PVC: %s with BackingType annotation %+v" , pvc .Name , updatedpvc .Annotations )
958+ return nil
959+ }
960+
758961// pvcHasUsedByAnnotaion goes through all annotations on the PVC to find out if the PVC is used by any VM or not.
759962func pvcHasUsedByAnnotaion (ctx context.Context , pvc * v1.PersistentVolumeClaim ) bool {
760963 log := logger .GetLogger (ctx )
0 commit comments