From ecfe06bd7cda910ffa76ee615bf8ca6e745427dd Mon Sep 17 00:00:00 2001 From: Ekaterina Lebedeva Date: Tue, 4 Feb 2025 21:23:00 +0300 Subject: [PATCH] [#1635] cli: Add command to get object's shard info Added `frostfs-cli object locate` subcommand. It lists info about shards storing an object. Signed-off-by: Ekaterina Lebedeva --- cmd/frostfs-cli/modules/object/locate.go | 195 +++++++++++++++++++++++ cmd/frostfs-cli/modules/object/root.go | 2 + go.mod | 4 +- go.sum | 8 +- 4 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 cmd/frostfs-cli/modules/object/locate.go diff --git a/cmd/frostfs-cli/modules/object/locate.go b/cmd/frostfs-cli/modules/object/locate.go new file mode 100644 index 000000000..61dd0eabc --- /dev/null +++ b/cmd/frostfs-cli/modules/object/locate.go @@ -0,0 +1,195 @@ +package object + +import ( + "bytes" + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "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-node/pkg/services/control" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/server/ctrlmessage" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs" + rawclient "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/rpc/client" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + + "github.com/mr-tron/base58" + "github.com/spf13/cobra" +) + +var objectLocateCmd = &cobra.Command{ + Use: "locate", + Short: "List shards storing the object", + Long: "List shards storing the object", + Run: locateObject, +} + +func initObjectLocateCmd() { + commonflags.Init(objectLocateCmd) + initFlagSession(objectLocateCmd, "LOCATE") + + flags := objectLocateCmd.Flags() + + flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage) + _ = objectLocateCmd.MarkFlagRequired(commonflags.CIDFlag) + + flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage) + _ = objectLocateCmd.MarkFlagRequired(commonflags.OIDFlag) + + flags.Bool(commonflags.JSON, false, "Print shard info as a JSON array") +} + +func locateObject(cmd *cobra.Command, _ []string) { + var cnr cid.ID + var obj oid.ID + + _ = readObjectAddress(cmd, &cnr, &obj) + + pk := key.Get(cmd) + + req := new(control.GetShardByObjectIDRequest) + body := new(control.GetShardByObjectIDRequest_Body) + req.SetBody(body) + body.SetCid(cnr.EncodeToString()) + body.SetOid(obj.EncodeToString()) + signRequest(cmd, pk, req) + + cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC) + + var resp *control.GetShardByObjectIDResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.GetShardByObjectID(client, req) + return err + }) + commonCmd.ExitOnErr(cmd, "rpc error loc: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + shards := resp.GetBody().GetShards() + isJSON, _ := cmd.Flags().GetBool(commonflags.JSON) + if isJSON { + prettyPrintShardsJSON(cmd, shards) + } else { + prettyPrintShards(cmd, shards) + } +} + +func signRequest(cmd *cobra.Command, pk *ecdsa.PrivateKey, req ctrlmessage.SignedMessage) { + err := ctrlmessage.Sign(pk, req) + commonCmd.ExitOnErr(cmd, "could not sign request: %w", err) +} + +func verifyResponse(cmd *cobra.Command, + sigControl interface { + GetKey() []byte + GetSign() []byte + }, + body interface { + MarshalProtobuf([]byte) []byte + }, +) { + if sigControl == nil { + commonCmd.ExitOnErr(cmd, "", errors.New("missing response signature")) + } + + // TODO(@cthulhu-rider): #468 use Signature message from FrostFS API to avoid conversion + var sigV2 refs.Signature + sigV2.SetScheme(refs.ECDSA_SHA512) + sigV2.SetKey(sigControl.GetKey()) + sigV2.SetSign(sigControl.GetSign()) + + var sig frostfscrypto.Signature + commonCmd.ExitOnErr(cmd, "can't read signature: %w", sig.ReadFromV2(sigV2)) + + if !sig.Verify(body.MarshalProtobuf(nil)) { + commonCmd.ExitOnErr(cmd, "", errors.New("invalid response signature")) + } +} + +func prettyPrintShardsJSON(cmd *cobra.Command, ii []control.ShardInfo) { + output := make([]map[string]any, 0, len(ii)) + for _, i := range ii { + output = append(output, map[string]any{ + "shard_id": base58.Encode(i.GetShard_ID()), + "mode": shardModeToString(i.GetMode()), + "metabase": i.GetMetabasePath(), + "blobstor": i.GetBlobstor(), + "writecache": i.GetWritecachePath(), + "pilorama": i.GetPiloramaPath(), + "error_count": i.GetErrorCount(), + "evacuation_in_progress": i.GetEvacuationInProgress(), + }) + } + + buf := bytes.NewBuffer(nil) + enc := json.NewEncoder(buf) + enc.SetIndent("", " ") + commonCmd.ExitOnErr(cmd, "cannot shard info to JSON: %w", enc.Encode(output)) + + cmd.Print(buf.String()) // pretty printer emits newline, so no need for Println +} + +func prettyPrintShards(cmd *cobra.Command, ii []control.ShardInfo) { + for _, i := range ii { + pathPrinter := func(name, path string) string { + if path == "" { + return "" + } + + return fmt.Sprintf("%s: %s\n", name, path) + } + + var sb strings.Builder + sb.WriteString("Blobstor:\n") + for j, info := range i.GetBlobstor() { + sb.WriteString(fmt.Sprintf("\tPath %d: %s\n\tType %d: %s\n", + j, info.GetPath(), j, info.GetType())) + } + + cmd.Printf("Shard %s:\nMode: %s\n"+ + pathPrinter("Metabase", i.GetMetabasePath())+ + sb.String()+ + pathPrinter("Write-cache", i.GetWritecachePath())+ + pathPrinter("Pilorama", i.GetPiloramaPath())+ + fmt.Sprintf("Error count: %d\n", i.GetErrorCount())+ + fmt.Sprintf("Evacuation in progress: %t\n", i.GetEvacuationInProgress()), + base58.Encode(i.GetShard_ID()), + shardModeToString(i.GetMode()), + ) + } +} + +func shardModeToString(m control.ShardMode) string { + strMode, ok := lookUpShardModeString(m) + if ok { + return strMode + } + + return "unknown" +} + +// looks up for string representation of supported shard mode. Returns false +// if mode is not supported. +func lookUpShardModeString(m control.ShardMode) (string, bool) { + mShardModes := map[string]control.ShardMode{ + "read-only": control.ShardMode_READ_ONLY, + "read-write": control.ShardMode_READ_WRITE, + "degraded-read-write": control.ShardMode_DEGRADED, + "degraded-read-only": control.ShardMode_DEGRADED_READ_ONLY, + } + for strMode, mode := range mShardModes { + if mode == m { + return strMode, true + } + } + + return "", false +} diff --git a/cmd/frostfs-cli/modules/object/root.go b/cmd/frostfs-cli/modules/object/root.go index b808a509e..c2d1b071d 100644 --- a/cmd/frostfs-cli/modules/object/root.go +++ b/cmd/frostfs-cli/modules/object/root.go @@ -30,6 +30,7 @@ func init() { objectLockCmd, objectNodesCmd, objectPatchCmd, + objectLocateCmd, } Cmd.AddCommand(objectChildCommands...) @@ -49,4 +50,5 @@ func init() { initObjectRangeCmd() initCommandObjectLock() initObjectNodesCmd() + initObjectLocateCmd() } diff --git a/go.mod b/go.mod index cc6b0a202..f8f4243e2 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.22 require ( code.gitea.io/sdk/gitea v0.17.1 - git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1-0.20241205083807-762d7f9f9f08 + git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1 git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 git.frostfs.info/TrueCloudLab/frostfs-locode-db v0.4.1-0.20240710074952-65761deb5c0d git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88 git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250202151421-8389887a3421 git.frostfs.info/TrueCloudLab/hrw v1.2.1 git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 - git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240814080254-96225afacb88 + git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b git.frostfs.info/TrueCloudLab/tzhash v1.8.0 git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 github.com/VictoriaMetrics/easyproto v0.1.4 diff --git a/go.sum b/go.sum index eae467b31..b9d745d91 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= -git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1-0.20241205083807-762d7f9f9f08 h1:tl1TT+zNk1lF/J5EaD3syDrTaYbQwvJKVOVENM4oQ+k= -git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1-0.20241205083807-762d7f9f9f08/go.mod h1:5fSm/l5xSjGWqsPUffSdboiGFUHa7y/1S0fvxzQowN8= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1 h1:k1Qw8dWUQczfo0eVXlhrq9eXEbUMyDLW8jEMzY+gxMc= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.21.1/go.mod h1:5fSm/l5xSjGWqsPUffSdboiGFUHa7y/1S0fvxzQowN8= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-locode-db v0.4.1-0.20240710074952-65761deb5c0d h1:uJ/wvuMdepbkaV8XMS5uN9B0FQWMep0CttSuDZiDhq0= @@ -16,8 +16,8 @@ git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/96 git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972/go.mod h1:2hM42MBrlhvN6XToaW6OWNk5ZLcu1FhaukGgxtfpDDI= git.frostfs.info/TrueCloudLab/neoneo-go v0.106.1-0.20241015133823-8aee80dbdc07 h1:gPaqGsk6gSWQyNVjaStydfUz6Z/loHc9XyvGrJ5qSPY= git.frostfs.info/TrueCloudLab/neoneo-go v0.106.1-0.20241015133823-8aee80dbdc07/go.mod h1:bZyJexBlrja4ngxiBgo8by5pVHuAbhg9l09/8yVGDyg= -git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240814080254-96225afacb88 h1:vgbfkcnIexZUm3vREBBSa/Gv1Whjd1SFCUd0A+IaGPQ= -git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240814080254-96225afacb88/go.mod h1:SgioiGhQNWqiV5qpFAXRDJF81SEFRBhtwGEiU0FViyA= +git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b h1:M50kdfrf/h8c3cz0bJ2AEUcbXvAlPFVC1Wp1WkfZ/8E= +git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b/go.mod h1:GZTk55RI4dKzsK6BCn5h2xxE28UHNfgoq/NJxW/LQ6A= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc= git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=