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
65 changes: 65 additions & 0 deletions pkg/csi/service/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import (
const (
defaultK8sCloudOperatorServicePort = 10000
MissingSnapshotAggregatedCapacity = "csi.vsphere.missing-snapshot-aggregated-capacity"
vmfsNamespace = "com.vmware.storage.volumeallocation"
vmfsNamespaceEztValue = "Fully initialized"
)

var ErrAvailabilityZoneCRNotRegistered = errors.New("AvailabilityZone custom resource not registered")
Expand Down Expand Up @@ -535,3 +537,66 @@ func GetCNSVolumeInfoPatch(ctx context.Context, CapacityInMb int64, volumeId str
}
return patch, nil
}

// ValidateStoragePolicyForRWXVolume returns an error if the storagepolicy is not compatible
// for RWX volumes.
// Currently it returns an error if policy is for VMFS datastores but it is not EagerZeroedThick.
func ValidateStoragePolicyForRWXVolume(ctx context.Context,
vc *cnsvsphere.VirtualCenter, storagePolicyId string) error {
log := logger.GetLogger(ctx)

policies, err := vc.PbmRetrieveContent(ctx, []string{storagePolicyId})
if err != nil {
log.Errorf("failed to retrieve policy %s. Err %s", storagePolicyId, err)
return err
}

if len(policies) != 1 {
msg := fmt.Sprintf("Retrieved %d polciies for policyID %s", len(policies), storagePolicyId)
log.Errorf(msg)
return errors.New(msg)
}

return verifyStoragePolicyForVmfsWithEagerZeroedThick(ctx, policies[0], storagePolicyId)
}

// verifyStoragePolicyForVmfsWithEagerZeroedThick goes through each rule in the policy to
// find out if it is fully intialized for VMFS datastores.
// This check is required for RWX shared block volumes as for VMFS datastores, the policy must be EZT.
func verifyStoragePolicyForVmfsWithEagerZeroedThick(
ctx context.Context,
policy cnsvsphere.SpbmPolicyContent,
storagePolicyID string,
) error {
log := logger.GetLogger(ctx)

log.Infof("Validating policy %s", storagePolicyID)

for _, profile := range policy.Profiles {
isVmfs, isEzt := isVmfsEagerZeroed(profile)
if !isVmfs {
continue
}
if !isEzt {
return fmt.Errorf(
"policy %s is for VMFS datastores. It must be Thick Provision Eager Zero for RWX block volumes",
storagePolicyID)
}
log.Infof("Policy %s is for VMFS and is fully initialized", storagePolicyID)
return nil
}

return nil
}

// isVmfsEagerZeroed returns two boolean values:
// 1. First indicates if the policy is for a VMFS datastores.
// 2. Second indicates if the policy is eager zeroed thick or fully initialised.
func isVmfsEagerZeroed(profile cnsvsphere.SpbmPolicySubProfile) (bool, bool) {
for _, rule := range profile.Rules {
if rule.Ns == vmfsNamespace {
return true, rule.Value == vmfsNamespaceEztValue
}
}
return false, false
}
104 changes: 104 additions & 0 deletions pkg/csi/service/common/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/container-storage-interface/spec/lib/go/csi"
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/vsphere"
)

var (
Expand Down Expand Up @@ -622,3 +623,106 @@ func TestGetClusterComputeResourceMoIds_SingleClusterPerAZ(t *testing.T) {
gomega.Expect(multiple).To(gomega.BeFalse())
gomega.Expect(moIDs).To(gomega.ContainElements("domain-c1", "domain-c2"))
}

func TestIsVmfsEagerZeroed(t *testing.T) {
tests := []struct {
name string
profile vsphere.SpbmPolicySubProfile
wantIsVmfs bool
wantIsEzt bool
}{
{
name: "VMFS eager zeroed",
profile: makeProfile(makeRule(vmfsNamespace, vmfsNamespaceEztValue)),
wantIsVmfs: true,
wantIsEzt: true,
},
{
name: "VMFS not eager zeroed",
profile: makeProfile(makeRule(vmfsNamespace, "THIN")),
wantIsVmfs: true,
wantIsEzt: false,
},
{
name: "Non-VMFS",
profile: makeProfile(makeRule("NFS", "ANY")),
wantIsVmfs: false,
wantIsEzt: false,
},
{
name: "Empty rules",
profile: makeProfile(),
wantIsVmfs: false,
wantIsEzt: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotVmfs, gotEzt := isVmfsEagerZeroed(tt.profile)
assert.Equal(t, tt.wantIsVmfs, gotVmfs)
assert.Equal(t, tt.wantIsEzt, gotEzt)
})
}
}

func TestVerifyStoragePolicyForVmfsWithEagerZeroedThick(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
policy vsphere.SpbmPolicyContent
expectError bool
}{
{
name: "VMFS eager zeroed",
policy: makePolicy(
makeProfile(makeRule(vmfsNamespace, vmfsNamespaceEztValue)),
),
expectError: false,
},
{
name: "VMFS but not eager zeroed",
policy: makePolicy(
makeProfile(makeRule(vmfsNamespace, "THIN")),
),
expectError: true,
},
{
name: "Non-VMFS policy",
policy: makePolicy(
makeProfile(makeRule("NFS", "ANY")),
),
expectError: false,
},
{
name: "Empty profiles",
policy: makePolicy(),
expectError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := verifyStoragePolicyForVmfsWithEagerZeroedThick(ctx, tt.policy, "test-policy")
if tt.expectError {
assert.Error(t, err)
assert.Contains(t, err.Error(), "It must be Thick Provision Eager Zero for RWX block volumes")
} else {
assert.NoError(t, err)
}
})
}
}

func makeRule(ns, value string) vsphere.SpbmPolicyRule {
return vsphere.SpbmPolicyRule{Ns: ns, Value: value}
}

func makeProfile(rules ...vsphere.SpbmPolicyRule) vsphere.SpbmPolicySubProfile {
return vsphere.SpbmPolicySubProfile{Rules: rules}
}

func makePolicy(profiles ...vsphere.SpbmPolicySubProfile) vsphere.SpbmPolicyContent {
return vsphere.SpbmPolicyContent{Profiles: profiles}
}
10 changes: 10 additions & 0 deletions pkg/csi/service/wcp/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,16 @@ func (c *controller) createBlockVolume(ctx context.Context, req *csi.CreateVolum
"failed to get vCenter from Manager. Error: %v", err)
}

if commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx,
common.SharedDiskFss) && isSharedRawBlockRequest(ctx, req.VolumeCapabilities) {
log.Infof("Volume request is for shared RWX volume. Validatig if policy is compatible for VMFS datastores.")
err := common.ValidateStoragePolicyForRWXVolume(ctx, vc, storagePolicyID)
if err != nil {
log.Errorf("failed validation for policy %s", storagePolicyID)
return nil, csifault.CSIInternalFault, err
}
}

// Fetch the accessibility requirements from the request.
topologyRequirement = req.GetAccessibilityRequirements()
filterSuspendedDatastores := commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx, common.CnsMgrSuspendCreateVolume)
Expand Down
Loading