package shard import ( "context" "math" "path/filepath" "testing" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/testutil" writecacheconfig "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/config" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache/writecachebbolt" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "github.com/nspcc-dev/neo-go/pkg/util/slice" "github.com/stretchr/testify/require" ) func TestShard_GetRange(t *testing.T) { t.Parallel() t.Run("without write cache", func(t *testing.T) { t.Parallel() testShardGetRange(t, false) }) t.Run("with write cache", func(t *testing.T) { t.Parallel() testShardGetRange(t, true) }) } func testShardGetRange(t *testing.T, hasWriteCache bool) { type testCase struct { hasErr bool name string payloadSize int rng *objectSDK.Range } const ( writeCacheMaxSize = 1024 smallObjectSize = 2048 ) newRange := func(off, ln uint64) *objectSDK.Range { rng := objectSDK.NewRange() rng.SetOffset(off) rng.SetLength(ln) return rng } testCases := []testCase{ {false, "small object, good", 1024, newRange(11, 123)}, {true, "small object, out of range, big len", 1024, newRange(10, 1020)}, {true, "small object, out of range, big offset", 1024, newRange(1025, math.MaxUint64-10)}, {false, "big object, good", 2048, newRange(11, 123)}, {true, "big object, out of range, big len", 2048, newRange(100, 2000)}, {true, "big object, out of range, big offset", 2048, newRange(2048, math.MaxUint64-10)}, } if hasWriteCache { testCases = append(testCases, testCase{false, "object in write-cache, good", 100, newRange(2, 18)}, testCase{true, "object in write-cache, out of range, big len", 100, newRange(4, 99)}, testCase{true, "object in write-cache, out of range, big offset", 100, newRange(101, math.MaxUint64-10)}) } wcOpts := writecacheconfig.Options{ Type: writecacheconfig.TypeBBolt, BBoltOptions: []writecachebbolt.Option{ writecachebbolt.WithMaxObjectSize(writeCacheMaxSize), }, } sh := newCustomShard(t, hasWriteCache, shardOptions{ rootPath: t.TempDir(), wcOpts: wcOpts, bsOpts: []blobstor.Option{ blobstor.WithStorages([]blobstor.SubStorage{ { Storage: blobovniczatree.NewBlobovniczaTree( blobovniczatree.WithLogger(test.NewLogger(t, true)), blobovniczatree.WithRootPath(filepath.Join(t.TempDir(), "blob", "blobovnicza")), blobovniczatree.WithBlobovniczaShallowDepth(1), blobovniczatree.WithBlobovniczaShallowWidth(1)), Policy: func(_ *objectSDK.Object, data []byte) bool { return len(data) <= smallObjectSize }, }, { Storage: fstree.New( fstree.WithPath(filepath.Join(t.TempDir(), "blob"))), }, }), }, }) defer releaseShard(sh, t) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { obj := testutil.GenerateObject() testutil.AddAttribute(obj, "foo", "bar") testutil.AddPayload(obj, tc.payloadSize) addr := object.AddressOf(obj) payload := slice.Copy(obj.Payload()) var putPrm PutPrm putPrm.SetObject(obj) _, err := sh.Put(context.Background(), putPrm) require.NoError(t, err) var rngPrm RngPrm rngPrm.SetAddress(addr) rngPrm.SetRange(tc.rng.GetOffset(), tc.rng.GetLength()) res, err := sh.GetRange(context.Background(), rngPrm) if tc.hasErr { var target *apistatus.ObjectOutOfRange require.ErrorAs(t, err, &target) } else { require.Equal(t, payload[tc.rng.GetOffset():tc.rng.GetOffset()+tc.rng.GetLength()], res.Object().Payload()) } }) } }