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 }