diff --git a/cmd/frostfs-lens/internal/schema/common/format.go b/cmd/frostfs-lens/internal/schema/common/format.go new file mode 100644 index 000000000..4ed7e96f2 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/common/format.go @@ -0,0 +1,43 @@ +package common + +import ( + "fmt" + "strconv" + + "github.com/gdamore/tcell/v2" +) + +type FormatOptions struct { + Color tcell.Color + + Bold, + Italic, + Underline, + StrikeThrough bool +} + +func Format(s string, opts FormatOptions) string { + var boldTag, italicTag, underlineTag, strikeThroughTag string + + switch { + case opts.Bold: + boldTag = "b" + case opts.Italic: + italicTag = "i" + case opts.Underline: + underlineTag = "u" + case opts.StrikeThrough: + strikeThroughTag = "s" + } + + attrs := fmt.Sprintf( + "%s%s%s%s", boldTag, italicTag, underlineTag, strikeThroughTag, + ) + color := strconv.FormatInt(int64(opts.Color.Hex()), 16) + + return fmt.Sprintf("[#%06s::%s]%s[-::-]", color, attrs, s) +} + +func FormatSimple(s string, c tcell.Color) string { + return Format(s, FormatOptions{Color: c}) +} diff --git a/cmd/frostfs-lens/internal/schema/common/raw.go b/cmd/frostfs-lens/internal/schema/common/raw.go new file mode 100644 index 000000000..0990e24c3 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/common/raw.go @@ -0,0 +1,29 @@ +package common + +import ( + "github.com/davecgh/go-spew/spew" + "github.com/gdamore/tcell/v2" + "github.com/mr-tron/base58" +) + +type RawEntry struct { + key, value []byte +} + +var RawParser Parser = rawParser + +func rawParser(key, value []byte) (SchemaEntry, Parser, error) { + return &RawEntry{key: key, value: value}, rawParser, nil +} + +func (r *RawEntry) String() string { + return FormatSimple(base58.Encode(r.key), tcell.ColorRed) +} + +func (r *RawEntry) DetailedString() string { + return spew.Sdump(r) +} + +func (r *RawEntry) Filter(string, any) FilterResult { + return No +} diff --git a/cmd/frostfs-lens/internal/schema/common/schema.go b/cmd/frostfs-lens/internal/schema/common/schema.go new file mode 100644 index 000000000..9bad19032 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/common/schema.go @@ -0,0 +1,81 @@ +package common + +import ( + "errors" + "fmt" +) + +type FilterResult byte + +const ( + No FilterResult = iota + Maybe + Yes +) + +func IfThenElse(condition bool, onSuccess, onFailure FilterResult) FilterResult { + var res FilterResult + if condition { + res = onSuccess + } else { + res = onFailure + } + return res +} + +type SchemaEntry interface { + String() string + DetailedString() string + Filter(typ string, val any) FilterResult +} + +type ( + Parser func(key, value []byte) (SchemaEntry, Parser, error) + FallbackParser func(key, value []byte) (SchemaEntry, Parser) +) + +func Any(parsers ...Parser) Parser { + return func(key, value []byte) (SchemaEntry, Parser, error) { + var errs error + for _, parser := range parsers { + ret, next, err := parser(key, value) + if err == nil { + return ret, next, nil + } + errs = errors.Join(errs, err) + } + return nil, nil, fmt.Errorf("no parser succeeded: %w", errs) + } +} + +func WithFallback(parser Parser, fallback FallbackParser) Parser { + if parser == nil { + return fallback.ToParser() + } + return func(key, value []byte) (SchemaEntry, Parser, error) { + entry, next, err := parser(key, value) + if err == nil { + return entry, WithFallback(next, fallback), nil + } + return fallback.ToParser()(key, value) + } +} + +func (fp FallbackParser) ToParser() Parser { + return func(key, value []byte) (SchemaEntry, Parser, error) { + entry, next := fp(key, value) + return entry, next, nil + } +} + +func (p Parser) ToFallbackParser() FallbackParser { + return func(key, value []byte) (SchemaEntry, Parser) { + entry, next, err := p(key, value) + if err != nil { + panic(fmt.Errorf( + "couldn't use that parser as a fallback parser, it returned an error: %w", err, + )) + } + return entry, next + } +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/detailed.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/detailed.go new file mode 100644 index 000000000..6a08a723e --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/detailed.go @@ -0,0 +1,29 @@ +package buckets + +import ( + "github.com/davecgh/go-spew/spew" +) + +func (b *PrefixBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *PrefixContainerBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *UserBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *ContainerBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *UserAttributeKeyBucket) DetailedString() string { + return spew.Sdump(*b) +} + +func (b *UserAttributeValueBucket) DetailedString() string { + return spew.Sdump(*b) +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/filter.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/filter.go new file mode 100644 index 000000000..891c4004f --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/filter.go @@ -0,0 +1,81 @@ +package buckets + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" +) + +func (b *PrefixBucket) Filter(typ string, _ any) common.FilterResult { + switch typ { + case "cid": + return b.resolvers.cidResolver(false) + case "oid": + return b.resolvers.oidResolver(false) + default: + return common.No + } +} + +func (b *PrefixContainerBucket) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return b.resolvers.cidResolver(b.id.Equals(id)) + case "oid": + return b.resolvers.oidResolver(false) + default: + return common.No + } +} + +func (b *UserBucket) Filter(typ string, _ any) common.FilterResult { + switch typ { + case "cid": + return b.resolvers.cidResolver(false) + case "oid": + return b.resolvers.oidResolver(false) + default: + return common.No + } +} + +func (b *ContainerBucket) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return b.resolvers.cidResolver(b.id.Equals(id)) + case "oid": + return b.resolvers.oidResolver(false) + default: + return common.No + } +} + +func (b *UserAttributeKeyBucket) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return common.IfThenElse(b.id.Equals(id), common.Yes, common.No) + case "oid": + return common.Maybe + case "key": + key := val.(string) + return common.IfThenElse(b.key == key, common.Yes, common.No) + case "value": + return common.Maybe + default: + return common.No + } +} + +func (b *UserAttributeValueBucket) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + return common.Maybe + case "value": + value := val.(string) + return common.IfThenElse(b.value == value, common.Yes, common.No) + default: + return common.No + } +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/parsers.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/parsers.go new file mode 100644 index 000000000..24cc0e52d --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/parsers.go @@ -0,0 +1,111 @@ +package buckets + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/records" +) + +var ( + GraveyardParser = NewPrefixBucketParser(Graveyard, records.GraveyardRecordParser, Resolvers{ + cidResolver: LenientResolver, + oidResolver: LenientResolver, + }) + + GarbageParser = NewPrefixBucketParser(Garbage, records.GarbageRecordParser, Resolvers{ + cidResolver: LenientResolver, + oidResolver: LenientResolver, + }) + + ContainerVolumeParser = NewPrefixBucketParser(ContainerVolume, records.ContainerVolumeRecordParser, Resolvers{ + cidResolver: LenientResolver, + oidResolver: StrictResolver, + }) + + LockedParser = NewPrefixBucketParser( + Locked, + NewContainerBucketParser( + records.LockedRecordParser, + Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }, + ), + Resolvers{ + cidResolver: LenientResolver, + oidResolver: LenientResolver, + }, + ) + + ShardInfoParser = NewPrefixBucketParser(ShardInfo, records.ShardInfoRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: StrictResolver, + }) + + PrimaryParser = NewPrefixContainerBucketParser(Primary, records.ObjectRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + LockersParser = NewPrefixContainerBucketParser(Lockers, records.ObjectRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + TombstoneParser = NewPrefixContainerBucketParser(Tombstone, records.ObjectRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + SmallParser = NewPrefixContainerBucketParser(Small, records.SmallRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + RootParser = NewPrefixContainerBucketParser(Root, records.RootRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + OwnerParser = NewPrefixContainerBucketParser( + Owner, + NewUserBucketParser( + records.OwnerRecordParser, + Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }, + ), + Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }, + ) + + UserAttributeParser = NewUserAttributeKeyBucketParser( + NewUserAttributeValueBucketParser(records.UserAttributeRecordParser), + ) + + PayloadHashParser = NewPrefixContainerBucketParser(PayloadHash, records.PayloadHashRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: StrictResolver, + }) + + ParentParser = NewPrefixContainerBucketParser(Parent, records.ParentRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) + + SplitParser = NewPrefixContainerBucketParser(Split, records.SplitRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: StrictResolver, + }) + + ContainerCountersParser = NewPrefixBucketParser(ContainerCounters, records.ContainerCountersRecordParser, Resolvers{ + cidResolver: LenientResolver, + oidResolver: StrictResolver, + }) + + ECInfoParser = NewPrefixContainerBucketParser(ECInfo, records.ECInfoRecordParser, Resolvers{ + cidResolver: StrictResolver, + oidResolver: LenientResolver, + }) +) diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/prefix.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/prefix.go new file mode 100644 index 000000000..2fb122940 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/prefix.go @@ -0,0 +1,53 @@ +package buckets + +type Prefix byte + +const ( + Graveyard Prefix = iota + Garbage + ToMoveIt + ContainerVolume + Locked + ShardInfo + Primary + Lockers + _ + Tombstone + Small + Root + Owner + UserAttribute + PayloadHash + Parent + Split + ContainerCounters + ECInfo +) + +var x = map[Prefix]string{ + Graveyard: "Graveyard", + Garbage: "Garbage", + ToMoveIt: "To Move It", + ContainerVolume: "Container Volume", + Locked: "Locked", + ShardInfo: "Shard Info", + Primary: "Primary", + Lockers: "Lockers", + Tombstone: "Tombstone", + Small: "Small", + Root: "Root", + Owner: "Owner", + UserAttribute: "User Attribute", + PayloadHash: "Payload Hash", + Parent: "Parent", + Split: "Split", + ContainerCounters: "Container Counters", + ECInfo: "EC Info", +} + +func (p Prefix) String() string { + if s, ok := x[p]; ok { + return s + } + return "Unknown Prefix" +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/string.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/string.go new file mode 100644 index 000000000..db90bddbd --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/string.go @@ -0,0 +1,48 @@ +package buckets + +import ( + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + "github.com/gdamore/tcell/v2" +) + +func (b *PrefixBucket) String() string { + return common.FormatSimple( + fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime, + ) +} + +func (b *PrefixContainerBucket) String() string { + return fmt.Sprintf( + "%s CID %s", + common.FormatSimple( + fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime, + ), + common.FormatSimple(b.id.String(), tcell.ColorAqua), + ) +} + +func (b *UserBucket) String() string { + return "UID " + common.FormatSimple(b.id.String(), tcell.ColorAqua) +} + +func (b *ContainerBucket) String() string { + return "CID " + common.FormatSimple(b.id.String(), tcell.ColorAqua) +} + +func (b *UserAttributeKeyBucket) String() string { + return fmt.Sprintf("%s CID %s ATTR-KEY %s", + common.FormatSimple( + fmt.Sprintf("(%2d %-18s)", b.prefix, b.prefix), tcell.ColorLime, + ), + common.FormatSimple( + fmt.Sprintf("%-44s", b.id), tcell.ColorAqua, + ), + common.FormatSimple(b.key, tcell.ColorAqua), + ) +} + +func (b *UserAttributeValueBucket) String() string { + return "ATTR-VALUE " + common.FormatSimple(b.value, tcell.ColorAqua) +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/buckets/types.go b/cmd/frostfs-lens/internal/schema/metabase/buckets/types.go new file mode 100644 index 000000000..82b47dd85 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/buckets/types.go @@ -0,0 +1,166 @@ +package buckets + +import ( + "errors" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "github.com/mr-tron/base58" +) + +type ( + PrefixBucket struct { + prefix Prefix + resolvers Resolvers + } + + PrefixContainerBucket struct { + prefix Prefix + id cid.ID + resolvers Resolvers + } + + ContainerBucket struct { + id cid.ID + resolvers Resolvers + } + + UserBucket struct { + id user.ID + resolvers Resolvers + } + + UserAttributeKeyBucket struct { + prefix Prefix + id cid.ID + key string + } + + UserAttributeValueBucket struct { + value string + } +) + +type ( + FilterResolver = func(result bool) common.FilterResult + + Resolvers struct { + cidResolver FilterResolver + oidResolver FilterResolver + } +) + +var ( + StrictResolver = func(x bool) common.FilterResult { return common.IfThenElse(x, common.Yes, common.No) } + LenientResolver = func(x bool) common.FilterResult { return common.IfThenElse(x, common.Yes, common.Maybe) } +) + +var ( + ErrNotBucket = errors.New("not a bucket") + ErrInvalidKeyLength = errors.New("invalid key length") + ErrInvalidValueLength = errors.New("invalid value length") + ErrInvalidPrefix = errors.New("invalid prefix") +) + +func NewPrefixBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) != 1 { + return nil, nil, ErrInvalidKeyLength + } + var b PrefixBucket + if b.prefix = Prefix(key[0]); b.prefix != prefix { + return nil, nil, ErrInvalidPrefix + } + b.resolvers = resolvers + return &b, next, nil + } +} + +func NewPrefixContainerBucketParser(prefix Prefix, next common.Parser, resolvers Resolvers) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) != 33 { + return nil, nil, ErrInvalidKeyLength + } + var b PrefixContainerBucket + if b.prefix = Prefix(key[0]); b.prefix != prefix { + return nil, nil, ErrInvalidPrefix + } + if err := b.id.Decode(key[1:]); err != nil { + return nil, nil, err + } + b.resolvers = resolvers + return &b, next, nil + } +} + +func NewUserBucketParser(next common.Parser, resolvers Resolvers) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + var b UserBucket + if err := b.id.DecodeString(base58.Encode(key)); err != nil { + return nil, nil, err + } + b.resolvers = resolvers + return &b, next, nil + } +} + +func NewContainerBucketParser(next common.Parser, resolvers Resolvers) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) != 32 { + return nil, nil, ErrInvalidKeyLength + } + var b ContainerBucket + if err := b.id.Decode(key); err != nil { + return nil, nil, err + } + b.resolvers = resolvers + return &b, next, nil + } +} + +func NewUserAttributeKeyBucketParser(next common.Parser) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) < 34 { + return nil, nil, ErrInvalidKeyLength + } + var b UserAttributeKeyBucket + if b.prefix = Prefix(key[0]); b.prefix != UserAttribute { + return nil, nil, ErrInvalidPrefix + } + if err := b.id.Decode(key[1:33]); err != nil { + return nil, nil, err + } + b.key = string(key[33:]) + return &b, next, nil + } +} + +func NewUserAttributeValueBucketParser(next common.Parser) common.Parser { + return func(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if value != nil { + return nil, nil, ErrNotBucket + } + if len(key) == 0 { + return nil, nil, ErrInvalidKeyLength + } + var b UserAttributeValueBucket + b.value = string(key) + return &b, next, nil + } +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/parser.go b/cmd/frostfs-lens/internal/schema/metabase/parser.go new file mode 100644 index 000000000..ea095e207 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/parser.go @@ -0,0 +1,29 @@ +package metabase + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/metabase/buckets" +) + +var MetabaseParser = 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.OwnerParser, + buckets.UserAttributeParser, + buckets.PayloadHashParser, + buckets.ParentParser, + buckets.SplitParser, + buckets.ContainerCountersParser, + buckets.ECInfoParser, + ), + common.RawParser.ToFallbackParser(), +) diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/detailed.go b/cmd/frostfs-lens/internal/schema/metabase/records/detailed.go new file mode 100644 index 000000000..2dda15b4f --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/detailed.go @@ -0,0 +1,65 @@ +package records + +import ( + "github.com/davecgh/go-spew/spew" +) + +func (r *GraveyardRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *GarbageRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ContainerVolumeRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *LockedRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ShardInfoRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ObjectRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *SmallRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *RootRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *OwnerRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *UserAttributeRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *PayloadHashRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ParentRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *SplitRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ContainerCountersRecord) DetailedString() string { + return spew.Sdump(*r) +} + +func (r *ECInfoRecord) DetailedString() string { + return spew.Sdump(*r) +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/filter.go b/cmd/frostfs-lens/internal/schema/metabase/records/filter.go new file mode 100644 index 000000000..880a7a8ff --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/filter.go @@ -0,0 +1,145 @@ +package records + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" +) + +func (r *GraveyardRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return common.IfThenElse(r.object.Container().Equals(id), common.Yes, common.No) + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.object.Object().Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *GarbageRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return common.IfThenElse(r.addr.Container().Equals(id), common.Yes, common.No) + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.addr.Object().Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *ContainerVolumeRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *ShardInfoRecord) Filter(string, any) common.FilterResult { + return common.No +} + +func (r *LockedRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *ObjectRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *SmallRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *RootRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *OwnerRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *UserAttributeRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *PayloadHashRecord) Filter(string, any) common.FilterResult { + return common.No +} + +func (r *ParentRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.parent.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *SplitRecord) Filter(string, any) common.FilterResult { + return common.No +} + +func (r *ContainerCountersRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "cid": + id := val.(cid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} + +func (r *ECInfoRecord) Filter(typ string, val any) common.FilterResult { + switch typ { + case "oid": + id := val.(oid.ID) + return common.IfThenElse(r.id.Equals(id), common.Yes, common.No) + default: + return common.No + } +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/parsers.go b/cmd/frostfs-lens/internal/schema/metabase/records/parsers.go new file mode 100644 index 000000000..1b070e2a0 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/parsers.go @@ -0,0 +1,251 @@ +package records + +import ( + "encoding/binary" + "errors" + "strconv" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" +) + +var ( + ErrInvalidKeyLength = errors.New("invalid key length") + ErrInvalidValueLength = errors.New("invalid value length") + ErrInvalidPrefix = errors.New("invalid prefix") +) + +func GraveyardRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if len(key) != 64 { + return nil, nil, ErrInvalidKeyLength + } + if len(value) != 64 { + return nil, nil, ErrInvalidValueLength + } + var ( + cnr cid.ID + obj oid.ID + r GraveyardRecord + ) + + _ = cnr.Decode(key[:32]) + _ = obj.Decode(key[32:]) + + r.object.SetContainer(cnr) + r.object.SetObject(obj) + + _ = cnr.Decode(value[:32]) + _ = obj.Decode(value[32:]) + + r.tombstone.SetContainer(cnr) + r.tombstone.SetObject(obj) + + return &r, nil, nil +} + +func GarbageRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) { + if len(key) != 64 { + return nil, nil, ErrInvalidKeyLength + } + var ( + cnr cid.ID + obj oid.ID + r GarbageRecord + ) + + _ = cnr.Decode(key[:32]) + _ = obj.Decode(key[32:]) + + r.addr.SetContainer(cnr) + r.addr.SetObject(obj) + + return &r, nil, nil +} + +func ContainerVolumeRecordParser(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 ContainerVolumeRecord + + _ = r.id.Decode(key) + r.volume = binary.LittleEndian.Uint64(value) + + return &r, nil, nil +} + +func LockedRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var ( + r LockedRecord + err error + ) + + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + if r.ids, err = DecodeOIDs(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func ShardInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if len(key) == 0 { + return nil, nil, ErrInvalidKeyLength + } + + var r ShardInfoRecord + if string(key) == "id" { + r.label = string(key) + r.value = shard.ID(value).String() + + return &r, nil, nil + } + + if len(value) != 8 { + return nil, nil, ErrInvalidValueLength + } + r.label = string(key) + r.value = strconv.FormatUint(binary.LittleEndian.Uint64(value), 10) + + return &r, nil, nil +} + +func ObjectRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if len(key) != 32 { + return nil, nil, ErrInvalidKeyLength + } + var r ObjectRecord + + _ = r.id.Decode(key) + if err := r.object.Unmarshal(value); err != nil { + return nil, nil, err + } + + return &r, nil, nil +} + +func SmallRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var r SmallRecord + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + if len(value) != 0 { + x := string(value) + r.storageID = &x + } + return &r, nil, nil +} + +func RootRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var r RootRecord + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + if len(value) == 0 { + return &r, nil, nil + } + r.info = &objectSDK.SplitInfo{} + if err := r.info.Unmarshal(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func OwnerRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) { + var r OwnerRecord + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func UserAttributeRecordParser(key, _ []byte) (common.SchemaEntry, common.Parser, error) { + var r UserAttributeRecord + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func PayloadHashRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if len(key) != 32 { + return nil, nil, ErrInvalidKeyLength + } + var ( + err error + r PayloadHashRecord + ) + + r.checksum.SetSHA256([32]byte(key)) + if r.ids, err = DecodeOIDs(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func ParentRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var ( + r ParentRecord + err error + ) + if err = r.parent.Decode(key); err != nil { + return nil, nil, err + } + if r.ids, err = DecodeOIDs(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func SplitRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var ( + err error + r SplitRecord + ) + if err = r.id.UnmarshalBinary(key); err != nil { + return nil, nil, err + } + if r.ids, err = DecodeOIDs(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} + +func ContainerCountersRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + if len(value) != 24 { + return nil, nil, ErrInvalidValueLength + } + + var r ContainerCountersRecord + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + + r.logical = binary.LittleEndian.Uint64(value[:8]) + r.physical = binary.LittleEndian.Uint64(value[8:16]) + r.user = binary.LittleEndian.Uint64(value[16:24]) + + return &r, nil, nil +} + +func ECInfoRecordParser(key, value []byte) (common.SchemaEntry, common.Parser, error) { + var ( + r ECInfoRecord + err error + ) + + if err := r.id.Decode(key); err != nil { + return nil, nil, err + } + if r.ids, err = DecodeOIDs(value); err != nil { + return nil, nil, err + } + return &r, nil, nil +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/string.go b/cmd/frostfs-lens/internal/schema/metabase/records/string.go new file mode 100644 index 000000000..a6c70d537 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/string.go @@ -0,0 +1,135 @@ +package records + +import ( + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +func (r *GraveyardRecord) String() string { + return fmt.Sprintf( + "Object CID %s OID %s %c Tombstone CID %s OID %s", + common.FormatSimple(fmt.Sprintf("%-44s", r.object.Container()), tcell.ColorAqua), + common.FormatSimple(fmt.Sprintf("%-44s", r.object.Object()), tcell.ColorAqua), + tview.Borders.Vertical, + common.FormatSimple(fmt.Sprintf("%-44s", r.tombstone.Container()), tcell.ColorAqua), + common.FormatSimple(fmt.Sprintf("%-44s", r.tombstone.Object()), tcell.ColorAqua), + ) +} + +func (r *GarbageRecord) String() string { + return fmt.Sprintf( + "CID %-44s OID %-44s", + common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Container()), tcell.ColorAqua), + common.FormatSimple(fmt.Sprintf("%-44s", r.addr.Object()), tcell.ColorAqua), + ) +} + +func (r *ContainerVolumeRecord) String() string { + return fmt.Sprintf( + "CID %-44s %c %d", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + r.volume, + ) +} + +func (r *LockedRecord) String() string { + return fmt.Sprintf( + "Locker OID %s %c Locked [%d]OID {...}", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + len(r.ids), + ) +} + +func (r *ShardInfoRecord) String() string { + return fmt.Sprintf("%-13s %c %s", r.label, tview.Borders.Vertical, r.value) +} + +func (r *ObjectRecord) String() string { + return fmt.Sprintf( + "OID %s %c Object {...}", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + ) +} + +func (r *SmallRecord) String() string { + s := fmt.Sprintf( + "OID %s %c", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + ) + if r.storageID != nil { + s = fmt.Sprintf("%s %s", s, *r.storageID) + } + return s +} + +func (r *RootRecord) String() string { + s := fmt.Sprintf( + "Root OID %s %c", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + ) + if r.info != nil { + s += " Split info {...}" + } + return s +} + +func (r *OwnerRecord) String() string { + return "OID " + common.FormatSimple(r.id.String(), tcell.ColorAqua) +} + +func (r *UserAttributeRecord) String() string { + return "OID " + common.FormatSimple(r.id.String(), tcell.ColorAqua) +} + +func (r *PayloadHashRecord) String() string { + return fmt.Sprintf( + "Checksum %s %c [%d]OID {...}", + common.FormatSimple(r.checksum.String(), tcell.ColorAqua), + tview.Borders.Vertical, + len(r.ids), + ) +} + +func (r *ParentRecord) String() string { + return fmt.Sprintf( + "Parent OID %s %c [%d]OID {...}", + common.FormatSimple(fmt.Sprintf("%-44s", r.parent), tcell.ColorAqua), + tview.Borders.Vertical, + len(r.ids), + ) +} + +func (r *SplitRecord) String() string { + return fmt.Sprintf( + "Split ID %s %c [%d]OID {...}", + common.FormatSimple(r.id.String(), tcell.ColorAqua), + tview.Borders.Vertical, + len(r.ids), + ) +} + +func (r *ContainerCountersRecord) String() string { + return fmt.Sprintf( + "CID %s %c logical %d, physical %d, user %d", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + r.logical, r.physical, r.user, + ) +} + +func (r *ECInfoRecord) String() string { + return fmt.Sprintf( + "OID %s %c [%d]OID {...}", + common.FormatSimple(fmt.Sprintf("%-44s", r.id), tcell.ColorAqua), + tview.Borders.Vertical, + len(r.ids), + ) +} diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/types.go b/cmd/frostfs-lens/internal/schema/metabase/records/types.go new file mode 100644 index 000000000..34c1c29fd --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/types.go @@ -0,0 +1,82 @@ +package records + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "github.com/google/uuid" +) + +type ( + GraveyardRecord struct { + object, tombstone oid.Address + } + + GarbageRecord struct { + addr oid.Address + } + + ContainerVolumeRecord struct { + id cid.ID + volume uint64 + } + + LockedRecord struct { + id oid.ID + ids []oid.ID + } + + ShardInfoRecord struct { + label string + value string + } + + ObjectRecord struct { + id oid.ID + object objectSDK.Object + } + + SmallRecord struct { + id oid.ID + storageID *string // optional + } + + RootRecord struct { + id oid.ID + info *objectSDK.SplitInfo // optional + } + + OwnerRecord struct { + id oid.ID + } + + UserAttributeRecord struct { + id oid.ID + } + + PayloadHashRecord struct { + checksum checksum.Checksum + ids []oid.ID + } + + ParentRecord struct { + parent oid.ID + ids []oid.ID + } + + SplitRecord struct { + id uuid.UUID + ids []oid.ID + } + + ContainerCountersRecord struct { + id cid.ID + logical, physical, user uint64 + } + + ECInfoRecord struct { + id oid.ID + ids []oid.ID + } +) diff --git a/cmd/frostfs-lens/internal/schema/metabase/records/util.go b/cmd/frostfs-lens/internal/schema/metabase/records/util.go new file mode 100644 index 000000000..f50ebe951 --- /dev/null +++ b/cmd/frostfs-lens/internal/schema/metabase/records/util.go @@ -0,0 +1,20 @@ +package records + +import ( + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "github.com/nspcc-dev/neo-go/pkg/io" +) + +func DecodeOIDs(data []byte) ([]oid.ID, error) { + r := io.NewBinReaderFromBuf(data) + + size := r.ReadVarUint() + oids := make([]oid.ID, size) + + for i := uint64(0); i < size; i++ { + if err := oids[i].Decode(r.ReadVarBytes()); err != nil { + return nil, err + } + } + return oids, nil +} diff --git a/go.mod b/go.mod index 358370201..927d978ac 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,9 @@ require ( github.com/VictoriaMetrics/easyproto v0.1.4 github.com/cheggaaa/pb v1.0.29 github.com/chzyer/readline v1.5.1 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 + github.com/gdamore/tcell/v2 v2.7.4 github.com/go-pkgz/expirable-cache/v3 v3.0.0 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 @@ -65,10 +67,10 @@ require ( github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.2-0.20231222162921-eb75782795d2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gdamore/encoding v1.0.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -85,6 +87,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.12.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/minio/sha256-simd v1.0.1 // indirect diff --git a/go.sum b/go.sum index be82bff70..3fb24b390 100644 Binary files a/go.sum and b/go.sum differ