diff --git a/cmd/neofs-lens/internal/commands/inspect/inspect.go b/cmd/neofs-lens/internal/commands/inspect/inspect.go new file mode 100644 index 000000000..802165eda --- /dev/null +++ b/cmd/neofs-lens/internal/commands/inspect/inspect.go @@ -0,0 +1,116 @@ +package inspect + +import ( + "fmt" + "io/ioutil" + + "github.com/nspcc-dev/neofs-api-go/pkg/object" + common "github.com/nspcc-dev/neofs-node/cmd/neofs-lens/internal" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobovnicza" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/writecache" + "github.com/spf13/cobra" +) + +const ( + flagAddress = "address" + flagEnginePath = "path" + flagHeader = "header" + flagOutFile = "out" + flagWriteCache = "writecache" +) + +var ( + vAddress string + vHeader bool + vPath string + vOut string + vWriteCache bool +) + +// Command contains `inspect` command definition. +var Command = &cobra.Command{ + Use: "inspect", + Short: "Object inspection", + Long: `Inspect specific object in storage engine component.`, + Run: objectInspectCmd, +} + +func init() { + Command.Flags().StringVar(&vAddress, flagAddress, "", "Object address") + _ = Command.MarkFlagRequired(flagAddress) + + Command.Flags().StringVar(&vPath, flagEnginePath, "", + "Path to storage engine component", + ) + _ = Command.MarkFlagFilename(flagEnginePath) + _ = Command.MarkFlagRequired(flagEnginePath) + + Command.Flags().StringVar(&vOut, flagOutFile, "", + "File to save object payload") + + Command.Flags().BoolVar(&vHeader, flagHeader, false, "Inspect only header") + Command.Flags().BoolVar(&vWriteCache, flagWriteCache, false, + "Process write-cache") +} + +func objectInspectCmd(cmd *cobra.Command, _ []string) { + addr := object.NewAddress() + err := addr.Parse(vAddress) + common.ExitOnErr(cmd, common.Errf("invalid address argument: %w", err)) + + if vOut == "" && !vHeader { + common.ExitOnErr(cmd, fmt.Errorf("either --%s or --%s flag must be provided", + flagHeader, flagOutFile)) + } + + if vWriteCache { + db, err := writecache.OpenDB(vPath, true) + common.ExitOnErr(cmd, common.Errf("could not open write-cache db: %w", err)) + + defer db.Close() + + data, err := writecache.Get(db, []byte(addr.String())) + common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err)) + printObjectInfo(cmd, data) + return + } + + blz := blobovnicza.New( + blobovnicza.WithPath(vPath), + blobovnicza.ReadOnly()) + common.ExitOnErr(cmd, blz.Open()) + + defer blz.Close() + + prm := new(blobovnicza.GetPrm) + prm.SetAddress(addr) + res, err := blz.Get(prm) + common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err)) + + printObjectInfo(cmd, res.Object()) +} + +func printObjectInfo(cmd *cobra.Command, data []byte) { + obj := object.New() + err := obj.Unmarshal(data) + common.ExitOnErr(cmd, common.Errf("can't unmarshal object: %w", err)) + + if vHeader { + cmd.Println("Version:", obj.Version()) + cmd.Println("Type:", obj.Type()) + cmd.Println("CID:", obj.ContainerID()) + cmd.Println("ID:", obj.ID()) + cmd.Println("Owner:", obj.OwnerID()) + cmd.Println("CreatedAt:", obj.CreationEpoch()) + cmd.Println("PayloadSize:", obj.PayloadSize()) + cmd.Println("Attributes:") + for _, attr := range obj.Attributes() { + cmd.Printf(" %s: %s\n", attr.Key(), attr.Value()) + } + } + + if vOut != "" { + err := ioutil.WriteFile(vOut, obj.Payload(), 0644) + common.ExitOnErr(cmd, common.Errf("couldn't write payload: %w", err)) + } +} diff --git a/cmd/neofs-lens/root.go b/cmd/neofs-lens/root.go index ba1f6bd7d..f27ae7744 100644 --- a/cmd/neofs-lens/root.go +++ b/cmd/neofs-lens/root.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/nspcc-dev/neofs-node/cmd/neofs-lens/internal/commands/inspect" cmdlist "github.com/nspcc-dev/neofs-node/cmd/neofs-lens/internal/commands/list" "github.com/nspcc-dev/neofs-node/misc" "github.com/spf13/cobra" @@ -34,6 +35,7 @@ func entryPoint(cmd *cobra.Command, _ []string) error { func init() { command.AddCommand( cmdlist.Command, + inspect.Command, ) } diff --git a/pkg/local_object_storage/writecache/get.go b/pkg/local_object_storage/writecache/get.go index 5c3593b41..d93eee45e 100644 --- a/pkg/local_object_storage/writecache/get.go +++ b/pkg/local_object_storage/writecache/get.go @@ -20,17 +20,8 @@ func (c *cache) Get(addr *objectSDK.Address) (*object.Object, error) { } c.mtx.RUnlock() - var value []byte - _ = c.db.View(func(tx *bbolt.Tx) error { - b := tx.Bucket(defaultBucket) - val := b.Get([]byte(saddr)) - if val != nil { - value = cloneBytes(val) - } - return nil - }) - - if value != nil { + value, err := Get(c.db, []byte(saddr)) + if err == nil { obj := object.New() c.flushed.Get(saddr) return obj, obj.Unmarshal(value) @@ -66,3 +57,22 @@ func (c *cache) Head(addr *objectSDK.Address) (*object.Object, error) { // NOTE: resetting the payload via the setter can lead to data corruption of in-memory objects, but ok for others return object.NewRawFromObject(obj).CutPayload().Object(), nil } + +// Get fetches object from the underlying database. +// Key should be a stringified address. +func Get(db *bbolt.DB, key []byte) ([]byte, error) { + var value []byte + err := db.View(func(tx *bbolt.Tx) error { + b := tx.Bucket(defaultBucket) + if b == nil { + return ErrNoDefaultBucket + } + value = b.Get(key) + if value == nil { + return object.ErrNotFound + } + value = cloneBytes(value) + return nil + }) + return value, err +}