@@ -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"
@@ -512,12 +514,145 @@ func listAttachedFcdsForVM(ctx context.Context,
512514 return attachedFCDs , nil
513515}
514516
517+ // convertDiskTypeToBackingType converts the diskType returned by QueryVirtualDiskInfo
518+ // to the appropriate CnsVolumeBackingType string used for batch attach operations.
519+ // The returned values correspond to VirtualDevice.FileBackingInfo subclasses as defined in
520+ // github.com/vmware/govmomi/cns/types (CnsVolumeBackingType constants).
521+ func convertDiskTypeToBackingType (diskType string ) string {
522+ switch diskType {
523+ case "thin" , "preallocated" , "thick" , "eagerZeroedThick" , "thick2Gb" , "flatMonolithic" :
524+ // All flat/thick disk types use FlatVer2BackingInfo
525+ // thin -> FlatVer2BackingInfo (thinProvisioned=true)
526+ // preallocated/thick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=false)
527+ // eagerZeroedThick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=true)
528+ // thick2Gb/flatMonolithic -> FlatVer2BackingInfo (split variations)
529+ return string (cnstypes .CnsVolumeBackingTypeFlatVer2BackingInfo )
530+ case "sparse2Gb" , "sparseMonolithic" , "delta" , "vmfsSparse" :
531+ // sparse types -> SparseVer2BackingInfo
532+ return string (cnstypes .CnsVolumeBackingTypeSparseVer2BackingInfo )
533+ case "seSparse" :
534+ // seSparse -> SeSparseBackingInfo
535+ return string (cnstypes .CnsVolumeBackingTypeSeSparseBackingInfo )
536+ case "rdm" , "rdmp" :
537+ // rdm -> RawDiskMappingVer1BackingInfo (compatibilityMode="virtualMode")
538+ // rdmp -> RawDiskMappingVer1BackingInfo (compatibilityMode="physicalMode")
539+ return string (cnstypes .CnsVolumeBackingTypeRawDiskMappingVer1BackingInfo )
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+ // Get VirtualCenter instance
557+ vc , err := cnsvsphere .GetVirtualCenterInstance (ctx , & configInfo , false )
558+ if err != nil {
559+ return "" , fmt .Errorf ("failed to get VirtualCenter instance: %w" , err )
560+ }
561+ err = vc .Connect (ctx )
562+ if err != nil {
563+ return "" , fmt .Errorf ("failed to connect to VirtualCenter: %w" , err )
564+ }
565+
566+ // Get the datacenter
567+ dcs , err := vc .GetDatacenters (ctx )
568+ if err != nil {
569+ return "" , fmt .Errorf ("failed to get datacenters: %w" , err )
570+ }
571+
572+ if len (dcs ) == 0 {
573+ return "" , fmt .Errorf ("no datacenters found" )
574+ }
575+
576+ // Create VirtualDiskManager and query disk info
577+ virtualDiskManager := object .NewVirtualDiskManager (vc .Client .Client )
578+ queryFilterForPath := cnstypes.CnsQueryFilter {
579+ VolumeIds : []cnstypes.CnsVolumeId {
580+ {
581+ Id : volumeID ,
582+ },
583+ },
584+ }
585+
586+ querySelectionForPath := cnstypes.CnsQuerySelection {
587+ Names : []string {string (cnstypes .QuerySelectionNameTypeBackingObjectDetails )},
588+ }
589+
590+ queryResultForPath , err := volumeManager .QueryVolumeAsync (ctx , queryFilterForPath , & querySelectionForPath )
591+ if err != nil {
592+ return "" , fmt .Errorf ("failed to query volume async for backing details %s: %w" , volumeID , err )
593+ }
594+
595+ if queryResultForPath == nil || len (queryResultForPath .Volumes ) == 0 {
596+ return "" , fmt .Errorf ("no volume found for volumeID %s when querying backing details" , volumeID )
597+ }
598+
599+ backingObjectDetails := queryResultForPath .Volumes [0 ].BackingObjectDetails
600+ if backingObjectDetails == nil {
601+ return "" , fmt .Errorf ("backing object details not found for volumeID %s" , volumeID )
602+ }
603+
604+ // Get the backing file path from BackingObjectDetails
605+ // BackingDiskPath is available on CnsBlockBackingDetails
606+ blockBackingDetails , ok := backingObjectDetails .(* cnstypes.CnsBlockBackingDetails )
607+ if ! ok {
608+ return "" , fmt .Errorf ("backing object details is not of type CnsBlockBackingDetails for volumeID %s" , volumeID )
609+ }
610+
611+ backingFilePath := blockBackingDetails .BackingDiskPath
612+ if backingFilePath == "" {
613+ return "" , fmt .Errorf ("backing disk path not found for volumeID %s" , volumeID )
614+ }
615+
616+ log .Debugf ("Retrieved backing disk path %s for volumeID %s" , backingFilePath , volumeID )
617+
618+ // Call QueryVirtualDiskInfo
619+ // Use the first datacenter for the query
620+ var dc * object.Datacenter
621+ if len (dcs ) > 0 {
622+ dc = dcs [0 ].Datacenter
623+ }
624+
625+ diskInfoList , err := virtualDiskManager .QueryVirtualDiskInfo (ctx , backingFilePath , dc , false )
626+ if err != nil {
627+ return "" , fmt .Errorf ("failed to query virtual disk info for %s: %w" , backingFilePath , err )
628+ }
629+
630+ if len (diskInfoList ) == 0 {
631+ return "" , fmt .Errorf ("no disk info returned for %s" , backingFilePath )
632+ }
633+
634+ diskType := diskInfoList [0 ].DiskType
635+ log .Infof ("Retrieved diskType %s for volumeID %s from VirtualDiskManager" , diskType , volumeID )
636+
637+ // Convert diskType to backing type
638+ backingType := convertDiskTypeToBackingType (diskType )
639+ if backingType == "" {
640+ return "" , fmt .Errorf ("unable to find backingType for diskType:%s for the volume %s" ,
641+ diskType , volumeID )
642+ }
643+ log .Infof ("Converted diskType %s to backingType %s for volumeID %s" , diskType , backingType , volumeID )
644+ return backingType , nil
645+ }
646+
515647// constructBatchAttachRequest goes through all volumes in instance spec and
516648// constructs the batchAttach request for each of them.
517649// It also validates each of the requests to make sure user input is correct.
518650func constructBatchAttachRequest (ctx context.Context ,
519651 volumesToAttach map [string ]string ,
520- instance * v1alpha1.CnsNodeVMBatchAttachment ) (pvcsInSpec map [string ]string ,
652+ instance * v1alpha1.CnsNodeVMBatchAttachment ,
653+ volumeManager volumes.Manager ,
654+ configInfo config.ConfigurationInfo ,
655+ k8sClient kubernetes.Interface ) (pvcsInSpec map [string ]string ,
521656 volumeIdsInSpec map [string ]string ,
522657 batchAttachRequest []volumes.BatchAttachRequest , err error ) {
523658 log := logger .GetLogger (ctx )
@@ -552,14 +687,34 @@ func constructBatchAttachRequest(ctx context.Context,
552687 isPvcEncrypted := isPvcEncrypted (pvcObj .Annotations )
553688 log .Infof ("PVC %s has encryption enabled: %t" , pvcName , isPvcEncrypted )
554689
690+ // Get BackingType from PVC annotation, if not available query from VirtualDiskManager
691+ backingType := pvcObj .GetAnnotations ()[common .AnnKeyBackingDiskType ]
692+ if backingType == "" {
693+ log .Infof ("BackingType annotation not found on PVC %s, querying from VirtualDiskManager" , pvcName )
694+ queriedBackingType , queryErr := queryBackingTypeFromVirtualDiskInfo (ctx , volumeManager , configInfo , volumeID )
695+ if queryErr != nil {
696+ log .With ("pvc" , pvcName ).With ("namespace" , instance .Namespace ).Error (queryErr )
697+ return pvcsInSpec , volumeIdsInSpec , batchAttachRequest , queryErr
698+ }
699+ backingType = queriedBackingType
700+ log .Infof ("Successfully retrieved BackingType %s for PVC %s from VirtualDiskManager" ,
701+ backingType , pvcName )
702+ // Update the PVC annotation with the queried BackingType so it can be reused in future attach operations
703+ patchErr := patchPVCBackingTypeAnnotation (ctx , k8sClient , pvcObj , backingType )
704+ if patchErr != nil {
705+ log .With ("pvc" , pvcName ).With ("namespace" , instance .Namespace ).Error (patchErr )
706+ return pvcsInSpec , volumeIdsInSpec , batchAttachRequest , patchErr
707+ }
708+ }
709+
555710 // Populate values for attach request.
556711 currentBatchAttachRequest := volumes.BatchAttachRequest {
557712 VolumeID : volumeID ,
558713 SharingMode : string (volume .PersistentVolumeClaim .SharingMode ),
559714 DiskMode : string (volume .PersistentVolumeClaim .DiskMode ),
560715 ControllerKey : volume .PersistentVolumeClaim .ControllerKey ,
561716 UnitNumber : volume .PersistentVolumeClaim .UnitNumber ,
562- BackingType : pvcObj . GetAnnotations ()[ common . AnnKeyBackingDiskType ] ,
717+ BackingType : backingType ,
563718 VolumeEncrypted : & isPvcEncrypted ,
564719 }
565720 batchAttachRequest = append (batchAttachRequest , currentBatchAttachRequest )
@@ -763,6 +918,54 @@ func patchPVCAnnotations(ctx context.Context, k8sClient kubernetes.Interface,
763918 return nil
764919}
765920
921+ // patchPVCBackingTypeAnnotation updates the BackingType annotation on the PVC.
922+ // This is used to cache the backing type so it doesn't need to be queried again on future attach operations.
923+ func patchPVCBackingTypeAnnotation (ctx context.Context , k8sClient kubernetes.Interface ,
924+ pvc * v1.PersistentVolumeClaim , backingType string ) error {
925+ log := logger .GetLogger (ctx )
926+
927+ patchAnnotations := make (map [string ]interface {})
928+ if pvc .Annotations != nil {
929+ for k , v := range pvc .Annotations {
930+ patchAnnotations [k ] = v
931+ }
932+ }
933+
934+ log .Infof ("Setting BackingType annotation %s=%s on PVC %s" ,
935+ common .AnnKeyBackingDiskType , backingType , pvc .Name )
936+ patchAnnotations [common .AnnKeyBackingDiskType ] = backingType
937+
938+ // Build patch structure
939+ patch := map [string ]interface {}{
940+ "metadata" : map [string ]interface {}{
941+ "annotations" : patchAnnotations ,
942+ },
943+ }
944+
945+ patchBytes , err := json .Marshal (patch )
946+ if err != nil {
947+ log .Errorf ("failed to marshal BackingType annotation for PVC %s. Err: %s" , pvc .Name , err )
948+ return fmt .Errorf ("failed to marshal patch: %v" , err )
949+ }
950+
951+ log .Infof ("Patching PVC %s with BackingType annotation" , pvc .Name )
952+
953+ // Apply the patch
954+ updatedpvc , err := k8sClient .CoreV1 ().PersistentVolumeClaims (pvc .Namespace ).Patch (
955+ ctx ,
956+ pvc .Name ,
957+ types .MergePatchType ,
958+ patchBytes ,
959+ metav1.PatchOptions {},
960+ )
961+ if err != nil {
962+ log .Errorf ("failed to patch PVC %s with BackingType annotation. Err: %s" , pvc .Name , err )
963+ return fmt .Errorf ("failed to patch PVC %s: %v" , pvc .Name , err )
964+ }
965+ log .Infof ("Successfully patched PVC: %s with BackingType annotation %+v" , pvc .Name , updatedpvc .Annotations )
966+ return nil
967+ }
968+
766969// pvcHasUsedByAnnotaion goes through all annotations on the PVC to find out if the PVC is used by any VM or not.
767970func pvcHasUsedByAnnotaion (ctx context.Context , pvc * v1.PersistentVolumeClaim ) bool {
768971 log := logger .GetLogger (ctx )
0 commit comments