frostfs-node/cmd/frostfs-cli/modules/object/locate.go
Ekaterina Lebedeva a7ce8dc3ec
All checks were successful
DCO action / DCO (pull_request) Successful in 35s
Tests and linters / Run gofumpt (pull_request) Successful in 26s
Vulncheck / Vulncheck (pull_request) Successful in 58s
Pre-commit hooks / Pre-commit (pull_request) Successful in 1m29s
Build / Build Components (pull_request) Successful in 1m40s
Tests and linters / Staticcheck (pull_request) Successful in 2m12s
Tests and linters / Tests (pull_request) Successful in 2m45s
Tests and linters / Tests with -race (pull_request) Successful in 3m4s
Tests and linters / Lint (pull_request) Successful in 3m8s
Tests and linters / gopls check (pull_request) Successful in 3m31s
[#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 <ekaterina.lebedeva@yadro.com>
2025-02-06 16:53:49 +03:00

195 lines
5.7 KiB
Go

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
}