diff --git a/cmd/csi-provisioner/csi-provisioner.go b/cmd/csi-provisioner/csi-provisioner.go index bdfa022acf..3b7a5528c7 100644 --- a/cmd/csi-provisioner/csi-provisioner.go +++ b/cmd/csi-provisioner/csi-provisioner.go @@ -85,14 +85,15 @@ var ( enableLeaderElection = flag.Bool("leader-election", false, "Enables leader election. If leader election is enabled, additional RBAC rules are required. Please refer to the Kubernetes CSI documentation for instructions on setting up these RBAC rules.") - leaderElectionNamespace = flag.String("leader-election-namespace", "", "Namespace where the leader election resource lives. Defaults to the pod namespace if not set.") - strictTopology = flag.Bool("strict-topology", false, "Late binding: pass only selected node topology to CreateVolume Request, unlike default behavior of passing aggregated cluster topologies that match with topology keys of the selected node.") - immediateTopology = flag.Bool("immediate-topology", true, "Immediate binding: pass aggregated cluster topologies for all nodes where the CSI driver is available (enabled, the default) or no topology requirements (if disabled).") - extraCreateMetadata = flag.Bool("extra-create-metadata", false, "If set, add pv/pvc metadata to plugin create requests as parameters.") - metricsAddress = flag.String("metrics-address", "", "(deprecated) The TCP network address where the prometheus metrics endpoint will listen (example: `:8080`). The default is empty string, which means metrics endpoint is disabled. Only one of `--metrics-address` and `--http-endpoint` can be set.") - httpEndpoint = flag.String("http-endpoint", "", "The TCP network address where the HTTP server for diagnostics, including pprof, metrics and leader election health check, will listen (example: `:8080`). The default is empty string, which means the server is disabled. Only one of `--metrics-address` and `--http-endpoint` can be set.") - metricsPath = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed. Default is `/metrics`.") - enableProfile = flag.Bool("enable-pprof", false, "Enable pprof profiling on the TCP network address specified by --http-endpoint. The HTTP path is `/debug/pprof/`.") + leaderElectionNamespace = flag.String("leader-election-namespace", "", "Namespace where the leader election resource lives. Defaults to the pod namespace if not set.") + strictTopology = flag.Bool("strict-topology", false, "Late binding: pass only selected node topology to CreateVolume Request, unlike default behavior of passing aggregated cluster topologies that match with topology keys of the selected node.") + immediateTopology = flag.Bool("immediate-topology", true, "Immediate binding: pass aggregated cluster topologies for all nodes where the CSI driver is available (enabled, the default) or no topology requirements (if disabled).") + extraCreateMetadata = flag.Bool("extra-create-metadata", false, "If set, add pv/pvc metadata to plugin create requests as parameters.") + extraCreateMetadataPrefix = flag.String("extra-create-metadata-prefix", "", "If set, add pvc annotations starting with this prefix to plugin create requests as parameters.") + metricsAddress = flag.String("metrics-address", "", "(deprecated) The TCP network address where the prometheus metrics endpoint will listen (example: `:8080`). The default is empty string, which means metrics endpoint is disabled. Only one of `--metrics-address` and `--http-endpoint` can be set.") + httpEndpoint = flag.String("http-endpoint", "", "The TCP network address where the HTTP server for diagnostics, including pprof, metrics and leader election health check, will listen (example: `:8080`). The default is empty string, which means the server is disabled. Only one of `--metrics-address` and `--http-endpoint` can be set.") + metricsPath = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed. Default is `/metrics`.") + enableProfile = flag.Bool("enable-pprof", false, "Enable pprof profiling on the TCP network address specified by --http-endpoint. The HTTP path is `/debug/pprof/`.") leaderElectionLeaseDuration = flag.Duration("leader-election-lease-duration", 15*time.Second, "Duration, in seconds, that non-leader candidates will wait to force acquire leadership. Defaults to 15 seconds.") leaderElectionRenewDeadline = flag.Duration("leader-election-renew-deadline", 10*time.Second, "Duration, in seconds, that the acting leader will retry refreshing leadership before giving up. Defaults to 10 seconds.") @@ -424,6 +425,7 @@ func main() { vaLister, referenceGrantLister, *extraCreateMetadata, + *extraCreateMetadataPrefix, *defaultFSType, nodeDeployment, *controllerPublishReadOnly, diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index d5011b83bf..0917bd804f 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -272,6 +272,7 @@ type csiProvisioner struct { vaLister storagelistersv1.VolumeAttachmentLister referenceGrantLister referenceGrantv1beta1.ReferenceGrantLister extraCreateMetadata bool + extraCreateMetadataPrefix string eventRecorder record.EventRecorder nodeDeployment *internalNodeDeployment controllerPublishReadOnly bool @@ -353,6 +354,7 @@ func NewCSIProvisioner(client kubernetes.Interface, vaLister storagelistersv1.VolumeAttachmentLister, referenceGrantLister referenceGrantv1beta1.ReferenceGrantLister, extraCreateMetadata bool, + extraCreateMetadataPrefix string, defaultFSType string, nodeDeployment *NodeDeployment, controllerPublishReadOnly bool, @@ -389,6 +391,7 @@ func NewCSIProvisioner(client kubernetes.Interface, vaLister: vaLister, referenceGrantLister: referenceGrantLister, extraCreateMetadata: extraCreateMetadata, + extraCreateMetadataPrefix: extraCreateMetadataPrefix, eventRecorder: eventRecorder, controllerPublishReadOnly: controllerPublishReadOnly, preventVolumeModeConversion: preventVolumeModeConversion, @@ -749,6 +752,15 @@ func (p *csiProvisioner) prepareProvision(ctx context.Context, claim *v1.Persist req.Parameters[pvcNamespaceKey] = claim.GetNamespace() req.Parameters[pvNameKey] = pvName } + + if p.extraCreateMetadataPrefix != "" { + // add pvc annotations starting with this prefix as parameters to request for use by the plugin + for annotation, value := range claim.Annotations { + if strings.HasPrefix(annotation, p.extraCreateMetadataPrefix) { + req.Parameters[annotation] = value + } + } + } deletionAnnSecrets := new(deletionSecretParams) if provisionerSecretRef != nil { diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index d84ffae2ca..78f327e6ea 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -426,7 +426,7 @@ func TestCreateDriverReturnsInvalidCapacityDuringProvision(t *testing.T) { pluginCaps, controllerCaps := provisionCapabilities() csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", - 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, defaultfsType, nil, true, false) + 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, "", defaultfsType, nil, true, false) // Requested PVC with requestedBytes storage deletePolicy := v1.PersistentVolumeReclaimDelete @@ -871,6 +871,7 @@ type provisioningTestcase struct { expectState controller.ProvisioningState expectCreateVolDo func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) withExtraMetadata bool + withExtraMetadataPrefix string skipCreateVolume bool deploymentNode string // fake distributed provisioning with this node as host immediateBinding bool // enable immediate binding support for distributed provisioning @@ -1181,6 +1182,44 @@ func provisionTestcases() (int64, map[string]provisioningTestcase) { }, expectState: controller.ProvisioningFinished, }, + "normal provision with extra metadata prefix": { + volOpts: controller.ProvisionOptions{ + StorageClass: &storagev1.StorageClass{ + ReclaimPolicy: &deletePolicy, + Parameters: map[string]string{ + "fstype": "ext3", + }, + }, + PVName: "test-name", + PVC: createFakeNamedPVC(requestedBytes, "fake-pvc", map[string]string{"csi.my.company.org/some_annotation": "1234"}), + }, + withExtraMetadataPrefix: "csi.my.company.org", + expectedPVSpec: &pvSpec{ + Name: "test-testi", + ReclaimPolicy: v1.PersistentVolumeReclaimDelete, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): bytesToQuantity(requestedBytes), + }, + CSIPVS: &v1.CSIPersistentVolumeSource{ + Driver: "test-driver", + VolumeHandle: "test-volume-id", + FSType: "ext3", + VolumeAttributes: map[string]string{ + "storage.kubernetes.io/csiProvisionerIdentity": "test-provisioner", + }, + }, + }, + expectCreateVolDo: func(t *testing.T, ctx context.Context, req *csi.CreateVolumeRequest) { + expectedParams := map[string]string{ + "csi.my.company.org/some_annotation": "1234", + "fstype": "ext3", + } + if fmt.Sprintf("%v", req.Parameters) != fmt.Sprintf("%v", expectedParams) { // only pvc name/namespace left + t.Errorf("Unexpected parameters: %v", req.Parameters) + } + }, + expectState: controller.ProvisioningFinished, + }, "multiple fsType provision": { volOpts: controller.ProvisionOptions{ StorageClass: &storagev1.StorageClass{ @@ -2566,7 +2605,7 @@ func runFSTypeProvisionTest(t *testing.T, k string, tc provisioningFSTypeTestcas myDefaultfsType = "" } csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, - nil, provisionDriverName, pluginCaps, controllerCaps, supportsMigrationFromInTreePluginName, false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, myDefaultfsType, nil, false, false) + nil, provisionDriverName, pluginCaps, controllerCaps, supportsMigrationFromInTreePluginName, false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, "", myDefaultfsType, nil, false, false) out := &csi.CreateVolumeResponse{ Volume: &csi.Volume{ CapacityBytes: requestedBytes, @@ -2750,7 +2789,7 @@ func runProvisionTest(t *testing.T, tc provisioningTestcase, requestedBytes int6 } mycontrollerPublishReadOnly := tc.controllerPublishReadOnly csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, - nil, provisionDriverName, pluginCaps, controllerCaps, supportsMigrationFromInTreePluginName, false, true, csitrans.New(), scInformer.Lister(), csiNodeInformer.Lister(), nodeInformer.Lister(), nil, nil, nil, tc.withExtraMetadata, defaultfsType, nodeDeployment, mycontrollerPublishReadOnly, false) + nil, provisionDriverName, pluginCaps, controllerCaps, supportsMigrationFromInTreePluginName, false, true, csitrans.New(), scInformer.Lister(), csiNodeInformer.Lister(), nodeInformer.Lister(), nil, nil, nil, tc.withExtraMetadata, tc.withExtraMetadataPrefix, defaultfsType, nodeDeployment, mycontrollerPublishReadOnly, false) // Adding objects to the informer ensures that they are consistent with // the fake storage without having to start the informers. @@ -4539,7 +4578,7 @@ func TestProvisionFromSnapshot(t *testing.T) { pluginCaps, controllerCaps := provisionFromSnapshotCapabilities() csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, - client, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, refGrantLister, false, defaultfsType, nil, true, true) + client, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, refGrantLister, false, "", defaultfsType, nil, true, true) out := &csi.CreateVolumeResponse{ Volume: &csi.Volume{ @@ -4719,7 +4758,7 @@ func TestProvisionWithTopologyEnabled(t *testing.T) { defer close(stopChan) csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, - csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, csiNodeLister, nodeLister, claimLister, vaLister, nil, false, defaultfsType, nil, true, false) + csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, csiNodeLister, nodeLister, claimLister, vaLister, nil, false, "", defaultfsType, nil, true, false) pv, _, err := csiProvisioner.Provision(context.Background(), controller.ProvisionOptions{ StorageClass: &storagev1.StorageClass{}, @@ -4813,7 +4852,7 @@ func TestProvisionErrorHandling(t *testing.T) { defer close(stopChan) csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, - csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, csiNodeLister, nodeLister, claimLister, vaLister, nil, false, defaultfsType, nil, true, false) + csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, csiNodeLister, nodeLister, claimLister, vaLister, nil, false, "", defaultfsType, nil, true, false) options := controller.ProvisionOptions{ StorageClass: &storagev1.StorageClass{}, @@ -4886,7 +4925,7 @@ func TestProvisionWithTopologyDisabled(t *testing.T) { clientSet := fakeclientset.NewSimpleClientset() pluginCaps, controllerCaps := provisionWithTopologyCapabilities() csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, - csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, defaultfsType, nil, true, false) + csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, nil, nil, nil, false, "", defaultfsType, nil, true, false) out := &csi.CreateVolumeResponse{ Volume: &csi.Volume{ @@ -5582,7 +5621,7 @@ func runDeleteTest(t *testing.T, k string, tc deleteTestcase) { pluginCaps, controllerCaps := provisionCapabilities() scLister, _, _, _, vaLister, _ := listers(clientSet) csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, - csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, nil, nil, nil, vaLister, nil, false, defaultfsType, nodeDeployment, true, false) + csiConn.conn, nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), scLister, nil, nil, nil, vaLister, nil, false, "", defaultfsType, nodeDeployment, true, false) err = csiProvisioner.Delete(context.Background(), tc.persistentVolume) if tc.expectErr && err == nil { @@ -6668,7 +6707,7 @@ func TestProvisionFromPVC(t *testing.T) { // Phase: execute the test csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, - nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, claimLister, nil, refGrantLister, false, defaultfsType, nil, true, false) + nil, driverName, pluginCaps, controllerCaps, "", false, true, csitrans.New(), nil, nil, nil, claimLister, nil, refGrantLister, false, "", defaultfsType, nil, true, false) pv, _, err = csiProvisioner.Provision(context.Background(), tc.volOpts) if tc.expectErr && err == nil { @@ -6802,7 +6841,7 @@ func TestProvisionWithMigration(t *testing.T) { pluginCaps, controllerCaps := provisionCapabilities() csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, - inTreePluginName, false, true, mockTranslator, nil, nil, nil, nil, nil, nil, false, defaultfsType, nil, true, false) + inTreePluginName, false, true, mockTranslator, nil, nil, nil, nil, nil, nil, false, "", defaultfsType, nil, true, false) // Set up return values (AnyTimes to avoid overfitting on implementation) @@ -6978,7 +7017,7 @@ func TestDeleteMigration(t *testing.T) { defer close(stopCh) csiProvisioner := NewCSIProvisioner(clientSet, 5*time.Second, "test-provisioner", "test", 5, csiConn.conn, nil, driverName, pluginCaps, controllerCaps, inTreePluginName, - false, true, mockTranslator, scLister, nil, nil, nil, vaLister, nil, false, defaultfsType, nil, true, false) + false, true, mockTranslator, scLister, nil, nil, nil, vaLister, nil, false, "", defaultfsType, nil, true, false) // Set mock return values (AnyTimes to avoid overfitting on implementation details) mockTranslator.EXPECT().IsPVMigratable(gomock.Any()).Return(tc.expectTranslation).AnyTimes()