Skip to content

Commit 3deff1e

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

File tree

2 files changed

+222
-36
lines changed

2 files changed

+222
-36
lines changed

pkg/controller/controller.go

Lines changed: 36 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,
@@ -920,6 +949,11 @@ func (p *csiProvisioner) Provision(ctx context.Context, options controller.Provi
920949
pv.Spec.PersistentVolumeSource.CSI.FSType = result.fsType
921950
}
922951

952+
vacName := claim.Spec.VolumeAttributesClassName
953+
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) && vacName != nil {
954+
pv.Spec.VolumeAttributesClassName = vacName
955+
}
956+
923957
klog.V(2).Infof("successfully created PV %v for PVC %v and csi volume name %v", pv.Name, options.PVC.Name, pv.Spec.CSI.VolumeHandle)
924958

925959
if result.migratedVolume {

pkg/controller/controller_test.go

Lines changed: 186 additions & 34 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 {
@@ -857,14 +876,15 @@ type provisioningFSTypeTestcase struct {
857876
}
858877

859878
type pvSpec struct {
860-
Name string
861-
Annotations map[string]string
862-
ReclaimPolicy v1.PersistentVolumeReclaimPolicy
863-
AccessModes []v1.PersistentVolumeAccessMode
864-
MountOptions []string
865-
VolumeMode *v1.PersistentVolumeMode
866-
Capacity v1.ResourceList
867-
CSIPVS *v1.CSIPersistentVolumeSource
879+
Name string
880+
Annotations map[string]string
881+
ReclaimPolicy v1.PersistentVolumeReclaimPolicy
882+
AccessModes []v1.PersistentVolumeAccessMode
883+
MountOptions []string
884+
VolumeMode *v1.PersistentVolumeMode
885+
Capacity v1.ResourceList
886+
CSIPVS *v1.CSIPersistentVolumeSource
887+
VolumeAttributesClassName *string
868888
}
869889

870890
const defaultSecretNsName = "default"
@@ -1068,6 +1088,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
10681088
Name: "bar",
10691089
},
10701090
}
1091+
vacName := "test-vac"
10711092
return requestedBytes, map[string]provisioningTestcase{
10721093
"normal provision": {
10731094
volOpts: controller.ProvisionOptions{
@@ -1403,7 +1424,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
14031424
expectState: controller.ProvisioningFinished,
14041425
},
14051426
"provision with access mode single node writer and single node multi writer capability": {
1406-
supportsSingleNodeMultiWriter: true,
1427+
pluginCapabilities: provisionWithSingleNodeMultiWriterCapabilities,
14071428
volOpts: controller.ProvisionOptions{
14081429
StorageClass: &storagev1.StorageClass{
14091430
ReclaimPolicy: &deletePolicy,
@@ -1456,7 +1477,7 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
14561477
expectState: controller.ProvisioningFinished,
14571478
},
14581479
"provision with access mode single node single writer and single node multi writer capability": {
1459-
supportsSingleNodeMultiWriter: true,
1480+
pluginCapabilities: provisionWithSingleNodeMultiWriterCapabilities,
14601481
volOpts: controller.ProvisionOptions{
14611482
StorageClass: &storagev1.StorageClass{
14621483
ReclaimPolicy: &deletePolicy,
@@ -2282,6 +2303,130 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) {
22822303
expectNoProvision: true, // not owner and will not change that either
22832304
expectSelectedNode: "", // not changed by ShouldProvision
22842305
},
2306+
"normal provision with VolumeAttributesClass": {
2307+
featureGates: map[featuregate.Feature]bool{
2308+
features.VolumeAttributesClass: true,
2309+
},
2310+
pluginCapabilities: provisionWithVACCapabilities,
2311+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2312+
ObjectMeta: metav1.ObjectMeta{
2313+
Name: vacName,
2314+
},
2315+
DriverName: driverName,
2316+
Parameters: map[string]string{
2317+
"test-param": "from-vac",
2318+
},
2319+
}},
2320+
expectCreateVolDo: func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) {
2321+
// TODO: define a constant for this? kind of ugly
2322+
if !reflect.DeepEqual(req.MutableParameters, map[string]string{"test-param": "from-vac"}) {
2323+
t.Errorf("Missing or incorrect VolumeAttributesClass (mutable) parameters")
2324+
}
2325+
},
2326+
volOpts: controller.ProvisionOptions{
2327+
StorageClass: &storagev1.StorageClass{
2328+
ReclaimPolicy: &deletePolicy,
2329+
Parameters: map[string]string{
2330+
"fstype": "ext3",
2331+
"test-param": "from-sc",
2332+
},
2333+
},
2334+
PVName: "test-name",
2335+
PVC: createFakePVCWithVAC(requestedBytes, vacName),
2336+
},
2337+
expectedPVSpec: &pvSpec{
2338+
Name: "test-testi",
2339+
Annotations: map[string]string{
2340+
annDeletionProvisionerSecretRefName: "",
2341+
annDeletionProvisionerSecretRefNamespace: "",
2342+
},
2343+
ReclaimPolicy: v1.PersistentVolumeReclaimDelete,
2344+
Capacity: v1.ResourceList{
2345+
v1.ResourceName(v1.ResourceStorage): bytesToQuantity(requestedBytes),
2346+
},
2347+
CSIPVS: &v1.CSIPersistentVolumeSource{
2348+
Driver: "test-driver",
2349+
VolumeHandle: "test-volume-id",
2350+
FSType: "ext3",
2351+
VolumeAttributes: map[string]string{
2352+
"storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner",
2353+
},
2354+
},
2355+
VolumeAttributesClassName: &vacName,
2356+
},
2357+
expectState: controller.ProvisioningFinished,
2358+
},
2359+
"fail with VolumeAttributesClass but driver does not support MODIFY_VOLUME": {
2360+
featureGates: map[featuregate.Feature]bool{
2361+
features.VolumeAttributesClass: true,
2362+
},
2363+
volOpts: controller.ProvisionOptions{
2364+
StorageClass: &storagev1.StorageClass{
2365+
ReclaimPolicy: &deletePolicy,
2366+
Parameters: map[string]string{
2367+
"fstype": "ext3",
2368+
},
2369+
},
2370+
PVName: "test-name",
2371+
PVC: createFakePVCWithVAC(requestedBytes, vacName),
2372+
},
2373+
expectErr: true,
2374+
expectState: controller.ProvisioningFinished,
2375+
},
2376+
"fail with VolumeAttributesClass but VAC does not exist": {
2377+
featureGates: map[featuregate.Feature]bool{
2378+
features.VolumeAttributesClass: true,
2379+
},
2380+
pluginCapabilities: provisionWithVACCapabilities,
2381+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2382+
ObjectMeta: metav1.ObjectMeta{
2383+
Name: vacName,
2384+
},
2385+
DriverName: driverName,
2386+
Parameters: map[string]string{
2387+
"test-param": "from-vac",
2388+
},
2389+
}},
2390+
volOpts: controller.ProvisionOptions{
2391+
StorageClass: &storagev1.StorageClass{
2392+
ReclaimPolicy: &deletePolicy,
2393+
Parameters: map[string]string{
2394+
"fstype": "ext3",
2395+
},
2396+
},
2397+
PVName: "test-name",
2398+
PVC: createFakePVCWithVAC(requestedBytes, "does-not-exist"),
2399+
},
2400+
expectErr: true,
2401+
expectState: controller.ProvisioningNoChange,
2402+
},
2403+
"fail with VolumeAttributesClass but driver name does not match": {
2404+
featureGates: map[featuregate.Feature]bool{
2405+
features.VolumeAttributesClass: true,
2406+
},
2407+
pluginCapabilities: provisionWithVACCapabilities,
2408+
clientSetObjects: []runtime.Object{&storagev1alpha1.VolumeAttributesClass{
2409+
ObjectMeta: metav1.ObjectMeta{
2410+
Name: vacName,
2411+
},
2412+
DriverName: "not-" + driverName,
2413+
Parameters: map[string]string{
2414+
"test-param": "from-vac",
2415+
},
2416+
}},
2417+
volOpts: controller.ProvisionOptions{
2418+
StorageClass: &storagev1.StorageClass{
2419+
ReclaimPolicy: &deletePolicy,
2420+
Parameters: map[string]string{
2421+
"fstype": "ext3",
2422+
},
2423+
},
2424+
PVName: "test-name",
2425+
PVC: createFakePVCWithVAC(requestedBytes, vacName),
2426+
},
2427+
expectErr: true,
2428+
expectState: controller.ProvisioningFinished,
2429+
},
22852430
}
22862431
}
22872432

@@ -2410,10 +2555,17 @@ func runFSTypeProvisionTest(t *testing.T, k string, tc provisioningFSTypeTestcas
24102555
}
24112556
}
24122557

2558+
if !reflect.DeepEqual(pv.Spec.VolumeAttributesClassName, tc.expectedPVSpec.VolumeAttributesClassName) {
2559+
t.Errorf("test %q: expected VAC: %v, got: %v", k, tc.expectedPVSpec.VolumeAttributesClassName, pv.Spec.VolumeAttributesClassName)
2560+
}
24132561
}
24142562
}
24152563

24162564
func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int64, provisionDriverName, supportsMigrationFromInTreePluginName string, testProvision bool) {
2565+
for featureName, featureValue := range tc.featureGates {
2566+
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, featureName, featureValue)()
2567+
}
2568+
24172569
tmpdir := tempDir(t)
24182570
defer os.RemoveAll(tmpdir)
24192571
mockController, driver, _, controllerServer, csiConn, err := createMockServer(t, tmpdir)
@@ -2524,8 +2676,8 @@ func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int6
25242676

25252677
var pluginCaps rpc.PluginCapabilitySet
25262678
var controllerCaps rpc.ControllerCapabilitySet
2527-
if tc.supportsSingleNodeMultiWriter {
2528-
pluginCaps, controllerCaps = provisionWithSingleNodeMultiWriterCapabilities()
2679+
if tc.pluginCapabilities != nil {
2680+
pluginCaps, controllerCaps = tc.pluginCapabilities()
25292681
} else {
25302682
pluginCaps, controllerCaps = provisionCapabilities()
25312683
}

0 commit comments

Comments
 (0)