Skip to content

Commit abfb84f

Browse files
committed
Block RWX volume creation for VMFS datastores if policy is not EZT
1 parent b7552df commit abfb84f

File tree

4 files changed

+202
-5
lines changed

4 files changed

+202
-5
lines changed

pkg/csi/service/common/util.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"k8s.io/client-go/dynamic"
3838
crconfig "sigs.k8s.io/controller-runtime/pkg/client/config"
3939
cnsvolume "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/volume"
40+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/vsphere"
4041
cnsvsphere "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/vsphere"
4142
cnsconfig "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
4243
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
@@ -45,6 +46,8 @@ import (
4546
const (
4647
defaultK8sCloudOperatorServicePort = 10000
4748
MissingSnapshotAggregatedCapacity = "csi.vsphere.missing-snapshot-aggregated-capacity"
49+
vmfsNamespace = "com.vmware.storage.volumeallocation"
50+
vmfsNamespaceEztValue = "Fully initialized"
4851
)
4952

5053
var ErrAvailabilityZoneCRNotRegistered = errors.New("AvailabilityZone custom resource not registered")
@@ -535,3 +538,69 @@ func GetCNSVolumeInfoPatch(ctx context.Context, CapacityInMb int64, volumeId str
535538
}
536539
return patch, nil
537540
}
541+
542+
// ValidateStoragePolicyForRWXVolume returns an error if the storagepolicy is not compatible
543+
// for RWX volumes.
544+
// Currently it returns an error if policy is for VMFS datastores but it is not EagerZeroedThick.
545+
func ValidateStoragePolicyForRWXVolume(ctx context.Context,
546+
vc *vsphere.VirtualCenter, storagePolicyId string) error {
547+
log := logger.GetLogger(ctx)
548+
549+
policies, err := vc.PbmRetrieveContent(ctx, []string{storagePolicyId})
550+
if err != nil {
551+
log.Errorf("failed to retrieve policy %s. Err %s", storagePolicyId, err)
552+
return err
553+
}
554+
555+
if len(policies) != 1 {
556+
msg := fmt.Sprintf("Retrieved %d polciies for policyID %s", len(policies), storagePolicyId)
557+
log.Errorf(msg)
558+
return errors.New(msg)
559+
}
560+
561+
return verifyStoragePolicyForVmfsWithEagerZeroedThick(ctx, policies[0], storagePolicyId)
562+
}
563+
564+
// verifyStoragePolicyForVmfsWithEagerZeroedThick goes through each rule in the policy to
565+
// find out if it is fully intialized for VMFS datastores.
566+
// This check is required for RWX shared block volumes as for VMFS datastores, the policy must be EZT.
567+
func verifyStoragePolicyForVmfsWithEagerZeroedThick(
568+
ctx context.Context,
569+
policy vsphere.SpbmPolicyContent,
570+
storagePolicyID string,
571+
) error {
572+
log := logger.GetLogger(ctx)
573+
574+
log.Infof("Validating policy %s", storagePolicyID)
575+
576+
for _, profile := range policy.Profiles {
577+
isVmfs, isEzt := isVmfsEagerZeroed(profile)
578+
if !isVmfs {
579+
continue
580+
}
581+
if !isEzt {
582+
msg := fmt.Sprintf(
583+
"Policy %s is for VMFS datastores. It must be Thick Provision Eager Zero for RWX block volumes.",
584+
storagePolicyID,
585+
)
586+
log.Errorf(msg)
587+
return errors.New(msg)
588+
}
589+
log.Infof("Policy %s is for VMFS and is fully initialized", storagePolicyID)
590+
return nil
591+
}
592+
593+
return nil
594+
}
595+
596+
// isVmfsEagerZeroed returns two boolean values:
597+
// 1. First indicates if the policy is for a VMFS datastores.
598+
// 2. Second indicates if the policy is eager zeroed thick or fully initialised.
599+
func isVmfsEagerZeroed(profile vsphere.SpbmPolicySubProfile) (bool, bool) {
600+
for _, rule := range profile.Rules {
601+
if rule.Ns == vmfsNamespace {
602+
return true, rule.Value == vmfsNamespaceEztValue
603+
}
604+
}
605+
return false, false
606+
}

pkg/csi/service/common/util_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/stretchr/testify/assert"
3737

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

4142
var (
@@ -622,3 +623,106 @@ func TestGetClusterComputeResourceMoIds_SingleClusterPerAZ(t *testing.T) {
622623
gomega.Expect(multiple).To(gomega.BeFalse())
623624
gomega.Expect(moIDs).To(gomega.ContainElements("domain-c1", "domain-c2"))
624625
}
626+
627+
func TestIsVmfsEagerZeroed(t *testing.T) {
628+
tests := []struct {
629+
name string
630+
profile vsphere.SpbmPolicySubProfile
631+
wantIsVmfs bool
632+
wantIsEzt bool
633+
}{
634+
{
635+
name: "VMFS eager zeroed",
636+
profile: makeProfile(makeRule(vmfsNamespace, vmfsNamespaceEztValue)),
637+
wantIsVmfs: true,
638+
wantIsEzt: true,
639+
},
640+
{
641+
name: "VMFS not eager zeroed",
642+
profile: makeProfile(makeRule(vmfsNamespace, "THIN")),
643+
wantIsVmfs: true,
644+
wantIsEzt: false,
645+
},
646+
{
647+
name: "Non-VMFS",
648+
profile: makeProfile(makeRule("NFS", "ANY")),
649+
wantIsVmfs: false,
650+
wantIsEzt: false,
651+
},
652+
{
653+
name: "Empty rules",
654+
profile: makeProfile(),
655+
wantIsVmfs: false,
656+
wantIsEzt: false,
657+
},
658+
}
659+
660+
for _, tt := range tests {
661+
t.Run(tt.name, func(t *testing.T) {
662+
gotVmfs, gotEzt := isVmfsEagerZeroed(tt.profile)
663+
assert.Equal(t, tt.wantIsVmfs, gotVmfs)
664+
assert.Equal(t, tt.wantIsEzt, gotEzt)
665+
})
666+
}
667+
}
668+
669+
func TestVerifyStoragePolicyForVmfsWithEagerZeroedThick(t *testing.T) {
670+
ctx := context.Background()
671+
672+
tests := []struct {
673+
name string
674+
policy vsphere.SpbmPolicyContent
675+
expectError bool
676+
}{
677+
{
678+
name: "VMFS eager zeroed",
679+
policy: makePolicy(
680+
makeProfile(makeRule(vmfsNamespace, vmfsNamespaceEztValue)),
681+
),
682+
expectError: false,
683+
},
684+
{
685+
name: "VMFS but not eager zeroed",
686+
policy: makePolicy(
687+
makeProfile(makeRule(vmfsNamespace, "THIN")),
688+
),
689+
expectError: true,
690+
},
691+
{
692+
name: "Non-VMFS policy",
693+
policy: makePolicy(
694+
makeProfile(makeRule("NFS", "ANY")),
695+
),
696+
expectError: false,
697+
},
698+
{
699+
name: "Empty profiles",
700+
policy: makePolicy(),
701+
expectError: false,
702+
},
703+
}
704+
705+
for _, tt := range tests {
706+
t.Run(tt.name, func(t *testing.T) {
707+
err := verifyStoragePolicyForVmfsWithEagerZeroedThick(ctx, tt.policy, "test-policy")
708+
if tt.expectError {
709+
assert.Error(t, err)
710+
assert.Contains(t, err.Error(), "It must be Thick Provision Eager Zero for RWX block volumes.")
711+
} else {
712+
assert.NoError(t, err)
713+
}
714+
})
715+
}
716+
}
717+
718+
func makeRule(ns, value string) vsphere.SpbmPolicyRule {
719+
return vsphere.SpbmPolicyRule{Ns: ns, Value: value}
720+
}
721+
722+
func makeProfile(rules ...vsphere.SpbmPolicyRule) vsphere.SpbmPolicySubProfile {
723+
return vsphere.SpbmPolicySubProfile{Rules: rules}
724+
}
725+
726+
func makePolicy(profiles ...vsphere.SpbmPolicySubProfile) vsphere.SpbmPolicyContent {
727+
return vsphere.SpbmPolicyContent{Profiles: profiles}
728+
}

pkg/csi/service/wcp/controller.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,16 @@ func (c *controller) createBlockVolume(ctx context.Context, req *csi.CreateVolum
522522
"failed to get vCenter from Manager. Error: %v", err)
523523
}
524524

525+
if commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx,
526+
common.SharedDiskFss) && isSharedRawBlockRequest(ctx, req.VolumeCapabilities) {
527+
log.Infof("Volume request is for shared RWX volume. Validatig if policy is compatible for VMFS datastores.")
528+
err := common.ValidateStoragePolicyForRWXVolume(ctx, vc, storagePolicyID)
529+
if err != nil {
530+
log.Errorf("failed validation for policy %s", storagePolicyID)
531+
return nil, csifault.CSIInternalFault, err
532+
}
533+
}
534+
525535
// Fetch the accessibility requirements from the request.
526536
topologyRequirement = req.GetAccessibilityRequirements()
527537
filterSuspendedDatastores := commonco.ContainerOrchestratorUtility.IsFSSEnabled(ctx, common.CnsMgrSuspendCreateVolume)

pkg/syncer/cnsoperator/controller/cnsregistervolume/cnsregistervolume_controller.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,25 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
432432
return reconcile.Result{RequeueAfter: timeout}, nil
433433
}
434434

435+
accessMode := instance.Spec.AccessMode
436+
// If accessMode is not provided, set it to the default value - ReadWriteOnce.
437+
if accessMode == "" {
438+
accessMode = v1.ReadWriteOnce
439+
}
440+
441+
// Here AccessMode ReadWriteMany indicates that it is a shared block volume.
442+
// Validation that it does not indicate a file volume has already been done as part of
443+
// isBlockVolumeRegisterRequest.
444+
if isSharedDiskEnabled && accessMode == v1.ReadWriteMany {
445+
log.Infof("Volume request is for shared RWX volume. Validatig if policy is compatible for VMFS datastores.")
446+
err := common.ValidateStoragePolicyForRWXVolume(ctx, vc, volume.StoragePolicyId)
447+
if err != nil {
448+
log.Errorf("failed validation for policy %s. Err: %s", volume.StoragePolicyId, err)
449+
setInstanceError(ctx, r, instance, err.Error())
450+
return reconcile.Result{RequeueAfter: timeout}, nil
451+
}
452+
}
453+
435454
// Get K8S storageclass name mapping the storagepolicy id with Immediate volume binding mode
436455
storageClassName, err := getK8sStorageClassNameWithImmediateBindingModeForPolicy(ctx, k8sclient, r.client,
437456
volume.StoragePolicyId, request.Namespace, syncer.IsPodVMOnStretchSupervisorFSSEnabled)
@@ -584,11 +603,6 @@ func (r *ReconcileCnsRegisterVolume) Reconcile(ctx context.Context,
584603
}
585604

586605
capacityInMb := volume.BackingObjectDetails.GetCnsBackingObjectDetails().CapacityInMb
587-
accessMode := instance.Spec.AccessMode
588-
// Set accessMode to ReadWriteOnce if DiskURLPath is used for import.
589-
if accessMode == "" && instance.Spec.DiskURLPath != "" {
590-
accessMode = v1.ReadWriteOnce
591-
}
592606
pv, err := k8sclient.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{})
593607
if err != nil {
594608
if apierrors.IsNotFound(err) {

0 commit comments

Comments
 (0)