forked from TrueCloudLab/frostfs-node
[#1416] lens/explorer: Support metabase schema v3
Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
This commit is contained in:
parent
5ba0e2918e
commit
bf8914fedc
11 changed files with 233 additions and 29 deletions
|
@ -2,13 +2,17 @@ package meta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
common "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal"
|
||||||
|
schemaCommon "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase"
|
schema "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/tui"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tuiCMD = &cobra.Command{
|
var tuiCMD = &cobra.Command{
|
||||||
|
@ -27,6 +31,11 @@ Available search filters:
|
||||||
|
|
||||||
var initialPrompt string
|
var initialPrompt string
|
||||||
|
|
||||||
|
var parserPerSchemaVersion = map[uint64]schemaCommon.Parser{
|
||||||
|
2: schema.MetabaseParserV2,
|
||||||
|
3: schema.MetabaseParserV3,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.AddComponentPathFlag(tuiCMD, &vPath)
|
common.AddComponentPathFlag(tuiCMD, &vPath)
|
||||||
|
|
||||||
|
@ -49,12 +58,22 @@ func runTUI(cmd *cobra.Command) error {
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
|
schemaVersion, hasVersion := lookupSchemaVersion(cmd, db)
|
||||||
|
if !hasVersion {
|
||||||
|
return errors.New("couldn't detect schema version")
|
||||||
|
}
|
||||||
|
|
||||||
|
metabaseParser, ok := parserPerSchemaVersion[schemaVersion]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown schema version %d", schemaVersion)
|
||||||
|
}
|
||||||
|
|
||||||
// Need if app was stopped with Ctrl-C.
|
// Need if app was stopped with Ctrl-C.
|
||||||
ctx, cancel := context.WithCancel(cmd.Context())
|
ctx, cancel := context.WithCancel(cmd.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
app := tview.NewApplication()
|
app := tview.NewApplication()
|
||||||
ui := tui.NewUI(ctx, app, db, schema.MetabaseParser, nil)
|
ui := tui.NewUI(ctx, app, db, metabaseParser, nil)
|
||||||
|
|
||||||
_ = ui.AddFilter("cid", tui.CIDParser, "CID")
|
_ = ui.AddFilter("cid", tui.CIDParser, "CID")
|
||||||
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
_ = ui.AddFilter("oid", tui.OIDParser, "OID")
|
||||||
|
@ -69,3 +88,31 @@ func runTUI(cmd *cobra.Command) error {
|
||||||
app.SetRoot(ui, true).SetFocus(ui)
|
app.SetRoot(ui, true).SetFocus(ui)
|
||||||
return app.Run()
|
return app.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
shardInfoBucket = []byte{5}
|
||||||
|
versionRecord = []byte("version")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupSchemaVersion(cmd *cobra.Command, db *bbolt.DB) (version uint64, ok bool) {
|
||||||
|
err := db.View(func(tx *bbolt.Tx) error {
|
||||||
|
bkt := tx.Bucket(shardInfoBucket)
|
||||||
|
if bkt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rec := bkt.Get(versionRecord)
|
||||||
|
if rec == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
version = binary.LittleEndian.Uint64(rec)
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
common.ExitOnErr(cmd, fmt.Errorf("couldn't lookup version: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -80,10 +80,15 @@ var (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
UserAttributeParser = NewUserAttributeKeyBucketParser(
|
UserAttributeParserV2 = NewUserAttributeKeyBucketParser(
|
||||||
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UserAttributeParserV3 = NewUserAttributeKeyBucketParserWithSpecificKeys(
|
||||||
|
NewUserAttributeValueBucketParser(records.UserAttributeRecordParser),
|
||||||
|
[]string{"FilePath", "S3-Access-Box-CRDT-Name"},
|
||||||
|
)
|
||||||
|
|
||||||
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{
|
||||||
cidResolver: StrictResolver,
|
cidResolver: StrictResolver,
|
||||||
oidResolver: StrictResolver,
|
oidResolver: StrictResolver,
|
||||||
|
@ -108,4 +113,14 @@ var (
|
||||||
cidResolver: StrictResolver,
|
cidResolver: StrictResolver,
|
||||||
oidResolver: LenientResolver,
|
oidResolver: LenientResolver,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ExpirationEpochToObjectParser = NewPrefixBucketParser(ExpirationEpochToObject, records.ExpirationEpochToObjectRecordParser, Resolvers{
|
||||||
|
cidResolver: LenientResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
|
|
||||||
|
ObjectToExpirationEpochParser = NewPrefixContainerBucketParser(ObjectToExpirationEpoch, records.ObjectToExpirationEpochRecordParser, Resolvers{
|
||||||
|
cidResolver: StrictResolver,
|
||||||
|
oidResolver: LenientResolver,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,6 +22,8 @@ const (
|
||||||
Split
|
Split
|
||||||
ContainerCounters
|
ContainerCounters
|
||||||
ECInfo
|
ECInfo
|
||||||
|
ExpirationEpochToObject
|
||||||
|
ObjectToExpirationEpoch
|
||||||
)
|
)
|
||||||
|
|
||||||
var x = map[Prefix]string{
|
var x = map[Prefix]string{
|
||||||
|
@ -43,6 +45,8 @@ var x = map[Prefix]string{
|
||||||
Split: "Split",
|
Split: "Split",
|
||||||
ContainerCounters: "Container Counters",
|
ContainerCounters: "Container Counters",
|
||||||
ECInfo: "EC Info",
|
ECInfo: "EC Info",
|
||||||
|
ExpirationEpochToObject: "Exp. Epoch to Object",
|
||||||
|
ObjectToExpirationEpoch: "Object to Exp. Epoch",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Prefix) String() string {
|
func (p Prefix) String() string {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
func (b *PrefixBucket) String() string {
|
func (b *PrefixBucket) String() string {
|
||||||
return common.FormatSimple(
|
return common.FormatSimple(
|
||||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ func (b *PrefixContainerBucket) String() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"%s CID %s",
|
"%s CID %s",
|
||||||
common.FormatSimple(
|
common.FormatSimple(
|
||||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
),
|
),
|
||||||
common.FormatSimple(b.id.String(), tcell.ColorAqua),
|
common.FormatSimple(b.id.String(), tcell.ColorAqua),
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ func (b *ContainerBucket) String() string {
|
||||||
func (b *UserAttributeKeyBucket) String() string {
|
func (b *UserAttributeKeyBucket) String() string {
|
||||||
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
return fmt.Sprintf("%s CID %s ATTR-KEY %s",
|
||||||
common.FormatSimple(
|
common.FormatSimple(
|
||||||
fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime,
|
fmt.Sprintf("(%2d %-20s)", b.prefix, b.prefix), tcell.ColorLime,
|
||||||
),
|
),
|
||||||
common.FormatSimple(
|
common.FormatSimple(
|
||||||
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
fmt.Sprintf("%-44s", b.id), tcell.ColorAqua,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package buckets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
@ -61,6 +62,7 @@ var (
|
||||||
ErrInvalidKeyLength = errors.New("invalid key length")
|
ErrInvalidKeyLength = errors.New("invalid key length")
|
||||||
ErrInvalidValueLength = errors.New("invalid value length")
|
ErrInvalidValueLength = errors.New("invalid value length")
|
||||||
ErrInvalidPrefix = errors.New("invalid prefix")
|
ErrInvalidPrefix = errors.New("invalid prefix")
|
||||||
|
ErrUnexpectedAttributeKey = errors.New("unexpected attribute key")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser {
|
||||||
|
@ -132,6 +134,10 @@ func NewContainerBucketParser(next common.Parser, resolvers Resolvers) common.Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
||||||
|
return NewUserAttributeKeyBucketParserWithSpecificKeys(next, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAttributeKeyBucketParserWithSpecificKeys(next common.Parser, keys []string) common.Parser {
|
||||||
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
return func(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
return nil, nil, ErrNotBucket
|
return nil, nil, ErrNotBucket
|
||||||
|
@ -147,6 +153,11 @@ func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
b.key = string(key[33:])
|
b.key = string(key[33:])
|
||||||
|
|
||||||
|
if len(keys) != 0 && !slices.Contains(keys, b.key) {
|
||||||
|
return nil, nil, ErrUnexpectedAttributeKey
|
||||||
|
}
|
||||||
|
|
||||||
return &b, next, nil
|
return &b, next, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,30 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets"
|
||||||
)
|
)
|
||||||
|
|
||||||
var MetabaseParser = common.WithFallback(
|
var MetabaseParserV3 = common.WithFallback(
|
||||||
|
common.Any(
|
||||||
|
buckets.GraveyardParser,
|
||||||
|
buckets.GarbageParser,
|
||||||
|
buckets.ContainerVolumeParser,
|
||||||
|
buckets.LockedParser,
|
||||||
|
buckets.ShardInfoParser,
|
||||||
|
buckets.PrimaryParser,
|
||||||
|
buckets.LockersParser,
|
||||||
|
buckets.TombstoneParser,
|
||||||
|
buckets.SmallParser,
|
||||||
|
buckets.RootParser,
|
||||||
|
buckets.UserAttributeParserV3,
|
||||||
|
buckets.ParentParser,
|
||||||
|
buckets.SplitParser,
|
||||||
|
buckets.ContainerCountersParser,
|
||||||
|
buckets.ECInfoParser,
|
||||||
|
buckets.ExpirationEpochToObjectParser,
|
||||||
|
buckets.ObjectToExpirationEpochParser,
|
||||||
|
),
|
||||||
|
common.RawParser.ToFallbackParser(),
|
||||||
|
)
|
||||||
|
|
||||||
|
var MetabaseParserV2 = common.WithFallback(
|
||||||
common.Any(
|
common.Any(
|
||||||
buckets.GraveyardParser,
|
buckets.GraveyardParser,
|
||||||
buckets.GarbageParser,
|
buckets.GarbageParser,
|
||||||
|
@ -18,7 +41,7 @@ var MetabaseParser = common.WithFallback(
|
||||||
buckets.SmallParser,
|
buckets.SmallParser,
|
||||||
buckets.RootParser,
|
buckets.RootParser,
|
||||||
buckets.OwnerParser,
|
buckets.OwnerParser,
|
||||||
buckets.UserAttributeParser,
|
buckets.UserAttributeParserV2,
|
||||||
buckets.PayloadHashParser,
|
buckets.PayloadHashParser,
|
||||||
buckets.ParentParser,
|
buckets.ParentParser,
|
||||||
buckets.SplitParser,
|
buckets.SplitParser,
|
||||||
|
|
|
@ -63,3 +63,11 @@ func (r *ContainerCountersRecord) DetailedString() string {
|
||||||
func (r *ECInfoRecord) DetailedString() string {
|
func (r *ECInfoRecord) DetailedString() string {
|
||||||
return spew.Sdump(*r)
|
return spew.Sdump(*r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ExpirationEpochToObjectRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectToExpirationEpochRecord) DetailedString() string {
|
||||||
|
return spew.Sdump(*r)
|
||||||
|
}
|
||||||
|
|
|
@ -143,3 +143,26 @@ func (r *ECInfoRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
return common.No
|
return common.No
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ExpirationEpochToObjectRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "cid":
|
||||||
|
id := val.(cid.ID)
|
||||||
|
return common.IfThenElse(r.cnt.Equals(id), common.Yes, common.No)
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.obj.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectToExpirationEpochRecord) Filter(typ string, val any) common.FilterResult {
|
||||||
|
switch typ {
|
||||||
|
case "oid":
|
||||||
|
id := val.(oid.ID)
|
||||||
|
return common.IfThenElse(r.obj.Equals(id), common.Yes, common.No)
|
||||||
|
default:
|
||||||
|
return common.No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -249,3 +249,45 @@ func ECInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, e
|
||||||
}
|
}
|
||||||
return &r, nil, nil
|
return &r, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExpirationEpochToObjectRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 72 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
r ExpirationEpochToObjectRecord
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
r.epoch = binary.BigEndian.Uint64(key[:8])
|
||||||
|
if err = r.cnt.Decode(key[8:40]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err = r.obj.Decode(key[40:]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObjectToExpirationEpochRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) {
|
||||||
|
if len(key) != 32 {
|
||||||
|
return nil, nil, ErrInvalidKeyLength
|
||||||
|
}
|
||||||
|
if len(value) != 8 {
|
||||||
|
return nil, nil, ErrInvalidValueLength
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
r ObjectToExpirationEpochRecord
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = r.obj.Decode(key); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
r.epoch = binary.LittleEndian.Uint64(value)
|
||||||
|
|
||||||
|
return &r, nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package records
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -133,3 +134,22 @@ func (r *ECInfoRecord) String() string {
|
||||||
len(r.ids),
|
len(r.ids),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ExpirationEpochToObjectRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"exp. epoch %s %c CID %s OID %s",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-20d", r.epoch), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.cnt), tcell.ColorAqua),
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.obj), tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ObjectToExpirationEpochRecord) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"OID %s %c exp. epoch %s",
|
||||||
|
common.FormatSimple(fmt.Sprintf("%-44s", r.obj), tcell.ColorAqua),
|
||||||
|
tview.Borders.Vertical,
|
||||||
|
common.FormatSimple(strconv.FormatUint(r.epoch, 10), tcell.ColorAqua),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -79,4 +79,15 @@ type (
|
||||||
id oid.ID
|
id oid.ID
|
||||||
ids []oid.ID
|
ids []oid.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpirationEpochToObjectRecord struct {
|
||||||
|
epoch uint64
|
||||||
|
cnt cid.ID
|
||||||
|
obj oid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectToExpirationEpochRecord struct {
|
||||||
|
obj oid.ID
|
||||||
|
epoch uint64
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue