diff --git a/pkg/local_object_storage/metabase/control.go b/pkg/local_object_storage/metabase/control.go index 55769fa3..e876d7cb 100644 --- a/pkg/local_object_storage/metabase/control.go +++ b/pkg/local_object_storage/metabase/control.go @@ -28,12 +28,53 @@ func (db *DB) Open() error { return nil } -// Init initializes metabase, however metabase doesn't need extra preparations, -// so it implemented to satisfy interface of storage engine components. +// Init initializes metabase. It creates static (CID-independent) buckets in underlying BoltDB instance. +// +// Does nothing if metabase has already been initialized and filled. To roll back the database to its initial state, +// use Reset. func (db *DB) Init() error { - db.log.Debug("Metabase has been initialized") + return db.init(false) +} - return nil +// Reset resets metabase. Works similar to Init but cleans up all static buckets and +// removes all dynamic (CID-dependent) ones in non-blank BoltDB instances. +func (db *DB) Reset() error { + return db.init(true) +} + +func (db *DB) init(reset bool) error { + mStaticBuckets := map[string]struct{}{ + string(containerVolumeBucketName): {}, + string(graveyardBucketName): {}, + string(toMoveItBucketName): {}, + } + + return db.boltDB.Update(func(tx *bbolt.Tx) error { + for k := range mStaticBuckets { + b, err := tx.CreateBucketIfNotExists([]byte(k)) + if err != nil { + return fmt.Errorf("could not create static bucket %s: %w", k, err) + } + + if reset { + if err = resetBucket(b); err != nil { + return fmt.Errorf("could not reset static bucket %s: %w", k, err) + } + } + } + + if !reset { + return nil + } + + return tx.ForEach(func(name []byte, b *bbolt.Bucket) error { + if _, ok := mStaticBuckets[string(name)]; !ok { + return tx.DeleteBucket(name) + } + + return nil + }) + }) } // Close closes boltDB instance. diff --git a/pkg/local_object_storage/metabase/control_test.go b/pkg/local_object_storage/metabase/control_test.go new file mode 100644 index 00000000..f4590ebb --- /dev/null +++ b/pkg/local_object_storage/metabase/control_test.go @@ -0,0 +1,47 @@ +package meta_test + +import ( + "testing" + + objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-node/pkg/core/object" + meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase" + "github.com/stretchr/testify/require" +) + +func TestReset(t *testing.T) { + db := newDB(t) + defer releaseDB(db) + + err := db.Reset() + require.NoError(t, err) + + obj := generateRawObject(t).Object() + addr := obj.Address() + + addrToInhume := generateAddress() + + assertExists := func(addr *objectSDK.Address, expExists bool, expErr error) { + exists, err := meta.Exists(db, addr) + require.ErrorIs(t, err, expErr) + require.Equal(t, expExists, exists) + } + + assertExists(addr, false, nil) + assertExists(addrToInhume, false, nil) + + err = putBig(db, obj) + require.NoError(t, err) + + err = meta.Inhume(db, addrToInhume, generateAddress()) + require.NoError(t, err) + + assertExists(addr, true, nil) + assertExists(addrToInhume, false, object.ErrAlreadyRemoved) + + err = db.Reset() + require.NoError(t, err) + + assertExists(addr, false, nil) + assertExists(addr, false, nil) +} diff --git a/pkg/local_object_storage/metabase/util.go b/pkg/local_object_storage/metabase/util.go index 2b20c7e2..33ce879a 100644 --- a/pkg/local_object_storage/metabase/util.go +++ b/pkg/local_object_storage/metabase/util.go @@ -6,6 +6,7 @@ import ( cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/object" + "go.etcd.io/bbolt" ) /* @@ -118,3 +119,14 @@ func addressFromKey(k []byte) (*object.Address, error) { func objectKey(oid *object.ID) []byte { return []byte(oid.String()) } + +// removes all bucket elements. +func resetBucket(b *bbolt.Bucket) error { + return b.ForEach(func(k, v []byte) error { + if v != nil { + return b.Delete(k) + } + + return b.DeleteBucket(k) + }) +}