From 9dff902a365dc192d7032281e7c35d78ccd4ee3b Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Tue, 18 Feb 2020 17:08:38 +0100 Subject: [PATCH] client: Implement ClientCSI.ControllerValidateVolume --- client/client_csi_endpoint.go | 30 ++++++++++ client/client_csi_endpoint_test.go | 89 ++++++++++++++++++++++++++++++ client/structs/csi.go | 11 ++++ 3 files changed, 130 insertions(+) diff --git a/client/client_csi_endpoint.go b/client/client_csi_endpoint.go index 6d3efab5b..0922f0dc6 100644 --- a/client/client_csi_endpoint.go +++ b/client/client_csi_endpoint.go @@ -30,6 +30,36 @@ var ( ErrPluginTypeError = errors.New("CSI Plugin loaded incorrectly") ) +// CSIControllerValidateVolume is used during volume registration to validate +// that a volume exists and that the capabilities it was registered with are +// supported by the CSI Plugin and external volume configuration. +func (c *ClientCSI) CSIControllerValidateVolume(req *structs.ClientCSIControllerValidateVolumeRequest, resp *structs.ClientCSIControllerValidateVolumeResponse) error { + defer metrics.MeasureSince([]string{"client", "csi_controller", "validate_volume"}, time.Now()) + + if req.VolumeID == "" { + return errors.New("VolumeID is required") + } + + if req.PluginID == "" { + return errors.New("PluginID is required") + } + + plugin, err := c.findControllerPlugin(req.PluginID) + if err != nil { + return err + } + defer plugin.Close() + + caps, err := csi.VolumeCapabilityFromStructs(req.AttachmentMode, req.AccessMode) + if err != nil { + return err + } + + ctx, cancelFn := c.requestContext() + defer cancelFn() + return plugin.ControllerValidateCapabilties(ctx, req.VolumeID, caps) +} + // CSIControllerAttachVolume is used to attach a volume from a CSI Cluster to // the storage node provided in the request. // diff --git a/client/client_csi_endpoint_test.go b/client/client_csi_endpoint_test.go index 7af35aa49..4e5da6bd6 100644 --- a/client/client_csi_endpoint_test.go +++ b/client/client_csi_endpoint_test.go @@ -147,3 +147,92 @@ func TestClientCSI_CSIControllerAttachVolume(t *testing.T) { }) } } + +func TestClientCSI_CSIControllerValidateVolume(t *testing.T) { + t.Parallel() + + cases := []struct { + Name string + ClientSetupFunc func(*fake.Client) + Request *structs.ClientCSIControllerValidateVolumeRequest + ExpectedErr error + ExpectedResponse *structs.ClientCSIControllerValidateVolumeResponse + }{ + { + Name: "validates volumeid is not empty", + Request: &structs.ClientCSIControllerValidateVolumeRequest{ + PluginID: fakePlugin.Name, + }, + ExpectedErr: errors.New("VolumeID is required"), + }, + { + Name: "returns plugin not found errors", + Request: &structs.ClientCSIControllerValidateVolumeRequest{ + PluginID: "some-garbage", + VolumeID: "foo", + }, + ExpectedErr: errors.New("plugin some-garbage for type csi-controller not found"), + }, + { + Name: "validates attachmentmode", + Request: &structs.ClientCSIControllerValidateVolumeRequest{ + PluginID: fakePlugin.Name, + VolumeID: "1234-4321-1234-4321", + AttachmentMode: nstructs.CSIVolumeAttachmentMode("bar"), + AccessMode: nstructs.CSIVolumeAccessModeMultiNodeReader, + }, + ExpectedErr: errors.New("Unknown volume attachment mode: bar"), + }, + { + Name: "validates AccessMode", + Request: &structs.ClientCSIControllerValidateVolumeRequest{ + PluginID: fakePlugin.Name, + VolumeID: "1234-4321-1234-4321", + AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem, + AccessMode: nstructs.CSIVolumeAccessMode("foo"), + }, + ExpectedErr: errors.New("Unknown volume access mode: foo"), + }, + { + Name: "returns transitive errors", + ClientSetupFunc: func(fc *fake.Client) { + fc.NextControllerValidateVolumeErr = errors.New("hello") + }, + Request: &structs.ClientCSIControllerValidateVolumeRequest{ + PluginID: fakePlugin.Name, + VolumeID: "1234-4321-1234-4321", + AccessMode: nstructs.CSIVolumeAccessModeSingleNodeWriter, + AttachmentMode: nstructs.CSIVolumeAttachmentModeFilesystem, + }, + ExpectedErr: errors.New("hello"), + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + require := require.New(t) + client, cleanup := TestClient(t, nil) + defer cleanup() + + fakeClient := &fake.Client{} + if tc.ClientSetupFunc != nil { + tc.ClientSetupFunc(fakeClient) + } + + dispenserFunc := func(*dynamicplugins.PluginInfo) (interface{}, error) { + return fakeClient, nil + } + client.dynamicRegistry.StubDispenserForType(dynamicplugins.PluginTypeCSIController, dispenserFunc) + + err := client.dynamicRegistry.RegisterPlugin(fakePlugin) + require.Nil(err) + + var resp structs.ClientCSIControllerValidateVolumeResponse + err = client.ClientRPC("ClientCSI.CSIControllerValidateVolume", tc.Request, &resp) + require.Equal(tc.ExpectedErr, err) + if tc.ExpectedResponse != nil { + require.Equal(tc.ExpectedResponse, &resp) + } + }) + } +} diff --git a/client/structs/csi.go b/client/structs/csi.go index 9616d1b78..6b9f891be 100644 --- a/client/structs/csi.go +++ b/client/structs/csi.go @@ -20,6 +20,17 @@ type CSIVolumeMountOptions struct { MountFlags []string } +type ClientCSIControllerValidateVolumeRequest struct { + PluginID string + VolumeID string + + AttachmentMode structs.CSIVolumeAttachmentMode + AccessMode structs.CSIVolumeAccessMode +} + +type ClientCSIControllerValidateVolumeResponse struct { +} + type ClientCSIControllerAttachVolumeRequest struct { PluginName string