Skip to content

Commit aaa0d8b

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 aaa0d8b

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
@@ -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: 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"
@@ -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.
512644
func 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.
759962
func pvcHasUsedByAnnotaion(ctx context.Context, pvc *v1.PersistentVolumeClaim) bool {
760963
log := logger.GetLogger(ctx)

0 commit comments

Comments
 (0)