diff --git a/cmd/frostfs-cli/modules/tree/add_by_path.go b/cmd/frostfs-cli/modules/tree/add_by_path.go index 90f4a6364..26e3dddfc 100644 --- a/cmd/frostfs-cli/modules/tree/add_by_path.go +++ b/cmd/frostfs-cli/modules/tree/add_by_path.go @@ -62,7 +62,6 @@ func addByPath(cmd *cobra.Command, _ []string) { commonCmd.ExitOnErr(cmd, "meta data parsing: %w", err) path, _ := cmd.Flags().GetString(pathFlagKey) - // pAttr, _ := cmd.Flags().GetString(pathAttributeFlagKey) req := new(tree.AddByPathRequest) req.Body = &tree.AddByPathRequest_Body{ diff --git a/cmd/frostfs-cli/modules/tree/get_by_path.go b/cmd/frostfs-cli/modules/tree/get_by_path.go index 38ad2bff5..c76027938 100644 --- a/cmd/frostfs-cli/modules/tree/get_by_path.go +++ b/cmd/frostfs-cli/modules/tree/get_by_path.go @@ -60,7 +60,11 @@ func getByPath(cmd *cobra.Command, _ []string) { latestOnly, _ := cmd.Flags().GetBool(latestOnlyFlagKey) path, _ := cmd.Flags().GetString(pathFlagKey) - // pAttr, _ := cmd.Flags().GetString(pathAttributeFlagKey) + + var bt []byte + if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil { + bt = t.Marshal() + } req := new(tree.GetNodeByPathRequest) req.Body = &tree.GetNodeByPathRequest_Body{ @@ -71,9 +75,7 @@ func getByPath(cmd *cobra.Command, _ []string) { Path: strings.Split(path, "/"), LatestOnly: latestOnly, AllAttributes: true, - } - if btok := common.ReadBearerToken(cmd, bearerFlagKey); btok != nil { - req.Body.BearerToken = btok.Marshal() + BearerToken: bt, } commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) diff --git a/cmd/frostfs-cli/modules/tree/get_op_log.go b/cmd/frostfs-cli/modules/tree/get_op_log.go new file mode 100644 index 000000000..6efa76133 --- /dev/null +++ b/cmd/frostfs-cli/modules/tree/get_op_log.go @@ -0,0 +1,83 @@ +package tree + +import ( + "crypto/sha256" + "errors" + "io" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "github.com/spf13/cobra" +) + +var getOpLogCmd = &cobra.Command{ + Use: "get-op-log", + Short: "Get logged operations starting with some height", + Run: getOpLog, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initGetOpLogCmd() { + commonflags.Init(getOpLogCmd) + initCTID(getOpLogCmd) + + ff := getOpLogCmd.Flags() + ff.Uint64(heightFlagKey, 0, "Height to start with") + ff.Uint64(countFlagKey, 10, "Logged operations count") + + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func getOpLog(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + + cidRaw, _ := cmd.Flags().GetString(commonflags.CIDFlag) + + var cnr cid.ID + err := cnr.DecodeString(cidRaw) + commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err) + + tid, _ := cmd.Flags().GetString(treeIDFlagKey) + ctx := cmd.Context() + + cli, err := _client(ctx) + commonCmd.ExitOnErr(cmd, "failed to create client: %w", err) + + rawCID := make([]byte, sha256.Size) + cnr.Encode(rawCID) + + height, _ := cmd.Flags().GetUint64(heightFlagKey) + count, _ := cmd.Flags().GetUint64(countFlagKey) + + req := &tree.GetOpLogRequest{ + Body: &tree.GetOpLogRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + Height: height, + Count: count, + }, + } + + commonCmd.ExitOnErr(cmd, "signing message: %w", tree.SignMessage(req, pk)) + + resp, err := cli.GetOpLog(ctx, req) + commonCmd.ExitOnErr(cmd, "get op log: %w", err) + + opLogResp, err := resp.Recv() + for ; err == nil; opLogResp, err = resp.Recv() { + o := opLogResp.GetBody().GetOperation() + + cmd.Println("Parent ID: ", o.GetParentId()) + + cmd.Println("\tChild ID: ", o.GetChildId()) + cmd.Printf("\tMeta: %s\n", o.GetMeta()) + } + if !errors.Is(err, io.EOF) { + commonCmd.ExitOnErr(cmd, "get op log response stream: %w", err) + } +} diff --git a/cmd/frostfs-cli/modules/tree/healthcheck.go b/cmd/frostfs-cli/modules/tree/healthcheck.go new file mode 100644 index 000000000..46d4c3409 --- /dev/null +++ b/cmd/frostfs-cli/modules/tree/healthcheck.go @@ -0,0 +1,42 @@ +package tree + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" + "github.com/spf13/cobra" +) + +var healthcheckCmd = &cobra.Command{ + Use: "healthcheck", + Short: "Check tree service availability", + Run: healthcheck, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initHealthcheckCmd() { + commonflags.Init(healthcheckCmd) + ff := healthcheckCmd.Flags() + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func healthcheck(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + ctx := cmd.Context() + + cli, err := _client(ctx) + commonCmd.ExitOnErr(cmd, "client: %w", err) + + req := &tree.HealthcheckRequest{ + Body: &tree.HealthcheckRequest_Body{}, + } + commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) + + _, err = cli.Healthcheck(ctx, req) + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + + cmd.Println("Success.") +} diff --git a/cmd/frostfs-cli/modules/tree/move.go b/cmd/frostfs-cli/modules/tree/move.go new file mode 100644 index 000000000..b91769185 --- /dev/null +++ b/cmd/frostfs-cli/modules/tree/move.go @@ -0,0 +1,107 @@ +package tree + +import ( + "crypto/sha256" + "errors" + "io" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "github.com/spf13/cobra" +) + +var moveCmd = &cobra.Command{ + Use: "move", + Short: "Move node", + Run: move, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initMoveCmd() { + commonflags.Init(moveCmd) + initCTID(moveCmd) + + ff := moveCmd.Flags() + ff.Uint64(nodeIDFlagKey, 0, "Node ID.") + ff.Uint64(parentIDFlagKey, 0, "Parent ID.") + + _ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey) + _ = getSubtreeCmd.MarkFlagRequired(parentIDFlagKey) + + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func move(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag) + + var cnr cid.ID + err := cnr.DecodeString(cidString) + commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err) + + ctx := cmd.Context() + + cli, err := _client(ctx) + commonCmd.ExitOnErr(cmd, "client: %w", err) + + rawCID := make([]byte, sha256.Size) + cnr.Encode(rawCID) + + tid, _ := cmd.Flags().GetString(treeIDFlagKey) + pid, _ := cmd.Flags().GetUint64(parentIDFlagKey) + nid, _ := cmd.Flags().GetUint64(nodeIDFlagKey) + + var bt []byte + if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil { + bt = t.Marshal() + } + + subTreeReq := &tree.GetSubTreeRequest{ + Body: &tree.GetSubTreeRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + RootId: nid, + Depth: 1, + BearerToken: bt, + }, + } + commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(subTreeReq, pk)) + resp, err := cli.GetSubTree(ctx, subTreeReq) + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + + var meta []*tree.KeyValue + subtreeResp, err := resp.Recv() + for ; err == nil; subtreeResp, err = resp.Recv() { + meta = subtreeResp.GetBody().GetMeta() + } + if !errors.Is(err, io.EOF) { + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + } + var metaErr error + if len(meta) == 0 { + metaErr = errors.New("no meta for given node ID") + } + commonCmd.ExitOnErr(cmd, "unexpected rpc call result: %w", metaErr) + + req := &tree.MoveRequest{ + Body: &tree.MoveRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + ParentId: pid, + NodeId: nid, + Meta: meta, + }, + } + + commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) + + _, err = cli.Move(ctx, req) + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + cmd.Println("Success.") +} diff --git a/cmd/frostfs-cli/modules/tree/remove.go b/cmd/frostfs-cli/modules/tree/remove.go new file mode 100644 index 000000000..7002ac9a4 --- /dev/null +++ b/cmd/frostfs-cli/modules/tree/remove.go @@ -0,0 +1,74 @@ +package tree + +import ( + "crypto/sha256" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove", + Short: "Remove node", + Run: remove, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initRemoveCmd() { + commonflags.Init(removeCmd) + initCTID(removeCmd) + + ff := removeCmd.Flags() + ff.Uint64(nodeIDFlagKey, 0, "Node ID.") + + _ = getSubtreeCmd.MarkFlagRequired(nodeIDFlagKey) + + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func remove(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag) + + var cnr cid.ID + err := cnr.DecodeString(cidString) + commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err) + + ctx := cmd.Context() + + cli, err := _client(ctx) + commonCmd.ExitOnErr(cmd, "client: %w", err) + + rawCID := make([]byte, sha256.Size) + cnr.Encode(rawCID) + + tid, _ := cmd.Flags().GetString(treeIDFlagKey) + + nid, _ := cmd.Flags().GetUint64(nodeIDFlagKey) + + var bt []byte + if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil { + bt = t.Marshal() + } + req := &tree.RemoveRequest{ + Body: &tree.RemoveRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + NodeId: nid, + BearerToken: bt, + }, + } + + commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) + + _, err = cli.Remove(ctx, req) + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + cmd.Println("Success.") +} diff --git a/cmd/frostfs-cli/modules/tree/root.go b/cmd/frostfs-cli/modules/tree/root.go index b5de0dd6f..701a78f2a 100644 --- a/cmd/frostfs-cli/modules/tree/root.go +++ b/cmd/frostfs-cli/modules/tree/root.go @@ -15,16 +15,28 @@ func init() { Cmd.AddCommand(getByPathCmd) Cmd.AddCommand(addByPathCmd) Cmd.AddCommand(listCmd) + Cmd.AddCommand(healthcheckCmd) + Cmd.AddCommand(moveCmd) + Cmd.AddCommand(removeCmd) + Cmd.AddCommand(getSubtreeCmd) + Cmd.AddCommand(getOpLogCmd) initAddCmd() initGetByPathCmd() initAddByPathCmd() initListCmd() + initHealthcheckCmd() + initMoveCmd() + initRemoveCmd() + initGetSubtreeCmd() + initGetOpLogCmd() } const ( treeIDFlagKey = "tid" parentIDFlagKey = "pid" + nodeIDFlagKey = "nid" + rootIDFlagKey = "root" metaFlagKey = "meta" @@ -34,6 +46,10 @@ const ( latestOnlyFlagKey = "latest" bearerFlagKey = "bearer" + + heightFlagKey = "height" + countFlagKey = "count" + depthFlagKey = "depth" ) func initCTID(cmd *cobra.Command) { @@ -45,5 +61,5 @@ func initCTID(cmd *cobra.Command) { ff.String(treeIDFlagKey, "", "Tree ID") _ = cmd.MarkFlagRequired(treeIDFlagKey) - ff.StringP(bearerFlagKey, "", "", "Path to bearer token") + ff.String(bearerFlagKey, "", "Path to bearer token") } diff --git a/cmd/frostfs-cli/modules/tree/subtree.go b/cmd/frostfs-cli/modules/tree/subtree.go new file mode 100644 index 000000000..f6dcf85ff --- /dev/null +++ b/cmd/frostfs-cli/modules/tree/subtree.go @@ -0,0 +1,101 @@ +package tree + +import ( + "crypto/sha256" + "errors" + "io" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "github.com/spf13/cobra" +) + +var getSubtreeCmd = &cobra.Command{ + Use: "get-subtree", + Short: "Get subtree", + Run: getSubTree, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initGetSubtreeCmd() { + commonflags.Init(getSubtreeCmd) + initCTID(getSubtreeCmd) + + ff := getSubtreeCmd.Flags() + ff.Uint64(rootIDFlagKey, 0, "Root ID to traverse from.") + ff.Uint32(depthFlagKey, 10, "Traversal depth.") + + _ = getSubtreeCmd.MarkFlagRequired(commonflags.CIDFlag) + _ = getSubtreeCmd.MarkFlagRequired(treeIDFlagKey) + + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func getSubTree(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + cidString, _ := cmd.Flags().GetString(commonflags.CIDFlag) + + var cnr cid.ID + err := cnr.DecodeString(cidString) + commonCmd.ExitOnErr(cmd, "decode container ID string: %w", err) + + ctx := cmd.Context() + + cli, err := _client(ctx) + commonCmd.ExitOnErr(cmd, "client: %w", err) + + rawCID := make([]byte, sha256.Size) + cnr.Encode(rawCID) + + tid, _ := cmd.Flags().GetString(treeIDFlagKey) + + rid, _ := cmd.Flags().GetUint64(rootIDFlagKey) + + depth, _ := cmd.Flags().GetUint32(depthFlagKey) + + var bt []byte + if t := common.ReadBearerToken(cmd, bearerFlagKey); t != nil { + bt = t.Marshal() + } + + req := &tree.GetSubTreeRequest{ + Body: &tree.GetSubTreeRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + RootId: rid, + Depth: depth, + BearerToken: bt, + }, + } + + commonCmd.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) + + resp, err := cli.GetSubTree(ctx, req) + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + + subtreeResp, err := resp.Recv() + for ; err == nil; subtreeResp, err = resp.Recv() { + b := subtreeResp.GetBody() + + cmd.Printf("Node ID: %d\n", b.GetNodeId()) + + cmd.Println("\tParent ID: ", b.GetParentId()) + cmd.Println("\tTimestamp: ", b.GetTimestamp()) + + if meta := b.GetMeta(); len(meta) > 0 { + cmd.Println("\tMeta pairs: ") + for _, kv := range meta { + cmd.Printf("\t\t%s: %s\n", kv.GetKey(), string(kv.GetValue())) + } + } + } + if !errors.Is(err, io.EOF) { + commonCmd.ExitOnErr(cmd, "rpc call: %w", err) + } +}