diff --git a/README.md b/README.md index 4e41f3a..1461203 100644 --- a/README.md +++ b/README.md @@ -91,11 +91,13 @@ alias dpu="docker run --rm --network host ghcr.io/opiproject/godpu:main" 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 +dpu storage get backend nvme path --name $path0 # 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 +dpu storage get backend nvme path --name $path1 # 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 8a84ac8..64c5daf 100644 --- a/cmd/storage/backend/backend.go +++ b/cmd/storage/backend/backend.go @@ -109,6 +109,7 @@ func newGetNvmeCommand() *cobra.Command { } cmd.AddCommand(newGetNvmeControllerCommand()) + cmd.AddCommand(newGetNvmePathCommand()) return cmd } diff --git a/cmd/storage/backend/nvme_path.go b/cmd/storage/backend/nvme_path.go index 282ba4c..e6e2d0c 100644 --- a/cmd/storage/backend/nvme_path.go +++ b/cmd/storage/backend/nvme_path.go @@ -11,6 +11,7 @@ import ( "github.com/opiproject/godpu/cmd/storage/common" backendclient "github.com/opiproject/godpu/storage/backend" "github.com/spf13/cobra" + "google.golang.org/protobuf/encoding/protojson" ) func newCreateNvmePathCommand() *cobra.Command { @@ -150,3 +151,37 @@ func newDeleteNvmePathCommand() *cobra.Command { return cmd } + +func newGetNvmePathCommand() *cobra.Command { + name := "" + cmd := &cobra.Command{ + Use: "path", + Aliases: []string{"p"}, + Short: "Gets nvme path to an external nvme device", + Args: cobra.NoArgs, + Run: func(c *cobra.Command, args []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.GetNvmePath(ctx, name) + cobra.CheckErr(err) + + common.PrintResponse(protojson.Format(ctrl)) + }, + } + + cmd.Flags().StringVar(&name, "name", "", "name of path to get") + + cobra.CheckErr(cmd.MarkFlagRequired("name")) + + return cmd +} diff --git a/docker-compose.yml b/docker-compose.yml index 6fb5292..6522b7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -187,6 +187,7 @@ services: 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) && \ + /dpu storage get backend nvme path --addr=opi-spdk-server:50051 --name $$path0" && \ 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") && \ ns0=$$(/dpu storage create frontend nvme namespace --addr=opi-spdk-server:50051 --id namespace0 --volume "Malloc0" --subsystem "$$ss0") && \ diff --git a/storage/backend/nvme_path.go b/storage/backend/nvme_path.go index 863a992..70387a2 100644 --- a/storage/backend/nvme_path.go +++ b/storage/backend/nvme_path.go @@ -108,3 +108,22 @@ func (c *Client) DeleteNvmePath( return err } + +// GetNvmePath gets an nvme path to an external nvme controller +func (c *Client) GetNvmePath( + ctx context.Context, + name string, +) (*pb.NvmePath, error) { + conn, connClose, err := c.connector.NewConn() + if err != nil { + return nil, err + } + defer connClose() + + client := c.createNvmeClient(conn) + return client.GetNvmePath( + ctx, + &pb.GetNvmePathRequest{ + Name: name, + }) +} diff --git a/storage/backend/nvme_path_test.go b/storage/backend/nvme_path_test.go index 36b39da..c4523d7 100644 --- a/storage/backend/nvme_path_test.go +++ b/storage/backend/nvme_path_test.go @@ -316,3 +316,87 @@ func TestDeleteNvmePath(t *testing.T) { }) } } + +func TestGetNvmePath(t *testing.T) { + testPathName := "path0" + testRequest := &pb.GetNvmePathRequest{ + Name: testPathName, + } + testPath := &pb.NvmePath{ + Name: testPathName, + Trtype: pb.NvmeTransportType_NVME_TRANSPORT_TYPE_TCP, + Traddr: "127.0.0.1", + Fabrics: &pb.FabricsPath{ + Trsvcid: 4420, + Subnqn: "nqn.2019-06.io.spdk:8", + Adrfam: pb.NvmeAddressFamily_NVME_ADDRESS_FAMILY_IPV4, + }, + } + tests := map[string]struct { + giveClientErr error + giveConnectorErr error + wantErr error + wantRequest *pb.GetNvmePathRequest + wantResponse *pb.NvmePath + wantConnClosed bool + }{ + "successful call": { + giveConnectorErr: nil, + giveClientErr: nil, + wantErr: nil, + wantRequest: proto.Clone(testRequest).(*pb.GetNvmePathRequest), + wantResponse: proto.Clone(testPath).(*pb.NvmePath), + wantConnClosed: true, + }, + "client err": { + giveConnectorErr: nil, + giveClientErr: errors.New("Some client error"), + wantErr: errors.New("Some client error"), + wantRequest: proto.Clone(testRequest).(*pb.GetNvmePathRequest), + 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().GetNvmePath(ctx, tt.wantRequest). + Return(proto.Clone(tt.wantResponse).(*pb.NvmePath), 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.GetNvmePath(ctx, testPathName) + + require.Equal(t, tt.wantErr, err) + require.True(t, proto.Equal(tt.wantResponse, response)) + require.Equal(t, tt.wantConnClosed, connClosed) + }) + } +}