diff --git a/CHANGELOG.md b/CHANGELOG.md index c97614fec4..eebf4438bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Changelog for NeoFS Node ### Added - Policer's setting to the SN's application configuration (#2600) - Support of verified domains for the storage nodes (#2280) +- `neofs-lens storage status` CLI command (#2550) ### Fixed - `neofs-cli netmap netinfo` documentation (#2555) diff --git a/cmd/neofs-lens/internal/printers.go b/cmd/neofs-lens/internal/printers.go index 853cc8156a..3f753b04a5 100644 --- a/cmd/neofs-lens/internal/printers.go +++ b/cmd/neofs-lens/internal/printers.go @@ -2,7 +2,9 @@ package common import ( "os" + "strings" + "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 +69,34 @@ 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) { + for _, shard := range status.Shards { + if len(shard.Shard.Blob.Substorages) != 0 { + cmd.Printf("Shard ID:\t%s\n", shard.ID) + for i, subblobs := range shard.Shard.Blob.Substorages { + cmd.Printf("\tBlobstor substorage %d\n", i) + cmd.Printf("\t\tStorage type:\t%s\n", subblobs.Type) + cmd.Printf("\t\tStorage Path:\t%s\n", subblobs.Path) + if subblobs.Error != nil { + cmd.Printf("\t\tStorage Error:\t%s\n", subblobs.Error) + } + } + + cmd.Printf("\tMetabase\n") + cmd.Printf("\t\tMetabase storage ID:\t%s\n", shard.Shard.Metabase.StorageID) + cmd.Printf("\t\tMetabase path:\t%s\n", shard.Shard.Metabase.Path) + cmd.Printf("\t\tMetabase object status:\t%s\n", strings.Join(shard.Shard.Metabase.State, " ")) + if shard.Shard.Metabase.Error != nil { + cmd.Printf("\t\tMetabase object error:\t%v\n", shard.Shard.Metabase.Error) + } + if shard.Shard.Writecache.PathDB != "" || shard.Shard.Writecache.PathFSTree != "" { + cmd.Printf("\tWritecache\n") + cmd.Printf("\t\tWritecache DB path:\t%s\n", shard.Shard.Writecache.PathDB) + cmd.Printf("\t\tWritecache FSTree path:\t%s\n", shard.Shard.Writecache.PathFSTree) + } + cmd.Println() + } + } +} diff --git a/cmd/neofs-lens/internal/storage/root.go b/cmd/neofs-lens/internal/storage/root.go index 42d2ab2a7a..03511cc135 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 0000000000..cf5fb06abe --- /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 0000000000..a9b0612dba --- /dev/null +++ b/pkg/local_object_storage/blobstor/status.go @@ -0,0 +1,43 @@ +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" +) + +// ObjectSubstorageStatus represents the status of the object in the Blob +// storage, containing the type and path of the storage and an error if it +// occurred. +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 +} + +// ObjectStatus returns the status of the object in the Blob storage. It contains +// status of the object in all blob substorages. +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 0000000000..e513955f04 --- /dev/null +++ b/pkg/local_object_storage/engine/status.go @@ -0,0 +1,37 @@ +package engine + +import ( + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" +) + +// ObjectShardStatus contains the status of the object in the Shard and Shard ID. +type ObjectShardStatus struct { + ID string + Shard shard.ObjectStatus +} + +// ObjectStatus represents the status of the object in the StorageEngine. +type ObjectStatus struct { + Shards []ObjectShardStatus +} + +// ObjectStatus returns the status of the object in the StorageEngine. It contains status of the object in all shards. +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 0000000000..8eceb99eec --- /dev/null +++ b/pkg/local_object_storage/metabase/status.go @@ -0,0 +1,65 @@ +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 +} + +// ObjectStatus returns the status of the object in the Metabase. It contains state, path and storageID. +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") + } + + 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) || inBucket(tx, primaryBucketName(cID, key), objKey) || inBucket(tx, parentBucketName(cID, key), objKey) { + res.State = append(res.State, "AVAILABLE") + } + if removedStatus == 1 { + res.State = append(res.State, "GC MARKED") + } + if removedStatus == 2 { + res.State = append(res.State, "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 0000000000..9137e1f0e6 --- /dev/null +++ b/pkg/local_object_storage/shard/status.go @@ -0,0 +1,37 @@ +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 +} + +// ObjectStatus returns the status of the object in the Shard. It contains status +// of the object in Blob storage, Metabase and Writecache. +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 0000000000..8162bbc95c --- /dev/null +++ b/pkg/local_object_storage/writecache/status.go @@ -0,0 +1,40 @@ +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" + "github.com/pkg/errors" + "go.etcd.io/bbolt" +) + +// ObjectStatus represents the status of the object in the Writecache. +type ObjectStatus struct { + PathDB string + PathFSTree string +} + +// ObjectStatus returns the status of the object in the Writecache. It contains path to the DB and path to the FSTree. +func (c *cache) ObjectStatus(address oid.Address) (ObjectStatus, error) { + saddr := address.EncodeToString() + var value []byte + var res ObjectStatus + + err := 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 errors.New("value not found") + }) + if err != nil { + return res, err + } + _, err = c.fsTree.Get(common.GetPrm{Address: address}) + if err == nil { + res.PathFSTree = c.fsTree.Path() + } + return res, err +} diff --git a/pkg/local_object_storage/writecache/writecache.go b/pkg/local_object_storage/writecache/writecache.go index 7d88d02455..9bd9977fde 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 {