Skip to content

Commit fdea7b0

Browse files
committed
Pass VolumeAttributesClass Parameters as MutableParameters to CreateVolume
Signed-off-by: Connor Catlett <conncatl@amazon.com>
1 parent 6e551db commit fdea7b0

File tree

2 files changed

+184
-28
lines changed

2 files changed

+184
-28
lines changed

pkg/controller/controller.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,9 @@ type ProvisionerCSITranslator interface {
217217

218218
// requiredCapabilities provides a set of extra capabilities required for special/optional features provided by a plugin
219219
type requiredCapabilities struct {
220-
snapshot bool
221-
clone bool
220+
snapshot bool
221+
clone bool
222+
modifyVolume bool
222223
}
223224

224225
// NodeDeployment contains additional parameters for running external-provisioner alongside a
@@ -440,6 +441,13 @@ func (p *csiProvisioner) checkDriverCapabilities(rc *requiredCapabilities) error
440441
return fmt.Errorf("CSI driver does not support clone operations: controller CLONE_VOLUME capability is not reported")
441442
}
442443
}
444+
if rc.modifyVolume {
445+
// Check whether plugin supports modifying volumes
446+
// If not, PVCs with an associated VolumeAttributesClass cannot be created
447+
if !p.controllerCapabilities[csi.ControllerServiceCapability_RPC_MODIFY_VOLUME] {
448+
return fmt.Errorf("CSI driver does not support VolumeAttributesClass: controller MODIFY_VOLUME capability is not reported")
449+
}
450+
}
443451

444452
return nil
445453
}
@@ -596,6 +604,14 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist
596604
}
597605
}
598606

607+
vacName := claim.Spec.VolumeAttributesClassName
608+
// TODO: For reviewers - Should we reject volumes that have a VAC specified if the VAC feature gate is disabled?
609+
// This will be a change in behavior (existing versions of external-provisioner out in the wild will just let those PVCs get provisioned),
610+
// but it could prevent provisioning a PVC in a misleading way
611+
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) && vacName != nil {
612+
rc.modifyVolume = true
613+
}
614+
599615
if err := p.checkDriverCapabilities(rc); err != nil {
600616
return nil, controller.ProvisioningFinished, err
601617
}
@@ -742,6 +758,19 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist
742758
deletionAnnSecrets.namespace = provisionerSecretRef.Namespace
743759
}
744760

761+
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) && vacName != nil {
762+
vac, err := p.client.StorageV1alpha1().VolumeAttributesClasses().Get(ctx, *vacName, metav1.GetOptions{})
763+
if err != nil {
764+
return nil, controller.ProvisioningNoChange, err
765+
}
766+
767+
if vac.DriverName != p.driverName {
768+
return nil, controller.ProvisioningFinished, fmt.Errorf("VAC %s referenced in PVC is for driver %s which does not match driver name %s", *vacName, vac.DriverName, p.driverName)
769+
}
770+
771+
req.MutableParameters = vac.Parameters
772+
}
773+
745774
return &prepareProvisionResult{
746775
fsType: fsType,
747776
migratedVolume: migratedVolume,

pkg/controller/controller_test.go

Lines changed: 153 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"google.golang.org/grpc/status"
3434
v1 "k8s.io/api/core/v1"
3535
storagev1 "k8s.io/api/storage/v1"
36+
storagev1alpha1 "k8s.io/api/storage/v1alpha1"
3637
"k8s.io/apimachinery/pkg/api/resource"
3738
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3839
"k8s.io/apimachinery/pkg/runtime"
@@ -42,6 +43,7 @@ import (
4243
"k8s.io/client-go/kubernetes"
4344
fakeclientset "k8s.io/client-go/kubernetes/fake"
4445
k8stesting "k8s.io/client-go/testing"
46+
"k8s.io/component-base/featuregate"
4547
utilfeaturetesting "k8s.io/component-base/featuregate/testing"
4648
csitrans "k8s.io/csi-translation-lib"
4749
"k8s.io/klog/v2"
@@ -504,6 +506,15 @@ func provisionFromPVCCapabilities() (rpc.PluginCapabilitySet, rpc.ControllerCapa
504506
}
505507
}
506508

509+
func provisionWithVACCapabilities() (rpc.PluginCapabilitySet, rpc.ControllerCapabilitySet) {
510+
return rpc.PluginCapabilitySet{
511+
csi.PluginCapability_Service_CONTROLLER_SERVICE: true,
512+
}, rpc.ControllerCapabilitySet{
513+
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME: true,
514+
csi.ControllerServiceCapability_RPC_MODIFY_VOLUME: true,
515+
}
516+
}
517+
507518
var fakeSCName = "fake-test-sc"
508519

509520
func createFakeNamedPVC(requestBytes int64, name string, userAnnotations map[string]string) *v1.PersistentVolumeClaim {
@@ -543,6 +554,13 @@ func createFakePVCWithVolumeMode(requestBytes int64, volumeMode v1.PersistentVol
543554
return claim
544555
}
545556

557+
// createFakePVCWithVAC returns PVC with VolumeAttributesClassName
558+
func createFakePVCWithVAC(requestBytes int64, vacName string) *v1.PersistentVolumeClaim {
559+
claim := createFakePVC(requestBytes)
560+
claim.Spec.VolumeAttributesClassName = &vacName
561+
return claim
562+
}
563+
546564
// fakeClaim returns a valid PVC with the requested settings
547565
func fakeClaim(name, namespace, claimUID string, capacity int64, boundToVolume string, phase v1.PersistentVolumeClaimPhase, class *string, mode string) *v1.PersistentVolumeClaim {
548566
claim := v1.PersistentVolumeClaim{
@@ -820,28 +838,29 @@ func TestGetSecretReference(t *testing.T) {
820838
}
821839

822840
type provisioningTestcase struct {
823-
capacity int64 // if zero, default capacity, otherwise available bytes
824-
volOpts controller.ProvisionOptions
825-
notNilSelector bool
826-
makeVolumeNameErr bool
827-
getSecretRefErr bool
828-
getCredentialsErr bool
829-
volWithLessCap bool
830-
volWithZeroCap bool
831-
expectedPVSpec *pvSpec
832-
clientSetObjects []runtime.Object
833-
createVolumeError error
834-
expectErr bool
835-
expectState controller.ProvisioningState
836-
expectCreateVolDo func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest)
837-
withExtraMetadata bool
838-
skipCreateVolume bool
839-
deploymentNode string // fake distributed provisioning with this node as host
840-
immediateBinding bool // enable immediate binding support for distributed provisioning
841-
expectSelectedNode string // a specific selected-node of the PVC in the apiserver after the test, same as before if empty
842-
expectNoProvision bool // if true, then ShouldProvision should return false
843-
supportsSingleNodeMultiWriter bool // if true, then provision with single node multi writer capabilities
844-
controllerPublishReadOnly bool
841+
capacity int64 // if zero, default capacity, otherwise available bytes
842+
volOpts controller.ProvisionOptions
843+
notNilSelector bool
844+
makeVolumeNameErr bool
845+
getSecretRefErr bool
846+
getCredentialsErr bool
847+
volWithLessCap bool
848+
volWithZeroCap bool
849+
expectedPVSpec *pvSpec
850+
clientSetObjects []runtime.Object
851+
createVolumeError error
852+
expectErr bool
853+
expectState controller.ProvisioningState
854+
expectCreateVolDo func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest)
855+
withExtraMetadata bool
856+
skipCreateVolume bool
857+
deploymentNode string // fake distributed provisioning with this node as host
858+
immediateBinding bool // enable immediate binding support for distributed provisioning
859+
expectSelectedNode string // a specific selected-node of the PVC in the apiserver after the test, same as before if empty
860+
expectNoProvision bool // if true, then ShouldProvision should return false
861+
controllerPublishReadOnly bool
862+
featureGates map[featuregate.Feature]bool
863+
pluginCapabilities func() (rpc.PluginCapabilitySet, rpc.ControllerCapabilitySet)
845864
}
846865

847866
type provisioningFSTypeTestcase struct {
@@ -1403,7 +1422,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
14031422
expectState: controller.ProvisioningFinished,
14041423
},
14051424
"provision with access mode single node writer and single node multi writer capability": {
1406-
supportsSingleNodeMultiWriter: true,
1425+
pluginCapabilities: provisionWithSingleNodeMultiWriterCapabilities,
14071426
volOpts: controller.ProvisionOptions{
14081427
StorageClass: &storagev1.StorageClass{
14091428
ReclaimPolicy: &deletePolicy,
@@ -1456,7 +1475,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
14561475
expectState: controller.ProvisioningFinished,
14571476
},
14581477
"provision with access mode single node single writer and single node multi writer capability": {
1459-
supportsSingleNodeMultiWriter: true,
1478+
pluginCapabilities: provisionWithSingleNodeMultiWriterCapabilities,
14601479
volOpts: controller.ProvisionOptions{
14611480
StorageClass: &storagev1.StorageClass{
14621481
ReclaimPolicy: &deletePolicy,
@@ -2282,6 +2301,110 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
22822301
expectNoProvision: true, // not owner and will not change that either
22832302
expectSelectedNode: "", // not changed by ShouldProvision
22842303
},
2304+
"normal provision with VolumeAttributesClass": {
2305+
featureGates: map[featuregate.Feature]bool{
2306+
features.VolumeAttributesClass: true,
2307+
},
2308+
pluginCapabilities: provisionWithVACCapabilities,
2309+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2310+
ObjectMeta: metav1.ObjectMeta{
2311+
Name: "test-vac",
2312+
},
2313+
DriverName: driverName,
2314+
Parameters: map[string]string{
2315+
"test-param": "from-vac",
2316+
},
2317+
}},
2318+
expectCreateVolDo: func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) {
2319+
// TODO: define a constant for this? kind of ugly
2320+
if !reflect.DeepEqual(req.MutableParameters, map[string]string{"test-param": "from-vac"}) {
2321+
t.Errorf("Missing or incorrect VolumeAttributeClass (mutable) parameters")
2322+
}
2323+
},
2324+
volOpts: controller.ProvisionOptions{
2325+
StorageClass: &storagev1.StorageClass{
2326+
ReclaimPolicy: &deletePolicy,
2327+
Parameters: map[string]string{
2328+
"fstype": "ext3",
2329+
"test-param": "from-sc",
2330+
},
2331+
},
2332+
PVName: "test-name",
2333+
PVC: createFakePVCWithVAC(requestedBytes, "test-vac"),
2334+
},
2335+
expectState: controller.ProvisioningFinished,
2336+
},
2337+
"fail with VolumeAttributesClass but driver does not support MODIFY_VOLUME": {
2338+
featureGates: map[featuregate.Feature]bool{
2339+
features.VolumeAttributesClass: true,
2340+
},
2341+
volOpts: controller.ProvisionOptions{
2342+
StorageClass: &storagev1.StorageClass{
2343+
ReclaimPolicy: &deletePolicy,
2344+
Parameters: map[string]string{
2345+
"fstype": "ext3",
2346+
},
2347+
},
2348+
PVName: "test-name",
2349+
PVC: createFakePVCWithVAC(requestedBytes, "test-vac"),
2350+
},
2351+
expectErr: true,
2352+
expectState: controller.ProvisioningFinished,
2353+
},
2354+
"fail with VolumeAttributesClass but VAC does not exist": {
2355+
featureGates: map[featuregate.Feature]bool{
2356+
features.VolumeAttributesClass: true,
2357+
},
2358+
pluginCapabilities: provisionWithVACCapabilities,
2359+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2360+
ObjectMeta: metav1.ObjectMeta{
2361+
Name: "test-vac",
2362+
},
2363+
DriverName: driverName,
2364+
Parameters: map[string]string{
2365+
"test-param": "from-vac",
2366+
},
2367+
}},
2368+
volOpts: controller.ProvisionOptions{
2369+
StorageClass: &storagev1.StorageClass{
2370+
ReclaimPolicy: &deletePolicy,
2371+
Parameters: map[string]string{
2372+
"fstype": "ext3",
2373+
},
2374+
},
2375+
PVName: "test-name",
2376+
PVC: createFakePVCWithVAC(requestedBytes, "test-vac-does-not-exist"),
2377+
},
2378+
expectErr: true,
2379+
expectState: controller.ProvisioningNoChange,
2380+
},
2381+
"fail with VolumeAttributesClass but driver name does not match": {
2382+
featureGates: map[featuregate.Feature]bool{
2383+
features.VolumeAttributesClass: true,
2384+
},
2385+
pluginCapabilities: provisionWithVACCapabilities,
2386+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2387+
ObjectMeta: metav1.ObjectMeta{
2388+
Name: "test-vac",
2389+
},
2390+
DriverName: "not-" + driverName,
2391+
Parameters: map[string]string{
2392+
"test-param": "from-vac",
2393+
},
2394+
}},
2395+
volOpts: controller.ProvisionOptions{
2396+
StorageClass: &storagev1.StorageClass{
2397+
ReclaimPolicy: &deletePolicy,
2398+
Parameters: map[string]string{
2399+
"fstype": "ext3",
2400+
},
2401+
},
2402+
PVName: "test-name",
2403+
PVC: createFakePVCWithVAC(requestedBytes, "test-vac"),
2404+
},
2405+
expectErr: true,
2406+
expectState: controller.ProvisioningFinished,
2407+
},
22852408
}
22862409
}
22872410

@@ -2414,6 +2537,10 @@ func runFSTypeProvisionTest(t *testing.T, k string, tc provisioningFSTypeTestcas
24142537
}
24152538

24162539
func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int64, provisionDriverName, supportsMigrationFromInTreePluginName string, testProvision bool) {
2540+
for featureName, featureValue := range tc.featureGates {
2541+
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, featureName, featureValue)()
2542+
}
2543+
24172544
tmpdir := tempDir(t)
24182545
defer os.RemoveAll(tmpdir)
24192546
mockController, driver, _, controllerServer, csiConn, err := createMockServer(t, tmpdir)
@@ -2524,8 +2651,8 @@ func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int6
25242651

25252652
var pluginCaps rpc.PluginCapabilitySet
25262653
var controllerCaps rpc.ControllerCapabilitySet
2527-
if tc.supportsSingleNodeMultiWriter {
2528-
pluginCaps, controllerCaps = provisionWithSingleNodeMultiWriterCapabilities()
2654+
if tc.pluginCapabilities != nil {
2655+
pluginCaps, controllerCaps = tc.pluginCapabilities()
25292656
} else {
25302657
pluginCaps, controllerCaps = provisionCapabilities()
25312658
}

0 commit comments

Comments
 (0)