From 2dd3fc8b7e7c4e00d1b808488f31a4afd94d5516 Mon Sep 17 00:00:00 2001 From: Alejandro Lopez Date: Thu, 1 Jun 2023 12:56:51 +0300 Subject: [PATCH] [#xx] cli: Add policy-playground command Signed-off-by: Alejandro Lopez --- .../modules/container/policy_playground.go | 178 ++++++++++++++++++ cmd/frostfs-cli/modules/container/root.go | 2 + 2 files changed, 180 insertions(+) create mode 100644 cmd/frostfs-cli/modules/container/policy_playground.go diff --git a/cmd/frostfs-cli/modules/container/policy_playground.go b/cmd/frostfs-cli/modules/container/policy_playground.go new file mode 100644 index 00000000..fa10dc10 --- /dev/null +++ b/cmd/frostfs-cli/modules/container/policy_playground.go @@ -0,0 +1,178 @@ +package container + +import ( + "bufio" + "encoding/hex" + "fmt" + "io" + "os" + "strings" + + internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" + "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-sdk-go/netmap" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type policyPlaygroundREPL struct { + cmd *cobra.Command + args []string + nodes map[string]netmap.NodeInfo +} + +func newPolicyPlaygroundREPL(cmd *cobra.Command, args []string) (*policyPlaygroundREPL, error) { + return &policyPlaygroundREPL{ + cmd: cmd, + args: args, + nodes: map[string]netmap.NodeInfo{}, + }, nil +} + +func (repl *policyPlaygroundREPL) handleLs(args []string) error { + if len(args) > 0 { + return fmt.Errorf("too many arguments for command 'ls': got %d, want 0", len(args)) + } + i := 1 + for id, node := range repl.nodes { + var attrs []string + node.IterateAttributes(func(k, v string) { + attrs = append(attrs, fmt.Sprintf("%s:%s", k, v)) + }) + fmt.Printf("\t%2d: id=%s attrs={%v}\n", i, id, strings.Join(attrs, " ")) + i++ + } + return nil +} + +func (repl *policyPlaygroundREPL) handleAdd(args []string) error { + if len(args) == 0 { + return fmt.Errorf("too few arguments for command 'add': got %d, want >0", len(args)) + } + id := args[0] + key, err := hex.DecodeString(id) + if err != nil { + return fmt.Errorf("node id must be a hex string: got %q: %v", id, err) + } + node := repl.nodes[id] + node.SetPublicKey(key) + for _, attr := range args[1:] { + kv := strings.Split(attr, ":") + if len(kv) != 2 { + return fmt.Errorf("node attributes must be in the format 'KEY:VALUE': got %q", attr) + } + node.SetAttribute(kv[0], kv[1]) + } + repl.nodes[id] = node + return nil +} + +func (repl *policyPlaygroundREPL) handleRemove(args []string) error { + if len(args) == 0 { + return fmt.Errorf("too few arguments for command 'remove': got %d, want >0", len(args)) + } + id := args[0] + if _, exists := repl.nodes[id]; exists { + delete(repl.nodes, id) + return nil + } + return fmt.Errorf("node not found: id=%q", id) +} + +func (repl *policyPlaygroundREPL) handleEval(args []string) error { + policyStr := strings.Join(args, " ") + placementPolicy, err := parseContainerPolicy(repl.cmd, policyStr) + if err != nil { + return fmt.Errorf("parsing placement policy: %v", err) + } + nm := repl.netMap() + nodes, err := nm.ContainerNodes(*placementPolicy, nil) + if err != nil { + return fmt.Errorf("building container nodes: %v", err) + } + for i, ns := range nodes { + var ids []string + for _, node := range ns { + ids = append(ids, hex.EncodeToString(node.PublicKey())) + } + fmt.Printf("\t%2d: %v\n", i+1, ids) + } + + return nil +} + +func (repl *policyPlaygroundREPL) netMap() netmap.NetMap { + var nm netmap.NetMap + var nodes []netmap.NodeInfo + for _, node := range repl.nodes { + nodes = append(nodes, node) + } + nm.SetNodes(nodes) + return nm +} + +func (repl *policyPlaygroundREPL) run() error { + if len(viper.GetString(commonflags.RPC)) > 0 { + key := key.GetOrGenerate(repl.cmd) + cli := internalclient.GetSDKClientByFlag(repl.cmd, key, commonflags.RPC) + + var prm internalclient.NetMapSnapshotPrm + prm.SetClient(cli) + + resp, err := internalclient.NetMapSnapshot(prm) + commonCmd.ExitOnErr(repl.cmd, "unable to get netmap snapshot to populate initial netmap: %w", err) + + for _, node := range resp.NetMap().Nodes() { + id := hex.EncodeToString(node.PublicKey()) + repl.nodes[id] = node + } + } + + cmdHandlers := map[string]func([]string) error{ + "ls": repl.handleLs, + "add": repl.handleAdd, + "remove": repl.handleRemove, + "eval": repl.handleEval, + } + for reader := bufio.NewReader(os.Stdin); ; { + fmt.Print("> ") + line, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + return nil + } + return fmt.Errorf("reading line: %v", err) + } + parts := strings.Fields(line) + if len(parts) == 0 { + continue + } + cmd := parts[0] + handler, exists := cmdHandlers[cmd] + if exists { + if err := handler(parts[1:]); err != nil { + fmt.Printf("error: %v\n", err) + } + } else { + fmt.Printf("error: unknown command %q\n", cmd) + } + } +} + +var policyPlaygroundCmd = &cobra.Command{ + Use: "policy-playground", + Short: "A REPL for testing placement policies", + Long: `A REPL for testing placement policies. +If a wallet and endpoint is provided, the initial netmap data will be loaded from the snapshot of the node. Otherwise, an empty playground is created.`, + Run: func(cmd *cobra.Command, args []string) { + repl, err := newPolicyPlaygroundREPL(cmd, args) + commonCmd.ExitOnErr(cmd, "could not create policy playground: %w", err) + commonCmd.ExitOnErr(cmd, "policy playground failed: %w", repl.run()) + }, +} + +func initContainerPolicyPlaygroundCmd() { + commonflags.Init(policyPlaygroundCmd) +} diff --git a/cmd/frostfs-cli/modules/container/root.go b/cmd/frostfs-cli/modules/container/root.go index 30a82954..ab7f5fb9 100644 --- a/cmd/frostfs-cli/modules/container/root.go +++ b/cmd/frostfs-cli/modules/container/root.go @@ -28,6 +28,7 @@ func init() { getExtendedACLCmd, setExtendedACLCmd, containerNodesCmd, + policyPlaygroundCmd, } Cmd.AddCommand(containerChildCommand...) @@ -40,6 +41,7 @@ func init() { initContainerGetEACLCmd() initContainerSetEACLCmd() initContainerNodesCmd() + initContainerPolicyPlaygroundCmd() for _, containerCommand := range containerChildCommand { commonflags.InitAPI(containerCommand)