2020-11-23 13:30:56 +00:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
2022-09-08 11:54:21 +00:00
|
|
|
"crypto/sha256"
|
2024-08-20 08:59:42 +00:00
|
|
|
"encoding/binary"
|
2024-03-11 14:55:50 +00:00
|
|
|
"errors"
|
2024-08-20 08:59:42 +00:00
|
|
|
"fmt"
|
2020-11-23 13:30:56 +00:00
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
2023-07-06 12:36:41 +00:00
|
|
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
2023-03-07 13:38:26 +00:00
|
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
2021-09-13 08:23:58 +00:00
|
|
|
"go.etcd.io/bbolt"
|
2020-11-23 13:30:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2021-04-08 14:19:31 +00:00
|
|
|
// graveyardBucketName stores rows with the objects that have been
|
|
|
|
// covered with Tombstone objects. That objects should not be returned
|
|
|
|
// from the node and should not be accepted by the node from other
|
|
|
|
// nodes.
|
2022-09-08 11:54:21 +00:00
|
|
|
graveyardBucketName = []byte{graveyardPrefix}
|
2021-04-08 14:19:31 +00:00
|
|
|
// garbageBucketName stores rows with the objects that should be physically
|
|
|
|
// deleted by the node (Garbage Collector routine).
|
2023-11-01 17:53:24 +00:00
|
|
|
garbageBucketName = []byte{garbagePrefix}
|
|
|
|
toMoveItBucketName = []byte{toMoveItPrefix}
|
|
|
|
containerVolumeBucketName = []byte{containerVolumePrefix}
|
|
|
|
containerCounterBucketName = []byte{containerCountersPrefix}
|
2024-08-20 08:59:42 +00:00
|
|
|
expEpochToObjectBucketName = []byte{expirationEpochToObjectPrefix}
|
2020-11-23 13:30:56 +00:00
|
|
|
|
|
|
|
zeroValue = []byte{0xFF}
|
2024-03-11 14:55:50 +00:00
|
|
|
|
|
|
|
errInvalidLength = errors.New("invalid length")
|
2022-09-08 11:54:21 +00:00
|
|
|
)
|
2020-11-23 13:30:56 +00:00
|
|
|
|
2022-09-08 11:54:21 +00:00
|
|
|
// Prefix bytes for database keys. All ids and addresses are encoded in binary
|
|
|
|
// unless specified otherwise.
|
2022-10-17 12:03:55 +00:00
|
|
|
//
|
|
|
|
//nolint:godot
|
2022-09-08 11:54:21 +00:00
|
|
|
const (
|
|
|
|
// graveyardPrefix is used for the graveyard bucket.
|
|
|
|
// Key: object address
|
|
|
|
// Value: tombstone address
|
|
|
|
graveyardPrefix = iota
|
|
|
|
// garbagePrefix is used for the garbage bucket.
|
|
|
|
// Key: object address
|
|
|
|
// Value: dummy value
|
|
|
|
garbagePrefix
|
|
|
|
// toMoveItPrefix is used for bucket containing IDs of objects that are candidates for moving
|
|
|
|
// to another shard.
|
|
|
|
toMoveItPrefix
|
|
|
|
// containerVolumePrefix is used for storing container size estimations.
|
|
|
|
// Key: container ID
|
|
|
|
// Value: container size in bytes as little-endian uint64
|
|
|
|
containerVolumePrefix
|
|
|
|
// lockedPrefix is used for storing locked objects information.
|
|
|
|
// Key: container ID
|
|
|
|
// Value: bucket mapping objects locked to the list of corresponding LOCK objects.
|
|
|
|
lockedPrefix
|
|
|
|
// shardInfoPrefix is used for storing shard ID. All keys are custom and are not connected to the container.
|
|
|
|
shardInfoPrefix
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
// ======================
|
2022-09-08 11:54:21 +00:00
|
|
|
// Unique index buckets.
|
2023-03-07 13:38:26 +00:00
|
|
|
// ======================
|
2022-09-08 11:54:21 +00:00
|
|
|
|
|
|
|
// primaryPrefix is used for prefixing buckets containing objects of REGULAR type.
|
|
|
|
// Key: object ID
|
|
|
|
// Value: marshalled object
|
|
|
|
primaryPrefix
|
|
|
|
// lockersPrefix is used for prefixing buckets containing objects of LOCK type.
|
|
|
|
// Key: object ID
|
|
|
|
// Value: marshalled object
|
|
|
|
lockersPrefix
|
2023-04-25 06:48:24 +00:00
|
|
|
// _ is unused. Previous usage was for prefixing buckets containing objects of STORAGEGROUP type.
|
2022-09-08 11:54:21 +00:00
|
|
|
// Key: object ID
|
|
|
|
// Value: marshaled object
|
2023-04-25 06:48:24 +00:00
|
|
|
_
|
2022-09-08 11:54:21 +00:00
|
|
|
// tombstonePrefix is used for prefixing buckets containing objects of TOMBSTONE type.
|
|
|
|
// Key: object ID
|
|
|
|
// Value: marshaled object
|
|
|
|
tombstonePrefix
|
|
|
|
// smallPrefix is used for prefixing buckets mapping objects to the blobovniczas they are stored in.
|
|
|
|
// Key: object ID
|
|
|
|
// Value: blobovnicza ID
|
|
|
|
smallPrefix
|
|
|
|
// rootPrefix is used for prefixing buckets mapping parent object to the split info.
|
|
|
|
// Key: object ID
|
|
|
|
// Value: split info
|
|
|
|
rootPrefix
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
// ====================
|
2022-09-08 11:54:21 +00:00
|
|
|
// FKBT index buckets.
|
2023-03-07 13:38:26 +00:00
|
|
|
// ====================
|
2022-09-08 11:54:21 +00:00
|
|
|
|
2024-08-19 14:13:39 +00:00
|
|
|
// ownerPrefix was used for prefixing FKBT index buckets mapping owner to object IDs.
|
2022-09-08 11:54:21 +00:00
|
|
|
// Key: owner ID
|
|
|
|
// Value: bucket containing object IDs as keys
|
2024-08-19 14:13:39 +00:00
|
|
|
_
|
2022-09-08 11:54:21 +00:00
|
|
|
// userAttributePrefix is used for prefixing FKBT index buckets containing objects.
|
|
|
|
// Key: attribute value
|
|
|
|
// Value: bucket containing object IDs as keys
|
|
|
|
userAttributePrefix
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
// ====================
|
2022-09-08 11:54:21 +00:00
|
|
|
// List index buckets.
|
2023-03-07 13:38:26 +00:00
|
|
|
// ====================
|
2022-09-08 11:54:21 +00:00
|
|
|
|
2024-08-19 14:20:55 +00:00
|
|
|
// payloadHashPrefix was used for prefixing List index buckets mapping payload hash to a list of object IDs.
|
2022-09-08 11:54:21 +00:00
|
|
|
// Key: payload hash
|
|
|
|
// Value: list of object IDs
|
2024-08-19 14:20:55 +00:00
|
|
|
_
|
2022-09-08 11:54:21 +00:00
|
|
|
// parentPrefix is used for prefixing List index buckets mapping parent ID to a list of children IDs.
|
|
|
|
// Key: parent ID
|
|
|
|
// Value: list of object IDs
|
|
|
|
parentPrefix
|
|
|
|
// splitPrefix is used for prefixing List index buckets mapping split ID to a list of object IDs.
|
|
|
|
// Key: split ID
|
|
|
|
// Value: list of object IDs
|
|
|
|
splitPrefix
|
2023-11-01 17:53:24 +00:00
|
|
|
|
|
|
|
// containerCountersPrefix is used for storing container object counters.
|
|
|
|
// Key: container ID + type
|
|
|
|
// Value: container size in bytes as little-endian uint64
|
|
|
|
containerCountersPrefix
|
2024-04-22 06:43:42 +00:00
|
|
|
|
|
|
|
// ecInfoPrefix is used for storing relation between EC parent id and chunk id.
|
|
|
|
// Key: container ID + type
|
|
|
|
// Value: Object id
|
|
|
|
ecInfoPrefix
|
2024-08-20 08:59:42 +00:00
|
|
|
|
|
|
|
// expirationEpochToObjectPrefix is used for storing relation between expiration epoch and object id.
|
|
|
|
// Key: expiration epoch + object address
|
|
|
|
// Value: zero
|
|
|
|
expirationEpochToObjectPrefix
|
|
|
|
|
|
|
|
// objectToExpirationEpochPrefix is used for storing relation between expiration epoch and object id.
|
|
|
|
// Key: object address
|
|
|
|
// Value: expiration epoch
|
|
|
|
objectToExpirationEpochPrefix
|
2022-09-08 11:54:21 +00:00
|
|
|
)
|
2020-12-03 09:31:51 +00:00
|
|
|
|
2022-09-08 11:54:21 +00:00
|
|
|
const (
|
|
|
|
cidSize = sha256.Size
|
|
|
|
bucketKeySize = 1 + cidSize
|
|
|
|
objectKeySize = sha256.Size
|
|
|
|
addressKeySize = cidSize + objectKeySize
|
2024-08-20 08:59:42 +00:00
|
|
|
epochSize = 8
|
2020-11-23 13:30:56 +00:00
|
|
|
)
|
|
|
|
|
2022-09-08 11:54:21 +00:00
|
|
|
func bucketName(cnr cid.ID, prefix byte, key []byte) []byte {
|
|
|
|
key[0] = prefix
|
|
|
|
cnr.Encode(key[1:])
|
|
|
|
return key[:bucketKeySize]
|
|
|
|
}
|
|
|
|
|
2020-11-23 13:30:56 +00:00
|
|
|
// primaryBucketName returns <CID>.
|
2022-09-08 11:54:21 +00:00
|
|
|
func primaryBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, primaryPrefix, key)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// tombstoneBucketName returns <CID>_TS.
|
2022-09-08 11:54:21 +00:00
|
|
|
func tombstoneBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, tombstonePrefix, key)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// smallBucketName returns <CID>_small.
|
2022-09-08 11:54:21 +00:00
|
|
|
func smallBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, smallPrefix, key)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// attributeBucketName returns <CID>_attr_<attributeKey>.
|
2022-09-08 11:54:21 +00:00
|
|
|
func attributeBucketName(cnr cid.ID, attributeKey string, key []byte) []byte {
|
|
|
|
key[0] = userAttributePrefix
|
|
|
|
cnr.Encode(key[1:])
|
|
|
|
return append(key[:bucketKeySize], attributeKey...)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// rootBucketName returns <CID>_root.
|
2022-09-08 11:54:21 +00:00
|
|
|
func rootBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, rootPrefix, key)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 08:46:18 +00:00
|
|
|
// parentBucketName returns <CID>_parent.
|
2022-09-08 11:54:21 +00:00
|
|
|
func parentBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, parentPrefix, key)
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 08:46:18 +00:00
|
|
|
// splitBucketName returns <CID>_splitid.
|
2022-09-08 11:54:21 +00:00
|
|
|
func splitBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, splitPrefix, key)
|
2020-11-30 08:46:18 +00:00
|
|
|
}
|
|
|
|
|
2024-04-22 06:43:42 +00:00
|
|
|
// ecInfoBucketName returns <CID>_ecinfo.
|
|
|
|
func ecInfoBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, ecInfoPrefix, key)
|
|
|
|
}
|
|
|
|
|
2024-08-20 08:59:42 +00:00
|
|
|
// objectToExpirationEpochBucketName returns objectToExpirationEpochPrefix_<CID>.
|
|
|
|
func objectToExpirationEpochBucketName(cnr cid.ID, key []byte) []byte {
|
|
|
|
return bucketName(cnr, objectToExpirationEpochPrefix, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func expirationEpochKey(epoch uint64, cnr cid.ID, obj oid.ID) []byte {
|
|
|
|
result := make([]byte, epochSize+addressKeySize)
|
|
|
|
binary.BigEndian.PutUint64(result, epoch)
|
|
|
|
cnr.Encode(result[epochSize:])
|
|
|
|
obj.Encode(result[epochSize+cidSize:])
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseExpirationEpochKey(key []byte) (uint64, cid.ID, oid.ID, error) {
|
|
|
|
if len(key) != epochSize+addressKeySize {
|
|
|
|
return 0, cid.ID{}, oid.ID{}, fmt.Errorf("unexpected expiration epoch to object key length: %d", len(key))
|
|
|
|
}
|
|
|
|
epoch := binary.BigEndian.Uint64(key)
|
|
|
|
var cnr cid.ID
|
|
|
|
if err := cnr.Decode(key[epochSize : epochSize+cidSize]); err != nil {
|
|
|
|
return 0, cid.ID{}, oid.ID{}, fmt.Errorf("failed to decode expiration epoch to object key (container ID): %w", err)
|
|
|
|
}
|
|
|
|
var obj oid.ID
|
|
|
|
if err := obj.Decode(key[epochSize+cidSize:]); err != nil {
|
|
|
|
return 0, cid.ID{}, oid.ID{}, fmt.Errorf("failed to decode expiration epoch to object key (object ID): %w", err)
|
|
|
|
}
|
|
|
|
return epoch, cnr, obj, nil
|
|
|
|
}
|
|
|
|
|
2020-11-23 13:30:56 +00:00
|
|
|
// addressKey returns key for K-V tables when key is a whole address.
|
2022-09-08 11:54:21 +00:00
|
|
|
func addressKey(addr oid.Address, key []byte) []byte {
|
|
|
|
addr.Container().Encode(key)
|
|
|
|
addr.Object().Encode(key[cidSize:])
|
|
|
|
return key[:addressKeySize]
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
|
|
|
|
2021-02-16 07:57:43 +00:00
|
|
|
// parses object address formed by addressKey.
|
2022-05-31 17:00:41 +00:00
|
|
|
func decodeAddressFromKey(dst *oid.Address, k []byte) error {
|
2022-09-08 11:54:21 +00:00
|
|
|
if len(k) != addressKeySize {
|
2024-03-11 14:55:50 +00:00
|
|
|
return errInvalidLength
|
2022-09-08 11:54:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var cnr cid.ID
|
|
|
|
if err := cnr.Decode(k[:cidSize]); err != nil {
|
|
|
|
return err
|
2022-05-31 17:00:41 +00:00
|
|
|
}
|
|
|
|
|
2022-09-08 11:54:21 +00:00
|
|
|
var obj oid.ID
|
|
|
|
if err := obj.Decode(k[cidSize:]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dst.SetObject(obj)
|
|
|
|
dst.SetContainer(cnr)
|
2022-05-31 17:00:41 +00:00
|
|
|
return nil
|
2021-02-16 07:57:43 +00:00
|
|
|
}
|
|
|
|
|
2020-11-23 13:30:56 +00:00
|
|
|
// objectKey returns key for K-V tables when key is an object id.
|
2022-09-08 11:54:21 +00:00
|
|
|
func objectKey(obj oid.ID, key []byte) []byte {
|
|
|
|
obj.Encode(key)
|
|
|
|
return key[:objectKeySize]
|
2020-11-23 13:30:56 +00:00
|
|
|
}
|
2021-09-13 08:23:58 +00:00
|
|
|
|
2022-02-15 11:35:13 +00:00
|
|
|
// if meets irregular object container in objs - returns its type, otherwise returns object.TypeRegular.
|
|
|
|
//
|
|
|
|
// firstIrregularObjectType(tx, cnr, obj) usage allows getting object type.
|
2023-07-06 12:36:41 +00:00
|
|
|
func firstIrregularObjectType(tx *bbolt.Tx, idCnr cid.ID, objs ...[]byte) objectSDK.Type {
|
2022-02-15 11:35:13 +00:00
|
|
|
if len(objs) == 0 {
|
|
|
|
panic("empty object list in firstIrregularObjectType")
|
|
|
|
}
|
|
|
|
|
2023-04-25 06:48:24 +00:00
|
|
|
var keys [2][1 + cidSize]byte
|
2022-09-08 11:54:21 +00:00
|
|
|
|
2022-02-15 11:35:13 +00:00
|
|
|
irregularTypeBuckets := [...]struct {
|
2023-07-06 12:36:41 +00:00
|
|
|
typ objectSDK.Type
|
2022-02-15 11:35:13 +00:00
|
|
|
name []byte
|
|
|
|
}{
|
2023-07-06 12:36:41 +00:00
|
|
|
{objectSDK.TypeTombstone, tombstoneBucketName(idCnr, keys[0][:])},
|
|
|
|
{objectSDK.TypeLock, bucketNameLockers(idCnr, keys[1][:])},
|
2022-02-15 11:35:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := range objs {
|
|
|
|
for j := range irregularTypeBuckets {
|
|
|
|
if inBucket(tx, irregularTypeBuckets[j].name, objs[i]) {
|
|
|
|
return irregularTypeBuckets[j].typ
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-06 12:36:41 +00:00
|
|
|
return objectSDK.TypeRegular
|
2022-02-15 11:35:13 +00:00
|
|
|
}
|
2022-05-26 17:38:32 +00:00
|
|
|
|
|
|
|
// return true if provided object is of LOCK type.
|
|
|
|
func isLockObject(tx *bbolt.Tx, idCnr cid.ID, obj oid.ID) bool {
|
2022-09-08 11:54:21 +00:00
|
|
|
return inBucket(tx,
|
|
|
|
bucketNameLockers(idCnr, make([]byte, bucketKeySize)),
|
|
|
|
objectKey(obj, make([]byte, objectKeySize)))
|
2022-05-26 17:38:32 +00:00
|
|
|
}
|