diff --git a/CHANGELOG.md b/CHANGELOG.md index b5eedecf039..3acd8317482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Changelog for NeoFS Node ## [Unreleased] +## Added +- `neofs-lens storage status` CLI command (#2550) + ### Fixed - `neofs-cli netmap netinfo` documentation (#2555) - `GETRANGEHASH` to a node without an object produced `GETRANGE` or `GET` requests (#2541, #2598) diff --git a/cmd/neofs-lens/internal/printers.go b/cmd/neofs-lens/internal/printers.go index 853cc8156af..cc01a9a4042 100644 --- a/cmd/neofs-lens/internal/printers.go +++ b/cmd/neofs-lens/internal/printers.go @@ -1,8 +1,12 @@ package common import ( + "fmt" "os" + "strings" + "text/tabwriter" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/engine" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -67,3 +71,38 @@ func WriteObjectToFile(cmd *cobra.Command, path string, data []byte, payloadOnly } cmd.Printf("\nSaved object to '%s' file\n", path) } + +// PrintStorageObjectStatus prints object status. +func PrintStorageObjectStatus(cmd *cobra.Command, status engine.ObjectStatus) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + + for _, shard := range status.Shards { + if len(shard.Shard.Blob.Substorages) != 0 { + fmt.Fprintf(w, "Shard ID:\t%s\n", shard.ID) + for i, subblobs := range shard.Shard.Blob.Substorages { + fmt.Fprintf(w, "\tSubstorage %d\n", i) + fmt.Fprintf(w, "\t\tBlob Type:\t%s\n", subblobs.Type) + fmt.Fprintf(w, "\t\tBlob Path:\t%s\n", subblobs.Path) + if subblobs.Error != nil { + fmt.Fprintf(w, "\t\tBlob Error:\t%s\n", subblobs.Error) + } + } + + fmt.Fprintln(w, "\tMetabase") + fmt.Fprintf(w, "\t\tMetabase storage ID:\t%s\n", shard.Shard.Metabase.StorageID) + fmt.Fprintf(w, "\t\tMetabase path:\t%s\n", shard.Shard.Metabase.Path) + fmt.Fprintf(w, "\t\tMetabase object status:\t%s\n", strings.Join(shard.Shard.Metabase.State, " ")) + if shard.Shard.Metabase.Error != nil { + fmt.Fprintf(w, "\t\tMetabase object error:\t%v\n", shard.Shard.Metabase.Error) + } + if shard.Shard.Writecache.PathDB != "" || shard.Shard.Writecache.PathFSTree != "" { + fmt.Fprintln(w, "\tWritecache") + fmt.Fprintf(w, "\t\tWritecache DB path:\t%s\n", shard.Shard.Writecache.PathDB) + fmt.Fprintf(w, "\t\tWritecache FSTree path:\t%s\n", shard.Shard.Writecache.PathFSTree) + } + fmt.Fprintln(w) + } + } + + w.Flush() +} diff --git a/cmd/neofs-lens/internal/storage/root.go b/cmd/neofs-lens/internal/storage/root.go index 4430924973c..1eae8129ba8 100644 --- a/cmd/neofs-lens/internal/storage/root.go +++ b/cmd/neofs-lens/internal/storage/root.go @@ -44,7 +44,9 @@ var Root = &cobra.Command{ func init() { Root.AddCommand( - storageInspectObjCMD, storageGetObjCMD, + storageInspectObjCMD, + storageGetObjCMD, + storageStatusObjCMD, ) } diff --git a/cmd/neofs-lens/internal/storage/status.go b/cmd/neofs-lens/internal/storage/status.go new file mode 100644 index 00000000000..cf5fb06abe3 --- /dev/null +++ b/cmd/neofs-lens/internal/storage/status.go @@ -0,0 +1,34 @@ +package storage + +import ( + common "github.com/nspcc-dev/neofs-node/cmd/neofs-lens/internal" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/spf13/cobra" +) + +var storageStatusObjCMD = &cobra.Command{ + Use: "status", + Short: "Get object from the NeoFS node's storage snapshot", + Long: "Get object from the NeoFS node's storage snapshot", + Args: cobra.NoArgs, + Run: statusObject, +} + +func init() { + common.AddAddressFlag(storageStatusObjCMD, &vAddress) + common.AddConfigFileFlag(storageStatusObjCMD, &vConfig) +} + +func statusObject(cmd *cobra.Command, _ []string) { + var addr oid.Address + + err := addr.DecodeString(vAddress) + common.ExitOnErr(cmd, common.Errf("invalid address argument: %w", err)) + + storage := openEngine(cmd) + defer storage.Close() + status, err := storage.ObjectStatus(addr) + common.ExitOnErr(cmd, common.Errf("could not fetch object: %w", err)) + + common.PrintStorageObjectStatus(cmd, status) +} diff --git a/pkg/local_object_storage/blobstor/status.go b/pkg/local_object_storage/blobstor/status.go new file mode 100644 index 00000000000..54b450a4126 --- /dev/null +++ b/pkg/local_object_storage/blobstor/status.go @@ -0,0 +1,38 @@ +package blobstor + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" +) + +type ObjectSubstorageStatus struct { + Type string + Path string + Error error +} + +// ObjectStatus represents the status of the object in the Blob storage. +type ObjectStatus struct { + Substorages []ObjectSubstorageStatus +} + +func (b *BlobStor) ObjectStatus(address oid.Address) (ObjectStatus, error) { + b.modeMtx.RLock() + defer b.modeMtx.RUnlock() + res := ObjectStatus{ + Substorages: []ObjectSubstorageStatus{}, + } + prm := common.GetPrm{ + Address: address, + } + for i := range b.storage { + _, err := b.storage[i].Storage.Get(prm) + if err == nil { + res.Substorages = append(res.Substorages, ObjectSubstorageStatus{ + Type: b.storage[i].Storage.Type(), + Path: b.storage[i].Storage.Path(), + }) + } + } + return res, nil +} diff --git a/pkg/local_object_storage/engine/status.go b/pkg/local_object_storage/engine/status.go new file mode 100644 index 00000000000..3013bf4bea1 --- /dev/null +++ b/pkg/local_object_storage/engine/status.go @@ -0,0 +1,34 @@ +package engine + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" +) + +type ObjectShardStatus struct { + ID string + Shard shard.ObjectStatus +} + +type ObjectStatus struct { + Shards []ObjectShardStatus +} + +func (e *StorageEngine) ObjectStatus(address oid.Address) (ObjectStatus, error) { + var res ObjectStatus + var err error + + e.iterateOverSortedShards(address, func(_ int, sh hashedShard) (stop bool) { + var shardStatus shard.ObjectStatus + shardStatus, err = sh.ObjectStatus(address) + id := *sh.ID() + if err == nil { + res.Shards = append(res.Shards, ObjectShardStatus{ + ID: id.String(), + Shard: shardStatus, + }) + } + return err != nil + }) + return res, err +} diff --git a/pkg/local_object_storage/metabase/status.go b/pkg/local_object_storage/metabase/status.go new file mode 100644 index 00000000000..b365dde4ea4 --- /dev/null +++ b/pkg/local_object_storage/metabase/status.go @@ -0,0 +1,67 @@ +package meta + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobovnicza" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "go.etcd.io/bbolt" +) + +// ObjectStatus represents the status of the object in the Metabase. +type ObjectStatus struct { + State []string + Path string + StorageID string + Error error +} + +func (db *DB) ObjectStatus(address oid.Address) (ObjectStatus, error) { + db.modeMtx.RLock() + defer db.modeMtx.RUnlock() + var res ObjectStatus + if db.mode.NoMetabase() { + return res, nil + } + + storageID := StorageIDPrm{} + storageID.SetAddress(address) + resStorageID, err := db.StorageID(storageID) + if id := resStorageID.StorageID(); id != nil { + res.StorageID = blobovnicza.NewIDFromBytes(id).String() + } else { + return res, nil + } + + err = db.boltDB.View(func(tx *bbolt.Tx) error { + oID := address.Object() + cID := address.Container() + objKey := objectKey(address.Object(), make([]byte, objectKeySize)) + key := make([]byte, bucketKeySize) + + if objectLocked(tx, cID, oID) { + res.State = append(res.State, "LOCKED") + } + if inBucket(tx, primaryBucketName(cID, key), objKey) || inBucket(tx, parentBucketName(cID, key), objKey) { + res.State = append(res.State, "AVAILABLE") + } + + graveyardBkt := tx.Bucket(graveyardBucketName) + garbageBkt := tx.Bucket(garbageBucketName) + addrKey := addressKey(address, make([]byte, addressKeySize)) + + removedStatus := inGraveyardWithKey(addrKey, graveyardBkt, garbageBkt) + if removedStatus != 0 && objectLocked(tx, cID, oID) { + res.State = append(res.State, "AVAILABLE") + } + if removedStatus == 1 { + res.State = append(res.State, "GC MARKED") + } else if removedStatus == 2 { + res.State = append(res.State, "IN GRAVEYARD") + } else { + res.State = append(res.State, "NOT IN GRAVEYARD") + } + return err + }) + res.Path = db.boltDB.Path() + res.Error = err + return res, err +} diff --git a/pkg/local_object_storage/shard/status.go b/pkg/local_object_storage/shard/status.go new file mode 100644 index 00000000000..e1a192dda57 --- /dev/null +++ b/pkg/local_object_storage/shard/status.go @@ -0,0 +1,35 @@ +package shard + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor" + meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/writecache" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" +) + +// ObjectStatus represents the status of an object in a storage system. It contains +// information about the object's status in various sub-components such as Blob storage, +// Metabase, and Writecache. Additionally, it includes a slice of errors that may have +// occurred at the object level. +type ObjectStatus struct { + Blob blobstor.ObjectStatus + Metabase meta.ObjectStatus + Writecache writecache.ObjectStatus + Errors []error +} + +func (s *Shard) ObjectStatus(address oid.Address) (ObjectStatus, error) { + var res ObjectStatus + var err error + res.Blob, err = s.blobStor.ObjectStatus(address) + if len(res.Blob.Substorages) != 0 { + res.Errors = append(res.Errors, err) + res.Metabase, err = s.metaBase.ObjectStatus(address) + res.Errors = append(res.Errors, err) + if s.hasWriteCache() { + res.Writecache, err = s.writeCache.ObjectStatus(address) + res.Errors = append(res.Errors, err) + } + } + return res, nil +} diff --git a/pkg/local_object_storage/writecache/status.go b/pkg/local_object_storage/writecache/status.go new file mode 100644 index 00000000000..0c191af37bb --- /dev/null +++ b/pkg/local_object_storage/writecache/status.go @@ -0,0 +1,37 @@ +package writecache + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "go.etcd.io/bbolt" +) + +// ObjectStatus represents the status of the object in the Writecache. +type ObjectStatus struct { + PathDB string + PathFSTree string +} + +func (c *cache) ObjectStatus(address oid.Address) (ObjectStatus, error) { + saddr := address.EncodeToString() + var value []byte + var res ObjectStatus + var err error + + _ = c.db.View(func(tx *bbolt.Tx) error { + + b := tx.Bucket(defaultBucket) + if b != nil { + value = b.Get([]byte(saddr)) + if value != nil { + res.PathDB = c.db.Path() + } + } + return nil + }) + _, err = c.fsTree.Get(common.GetPrm{Address: address}) + if err == nil { + res.PathFSTree = c.fsTree.Path() + } + return res, nil +} diff --git a/pkg/local_object_storage/writecache/writecache.go b/pkg/local_object_storage/writecache/writecache.go index 7d88d024551..9bd9977fdea 100644 --- a/pkg/local_object_storage/writecache/writecache.go +++ b/pkg/local_object_storage/writecache/writecache.go @@ -39,6 +39,7 @@ type Cache interface { Init() error Open(readOnly bool) error Close() error + ObjectStatus(address oid.Address) (ObjectStatus, error) } type cache struct {