From e9596b172fb0e20b5b0631be79153aec8502dd5b Mon Sep 17 00:00:00 2001 From: Artsiom Koltun Date: Fri, 9 Feb 2024 14:30:50 +0100 Subject: [PATCH] feat(storage/nvme): add get backend nvme controller cmd Signed-off-by: Artsiom Koltun --- README.md | 5 ++ cmd/storage/backend/backend.go | 35 +++++++++++ cmd/storage/backend/nvme_controller.go | 35 +++++++++++ cmd/storage/storage.go | 18 ++++++ docker-compose.yml | 1 + storage/backend/nvme_controller.go | 20 +++++++ storage/backend/nvme_controller_test.go | 78 +++++++++++++++++++++++++ 7 files changed, 192 insertions(+) diff --git a/README.md b/README.md index 8d1475d..e1556a4 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,15 @@ alias dpu="docker run --rm --network host ghcr.io/opiproject/godpu:main" # connect to remote nvme/tcp controller nvmf0=$(dpu storage create backend nvme controller --id nvmf0 --multipath disable) path0=$(dpu storage create backend nvme path tcp --controller "$nvmf0" --id path0 --ip "11.11.11.2" --port 4444 --nqn nqn.2016-06.io.spdk:cnode1 --hostnqn nqn.2014-08.org.nvmexpress:uuid:feb98abe-d51f-40c8-b348-2753f3571d3c) +dpu storage get backend nvme controller --name $nvmf0 + +# get remote controller +dpu storage get backend nvme controller --name $nvmf0 # connect to local nvme/pcie ssd controller nvmf1=$(dpu storage create backend nvme controller --id nvmf1 --multipath disable) path1=$(dpu storage create backend nvme path pcie --controller "$nvmf1" --id path1 --bdf "0000:40:00.0") +dpu storage get backend nvme controller --name $nvmf1 # expose volume over nvme/tcp controller ss0=$(dpu storage create frontend nvme subsystem --id subsys0 --nqn "nqn.2022-09.io.spdk:opitest0") diff --git a/cmd/storage/backend/backend.go b/cmd/storage/backend/backend.go index 0df0d1f..a74e2ae 100644 --- a/cmd/storage/backend/backend.go +++ b/cmd/storage/backend/backend.go @@ -77,3 +77,38 @@ func newDeleteNvmeCommand() *cobra.Command { return cmd } + +// NewGetCommand creates a new command to get backend resources +func NewGetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "backend", + Aliases: []string{"b"}, + Short: "Gets backend resource", + Args: cobra.NoArgs, + Run: func(c *cobra.Command, _ []string) { + err := c.Help() + cobra.CheckErr(err) + }, + } + + cmd.AddCommand(newGetNvmeCommand()) + + return cmd +} + +func newGetNvmeCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "nvme", + Aliases: []string{"n"}, + Short: "Gets nvme resource", + Args: cobra.NoArgs, + Run: func(c *cobra.Command, _ []string) { + err := c.Help() + cobra.CheckErr(err) + }, + } + + cmd.AddCommand(newGetNvmeControllerCommand()) + + return cmd +} diff --git a/cmd/storage/backend/nvme_controller.go b/cmd/storage/backend/nvme_controller.go index 298c8a1..ece917b 100644 --- a/cmd/storage/backend/nvme_controller.go +++ b/cmd/storage/backend/nvme_controller.go @@ -13,6 +13,7 @@ import ( backendclient "github.com/opiproject/godpu/storage/backend" pb "github.com/opiproject/opi-api/storage/v1alpha1/gen/go" "github.com/spf13/cobra" + "google.golang.org/protobuf/encoding/protojson" ) func newCreateNvmeControllerCommand() *cobra.Command { @@ -93,3 +94,37 @@ func newDeleteNvmeControllerCommand() *cobra.Command { return cmd } + +func newGetNvmeControllerCommand() *cobra.Command { + name := "" + cmd := &cobra.Command{ + Use: "controller", + Aliases: []string{"c"}, + Short: "Gets nvme controller representing an external nvme device", + Args: cobra.NoArgs, + Run: func(c *cobra.Command, _ []string) { + addr, err := c.Flags().GetString(common.AddrCmdLineArg) + cobra.CheckErr(err) + + timeout, err := c.Flags().GetDuration(common.TimeoutCmdLineArg) + cobra.CheckErr(err) + + client, err := backendclient.New(addr) + cobra.CheckErr(err) + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + ctrl, err := client.GetNvmeController(ctx, name) + cobra.CheckErr(err) + + common.PrintResponse(protojson.Format(ctrl)) + }, + } + + cmd.Flags().StringVar(&name, "name", "", "name of remote controller to get") + + cobra.CheckErr(cmd.MarkFlagRequired("name")) + + return cmd +} diff --git a/cmd/storage/storage.go b/cmd/storage/storage.go index c748762..12829b7 100644 --- a/cmd/storage/storage.go +++ b/cmd/storage/storage.go @@ -32,6 +32,7 @@ func NewStorageCommand() *cobra.Command { cmd.AddCommand(newStorageCreateCommand()) cmd.AddCommand(newStorageDeleteCommand()) + cmd.AddCommand(newStorageGetCommand()) cmd.AddCommand(newStorageTestCommand()) return cmd @@ -72,3 +73,20 @@ func newStorageDeleteCommand() *cobra.Command { return cmd } + +func newStorageGetCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "get", + Aliases: []string{"g"}, + Short: "Gets resource", + Args: cobra.NoArgs, + Run: func(c *cobra.Command, _ []string) { + err := c.Help() + cobra.CheckErr(err) + }, + } + + cmd.AddCommand(backend.NewGetCommand()) + + return cmd +} diff --git a/docker-compose.yml b/docker-compose.yml index 73c9467..6fb5292 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -185,6 +185,7 @@ services: command: | '/dpu storage test --addr opi-spdk-server:50051 && \ nvmf0=$$(/dpu storage create backend nvme controller --addr=opi-spdk-server:50051 --id nvmf0 --multipath failover) && \ + /dpu storage get backend nvme controller --addr=opi-spdk-server:50051 --name "$$nvmf0" && \ path0=$$(/dpu storage create backend nvme path tcp --addr=opi-spdk-server:50051 --controller "$$nvmf0" --id path0 --ip $$(getent hosts spdk | cut -d" " -f1) --port 4444 --nqn nqn.2016-06.io.spdk:cnode1 --hostnqn nqn.2014-08.org.nvmexpress:uuid:feb98abe-d51f-40c8-b348-2753f3571d3c) && \ ss0=$$(/dpu storage create frontend nvme subsystem --addr=opi-spdk-server:50051 --id subsys0 --nqn "nqn.2022-09.io.spdk:opitest1") && \ ctrl0=$$(/dpu storage create frontend nvme controller tcp --addr=opi-spdk-server:50051 --id ctrl0 --ip "127.0.0.1" --port 4420 --subsystem "$$ss0") && \ diff --git a/storage/backend/nvme_controller.go b/storage/backend/nvme_controller.go index 79d0c2e..5e3c1cc 100644 --- a/storage/backend/nvme_controller.go +++ b/storage/backend/nvme_controller.go @@ -59,3 +59,23 @@ func (c *Client) DeleteNvmeController( return err } + +// GetNvmeController gets an nvme controller representing +// an external nvme device +func (c *Client) GetNvmeController( + ctx context.Context, + name string, +) (*pb.NvmeRemoteController, error) { + conn, connClose, err := c.connector.NewConn() + if err != nil { + return nil, err + } + defer connClose() + + client := c.createNvmeClient(conn) + return client.GetNvmeRemoteController( + ctx, + &pb.GetNvmeRemoteControllerRequest{ + Name: name, + }) +} diff --git a/storage/backend/nvme_controller_test.go b/storage/backend/nvme_controller_test.go index 56be15a..57e1341 100644 --- a/storage/backend/nvme_controller_test.go +++ b/storage/backend/nvme_controller_test.go @@ -173,3 +173,81 @@ func TestDeleteNvmeController(t *testing.T) { }) } } + +func TestGetNvmeController(t *testing.T) { + testControllerName := "remotenvmeget" + testRequest := &pb.GetNvmeRemoteControllerRequest{ + Name: testControllerName, + } + testController := &pb.NvmeRemoteController{ + Name: testControllerName, + Multipath: pb.NvmeMultipath_NVME_MULTIPATH_FAILOVER, + } + tests := map[string]struct { + giveClientErr error + giveConnectorErr error + wantErr error + wantRequest *pb.GetNvmeRemoteControllerRequest + wantResponse *pb.NvmeRemoteController + wantConnClosed bool + }{ + "successful call": { + giveConnectorErr: nil, + giveClientErr: nil, + wantErr: nil, + wantRequest: proto.Clone(testRequest).(*pb.GetNvmeRemoteControllerRequest), + wantResponse: proto.Clone(testController).(*pb.NvmeRemoteController), + wantConnClosed: true, + }, + "client err": { + giveConnectorErr: nil, + giveClientErr: errors.New("Some client error"), + wantErr: errors.New("Some client error"), + wantRequest: proto.Clone(testRequest).(*pb.GetNvmeRemoteControllerRequest), + wantResponse: nil, + wantConnClosed: true, + }, + "connector err": { + giveConnectorErr: errors.New("Some conn error"), + giveClientErr: nil, + wantErr: errors.New("Some conn error"), + wantRequest: nil, + wantResponse: nil, + wantConnClosed: false, + }, + } + + for testName, tt := range tests { + t.Run(testName, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + mockClient := mocks.NewNvmeRemoteControllerServiceClient(t) + if tt.wantRequest != nil { + mockClient.EXPECT().GetNvmeRemoteController(ctx, tt.wantRequest). + Return(proto.Clone(tt.wantResponse).(*pb.NvmeRemoteController), tt.giveClientErr) + } + + connClosed := false + mockConn := mocks.NewConnector(t) + mockConn.EXPECT().NewConn().Return( + &grpc.ClientConn{}, + func() { connClosed = true }, + tt.giveConnectorErr, + ) + + c, _ := NewWithArgs( + mockConn, + func(grpc.ClientConnInterface) pb.NvmeRemoteControllerServiceClient { + return mockClient + }, + ) + + response, err := c.GetNvmeController(ctx, testControllerName) + + require.Equal(t, tt.wantErr, err) + require.True(t, proto.Equal(tt.wantResponse, response)) + require.Equal(t, tt.wantConnClosed, connClosed) + }) + } +}