2022-06-01 12:42:28 +00:00
|
|
|
package object
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
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"
|
|
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
2023-07-06 12:36:41 +00:00
|
|
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
2023-03-07 13:38:26 +00:00
|
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
2022-06-01 12:42:28 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
var objectRangeCmd = &cobra.Command{
|
|
|
|
Use: "range",
|
|
|
|
Short: "Get payload range data of an object",
|
|
|
|
Long: "Get payload range data of an object",
|
|
|
|
Run: getObjectRange,
|
|
|
|
}
|
|
|
|
|
|
|
|
func initObjectRangeCmd() {
|
|
|
|
commonflags.Init(objectRangeCmd)
|
2022-10-20 09:40:33 +00:00
|
|
|
initFlagSession(objectRangeCmd, "RANGE")
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
flags := objectRangeCmd.Flags()
|
|
|
|
|
2022-10-18 11:43:04 +00:00
|
|
|
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
|
|
|
_ = objectRangeCmd.MarkFlagRequired(commonflags.CIDFlag)
|
2022-06-01 12:42:28 +00:00
|
|
|
|
2022-10-18 11:43:04 +00:00
|
|
|
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
|
|
|
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
flags.String("range", "", "Range to take data from in the form offset:length")
|
2022-10-18 11:43:04 +00:00
|
|
|
flags.String(fileFlag, "", "File to write object payload to. Default: stdout.")
|
2022-06-01 12:42:28 +00:00
|
|
|
flags.Bool(rawFlag, false, rawFlagDesc)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getObjectRange(cmd *cobra.Command, _ []string) {
|
|
|
|
var cnr cid.ID
|
|
|
|
var obj oid.ID
|
|
|
|
|
|
|
|
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
|
|
|
|
|
|
|
ranges, err := getRangeList(cmd)
|
2023-01-16 09:20:16 +00:00
|
|
|
commonCmd.ExitOnErr(cmd, "", err)
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
if len(ranges) != 1 {
|
2023-01-16 09:20:16 +00:00
|
|
|
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("exactly one range must be specified, got: %d", len(ranges)))
|
2022-06-01 12:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var out io.Writer
|
|
|
|
|
2022-10-18 11:43:04 +00:00
|
|
|
filename := cmd.Flag(fileFlag).Value.String()
|
2022-06-01 12:42:28 +00:00
|
|
|
if filename == "" {
|
|
|
|
out = os.Stdout
|
|
|
|
} else {
|
|
|
|
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
|
|
|
if err != nil {
|
2023-01-16 09:20:16 +00:00
|
|
|
commonCmd.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
2022-06-01 12:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
out = f
|
|
|
|
}
|
|
|
|
|
|
|
|
pk := key.GetOrGenerate(cmd)
|
|
|
|
|
2022-10-10 18:31:19 +00:00
|
|
|
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
|
|
|
|
2022-06-01 12:42:28 +00:00
|
|
|
var prm internalclient.PayloadRangePrm
|
2022-10-10 18:31:19 +00:00
|
|
|
prm.SetClient(cli)
|
2022-06-01 12:42:28 +00:00
|
|
|
Prepare(cmd, &prm)
|
2022-10-10 18:31:19 +00:00
|
|
|
readSession(cmd, &prm, pk, cnr, obj)
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
raw, _ := cmd.Flags().GetBool(rawFlag)
|
|
|
|
prm.SetRawFlag(raw)
|
|
|
|
prm.SetAddress(objAddr)
|
2023-09-12 07:41:29 +00:00
|
|
|
prm.SetRange(&ranges[0])
|
2022-06-01 12:42:28 +00:00
|
|
|
prm.SetPayloadWriter(out)
|
|
|
|
|
2023-05-24 13:51:57 +00:00
|
|
|
_, err = internalclient.PayloadRange(cmd.Context(), prm)
|
2022-06-01 12:42:28 +00:00
|
|
|
if err != nil {
|
|
|
|
if ok := printSplitInfoErr(cmd, err); ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:20:16 +00:00
|
|
|
commonCmd.ExitOnErr(cmd, "can't get object payload range: %w", err)
|
2022-06-01 12:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if filename != "" {
|
|
|
|
cmd.Printf("[%s] Payload successfully saved\n", filename)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func printSplitInfoErr(cmd *cobra.Command, err error) bool {
|
2023-07-06 12:36:41 +00:00
|
|
|
var errSplitInfo *objectSDK.SplitInfoError
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
ok := errors.As(err, &errSplitInfo)
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
cmd.PrintErrln("Object is complex, split information received.")
|
|
|
|
printSplitInfo(cmd, errSplitInfo.SplitInfo())
|
|
|
|
}
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2023-07-06 12:36:41 +00:00
|
|
|
func printSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) {
|
2022-06-01 12:42:28 +00:00
|
|
|
bs, err := marshalSplitInfo(cmd, info)
|
2023-01-16 09:20:16 +00:00
|
|
|
commonCmd.ExitOnErr(cmd, "can't marshal split info: %w", err)
|
2022-06-01 12:42:28 +00:00
|
|
|
|
|
|
|
cmd.Println(string(bs))
|
|
|
|
}
|
|
|
|
|
2023-07-06 12:36:41 +00:00
|
|
|
func marshalSplitInfo(cmd *cobra.Command, info *objectSDK.SplitInfo) ([]byte, error) {
|
2022-06-23 13:52:47 +00:00
|
|
|
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
2022-06-01 12:42:28 +00:00
|
|
|
toProto, _ := cmd.Flags().GetBool("proto")
|
|
|
|
switch {
|
|
|
|
case toJSON && toProto:
|
|
|
|
return nil, errors.New("'--json' and '--proto' flags are mutually exclusive")
|
|
|
|
case toJSON:
|
|
|
|
return info.MarshalJSON()
|
|
|
|
case toProto:
|
|
|
|
return info.Marshal()
|
|
|
|
default:
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
|
|
if splitID := info.SplitID(); splitID != nil {
|
|
|
|
b.WriteString("Split ID: " + splitID.String() + "\n")
|
|
|
|
}
|
|
|
|
if link, ok := info.Link(); ok {
|
|
|
|
b.WriteString("Linking object: " + link.String() + "\n")
|
|
|
|
}
|
|
|
|
if last, ok := info.LastPart(); ok {
|
|
|
|
b.WriteString("Last object: " + last.String() + "\n")
|
|
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-22 06:43:42 +00:00
|
|
|
func printECInfoErr(cmd *cobra.Command, err error) bool {
|
|
|
|
var errECInfo *objectSDK.ECInfoError
|
|
|
|
|
|
|
|
ok := errors.As(err, &errECInfo)
|
|
|
|
|
|
|
|
if ok {
|
2024-05-27 18:31:59 +00:00
|
|
|
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
|
|
|
toProto, _ := cmd.Flags().GetBool("proto")
|
|
|
|
if !(toJSON || toProto) {
|
|
|
|
cmd.PrintErrln("Object is erasure-encoded, ec information received.")
|
|
|
|
}
|
2024-04-22 06:43:42 +00:00
|
|
|
printECInfo(cmd, errECInfo.ECInfo())
|
|
|
|
}
|
|
|
|
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func printECInfo(cmd *cobra.Command, info *objectSDK.ECInfo) {
|
|
|
|
bs, err := marshalECInfo(cmd, info)
|
|
|
|
commonCmd.ExitOnErr(cmd, "can't marshal split info: %w", err)
|
|
|
|
|
|
|
|
cmd.Println(string(bs))
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalECInfo(cmd *cobra.Command, info *objectSDK.ECInfo) ([]byte, error) {
|
|
|
|
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
|
|
|
toProto, _ := cmd.Flags().GetBool("proto")
|
|
|
|
switch {
|
|
|
|
case toJSON && toProto:
|
|
|
|
return nil, errors.New("'--json' and '--proto' flags are mutually exclusive")
|
|
|
|
case toJSON:
|
|
|
|
return info.MarshalJSON()
|
|
|
|
case toProto:
|
|
|
|
return info.Marshal()
|
|
|
|
default:
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
|
|
b.WriteString("Total chunks: " + strconv.Itoa(int(info.Chunks[0].Total)))
|
|
|
|
for _, chunk := range info.Chunks {
|
|
|
|
var id oid.ID
|
|
|
|
if err := id.Decode(chunk.ID.GetValue()); err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to decode chunk id: %w", err)
|
|
|
|
}
|
|
|
|
b.WriteString("\n Index: " + strconv.Itoa(int(chunk.Index)) + " ID: " + id.String())
|
|
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 07:41:29 +00:00
|
|
|
func getRangeList(cmd *cobra.Command) ([]objectSDK.Range, error) {
|
2022-06-01 12:42:28 +00:00
|
|
|
v := cmd.Flag("range").Value.String()
|
|
|
|
if len(v) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
vs := strings.Split(v, ",")
|
2023-09-12 07:41:29 +00:00
|
|
|
rs := make([]objectSDK.Range, len(vs))
|
2022-06-01 12:42:28 +00:00
|
|
|
for i := range vs {
|
2023-02-27 14:19:35 +00:00
|
|
|
before, after, found := strings.Cut(vs[i], rangeSep)
|
|
|
|
if !found {
|
2022-06-01 12:42:28 +00:00
|
|
|
return nil, fmt.Errorf("invalid range specifier: %s", vs[i])
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:19:35 +00:00
|
|
|
offset, err := strconv.ParseUint(before, 10, 64)
|
2022-06-01 12:42:28 +00:00
|
|
|
if err != nil {
|
2022-11-24 13:25:33 +00:00
|
|
|
return nil, fmt.Errorf("invalid '%s' range offset specifier: %w", vs[i], err)
|
2022-06-01 12:42:28 +00:00
|
|
|
}
|
2023-02-27 14:19:35 +00:00
|
|
|
length, err := strconv.ParseUint(after, 10, 64)
|
2022-06-01 12:42:28 +00:00
|
|
|
if err != nil {
|
2022-11-24 13:25:33 +00:00
|
|
|
return nil, fmt.Errorf("invalid '%s' range length specifier: %w", vs[i], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if length == 0 {
|
|
|
|
return nil, fmt.Errorf("invalid '%s' range: zero length", vs[i])
|
2022-06-01 12:42:28 +00:00
|
|
|
}
|
2022-11-24 13:25:33 +00:00
|
|
|
|
|
|
|
if offset+length <= offset {
|
|
|
|
return nil, fmt.Errorf("invalid '%s' range: uint64 overflow", vs[i])
|
|
|
|
}
|
|
|
|
|
2022-06-01 12:42:28 +00:00
|
|
|
rs[i].SetOffset(offset)
|
|
|
|
rs[i].SetLength(length)
|
|
|
|
}
|
|
|
|
return rs, nil
|
|
|
|
}
|