From 207ead865a6c16be9b8aa75c23d19a5982b5e2bd Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Mon, 22 Apr 2024 21:06:16 +0300 Subject: [PATCH] neofs-adm: Add container estimations inspector Signed-off-by: Pavel Karpy --- CHANGELOG.md | 1 + .../internal/modules/morph/estimation.go | 139 ++++++++++++++++++ cmd/neofs-adm/internal/modules/morph/root.go | 16 ++ 3 files changed, 156 insertions(+) create mode 100644 cmd/neofs-adm/internal/modules/morph/estimation.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 812cc76e0f..8eeabc0033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog for NeoFS Node ## [Unreleased] ### Added +- Container estimations inspector to neofs-amd (#2826) ### Fixed diff --git a/cmd/neofs-adm/internal/modules/morph/estimation.go b/cmd/neofs-adm/internal/modules/morph/estimation.go new file mode 100644 index 0000000000..5fff8791b8 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/estimation.go @@ -0,0 +1,139 @@ +package morph + +import ( + "fmt" + + "github.com/google/uuid" + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neofs-node/pkg/morph/client/container" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var estimationsCmd = &cobra.Command{ + Use: "estimations", + Short: "See container estimations reported by storage nodes", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + RunE: estimationFunc, + Args: cobra.NoArgs, +} + +func estimationFunc(cmd *cobra.Command, args []string) error { + cIDString, err := cmd.Flags().GetString(estimationsContainerFlag) + if err != nil { + panic(fmt.Errorf("reading %s flag: %w", estimationsContainerFlag, err)) + } + + var cID cid.ID + if err := cID.DecodeString(cIDString); err != nil { + return fmt.Errorf("invalid container ID: %w", err) + } + + c, err := getN3Client(viper.GetViper()) + if err != nil { + return fmt.Errorf("can't create N3 client: %w", err) + } + + inv := invoker.New(c, nil) + + nnsState, err := c.GetContractStateByID(1) + if err != nil { + return err + } + + epoch, err := cmd.Flags().GetInt64(estimationsEpochFlag) + if err != nil { + panic(fmt.Errorf("reading %s flag: %w", estimationsEpochFlag, err)) + } + + if epoch <= 0 { + netmapHash, err := nnsResolveHash(inv, nnsState.Hash, netmapContract+".neofs") + if err != nil { + return fmt.Errorf("netmap contract hash resolution: %w", err) + } + + epochFromContract, err := unwrap.Int64(inv.Call(netmapHash, "epoch")) + if err != nil { + return fmt.Errorf("reading epoch: %w", err) + } + + epoch = epochFromContract + epoch // epoch is negative here + } + + cIDBytes := make([]byte, 32) + cID.Encode(cIDBytes) + + cnrHash, err := nnsResolveHash(inv, nnsState.Hash, containerContract+".neofs") + if err != nil { + return fmt.Errorf("container contract hash resolution: %w", err) + } + + sID, iter, err := unwrap.SessionIterator(inv.Call(cnrHash, "iterateContainerSizes", epoch, cIDBytes)) + if err != nil { + return fmt.Errorf("iterator expansion: %w", err) + } + + defer func() { + if (sID != uuid.UUID{}) { + _ = inv.TerminateSession(sID) + } + }() + + ee, err := parseEstimations(inv, sID, iter) + if err != nil { + return fmt.Errorf("parsing estimations read from contract: %w", err) + } + + if len(ee) == 0 { + cmd.Printf("No estimations found for %s container in %d epoch\n", cID, epoch) + return nil + } + + printEstimations(cmd, epoch, ee) + + return nil +} + +func parseEstimations(inv *invoker.Invoker, sID uuid.UUID, iter result.Iterator) ([]container.Estimation, error) { + items := make([]stackitem.Item, 0) + + for { + ii, err := inv.TraverseIterator(sID, &iter, config.DefaultMaxIteratorResultItems) + if err != nil { + return nil, fmt.Errorf("iterator traversal; session: %s, error: %w", sID, err) + } + + if len(ii) == 0 { + break + } + + items = append(items, ii...) + } + + ee := make([]container.Estimation, len(items)) + for i := range items { + err := ee[i].FromStackItem(items[i]) + if err != nil { + return nil, fmt.Errorf("parsing stack item: %w", err) + } + } + + return ee, nil +} + +func printEstimations(cmd *cobra.Command, epoch int64, ee []container.Estimation) { + cmd.Printf("Estimations for %d epoch:\n", epoch) + + for _, estimation := range ee { + reporterString := base58.Encode(estimation.Reporter) + cmd.Printf("Size: %d, reporter's key (base58 encoding): %s\n", estimation.Size, reporterString) + } +} diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index 0842373e5c..8ba9cf7efb 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -52,6 +52,8 @@ const ( neoAddressesFlag = "neo-addresses" publicKeysFlag = "public-keys" walletFlag = "wallet" + estimationsEpochFlag = "epoch" + estimationsContainerFlag = "cid" ) var ( @@ -438,6 +440,20 @@ func init() { RootCmd.AddCommand(netmapCandidatesCmd) netmapCandidatesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + RootCmd.AddCommand(estimationsCmd) + ff := estimationsCmd.Flags() + ff.Int64(estimationsEpochFlag, 0, "Epoch for estimations, `0` for current, negative for relative epochs") + estimationsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint") + err := cobra.MarkFlagRequired(ff, endpointFlag) + if err != nil { + panic(fmt.Errorf("failed to mark required %s flag: %w", endpointFlag, err)) + } + ff.String(estimationsContainerFlag, "", "Inspected container, base58 encoded") + err = cobra.MarkFlagRequired(ff, estimationsContainerFlag) + if err != nil { + panic(fmt.Errorf("failed to mark required %s flag: %w", estimationsContainerFlag, err)) + } + cmd := verifiedNodesDomainAccessListCmd fs := cmd.Flags() fs.StringP(endpointFlag, "r", "", "NeoFS Sidechain RPC endpoint")