Skip to content

Commit c87ef5d

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 c87ef5d

File tree

3 files changed

+454
-3
lines changed

3 files changed

+454
-3
lines changed

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: 268 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,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.
512703
func 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.
7591025
func pvcHasUsedByAnnotaion(ctx context.Context, pvc *v1.PersistentVolumeClaim) bool {
7601026
log := logger.GetLogger(ctx)

0 commit comments

Comments
 (0)