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
Added `frostfs-cli object locate` subcommand. It lists info about shards storing an object. Signed-off-by: Ekaterina Lebedeva <ekaterina.lebedeva@yadro.com>
195 lines
5.7 KiB
Go
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
|
|
}
|