diff --git a/pkg/local_object_storage/blobstor/blobovniczatree/delete.go b/pkg/local_object_storage/blobstor/blobovniczatree/delete.go index 68928ae38..fe904de2d 100644 --- a/pkg/local_object_storage/blobstor/blobovniczatree/delete.go +++ b/pkg/local_object_storage/blobstor/blobovniczatree/delete.go @@ -14,6 +14,10 @@ import ( // If blobocvnicza ID is specified, only this blobovnicza is processed. // Otherwise, all Blobovniczas are processed descending weight. func (b *Blobovniczas) Delete(prm common.DeletePrm) (res common.DeleteRes, err error) { + if b.readOnly { + return common.DeleteRes{}, common.ErrReadOnly + } + var bPrm blobovnicza.DeletePrm bPrm.SetAddress(prm.Address) diff --git a/pkg/local_object_storage/blobstor/blobovniczatree/generic_test.go b/pkg/local_object_storage/blobstor/blobovniczatree/generic_test.go index afa568ee7..b62c85774 100644 --- a/pkg/local_object_storage/blobstor/blobovniczatree/generic_test.go +++ b/pkg/local_object_storage/blobstor/blobovniczatree/generic_test.go @@ -30,3 +30,23 @@ func TestGeneric(t *testing.T) { blobstortest.TestAll(t, newTree, 1024, maxObjectSize) } + +func TestControl(t *testing.T) { + const maxObjectSize = 2048 + + defer func() { _ = os.RemoveAll(t.Name()) }() + + var n int + newTree := func(t *testing.T) common.Storage { + dir := filepath.Join(t.Name(), strconv.Itoa(n)) + return NewBlobovniczaTree( + WithLogger(zaptest.NewLogger(t)), + WithObjectSizeLimit(maxObjectSize), + WithBlobovniczaShallowWidth(2), + WithBlobovniczaShallowDepth(2), + WithRootPath(dir), + WithBlobovniczaSize(1<<20)) + } + + blobstortest.TestControl(t, newTree, 1024, maxObjectSize) +} diff --git a/pkg/local_object_storage/blobstor/blobovniczatree/put.go b/pkg/local_object_storage/blobstor/blobovniczatree/put.go index 681f80cd4..37f6760cb 100644 --- a/pkg/local_object_storage/blobstor/blobovniczatree/put.go +++ b/pkg/local_object_storage/blobstor/blobovniczatree/put.go @@ -13,6 +13,10 @@ import ( // // returns error if could not save object in any blobovnicza. func (b *Blobovniczas) Put(prm common.PutPrm) (common.PutRes, error) { + if b.readOnly { + return common.PutRes{}, common.ErrReadOnly + } + if !prm.DontCompress { prm.RawData = b.compression.Compress(prm.RawData) } diff --git a/pkg/local_object_storage/blobstor/common/errors.go b/pkg/local_object_storage/blobstor/common/errors.go new file mode 100644 index 000000000..205c98aac --- /dev/null +++ b/pkg/local_object_storage/blobstor/common/errors.go @@ -0,0 +1,7 @@ +package common + +import "errors" + +// ErrReadOnly MUST be returned for modifying operations when the storage was opened +// in readonly mode. +var ErrReadOnly = errors.New("opened as read-only") diff --git a/pkg/local_object_storage/blobstor/fstree/control.go b/pkg/local_object_storage/blobstor/fstree/control.go index 93d0d08e4..2c90d9b68 100644 --- a/pkg/local_object_storage/blobstor/fstree/control.go +++ b/pkg/local_object_storage/blobstor/fstree/control.go @@ -5,7 +5,10 @@ import ( ) // Open implements common.Storage. -func (*FSTree) Open(bool) error { return nil } +func (t *FSTree) Open(ro bool) error { + t.readOnly = ro + return nil +} // Init implements common.Storage. func (t *FSTree) Init() error { diff --git a/pkg/local_object_storage/blobstor/fstree/fstree.go b/pkg/local_object_storage/blobstor/fstree/fstree.go index 6f9626937..06b4412b9 100644 --- a/pkg/local_object_storage/blobstor/fstree/fstree.go +++ b/pkg/local_object_storage/blobstor/fstree/fstree.go @@ -25,6 +25,8 @@ type FSTree struct { *compression.Config Depth int DirNameLen int + + readOnly bool } // Info groups the information about file storage. @@ -182,6 +184,10 @@ func (t *FSTree) treePath(addr oid.Address) string { // Delete removes the object with the specified address from the storage. func (t *FSTree) Delete(prm common.DeletePrm) (common.DeleteRes, error) { + if t.readOnly { + return common.DeleteRes{}, common.ErrReadOnly + } + p, err := t.getPath(prm.Address) if err != nil { if os.IsNotExist(err) { @@ -218,6 +224,10 @@ func (t *FSTree) getPath(addr oid.Address) (string, error) { // Put puts an object in the storage. func (t *FSTree) Put(prm common.PutPrm) (common.PutRes, error) { + if t.readOnly { + return common.PutRes{}, common.ErrReadOnly + } + p := t.treePath(prm.Address) if err := util.MkdirAllX(filepath.Dir(p), t.Permissions); err != nil { @@ -231,6 +241,10 @@ func (t *FSTree) Put(prm common.PutPrm) (common.PutRes, error) { // PutStream puts executes handler on a file opened for write. func (t *FSTree) PutStream(addr oid.Address, handler func(*os.File) error) error { + if t.readOnly { + return common.ErrReadOnly + } + p := t.treePath(addr) if err := util.MkdirAllX(filepath.Dir(p), t.Permissions); err != nil { diff --git a/pkg/local_object_storage/blobstor/fstree/generic_test.go b/pkg/local_object_storage/blobstor/fstree/generic_test.go index adbe328fb..173611cc4 100644 --- a/pkg/local_object_storage/blobstor/fstree/generic_test.go +++ b/pkg/local_object_storage/blobstor/fstree/generic_test.go @@ -24,3 +24,18 @@ func TestGeneric(t *testing.T) { blobstortest.TestAll(t, newTree, 2048, 16*1024) } + +func TestControl(t *testing.T) { + defer func() { _ = os.RemoveAll(t.Name()) }() + + var n int + newTree := func(t *testing.T) common.Storage { + dir := filepath.Join(t.Name(), strconv.Itoa(n)) + return New( + WithPath(dir), + WithDepth(2), + WithDirNameLen(2)) + } + + blobstortest.TestControl(t, newTree, 2048, 2048) +} diff --git a/pkg/local_object_storage/blobstor/internal/blobstortest/control.go b/pkg/local_object_storage/blobstor/internal/blobstortest/control.go new file mode 100644 index 000000000..2ab5d5bd7 --- /dev/null +++ b/pkg/local_object_storage/blobstor/internal/blobstortest/control.go @@ -0,0 +1,49 @@ +package blobstortest + +import ( + "math/rand" + "testing" + + objectCore "github.com/nspcc-dev/neofs-node/pkg/core/object" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common" + "github.com/stretchr/testify/require" +) + +// TestControl checks correctness of a read-only mode. +// cons must return a storage which is NOT opened. +func TestControl(t *testing.T, cons Constructor, min, max uint64) { + s := cons(t) + require.NoError(t, s.Open(false)) + require.NoError(t, s.Init()) + + objects := prepare(t, 10, s, min, max) + require.NoError(t, s.Close()) + + require.NoError(t, s.Open(true)) + for i := range objects { + var prm common.GetPrm + prm.Address = objects[i].addr + prm.StorageID = objects[i].storageID + prm.Raw = true + + _, err := s.Get(prm) + require.NoError(t, err) + } + + t.Run("put fails", func(t *testing.T) { + var prm common.PutPrm + prm.Object = NewObject(min + uint64(rand.Intn(int(max-min+1)))) + prm.Address = objectCore.AddressOf(prm.Object) + + _, err := s.Put(prm) + require.ErrorIs(t, err, common.ErrReadOnly) + }) + t.Run("delete fails", func(t *testing.T) { + var prm common.DeletePrm + prm.Address = objects[0].addr + prm.StorageID = objects[0].storageID + + _, err := s.Delete(prm) + require.ErrorIs(t, err, common.ErrReadOnly) + }) +}