Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/go-co-op/gocron v1.37.0
github.com/go-logr/zapr v1.3.0
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/kubernetes-csi/csi-proxy/v2 v2.0.0-alpha.1
Expand Down Expand Up @@ -100,7 +101,6 @@ require (
github.com/google/btree v1.1.3 // indirect
github.com/google/cadvisor v0.52.1 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
Expand Down
81 changes: 81 additions & 0 deletions pkg/common/cns-lib/volume/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ type Manager interface {
UnregisterVolume(ctx context.Context, volumeID string, unregisterDisk bool) (string, error)
// SyncVolume returns the aggregated capacity for volumes
SyncVolume(ctx context.Context, syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error)
// QueryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
// VirtualDiskManager's QueryVirtualDiskInfo API.
QueryBackingTypeFromVirtualDiskInfo(ctx context.Context, volumeID string) (string, error)
}

// CnsVolumeInfo hold information related to volume created by CNS.
Expand Down Expand Up @@ -3830,3 +3833,81 @@ func (m *defaultManager) unregisterVolume(ctx context.Context, volumeID string,
log.Infof("volume %q unregistered successfully", volumeID)
return "", nil
}

// QueryBackingTypeFromVirtualDiskInfo queries the backing type of a volume using
// VirtualDiskManager's QueryVirtualDiskInfo API.
// It first queries the volume to get the BackingDiskPath, then uses that path to call
// QueryVirtualDiskInfo which returns the disk type information.
func (m *defaultManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
volumeID string) (string, error) {
log := logger.GetLogger(ctx)

// Get the datacenters
dcs, err := m.virtualCenter.GetDatacenters(ctx)
if err != nil {
return "", fmt.Errorf("failed to get datacenters: %w", err)
}
if len(dcs) == 0 {
return "", fmt.Errorf("no datacenters found")
}

// Query volume to get the backing disk path
queryFilter := cnstypes.CnsQueryFilter{
VolumeIds: []cnstypes.CnsVolumeId{{Id: volumeID}},
}
querySelection := cnstypes.CnsQuerySelection{
Names: []string{string(cnstypes.QuerySelectionNameTypeBackingObjectDetails)},
}

queryResult, err := m.QueryVolumeAsync(ctx, queryFilter, &querySelection)
if err != nil {
return "", fmt.Errorf("failed to query volume for backing details %s: %w", volumeID, err)
}
if queryResult == nil || len(queryResult.Volumes) == 0 {
return "", fmt.Errorf("no volume found for volumeID %s", volumeID)
}

backingObjectDetails := queryResult.Volumes[0].BackingObjectDetails
if backingObjectDetails == nil {
return "", fmt.Errorf("backing object details not found for volumeID %s", volumeID)
}

blockBackingDetails, ok := backingObjectDetails.(*cnstypes.CnsBlockBackingDetails)
if !ok {
return "", fmt.Errorf("backing object details is not of type CnsBlockBackingDetails for volumeID %s", volumeID)
}

backingFilePath := blockBackingDetails.BackingDiskPath
if backingFilePath == "" {
return "", fmt.Errorf("backing disk path not found for volumeID %s", volumeID)
}

// Get the vim25 client that uses standard vim25 namespace.
// This is required because QueryVirtualDiskInfo uses internal vim25 namespace
// which doesn't work with the vsan service version used by the main client.
vim25Client, err := m.virtualCenter.GetVim25Client(ctx)
if err != nil {
return "", fmt.Errorf("failed to get vim25 client: %w", err)
}

// Query virtual disk info using the datacenter we already have
virtualDiskManager := object.NewVirtualDiskManager(vim25Client)
diskInfoList, err := virtualDiskManager.QueryVirtualDiskInfo(ctx, backingFilePath, dcs[0].Datacenter, false)
if err != nil {
return "", fmt.Errorf("failed to query virtual disk info for %s: %w", backingFilePath, err)
}
if len(diskInfoList) == 0 {
return "", fmt.Errorf("no disk info returned for %s", backingFilePath)
}

diskType := diskInfoList[0].DiskType
log.Debugf("Retrieved diskType %s for volumeID %s", diskType, volumeID)

// Convert diskType to backing type
backingType := ConvertDiskTypeToBackingType(diskType)
if backingType == "" {
return "", fmt.Errorf("unable to find backingType for diskType:%s for the volume %s",
diskType, volumeID)
}
return backingType, nil
}
6 changes: 6 additions & 0 deletions pkg/common/cns-lib/volume/manager_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,9 @@ func (m MockManager) SyncVolume(ctx context.Context, syncVolumeSpecs []cnstypes.
//TODO implement me
panic("implement me")
}

func (m MockManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
volumeID string) (string, error) {
//TODO implement me
panic("implement me")
}
29 changes: 29 additions & 0 deletions pkg/common/cns-lib/volume/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,3 +632,32 @@ func IsCnsVolumeAlreadyExistsFault(ctx context.Context, faultType string) bool {
log.Infof("Checking fault type: %q is vim.fault.CnsVolumeAlreadyExistsFault", faultType)
return faultType == "vim.fault.CnsVolumeAlreadyExistsFault"
}

// ConvertDiskTypeToBackingType converts the diskType returned by QueryVirtualDiskInfo
// to the appropriate CnsVolumeBackingType string used for batch attach operations.
// The returned values correspond to VirtualDevice.FileBackingInfo subclasses as defined in
// github.com/vmware/govmomi/cns/types (CnsVolumeBackingType constants).
func ConvertDiskTypeToBackingType(diskType string) string {
switch diskType {
case "thin", "preallocated", "thick", "eagerZeroedThick", "thick2Gb", "flatMonolithic":
// All flat/thick disk types use FlatVer2BackingInfo
// thin -> FlatVer2BackingInfo (thinProvisioned=true)
// preallocated/thick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=false)
// eagerZeroedThick -> FlatVer2BackingInfo (thinProvisioned=false, eagerlyScrub=true)
// thick2Gb/flatMonolithic -> FlatVer2BackingInfo (split variations)
return string(cnstypes.CnsVolumeBackingTypeFlatVer2BackingInfo)
case "sparse2Gb", "sparseMonolithic", "delta", "vmfsSparse":
// sparse types -> SparseVer2BackingInfo
return string(cnstypes.CnsVolumeBackingTypeSparseVer2BackingInfo)
case "seSparse":
// seSparse -> SeSparseBackingInfo
return string(cnstypes.CnsVolumeBackingTypeSeSparseBackingInfo)
case "rdm", "rdmp":
// rdm -> RawDiskMappingVer1BackingInfo (compatibilityMode="virtualMode")
// rdmp -> RawDiskMappingVer1BackingInfo (compatibilityMode="physicalMode")
return string(cnstypes.CnsVolumeBackingTypeRawDiskMappingVer1BackingInfo)
default:
// Unknown disk type, return empty string
return ""
}
}
114 changes: 114 additions & 0 deletions pkg/common/cns-lib/volume/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package volume

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestConvertDiskTypeToBackingType(t *testing.T) {
tests := []struct {
name string
diskType string
want string
}{
{
name: "thin disk type",
diskType: "thin",
want: "FlatVer2BackingInfo",
},
{
name: "preallocated disk type",
diskType: "preallocated",
want: "FlatVer2BackingInfo",
},
{
name: "thick disk type",
diskType: "thick",
want: "FlatVer2BackingInfo",
},
{
name: "eagerZeroedThick disk type",
diskType: "eagerZeroedThick",
want: "FlatVer2BackingInfo",
},
{
name: "sparse2Gb disk type",
diskType: "sparse2Gb",
want: "SparseVer2BackingInfo",
},
{
name: "sparseMonolithic disk type",
diskType: "sparseMonolithic",
want: "SparseVer2BackingInfo",
},
{
name: "delta disk type",
diskType: "delta",
want: "SparseVer2BackingInfo",
},
{
name: "vmfsSparse disk type",
diskType: "vmfsSparse",
want: "SparseVer2BackingInfo",
},
{
name: "thick2Gb disk type",
diskType: "thick2Gb",
want: "FlatVer2BackingInfo",
},
{
name: "flatMonolithic disk type",
diskType: "flatMonolithic",
want: "FlatVer2BackingInfo",
},
{
name: "seSparse disk type",
diskType: "seSparse",
want: "SeSparseBackingInfo",
},
{
name: "rdm disk type",
diskType: "rdm",
want: "RawDiskMappingVer1BackingInfo",
},
{
name: "rdmp disk type",
diskType: "rdmp",
want: "RawDiskMappingVer1BackingInfo",
},
{
name: "unknown disk type",
diskType: "unknown",
want: "",
},
{
name: "empty disk type",
diskType: "",
want: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ConvertDiskTypeToBackingType(tt.diskType)
assert.Equal(t, tt.want, got)
})
}
}
46 changes: 46 additions & 0 deletions pkg/common/cns-lib/vsphere/virtualcenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type VirtualCenter struct {
VsanClient *vsan.Client
// VslmClient represents the Vslm client instance.
VslmClient *vslm.Client
// Vim25Client is a vim25.Client that uses the standard vim25 namespace.
// This is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that
// use internal vim25 namespaces and don't work with the vsan service version.
// It shares the same http.Transport as the main Client but uses vim25.Path and vim25.Namespace.
Vim25Client *vim25.Client
// ClientMutex is used for exclusive connection creation.
ClientMutex *sync.Mutex
}
Expand Down Expand Up @@ -386,9 +391,50 @@ func (vc *VirtualCenter) connect(ctx context.Context) error {
}
vc.VsanClient.RoundTripper = &MetricRoundTripper{"vsan", vc.VsanClient.RoundTripper}
}
// Recreate Vim25Client if created using timed out VC Client.
if vc.Vim25Client != nil {
vc.Vim25Client = vc.newVim25Client()
}
return nil
}

// newVim25Client creates a vim25.Client that uses the standard vim25 namespace.
// This is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that use
// internal vim25 namespaces and don't work with the vsan service version.
func (vc *VirtualCenter) newVim25Client() *vim25.Client {
soapClient := vc.Client.Client.Client
vimSoapClient := soapClient.NewServiceClient(vim25.Path, vim25.Namespace)
vimSoapClient.Version = vc.Client.Client.ServiceContent.About.ApiVersion
return &vim25.Client{
Client: vimSoapClient,
ServiceContent: vc.Client.Client.ServiceContent,
RoundTripper: vimSoapClient,
}
}

// GetVim25Client returns a vim25.Client that uses the standard vim25 namespace.
// This client is needed for APIs like VirtualDiskManager.QueryVirtualDiskInfo that
// use internal vim25 namespaces and don't work with the vsan service version.
// The client is created lazily on first call and reused for subsequent calls.
// It shares the same http.Transport as the main Client.
func (vc *VirtualCenter) GetVim25Client(ctx context.Context) (*vim25.Client, error) {
log := logger.GetLogger(ctx)

// Ensure connection is established
if err := vc.Connect(ctx); err != nil {
return nil, err
}

vc.ClientMutex.Lock()
defer vc.ClientMutex.Unlock()

if vc.Vim25Client == nil {
log.Info("Creating Vim25Client for standard vim25 namespace operations")
vc.Vim25Client = vc.newVim25Client()
}
return vc.Vim25Client, nil
}

// ReadVCConfigs will ensure we are always reading the latest config
// before attempting to create a new govmomi client.
// It works in case of both vanilla (including multi-vc) and wcp
Expand Down
5 changes: 5 additions & 0 deletions pkg/common/unittestcommon/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,8 @@ func (m *MockVolumeManager) SyncVolume(ctx context.Context,
syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error) {
return "", nil
}

func (m *MockVolumeManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
volumeID string) (string, error) {
return "", nil
}
6 changes: 6 additions & 0 deletions pkg/csi/service/common/vsphereutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ func (m *mockVolumeManager) SyncVolume(ctx context.Context,
syncVolumeSpecs []cnstypes.CnsSyncVolumeSpec) (string, error) {
return "", nil
}

func (m *mockVolumeManager) QueryBackingTypeFromVirtualDiskInfo(ctx context.Context,
volumeID string) (string, error) {
return "", nil
}

func TestQueryVolumeSnapshotsByVolumeIDWithQuerySnapshotsCnsVolumeNotFoundFault(t *testing.T) {
// Skip test on ARM64 due to gomonkey limitations
if runtime.GOARCH == "arm64" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ func (r *Reconciler) processBatchAttach(ctx context.Context, k8sClient kubernete

// Construct batch attach request
pvcsInAttachList, volumeIdsInAttachList, batchAttachRequest, err := constructBatchAttachRequest(ctx,
volumesToAttach, instance)
volumesToAttach, instance, r.volumeManager, k8sClient)
if err != nil {
log.Errorf("failed to construct batch attach request. Err: %s", err)
return err
Expand Down
Loading