From 1e786233bf7281624c9f1fa692a45bc9df2edcad Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 28 Jun 2022 16:42:50 +0300 Subject: [PATCH] [#1559] local_object_storage: Provide readOnly flag to `Open` We should be able to reopen storage in readonly in runtime. Signed-off-by: Evgenii Stratonikov --- .../internal/commands/inspect/inspect.go | 2 +- cmd/neofs-lens/internal/commands/list/list.go | 2 +- .../blobovnicza/blobovnicza.go | 6 ++-- .../blobstor/blobovnicza.go | 1 + pkg/local_object_storage/blobstor/blobstor.go | 2 ++ .../blobstor/blobstor_test.go | 4 +-- pkg/local_object_storage/blobstor/control.go | 4 ++- .../blobstor/exists_test.go | 2 +- .../blobstor/iterate_test.go | 8 +++--- pkg/local_object_storage/metabase/control.go | 7 ++++- pkg/local_object_storage/metabase/db_test.go | 2 +- .../metabase/version_test.go | 14 +++++----- pkg/local_object_storage/pilorama/boltdb.go | 17 +++++++++-- pkg/local_object_storage/pilorama/forest.go | 2 +- .../pilorama/forest_test.go | 4 +-- .../pilorama/interface.go | 2 +- pkg/local_object_storage/shard/control.go | 28 +++++++++---------- pkg/local_object_storage/shard/id.go | 2 +- .../writecache/storage.go | 4 +-- .../writecache/writecache.go | 6 ++-- 20 files changed, 70 insertions(+), 49 deletions(-) diff --git a/cmd/neofs-lens/internal/commands/inspect/inspect.go b/cmd/neofs-lens/internal/commands/inspect/inspect.go index 85a70d1b..80c5e881 100644 --- a/cmd/neofs-lens/internal/commands/inspect/inspect.go +++ b/cmd/neofs-lens/internal/commands/inspect/inspect.go @@ -80,7 +80,7 @@ func objectInspectCmd(cmd *cobra.Command, _ []string) { blz := blobovnicza.New( blobovnicza.WithPath(vPath), - blobovnicza.ReadOnly()) + blobovnicza.WithReadOnly(true)) common.ExitOnErr(cmd, blz.Open()) defer blz.Close() diff --git a/cmd/neofs-lens/internal/commands/list/list.go b/cmd/neofs-lens/internal/commands/list/list.go index 18b8c562..8332acf3 100644 --- a/cmd/neofs-lens/internal/commands/list/list.go +++ b/cmd/neofs-lens/internal/commands/list/list.go @@ -60,7 +60,7 @@ var Command = &cobra.Command{ blz := blobovnicza.New( blobovnicza.WithPath(vPath), - blobovnicza.ReadOnly(), + blobovnicza.WithReadOnly(true), ) common.ExitOnErr(cmd, blz.Open()) diff --git a/pkg/local_object_storage/blobovnicza/blobovnicza.go b/pkg/local_object_storage/blobovnicza/blobovnicza.go index 44f6acb0..4466f58f 100644 --- a/pkg/local_object_storage/blobovnicza/blobovnicza.go +++ b/pkg/local_object_storage/blobovnicza/blobovnicza.go @@ -106,9 +106,9 @@ func WithLogger(l *logger.Logger) Option { } } -// ReadOnly returns an option to open Blobovnicza in read-only mode. -func ReadOnly() Option { +// WithReadOnly returns an option to open Blobovnicza in read-only mode. +func WithReadOnly(ro bool) Option { return func(c *cfg) { - c.boltOptions.ReadOnly = true + c.boltOptions.ReadOnly = ro } } diff --git a/pkg/local_object_storage/blobstor/blobovnicza.go b/pkg/local_object_storage/blobstor/blobovnicza.go index e63788fa..f132306b 100644 --- a/pkg/local_object_storage/blobstor/blobovnicza.go +++ b/pkg/local_object_storage/blobstor/blobovnicza.go @@ -910,6 +910,7 @@ func (b *blobovniczas) openBlobovnicza(p string) (*blobovnicza.Blobovnicza, erro } blz := blobovnicza.New(append(b.blzOpts, + blobovnicza.WithReadOnly(b.readOnly), blobovnicza.WithPath(filepath.Join(b.blzRootPath, p)), )...) diff --git a/pkg/local_object_storage/blobstor/blobstor.go b/pkg/local_object_storage/blobstor/blobstor.go index 4dbd52f5..64c68d76 100644 --- a/pkg/local_object_storage/blobstor/blobstor.go +++ b/pkg/local_object_storage/blobstor/blobstor.go @@ -44,6 +44,8 @@ type cfg struct { blzRootPath string + readOnly bool + blzOpts []blobovnicza.Option } diff --git a/pkg/local_object_storage/blobstor/blobstor_test.go b/pkg/local_object_storage/blobstor/blobstor_test.go index a69fe644..060e4401 100644 --- a/pkg/local_object_storage/blobstor/blobstor_test.go +++ b/pkg/local_object_storage/blobstor/blobstor_test.go @@ -24,7 +24,7 @@ func TestCompression(t *testing.T) { WithRootPath(dir), WithSmallSizeLimit(smallSizeLimit), WithBlobovniczaShallowWidth(1)) // default width is 16, slow init - require.NoError(t, bs.Open()) + require.NoError(t, bs.Open(false)) require.NoError(t, bs.Init()) return bs } @@ -90,7 +90,7 @@ func TestBlobstor_needsCompression(t *testing.T) { WithSmallSizeLimit(smallSizeLimit), WithBlobovniczaShallowWidth(1), WithUncompressableContentTypes(ct)) - require.NoError(t, bs.Open()) + require.NoError(t, bs.Open(false)) require.NoError(t, bs.Init()) return bs } diff --git a/pkg/local_object_storage/blobstor/control.go b/pkg/local_object_storage/blobstor/control.go index 048935ad..e9adcb18 100644 --- a/pkg/local_object_storage/blobstor/control.go +++ b/pkg/local_object_storage/blobstor/control.go @@ -6,9 +6,11 @@ import ( ) // Open opens BlobStor. -func (b *BlobStor) Open() error { +func (b *BlobStor) Open(readOnly bool) error { b.log.Debug("opening...") + b.blobovniczas.readOnly = readOnly + return nil } diff --git a/pkg/local_object_storage/blobstor/exists_test.go b/pkg/local_object_storage/blobstor/exists_test.go index 8af0c59a..20abb4ed 100644 --- a/pkg/local_object_storage/blobstor/exists_test.go +++ b/pkg/local_object_storage/blobstor/exists_test.go @@ -21,7 +21,7 @@ func TestExists(t *testing.T) { b := New(WithRootPath(dir), WithSmallSizeLimit(smallSizeLimit), WithBlobovniczaShallowWidth(1)) // default width is 16, slow init - require.NoError(t, b.Open()) + require.NoError(t, b.Open(false)) require.NoError(t, b.Init()) objects := []*objectSDK.Object{ diff --git a/pkg/local_object_storage/blobstor/iterate_test.go b/pkg/local_object_storage/blobstor/iterate_test.go index cea45651..522cb334 100644 --- a/pkg/local_object_storage/blobstor/iterate_test.go +++ b/pkg/local_object_storage/blobstor/iterate_test.go @@ -34,7 +34,7 @@ func TestIterateObjects(t *testing.T) { defer os.RemoveAll(p) // open Blobstor - require.NoError(t, blobStor.Open()) + require.NoError(t, blobStor.Open(false)) // initialize Blobstor require.NoError(t, blobStor.Init()) @@ -111,7 +111,7 @@ func TestIterate_IgnoreErrors(t *testing.T) { WithBlobovniczaShallowWidth(1), WithBlobovniczaShallowDepth(1)} bs := New(bsOpts...) - require.NoError(t, bs.Open()) + require.NoError(t, bs.Open(false)) require.NoError(t, bs.Init()) addrs := make([]oid.Address, objCount) @@ -148,7 +148,7 @@ func TestIterate_IgnoreErrors(t *testing.T) { // Increase width to have blobovnicza which is definitely empty. b := New(append(bsOpts, WithBlobovniczaShallowWidth(2))...) - require.NoError(t, b.Open()) + require.NoError(t, b.Open(false)) require.NoError(t, b.Init()) var p string @@ -163,7 +163,7 @@ func TestIterate_IgnoreErrors(t *testing.T) { require.NoError(t, os.Chmod(p, 0)) require.NoError(t, b.Close()) - require.NoError(t, bs.Open()) + require.NoError(t, bs.Open(false)) require.NoError(t, bs.Init()) var prm IteratePrm diff --git a/pkg/local_object_storage/metabase/control.go b/pkg/local_object_storage/metabase/control.go index 42f60d2c..044936fe 100644 --- a/pkg/local_object_storage/metabase/control.go +++ b/pkg/local_object_storage/metabase/control.go @@ -10,7 +10,7 @@ import ( ) // Open boltDB instance for metabase. -func (db *DB) Open() error { +func (db *DB) Open(readOnly bool) error { err := util.MkdirAllX(filepath.Dir(db.info.Path), db.info.Permission) if err != nil { return fmt.Errorf("can't create dir %s for metabase: %w", db.info.Path, err) @@ -18,6 +18,11 @@ func (db *DB) Open() error { db.log.Debug("created directory for Metabase", zap.String("path", db.info.Path)) + if db.boltOptions == nil { + db.boltOptions = bbolt.DefaultOptions + } + db.boltOptions.ReadOnly = readOnly + db.boltDB, err = bbolt.Open(db.info.Path, db.info.Permission, db.boltOptions) if err != nil { return fmt.Errorf("can't open boltDB database: %w", err) diff --git a/pkg/local_object_storage/metabase/db_test.go b/pkg/local_object_storage/metabase/db_test.go index dfa5c764..c170dcbc 100644 --- a/pkg/local_object_storage/metabase/db_test.go +++ b/pkg/local_object_storage/metabase/db_test.go @@ -39,7 +39,7 @@ func newDB(t testing.TB, opts ...meta.Option) *meta.DB { bdb := meta.New(append([]meta.Option{meta.WithPath(path), meta.WithPermissions(0600)}, opts...)...) - require.NoError(t, bdb.Open()) + require.NoError(t, bdb.Open(false)) require.NoError(t, bdb.Init()) t.Cleanup(func() { diff --git a/pkg/local_object_storage/metabase/version_test.go b/pkg/local_object_storage/metabase/version_test.go index 371bebfe..371332c8 100644 --- a/pkg/local_object_storage/metabase/version_test.go +++ b/pkg/local_object_storage/metabase/version_test.go @@ -36,13 +36,13 @@ func TestVersion(t *testing.T) { } t.Run("simple", func(t *testing.T) { db := newDB(t) - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.Init()) check(t, db) require.NoError(t, db.Close()) t.Run("reopen", func(t *testing.T) { - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.Init()) check(t, db) require.NoError(t, db.Close()) @@ -50,29 +50,29 @@ func TestVersion(t *testing.T) { }) t.Run("old data", func(t *testing.T) { db := newDB(t) - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.WriteShardID([]byte{1, 2, 3, 4})) require.NoError(t, db.Close()) - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.Init()) check(t, db) require.NoError(t, db.Close()) }) t.Run("invalid version", func(t *testing.T) { db := newDB(t) - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.boltDB.Update(func(tx *bbolt.Tx) error { return updateVersion(tx, version+1) })) require.NoError(t, db.Close()) - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.Error(t, db.Init()) require.NoError(t, db.Close()) t.Run("reset", func(t *testing.T) { - require.NoError(t, db.Open()) + require.NoError(t, db.Open(false)) require.NoError(t, db.Reset()) check(t, db) require.NoError(t, db.Close()) diff --git a/pkg/local_object_storage/pilorama/boltdb.go b/pkg/local_object_storage/pilorama/boltdb.go index cc034a29..2f3a3677 100644 --- a/pkg/local_object_storage/pilorama/boltdb.go +++ b/pkg/local_object_storage/pilorama/boltdb.go @@ -58,14 +58,14 @@ func NewBoltForest(opts ...Option) ForestStorage { return &b } -func (t *boltForest) Init() error { return nil } -func (t *boltForest) Open() error { +func (t *boltForest) Open(readOnly bool) error { err := util.MkdirAllX(filepath.Dir(t.path), t.perm) if err != nil { return fmt.Errorf("can't create dir %s for the pilorama: %w", t.path, err) } opts := *bbolt.DefaultOptions + opts.ReadOnly = readOnly opts.NoSync = t.noSync opts.Timeout = 100 * time.Millisecond @@ -77,6 +77,12 @@ func (t *boltForest) Open() error { t.db.MaxBatchSize = t.maxBatchSize t.db.MaxBatchDelay = t.maxBatchDelay + return nil +} +func (t *boltForest) Init() error { + if t.db.IsReadOnly() { + return nil + } return t.db.Update(func(tx *bbolt.Tx) error { _, err := tx.CreateBucketIfNotExists(dataBucket) if err != nil { @@ -89,7 +95,12 @@ func (t *boltForest) Open() error { return nil }) } -func (t *boltForest) Close() error { return t.db.Close() } +func (t *boltForest) Close() error { + if t.db != nil { + return t.db.Close() + } + return nil +} // TreeMove implements the Forest interface. func (t *boltForest) TreeMove(d CIDDescriptor, treeID string, m *Move) (*LogMove, error) { diff --git a/pkg/local_object_storage/pilorama/forest.go b/pkg/local_object_storage/pilorama/forest.go index d5f674ae..2d781cff 100644 --- a/pkg/local_object_storage/pilorama/forest.go +++ b/pkg/local_object_storage/pilorama/forest.go @@ -108,7 +108,7 @@ func (f *memoryForest) Init() error { return nil } -func (f *memoryForest) Open() error { +func (f *memoryForest) Open(bool) error { return nil } diff --git a/pkg/local_object_storage/pilorama/forest_test.go b/pkg/local_object_storage/pilorama/forest_test.go index 01f16573..53868fbf 100644 --- a/pkg/local_object_storage/pilorama/forest_test.go +++ b/pkg/local_object_storage/pilorama/forest_test.go @@ -18,8 +18,8 @@ var providers = []struct { }{ {"inmemory", func(t testing.TB) Forest { f := NewMemoryForest() + require.NoError(t, f.Open(false)) require.NoError(t, f.Init()) - require.NoError(t, f.Open()) t.Cleanup(func() { require.NoError(t, f.Close()) }) @@ -32,8 +32,8 @@ var providers = []struct { require.NoError(t, err) f := NewBoltForest(WithPath(filepath.Join(tmpDir, "test.db"))) + require.NoError(t, f.Open(false)) require.NoError(t, f.Init()) - require.NoError(t, f.Open()) t.Cleanup(func() { require.NoError(t, f.Close()) require.NoError(t, os.RemoveAll(tmpDir)) diff --git a/pkg/local_object_storage/pilorama/interface.go b/pkg/local_object_storage/pilorama/interface.go index 6d685e95..b7e98e32 100644 --- a/pkg/local_object_storage/pilorama/interface.go +++ b/pkg/local_object_storage/pilorama/interface.go @@ -39,7 +39,7 @@ type ForestStorage interface { // DumpInfo returns information about the pilorama. DumpInfo() Info Init() error - Open() error + Open(bool) error Close() error Forest } diff --git a/pkg/local_object_storage/shard/control.go b/pkg/local_object_storage/shard/control.go index 64713b46..e645128f 100644 --- a/pkg/local_object_storage/shard/control.go +++ b/pkg/local_object_storage/shard/control.go @@ -14,20 +14,20 @@ import ( // Open opens all Shard's components. func (s *Shard) Open() error { - components := []interface{ Open() error }{ + components := []interface{ Open(bool) error }{ s.blobStor, s.metaBase, } - if s.pilorama != nil { - components = append(components, s.pilorama) - } - if s.hasWriteCache() { components = append(components, s.writeCache) } + if s.pilorama != nil { + components = append(components, s.pilorama) + } + for _, component := range components { - if err := component.Open(); err != nil { + if err := component.Open(false); err != nil { return fmt.Errorf("could not open %T: %w", component, err) } } @@ -48,14 +48,14 @@ func (s *Shard) Init() error { s.blobStor.Init, fMetabase, } - if s.pilorama != nil { - components = append(components, s.pilorama.Init) - } - if s.hasWriteCache() { components = append(components, s.writeCache.Init) } + if s.pilorama != nil { + components = append(components, s.pilorama.Init) + } + for _, component := range components { if err := component(); err != nil { return fmt.Errorf("could not initialize %T: %w", component, err) @@ -162,14 +162,14 @@ func (s *Shard) refillMetabase() error { func (s *Shard) Close() error { components := []interface{ Close() error }{} - if s.hasWriteCache() { - components = append(components, s.writeCache) - } - if s.pilorama != nil { components = append(components, s.pilorama) } + if s.hasWriteCache() { + components = append(components, s.writeCache) + } + components = append(components, s.blobStor, s.metaBase) for _, component := range components { diff --git a/pkg/local_object_storage/shard/id.go b/pkg/local_object_storage/shard/id.go index a7218426..ef7614f6 100644 --- a/pkg/local_object_storage/shard/id.go +++ b/pkg/local_object_storage/shard/id.go @@ -27,7 +27,7 @@ func (s *Shard) ID() *ID { // UpdateID reads shard ID saved in the metabase and updates it if it is missing. func (s *Shard) UpdateID() (err error) { - if err = s.metaBase.Open(); err != nil { + if err = s.metaBase.Open(false); err != nil { return err } defer func() { diff --git a/pkg/local_object_storage/writecache/storage.go b/pkg/local_object_storage/writecache/storage.go index 48c88e80..6279ded3 100644 --- a/pkg/local_object_storage/writecache/storage.go +++ b/pkg/local_object_storage/writecache/storage.go @@ -27,13 +27,13 @@ const lruKeysCount = 256 * 1024 * 8 const dbName = "small.bolt" -func (c *cache) openStore() error { +func (c *cache) openStore(readOnly bool) error { err := util.MkdirAllX(c.path, os.ModePerm) if err != nil { return err } - c.db, err = OpenDB(c.path, false) + c.db, err = OpenDB(c.path, readOnly) if err != nil { return fmt.Errorf("could not open database: %w", err) } diff --git a/pkg/local_object_storage/writecache/writecache.go b/pkg/local_object_storage/writecache/writecache.go index c41cc940..a4b7248b 100644 --- a/pkg/local_object_storage/writecache/writecache.go +++ b/pkg/local_object_storage/writecache/writecache.go @@ -29,7 +29,7 @@ type Cache interface { DumpInfo() Info Init() error - Open() error + Open(readOnly bool) error Close() error } @@ -125,8 +125,8 @@ func (c *cache) DumpInfo() Info { } // Open opens and initializes database. Reads object counters from the ObjectCounters instance. -func (c *cache) Open() error { - err := c.openStore() +func (c *cache) Open(readOnly bool) error { + err := c.openStore(readOnly) if err != nil { return err }