Skip to content

Commit 0ebdcf1

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 0ebdcf1

File tree

12 files changed

+477
-5
lines changed

12 files changed

+477
-5
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/common/cns-lib/volume/manager.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ type Manager interface {
160160
UnregisterVolume(ctx context.Context, volumeID string, unregisterDisk bool) (string, error)
161161
// SyncVolume returns the aggregated capacity for volumes
162162
SyncVolume(ctx context.Context, syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error)
163+
// QueryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
164+
// VirtualDiskManager's QueryVirtualDiskInfo API.
165+
QueryBackingTypeFromVirtualDiskInfo(ctx context.Context, volumeID string) (string, error)
163166
}
164167

165168
// CnsVolumeInfo hold information related to volume created by CNS.
@@ -3830,3 +3833,81 @@ func (m *defaultManager) unregisterVolume(ctx context.Context, volumeID string,
38303833
log.Infof("volume %q unregistered successfully", volumeID)
38313834
return "", nil
38323835
}
3836+
3837+
// QueryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
3838+
// VirtualDiskManager's QueryVirtualDiskInfo API.
3839+
// It first queries the volume to get the BackingDiskPath, then uses that path to call
3840+
// QueryVirtualDiskInfo which returns the disk type information.
3841+
func (m *defaultManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
3842+
volumeID string) (string, error) {
3843+
log := logger.GetLogger(ctx)
3844+
3845+
// Get the datacenters
3846+
dcs, err := m.virtualCenter.GetDatacenters(ctx)
3847+
if err != nil {
3848+
return "", fmt.Errorf("failed to get datacenters: %w", err)
3849+
}
3850+
if len(dcs) == 0 {
3851+
return "", fmt.Errorf("no datacenters found")
3852+
}
3853+
3854+
// Query volume to get the backing disk path
3855+
queryFilter := cnstypes.CnsQueryFilter{
3856+
VolumeIds: []cnstypes.CnsVolumeId{{Id: volumeID}},
3857+
}
3858+
querySelection := cnstypes.CnsQuerySelection{
3859+
Names: []string{string(cnstypes.QuerySelectionNameTypeBackingObjectDetails)},
3860+
}
3861+
3862+
queryResult, err := m.QueryVolumeAsync(ctx, queryFilter, &querySelection)
3863+
if err != nil {
3864+
return "", fmt.Errorf("failed to query volume for backing details %s: %w", volumeID, err)
3865+
}
3866+
if queryResult == nil || len(queryResult.Volumes) == 0 {
3867+
return "", fmt.Errorf("no volume found for volumeID %s", volumeID)
3868+
}
3869+
3870+
backingObjectDetails := queryResult.Volumes[0].BackingObjectDetails
3871+
if backingObjectDetails == nil {
3872+
return "", fmt.Errorf("backing object details not found for volumeID %s", volumeID)
3873+
}
3874+
3875+
blockBackingDetails, ok := backingObjectDetails.(*cnstypes.CnsBlockBackingDetails)
3876+
if !ok {
3877+
return "", fmt.Errorf("backing object details is not of type CnsBlockBackingDetails for volumeID %s", volumeID)
3878+
}
3879+
3880+
backingFilePath := blockBackingDetails.BackingDiskPath
3881+
if backingFilePath == "" {
3882+
return "", fmt.Errorf("backing disk path not found for volumeID %s", volumeID)
3883+
}
3884+
3885+
// Get the vim25 client that uses standard vim25 namespace.
3886+
// This is required because QueryVirtualDiskInfo uses internal vim25 namespace
3887+
// which doesn't work with the vsan service version used by the main client.
3888+
vim25Client, err := m.virtualCenter.GetVim25Client(ctx)
3889+
if err != nil {
3890+
return "", fmt.Errorf("failed to get vim25 client: %w", err)
3891+
}
3892+
3893+
// Query virtual disk info using the datacenter we already have
3894+
virtualDiskManager := object.NewVirtualDiskManager(vim25Client)
3895+
diskInfoList, err := virtualDiskManager.QueryVirtualDiskInfo(ctx, backingFilePath, dcs[0].Datacenter, false)
3896+
if err != nil {
3897+
return "", fmt.Errorf("failed to query virtual disk info for %s: %w", backingFilePath, err)
3898+
}
3899+
if len(diskInfoList) == 0 {
3900+
return "", fmt.Errorf("no disk info returned for %s", backingFilePath)
3901+
}
3902+
3903+
diskType := diskInfoList[0].DiskType
3904+
log.Debugf("Retrieved diskType %s for volumeID %s", diskType, volumeID)
3905+
3906+
// Convert diskType to backing type
3907+
backingType := ConvertDiskTypeToBackingType(diskType)
3908+
if backingType == "" {
3909+
return "", fmt.Errorf("unable to find backingType for diskType:%s for the volume %s",
3910+
diskType, volumeID)
3911+
}
3912+
return backingType, nil
3913+
}

pkg/common/cns-lib/volume/manager_mock.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,9 @@ func (m MockManager) SyncVolume(ctx context.Context, syncVolumeSpecs []cnstypes.
199199
//TODO implement me
200200
panic("implement me")
201201
}
202+
203+
func (m MockManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
204+
volumeID string) (string, error) {
205+
//TODO implement me
206+
panic("implement me")
207+
}

pkg/common/cns-lib/volume/util.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,3 +632,32 @@ func IsCnsVolumeAlreadyExistsFault(ctx context.Context, faultType string) bool {
632632
log.Infof("Checking fault type: %q is vim.fault.CnsVolumeAlreadyExistsFault", faultType)
633633
return faultType == "vim.fault.CnsVolumeAlreadyExistsFault"
634634
}
635+
636+
// ConvertDiskTypeToBackingType converts the diskType returned by QueryVirtualDiskInfo
637+
// to the appropriate CnsVolumeBackingType string used for batch attach operations.
638+
// The returned values correspond to VirtualDevice.FileBackingInfo subclasses as defined in
639+
// github.com/vmware/govmomi/cns/types (CnsVolumeBackingType constants).
640+
func ConvertDiskTypeToBackingType(diskType string) string {
641+
switch diskType {
642+
case "thin", "preallocated", "thick", "eagerZeroedThick", "thick2Gb", "flatMonolithic":
643+
// All flat/thick disk types use FlatVer2BackingInfo
644+
// thin -> FlatVer2BackingInfo (thinProvisioned=true)
645+
// preallocated/thick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=false)
646+
// eagerZeroedThick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=true)
647+
// thick2Gb/flatMonolithic -> FlatVer2BackingInfo (split variations)
648+
return string(cnstypes.CnsVolumeBackingTypeFlatVer2BackingInfo)
649+
case "sparse2Gb", "sparseMonolithic", "delta", "vmfsSparse":
650+
// sparse types -> SparseVer2BackingInfo
651+
return string(cnstypes.CnsVolumeBackingTypeSparseVer2BackingInfo)
652+
case "seSparse":
653+
// seSparse -> SeSparseBackingInfo
654+
return string(cnstypes.CnsVolumeBackingTypeSeSparseBackingInfo)
655+
case "rdm", "rdmp":
656+
// rdm -> RawDiskMappingVer1BackingInfo (compatibilityMode="virtualMode")
657+
// rdmp -> RawDiskMappingVer1BackingInfo (compatibilityMode="physicalMode")
658+
return string(cnstypes.CnsVolumeBackingTypeRawDiskMappingVer1BackingInfo)
659+
default:
660+
// Unknown disk type, return empty string
661+
return ""
662+
}
663+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package volume
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestConvertDiskTypeToBackingType(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
diskType string
29+
want string
30+
}{
31+
{
32+
name: "thin disk type",
33+
diskType: "thin",
34+
want: "FlatVer2BackingInfo",
35+
},
36+
{
37+
name: "preallocated disk type",
38+
diskType: "preallocated",
39+
want: "FlatVer2BackingInfo",
40+
},
41+
{
42+
name: "thick disk type",
43+
diskType: "thick",
44+
want: "FlatVer2BackingInfo",
45+
},
46+
{
47+
name: "eagerZeroedThick disk type",
48+
diskType: "eagerZeroedThick",
49+
want: "FlatVer2BackingInfo",
50+
},
51+
{
52+
name: "sparse2Gb disk type",
53+
diskType: "sparse2Gb",
54+
want: "SparseVer2BackingInfo",
55+
},
56+
{
57+
name: "sparseMonolithic disk type",
58+
diskType: "sparseMonolithic",
59+
want: "SparseVer2BackingInfo",
60+
},
61+
{
62+
name: "delta disk type",
63+
diskType: "delta",
64+
want: "SparseVer2BackingInfo",
65+
},
66+
{
67+
name: "vmfsSparse disk type",
68+
diskType: "vmfsSparse",
69+
want: "SparseVer2BackingInfo",
70+
},
71+
{
72+
name: "thick2Gb disk type",
73+
diskType: "thick2Gb",
74+
want: "FlatVer2BackingInfo",
75+
},
76+
{
77+
name: "flatMonolithic disk type",
78+
diskType: "flatMonolithic",
79+
want: "FlatVer2BackingInfo",
80+
},
81+
{
82+
name: "seSparse disk type",
83+
diskType: "seSparse",
84+
want: "SeSparseBackingInfo",
85+
},
86+
{
87+
name: "rdm disk type",
88+
diskType: "rdm",
89+
want: "RawDiskMappingVer1BackingInfo",
90+
},
91+
{
92+
name: "rdmp disk type",
93+
diskType: "rdmp",
94+
want: "RawDiskMappingVer1BackingInfo",
95+
},
96+
{
97+
name: "unknown disk type",
98+
diskType: "unknown",
99+
want: "",
100+
},
101+
{
102+
name: "empty disk type",
103+
diskType: "",
104+
want: "",
105+
},
106+
}
107+
108+
for _, tt := range tests {
109+
t.Run(tt.name, func(t *testing.T) {
110+
got := ConvertDiskTypeToBackingType(tt.diskType)
111+
assert.Equal(t, tt.want, got)
112+
})
113+
}
114+
}

pkg/common/cns-lib/vsphere/virtualcenter.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ type VirtualCenter struct {
7676
VsanClient *vsan.Client
7777
// VslmClient represents the Vslm client instance.
7878
VslmClient *vslm.Client
79+
// Vim25Client is a vim25.Client that uses the standard vim25 namespace.
80+
// This is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that
81+
// use internal vim25 namespaces and don't work with the vsan service version.
82+
// It shares the same http.Transport as the main Client but uses vim25.Path and vim25.Namespace.
83+
Vim25Client *vim25.Client
7984
// ClientMutex is used for exclusive connection creation.
8085
ClientMutex *sync.Mutex
8186
}
@@ -386,9 +391,50 @@ func (vc *VirtualCenter) connect(ctx context.Context) error {
386391
}
387392
vc.VsanClient.RoundTripper = &MetricRoundTripper{"vsan", vc.VsanClient.RoundTripper}
388393
}
394+
// Recreate Vim25Client if created using timed out VC Client.
395+
if vc.Vim25Client != nil {
396+
vc.Vim25Client = vc.newVim25Client()
397+
}
389398
return nil
390399
}
391400

401+
// newVim25Client creates a vim25.Client that uses the standard vim25 namespace.
402+
// This is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that use
403+
// internal vim25 namespaces and don't work with the vsan service version.
404+
func (vc *VirtualCenter) newVim25Client() *vim25.Client {
405+
soapClient := vc.Client.Client.Client
406+
vimSoapClient := soapClient.NewServiceClient(vim25.Path, vim25.Namespace)
407+
vimSoapClient.Version = vc.Client.Client.ServiceContent.About.ApiVersion
408+
return &vim25.Client{
409+
Client: vimSoapClient,
410+
ServiceContent: vc.Client.Client.ServiceContent,
411+
RoundTripper: vimSoapClient,
412+
}
413+
}
414+
415+
// GetVim25Client returns a vim25.Client that uses the standard vim25 namespace.
416+
// This client is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that
417+
// use internal vim25 namespaces and don't work with the vsan service version.
418+
// The client is created lazily on first call and reused for subsequent calls.
419+
// It shares the same http.Transport as the main Client.
420+
func (vc *VirtualCenter) GetVim25Client(ctx context.Context) (*vim25.Client, error) {
421+
log := logger.GetLogger(ctx)
422+
423+
// Ensure connection is established
424+
if err := vc.Connect(ctx); err != nil {
425+
return nil, err
426+
}
427+
428+
vc.ClientMutex.Lock()
429+
defer vc.ClientMutex.Unlock()
430+
431+
if vc.Vim25Client == nil {
432+
log.Info("Creating Vim25Client for standard vim25 namespace operations")
433+
vc.Vim25Client = vc.newVim25Client()
434+
}
435+
return vc.Vim25Client, nil
436+
}
437+
392438
// ReadVCConfigs will ensure we are always reading the latest config
393439
// before attempting to create a new govmomi client.
394440
// It works in case of both vanilla (including multi-vc) and wcp

pkg/common/unittestcommon/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,8 @@ func (m *MockVolumeManager) SyncVolume(ctx context.Context,
224224
syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error) {
225225
return "", nil
226226
}
227+
228+
func (m *MockVolumeManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
229+
volumeID string) (string, error) {
230+
return "", nil
231+
}

pkg/csi/service/common/vsphereutil_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ func (m *mockVolumeManager) SyncVolume(ctx context.Context,
131131
syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error) {
132132
return "", nil
133133
}
134+
135+
func (m *mockVolumeManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
136+
volumeID string) (string, error) {
137+
return "", nil
138+
}
139+
134140
func TestQueryVolumeSnapshotsByVolumeIDWithQuerySnapshotsCnsVolumeNotFoundFault(t *testing.T) {
135141
// Skip test on ARM64 due to gomonkey limitations
136142
if runtime.GOARCH == "arm64" {

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, k8sClient)
542542
if err != nil {
543543
log.Errorf("failed to construct batch attach request. Err: %s", err)
544544
return err

0 commit comments

Comments
 (0)