diff --git a/CHANGELOG.md b/CHANGELOG.md index 094fb05f41..73677796b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog for NeoFS Node ## [Unreleased] ### Added +- Indexes inspection command to neofs-lens (#2882) ### Fixed - Control service's Drop call does not clean metabase (#2822) diff --git a/cmd/neofs-lens/internal/meta/root.go b/cmd/neofs-lens/internal/meta/root.go index e3cee079a8..79f575bb66 100644 --- a/cmd/neofs-lens/internal/meta/root.go +++ b/cmd/neofs-lens/internal/meta/root.go @@ -35,6 +35,7 @@ func init() { listGarbageCMD, writeObjectCMD, getCMD, + statCMD, ) } diff --git a/cmd/neofs-lens/internal/meta/status.go b/cmd/neofs-lens/internal/meta/status.go new file mode 100644 index 0000000000..2da737cadb --- /dev/null +++ b/cmd/neofs-lens/internal/meta/status.go @@ -0,0 +1,55 @@ +package meta + +import ( + "fmt" + + 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 statCMD = &cobra.Command{ + Use: "status", + Short: "Object status information", + Long: `Get metabase's indexes related to an object.`, + Args: cobra.NoArgs, + Run: statusFunc, +} + +func init() { + common.AddAddressFlag(statCMD, &vAddress) + common.AddComponentPathFlag(statCMD, &vPath) +} + +func statusFunc(cmd *cobra.Command, _ []string) { + var addr oid.Address + + err := addr.DecodeString(vAddress) + common.ExitOnErr(cmd, common.Errf("invalid address argument: %w", err)) + + db := openMeta(cmd, true) + defer db.Close() + + res, err := db.ObjectStatus(addr) + common.ExitOnErr(cmd, common.Errf("reading object status: %w", err)) + + const emptyValPlaceholder = "" + storageID := res.StorageID + if storageID == "" { + storageID = emptyValPlaceholder + } + + cmd.Printf("Metabase version: %d\n", res.Version) + cmd.Printf("Object state: %s\n", res.State) + cmd.Printf("Storage's ID: %s\n", storageID) + cmd.Println("Indexes:") + for _, bucket := range res.Buckets { + valStr := emptyValPlaceholder + if bucket.Value != nil { + valStr = fmt.Sprintf("%x", bucket.Value) + } + + cmd.Printf("\tBucket: %d\n"+ + "\tValue (HEX): %s\n", bucket.BucketIndex, valStr) + } +} diff --git a/pkg/local_object_storage/metabase/VERSION.md b/pkg/local_object_storage/metabase/VERSION.md index 3f3e28f8c0..b80a735470 100644 --- a/pkg/local_object_storage/metabase/VERSION.md +++ b/pkg/local_object_storage/metabase/VERSION.md @@ -47,55 +47,55 @@ The lowest not used bucket index: 20. - Key: object ID - Value: marshalled object - Bucket containing objects of LOCK type - - Name: container ID + `7` + - Name: `7` + container ID - Key: object ID - Value: marshalled object - Bucket containing objects of STORAGEGROUP type - - Name: container ID + 8 + - Name: `8` + container ID - Key: object ID - Value: marshaled object - Bucket containing objects of TOMBSTONE type - - Name: container ID + `9` + - Name: `9` + container ID - Key: object ID - Value: marshaled object - Bucket containing object or LINK type - - Name: container ID + `18` + - Name: `18` + container ID - Key: object ID - Value: marshaled object - Bucket mapping objects to the storage ID they are stored in - - Name: container ID + `10` + - Name: `10` + container ID - Key: object ID - Value: storage ID - Bucket for mapping parent object to the split info - - Name: container ID + `11` + - Name: `11` + container ID - Key: object ID - Value: split info ### FKBT index buckets - Bucket mapping owner to object IDs - - Name: containerID + `12` + - Name: `12` + container ID - Key: owner ID as base58 string - Value: bucket containing object IDs as keys - Bucket containing objects attributes indexes - - Name: containerID + `13` + attribute key + - Name: `13` + container ID + attribute key - Key: attribute value - Value: bucket containing object IDs as keys ### List index buckets - Bucket mapping payload hash to a list of object IDs - - Name: container ID + `14` + - Name: `14` + container ID - Key: payload hash - Value: list of object IDs - Bucket mapping parent ID to a list of children IDs - - Name: container ID + `15` + - Name: `15` + container ID - Key: parent ID - Value: list of children object IDs - Bucket mapping split ID to a list of object IDs - - Name: container ID + `16` + - Name: `16` + container ID - Key: split ID - Value: list of object IDs - Bucket mapping first object ID to a list of objects IDs - - Name: container ID + `19` + - Name: `19` + container ID - Key: first object ID - Value: objects for corresponding split chain diff --git a/pkg/local_object_storage/metabase/status.go b/pkg/local_object_storage/metabase/status.go index 3b481a92ca..a764be692d 100644 --- a/pkg/local_object_storage/metabase/status.go +++ b/pkg/local_object_storage/metabase/status.go @@ -1,21 +1,33 @@ package meta import ( - "errors" + "bytes" + "fmt" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "go.etcd.io/bbolt" ) +// BucketValue pairs a bucket index and a value that relates +// an object. +type BucketValue struct { + BucketIndex int + Value []byte +} + // ObjectStatus represents the status of the object in the Metabase. type ObjectStatus struct { + Version uint64 + Buckets []BucketValue State []string Path string StorageID string Error error } -// ObjectStatus returns the status of the object in the Metabase. It contains state, path and storageID. +// ObjectStatus returns the status of the object in the Metabase. It contains state, path, storageID +// and indexed information about an object. func (db *DB) ObjectStatus(address oid.Address) (ObjectStatus, error) { db.modeMtx.RLock() defer db.modeMtx.RUnlock() @@ -27,17 +39,25 @@ func (db *DB) ObjectStatus(address oid.Address) (ObjectStatus, error) { storageID := StorageIDPrm{} storageID.SetAddress(address) resStorageID, err := db.StorageID(storageID) - if id := resStorageID.StorageID(); id != nil { - res.Error = errors.New("unexpected storage id") + if err != nil { + res.Error = fmt.Errorf("reading storage ID: %w", err) return res, res.Error } + if id := resStorageID.StorageID(); id != nil { + res.StorageID = string(id) + } + err = db.boltDB.View(func(tx *bbolt.Tx) error { + res.Version = getVersion(tx) + oID := address.Object() cID := address.Container() objKey := objectKey(address.Object(), make([]byte, objectKeySize)) key := make([]byte, bucketKeySize) + res.Buckets = readBuckets(tx, cID, objKey) + if objectLocked(tx, cID, oID) { res.State = append(res.State, "LOCKED") } @@ -64,3 +84,64 @@ func (db *DB) ObjectStatus(address oid.Address) (ObjectStatus, error) { res.Error = err return res, err } + +func readBuckets(tx *bbolt.Tx, cID cid.ID, objKey []byte) []BucketValue { + var res []BucketValue + cIDRaw := containerKey(cID, make([]byte, cidSize)) + + objectBuckets := [][]byte{ + graveyardBucketName, + garbageObjectsBucketName, + toMoveItBucketName, + primaryBucketName(cID, make([]byte, bucketKeySize)), + bucketNameLockers(cID, make([]byte, bucketKeySize)), + storageGroupBucketName(cID, make([]byte, bucketKeySize)), + tombstoneBucketName(cID, make([]byte, bucketKeySize)), + smallBucketName(cID, make([]byte, bucketKeySize)), + rootBucketName(cID, make([]byte, bucketKeySize)), + parentBucketName(cID, make([]byte, bucketKeySize)), + linkObjectsBucketName(cID, make([]byte, bucketKeySize)), + firstObjectIDBucketName(cID, make([]byte, bucketKeySize)), + } + + for _, bucketKey := range objectBuckets { + b := tx.Bucket(bucketKey) + if b == nil { + continue + } + + res = append(res, BucketValue{ + BucketIndex: int(bucketKey[0]), // the first byte is always a prefix + Value: bytes.Clone(b.Get(objKey)), + }) + } + + containerBuckets := []byte{ + containerVolumePrefix, + garbageContainersPrefix, + } + + for _, bucketKey := range containerBuckets { + b := tx.Bucket([]byte{bucketKey}) + if b == nil { + continue + } + + res = append(res, BucketValue{ + BucketIndex: int(bucketKey), + Value: bytes.Clone(b.Get(cIDRaw)), + }) + } + + if b := tx.Bucket(bucketNameLocked); b != nil { + b = b.Bucket(cIDRaw) + if b != nil { + res = append(res, BucketValue{ + BucketIndex: lockedPrefix, + Value: bytes.Clone(b.Get(objKey)), + }) + } + } + + return res +} diff --git a/pkg/local_object_storage/metabase/util.go b/pkg/local_object_storage/metabase/util.go index 95c583692c..a26f37684e 100644 --- a/pkg/local_object_storage/metabase/util.go +++ b/pkg/local_object_storage/metabase/util.go @@ -251,6 +251,12 @@ func objectKey(obj oid.ID, key []byte) []byte { return key[:objectKeySize] } +// containerKey returns key for K-V tables when key is a container ID. +func containerKey(cID cid.ID, key []byte) []byte { + cID.Encode(key) + return key[:cidSize] +} + // if meets irregular object container in objs - returns its type, otherwise returns object.TypeRegular. // // firstIrregularObjectType(tx, cnr, obj) usage allows getting object type. diff --git a/pkg/local_object_storage/metabase/version.go b/pkg/local_object_storage/metabase/version.go index 155beedc10..23cd2c705a 100644 --- a/pkg/local_object_storage/metabase/version.go +++ b/pkg/local_object_storage/metabase/version.go @@ -59,3 +59,15 @@ func updateVersion(tx *bbolt.Tx, version uint64) error { } return b.Put(versionKey, data) } + +func getVersion(tx *bbolt.Tx) uint64 { + b := tx.Bucket(shardInfoBucket) + if b != nil { + data := b.Get(versionKey) + if len(data) == 8 { + return binary.LittleEndian.Uint64(data) + } + } + + return 0 +}