Skip to content

Commit 22fd7a7

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 635368f commit 22fd7a7

File tree

4 files changed

+392
-4
lines changed

4 files changed

+392
-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
@@ -538,7 +538,7 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete
538538

539539
// Construct batch attach request
540540
pvcsInAttachList, volumeIdsInAttachList, batchAttachRequest, err := constructBatchAttachRequest(ctx,
541-
volumesToAttach, instance)
541+
volumesToAttach, instance, r.volumeManager, r.configInfo, k8sClient)
542542
if err != nil {
543543
log.Errorf("failed to construct batch attach request. Err: %s", err)
544544
return err

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

Lines changed: 205 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"
@@ -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.
518650
func 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.
767970
func pvcHasUsedByAnnotaion(ctx context.Context, pvc *v1.PersistentVolumeClaim) bool {
768971
log := logger.GetLogger(ctx)

0 commit comments

Comments
 (0)