Skip to content

Commit 261e4f1

Browse files
committed
Add dynamic BackingType detection via VirtualDiskManager
When the disk-backing annotation is not present on a PVC, dynamically query the backing type from vSphere VirtualDiskManager API and cache it on the PVC annotation for future attach operations. Changes: - Add queryBackingTypeFromVirtualDiskInfo() to query disk type via CNS QueryVolume API and VirtualDiskManager.QueryVirtualDiskInfo() - Add convertDiskTypeToBackingType() to map vSphere disk types (thin, preallocated, eagerZeroedThick, etc.) to backing types - Add patchPVCBackingTypeAnnotation() to persist queried backing type on PVC annotation for caching - Update constructBatchAttachRequest() to query and cache backing type when annotation is missing - Add unit tests for convertDiskTypeToBackingType and patchPVCBackingTypeAnnotation functions
1 parent c05fac3 commit 261e4f1

File tree

4 files changed

+449
-4
lines changed

4 files changed

+449
-4
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/go-co-op/gocron v1.37.0
1515
github.com/go-logr/zapr v1.3.0
1616
github.com/golang/protobuf v1.5.4
17+
github.com/google/go-cmp v0.7.0
1718
github.com/google/uuid v1.6.0
1819
github.com/hashicorp/go-version v1.6.0
1920
github.com/kubernetes-csi/csi-proxy/v2 v2.0.0-alpha.1
@@ -100,7 +101,6 @@ require (
100101
github.com/google/btree v1.1.3 // indirect
101102
github.com/google/cadvisor v0.52.1 // indirect
102103
github.com/google/cel-go v0.26.0 // indirect
103-
github.com/google/go-cmp v0.7.0 // indirect
104104
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
105105
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
106106
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect

pkg/syncer/cnsoperator/controller/cnsnodevmbatchattachment/cnsnodevmbatchattachment_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete
524524

525525
// Construct batch attach request
526526
pvcsInAttachList, volumeIdsInAttachList, batchAttachRequest, err := constructBatchAttachRequest(ctx,
527-
volumesToAttach, instance)
527+
volumesToAttach, instance, r.volumeManager, r.configInfo, k8sClient)
528528
if err != nil {
529529
log.Errorf("failed to construct batch attach request. Err: %s", err)
530530
return err

pkg/syncer/cnsoperator/controller/cnsnodevmbatchattachment/cnsnodevmbatchattachment_helper.go

Lines changed: 262 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
512697
func 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.
7591019
func pvcHasUsedByAnnotaion(ctx context.Context, pvc *v1.PersistentVolumeClaim) bool {
7601020
log := logger.GetLogger(ctx)

0 commit comments

Comments
 (0)