cubefs/sdk/data/blobstore/reader_test.go
chihe 34fb8cdeb2 fix(client):use sync flush if open file as directIO
close:#1000464581

Signed-off-by: chihe <chihe@oppo.com>
2025-11-26 09:59:58 +08:00

484 lines
15 KiB
Go

// Copyright 2022 The CubeFS Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package blobstore
import (
"context"
"fmt"
"io"
"math/rand"
"os"
"strings"
"syscall"
"testing"
"github.com/brahma-adshonor/gohook"
"github.com/stretchr/testify/assert"
"github.com/cubefs/cubefs/client/blockcache/bcache"
"github.com/cubefs/cubefs/proto"
"github.com/cubefs/cubefs/sdk/data/manager"
"github.com/cubefs/cubefs/sdk/data/stream"
"github.com/cubefs/cubefs/sdk/meta"
"github.com/cubefs/cubefs/util/errors"
)
func TestNewReader(t *testing.T) {
mockConfig := ClientConfig{
VolName: "cfs",
VolType: 0,
BlockSize: 0,
Ino: 2,
Bc: nil,
Mw: nil,
Ec: nil,
Ebsc: nil,
EnableBcache: false,
WConcurrency: 0,
ReadConcurrency: 0,
FileCache: false,
FileSize: 0,
}
ec := &stream.ExtentClient{}
err := gohook.HookMethod(ec, "Write", MockWriteTrue, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
mockConfig.Ec = ec
reader := NewReader(mockConfig)
assert.NotEmpty(t, reader, nil)
}
func TestFileSize(t *testing.T) {
testCase := []struct {
valid bool
objEks []proto.ObjExtentKey
expectSize uint64
expectOk bool
}{
{false, nil, 0, false},
{true, nil, 0, true},
{true, []proto.ObjExtentKey{{Size: uint64(100), FileOffset: uint64(100)}}, 200, true},
}
for _, tc := range testCase {
reader := Reader{}
reader.limitManager = manager.NewLimitManager(nil)
reader.valid = tc.valid
reader.objExtentKeys = tc.objEks
gotSize, gotOk := reader.fileSize()
assert.Equal(t, tc.expectSize, gotSize)
assert.Equal(t, tc.expectOk, gotOk)
}
//// mock objExtentKey
//objEks := make([]proto.ObjExtentKey, 0)
//objEkLen := rand.Intn(20)
//expectedFileSize := 0
//for i := 0; i < objEkLen; i++ {
// size := rand.Intn(1000)
// objEks = append(objEks, proto.ObjExtentKey{Size: uint64(size), FileOffset: uint64(expectedFileSize)})
// expectedFileSize += size
//}
//
//// mock reader
//mockConfig := ClientConfig{
// VolName: "cfs",
// VolType: 0,
// BlockSize: 0,
// Ino: 2,
// Bc: nil,
// Mw: nil,
// Ec: nil,
// Bsc: nil,
// EnableBcache: false,
// WConcurrency: 0,
// ReadConcurrency: 0,
// CacheAction: 0,
// FileCache: false,
// FileSize: 0,
// CacheThreshold: 0,
//}
//reader := NewReader(mockConfig)
//reader.valid = true
//reader.objExtentKeys = objEks
//
//got, ok := reader.fileSize()
//assert.True(t, true, ok)
//assert.Equal(t, expectedFileSize, int(got))
//
//ctx := context.Background()
//reader.Close(ctx)
}
func TestRefreshEbsExtents(t *testing.T) {
testCase := []struct {
getObjFunc func(*meta.MetaWrapper, uint64) (uint64, uint64, []proto.ExtentKey, []proto.ObjExtentKey, error)
expectValid bool
}{
{MockGetObjExtentsTrue, true},
{MockGetObjExtentsFalse, false},
}
for _, tc := range testCase {
reader := Reader{}
reader.limitManager = manager.NewLimitManager(nil)
mw := &meta.MetaWrapper{}
err := gohook.HookMethod(mw, "GetObjExtents", tc.getObjFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
reader.mw = mw
reader.refreshEbsExtents()
assert.Equal(t, reader.valid, tc.expectValid)
}
}
func TestPrepareEbsSlice(t *testing.T) {
testCase := []struct {
getObjFunc func(*meta.MetaWrapper, uint64) (uint64, uint64, []proto.ExtentKey, []proto.ObjExtentKey, error)
offset int
size uint32
expectError error
}{
{nil, -1, 100, syscall.EIO},
{MockGetObjExtentsTrue, 0, 100, nil},
{MockGetObjExtentsTrue, 501, 100, io.EOF},
{MockGetObjExtentsTrue, 400, 101, nil},
{MockGetObjExtentsFalse, 0, 100, syscall.EIO},
{MockGetObjExtentsFalse, 501, 100, syscall.EIO},
{MockGetObjExtentsFalse, 400, 101, syscall.EIO},
}
for _, tc := range testCase {
mw := &meta.MetaWrapper{}
err := gohook.HookMethod(mw, "GetObjExtents", tc.getObjFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
reader := Reader{}
reader.limitManager = manager.NewLimitManager(nil)
reader.mw = mw
_, got := reader.prepareEbsSlice(tc.offset, tc.size)
assert.Equal(t, tc.expectError, got)
}
}
func TestRead(t *testing.T) {
testCase := []struct {
close bool
readConcurrency int
getObjFunc func(*meta.MetaWrapper, uint64) (uint64, uint64, []proto.ExtentKey, []proto.ObjExtentKey, error)
bcacheGetFunc func(*bcache.BcacheClient, string, string, []byte, uint64, uint32) (int, error)
checkDpExistFunc func(*stream.ExtentClient, uint64) error
readExtentFunc func(*stream.ExtentClient, uint64, *proto.ExtentKey, []byte, int, int, uint32) (int, error, bool)
ebsReadFunc func(*BlobStoreClient, context.Context, string, []byte, uint64, uint64, proto.ObjExtentKey) (int, error)
expectError error
}{
{true, 2, MockGetObjExtentsTrue, MockGetTrue, MockCheckDataPartitionExistTrue, MockReadExtentTrue, MockEbscReadTrue, os.ErrInvalid},
{false, 2, MockGetObjExtentsFalse, MockGetTrue, MockCheckDataPartitionExistTrue, MockReadExtentTrue, MockEbscReadTrue, syscall.EIO},
{false, 2, MockGetObjExtentsTrue, MockGetTrue, MockCheckDataPartitionExistTrue, MockReadExtentTrue, MockEbscReadFalse, syscall.EIO},
{false, 2, MockGetObjExtentsTrue, MockGetTrue, MockCheckDataPartitionExistTrue, MockReadExtentTrue, MockEbscReadTrue, nil},
}
for _, tc := range testCase {
reader := &Reader{}
reader.limitManager = manager.NewLimitManager(nil)
reader.close = tc.close
reader.readConcurrency = tc.readConcurrency
mw := &meta.MetaWrapper{}
ebsc := &BlobStoreClient{}
bc := &bcache.BcacheClient{}
ec := &stream.ExtentClient{}
err := gohook.HookMethod(mw, "GetObjExtents", tc.getObjFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(ec, "CheckDataPartitionExsit", tc.checkDpExistFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(ec, "ReadExtent", tc.readExtentFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(ebsc, "Read", tc.ebsReadFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(bc, "Get", tc.bcacheGetFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
reader.mw = mw
reader.ebs = ebsc
reader.bc = bc
ctx := context.Background()
buf := make([]byte, 500)
_, gotError := reader.Read(ctx, buf, 0, 100)
assert.Equal(t, tc.expectError, gotError)
}
}
func TestAsyncCache(t *testing.T) {
ebsc := &BlobStoreClient{}
ec := &stream.ExtentClient{}
bc := &bcache.BcacheClient{}
err := gohook.HookMethod(ec, "Write", MockWriteTrue, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(bc, "Put", MockPutTrue, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
testCase := []struct {
ebsReadFunc func(*BlobStoreClient, context.Context, string, []byte, uint64, uint64, proto.ObjExtentKey) (int, error)
cacheAction int
enableBcache bool
fileSize uint64
}{
{MockEbscReadTrue, proto.NoCache, true, rand.Uint64() % 1000},
{MockEbscReadTrue, proto.RCache, true, rand.Uint64() % 1000},
{MockEbscReadTrue, proto.RWCache, true, rand.Uint64() % 1000},
{MockEbscReadTrue, proto.NoCache, false, rand.Uint64() % 1000},
{MockEbscReadTrue, proto.RCache, false, rand.Uint64() % 1000},
{MockEbscReadTrue, proto.RWCache, false, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.NoCache, true, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.RCache, true, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.RWCache, true, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.NoCache, false, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.RCache, false, rand.Uint64() % 1000},
{MockEbscReadFalse, proto.RWCache, false, rand.Uint64() % 1000},
}
size := rand.Intn(1000)
objEk := proto.ObjExtentKey{
Cid: 0,
CodeMode: 0,
BlobSize: 0,
BlobsLen: 0,
Size: uint64(size),
Blobs: nil,
FileOffset: 0,
Crc: 0,
}
for _, tc := range testCase {
reader := Reader{}
reader.limitManager = manager.NewLimitManager(nil)
ctx := context.Background()
err := gohook.HookMethod(ebsc, "Read", tc.ebsReadFunc, nil)
if err != nil {
t.Fatalf("Hook advance instance method failed:%s", err.Error())
}
reader.fileLength = tc.fileSize
reader.asyncCache(ctx, "cacheKey", objEk)
}
}
func TestNeedCacheL1(t *testing.T) {
testCase := []struct {
enableCache bool
expectCache bool
}{
{true, true},
{false, false},
}
for _, tc := range testCase {
reader := Reader{}
reader.limitManager = manager.NewLimitManager(nil)
reader.enableBcache = tc.enableCache
got := reader.needCacheL1()
assert.Equal(t, tc.expectCache, got)
}
}
func TestReadSliceRange(t *testing.T) {
testCase := []struct {
enableBcache bool
extentKey proto.ExtentKey
bcacheGetFunc func(*bcache.BcacheClient, string, string, []byte, uint64, uint32) (int, error)
checkDpExistFunc func(*stream.ExtentClient, uint64) error
readExtentFunc func(*stream.ExtentClient, uint64, *proto.ExtentKey, []byte, int, int, uint32) (int, error, bool)
ebsReadFunc func(*BlobStoreClient, context.Context, string, []byte, uint64, uint64, proto.ObjExtentKey) (int, error)
expectError error
}{
{
false,
proto.ExtentKey{},
MockGetTrue, MockCheckDataPartitionExistTrue,
MockReadExtentTrue, MockEbscReadTrue, nil,
},
{
false,
proto.ExtentKey{},
MockGetTrue, MockCheckDataPartitionExistTrue,
MockReadExtentTrue, MockEbscReadFalse, syscall.EIO,
},
{
true,
proto.ExtentKey{},
MockGetTrue, MockCheckDataPartitionExistTrue,
MockReadExtentTrue, MockEbscReadFalse, nil,
},
{
true,
proto.ExtentKey{},
MockGetFalse, MockCheckDataPartitionExistTrue,
MockReadExtentTrue, MockEbscReadFalse, syscall.EIO,
},
{
true,
proto.ExtentKey{},
MockGetFalse, MockCheckDataPartitionExistTrue,
MockReadExtentTrue, MockEbscReadTrue, nil,
},
}
for _, tc := range testCase {
reader := &Reader{}
reader.limitManager = manager.NewLimitManager(nil)
ebsc := &BlobStoreClient{}
bc := &bcache.BcacheClient{}
ec := &stream.ExtentClient{}
reader.volName = "cfs"
reader.ino = 12407
reader.fileLength = 10
reader.ec = ec
reader.err = make(chan error)
rs := &rwSlice{}
rs.rSize = uint32(len("Hello world"))
rs.Data = make([]byte, len("Hello world"))
reader.enableBcache = tc.enableBcache
err := gohook.HookMethod(ebsc, "Read", tc.ebsReadFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(bc, "Get", tc.bcacheGetFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(ec, "CheckDataPartitionExsit", tc.checkDpExistFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
err = gohook.HookMethod(ec, "ReadExtent", tc.readExtentFunc, nil)
if err != nil {
panic(fmt.Sprintf("Hook advance instance method failed:%s", err.Error()))
}
reader.ebs = ebsc
reader.bc = bc
ctx := context.Background()
reader.wg.Add(1)
go func() {
<-reader.err
}()
gotError := reader.readSliceRange(ctx, rs)
assert.Equal(t, tc.expectError, gotError)
}
}
func MockGetObjExtentsTrue(m *meta.MetaWrapper, inode uint64) (gen uint64, size uint64,
extents []proto.ExtentKey, objExtents []proto.ObjExtentKey, err error,
) {
objEks := make([]proto.ObjExtentKey, 0)
objEkLen := 5
expectedFileSize := 0
for i := 0; i < objEkLen; i++ {
size := 100
objEks = append(objEks, proto.ObjExtentKey{Size: uint64(100), FileOffset: uint64(expectedFileSize)})
expectedFileSize += size
}
return 1, 1, nil, objEks, nil
}
func MockGetObjExtentsFalse(m *meta.MetaWrapper, inode uint64) (gen uint64, size uint64,
extents []proto.ExtentKey, objExtents []proto.ObjExtentKey, err error,
) {
return 1, 1, nil, nil, errors.New("Get objEks failed")
}
func MockEbscReadTrue(ebsc *BlobStoreClient, ctx context.Context, volName string,
buf []byte, offset uint64, size uint64,
oek proto.ObjExtentKey,
) (readN int, err error) {
reader := strings.NewReader("Hello world.")
readN, _ = io.ReadFull(reader, buf)
return readN, nil
}
func MockEbscReadFalse(ebsc *BlobStoreClient, ctx context.Context, volName string,
buf []byte, offset uint64, size uint64,
oek proto.ObjExtentKey,
) (readN int, err error) {
return 0, syscall.EIO
}
func MockReadExtentTrue(client *stream.ExtentClient, inode uint64, ek *proto.ExtentKey,
data []byte, offset int, size int, storageClass uint32,
) (read int, err error, b bool) {
return len("Hello world"), nil, true
}
func MockReadExtentFalse(client *stream.ExtentClient, inode uint64, ek *proto.ExtentKey,
data []byte, offset int, size int, storageClass uint32,
) (read int, err error) {
return 0, errors.New("Read extent failed")
}
func MockCheckDataPartitionExistTrue(client *stream.ExtentClient, partitionID uint64) error {
return nil
}
func MockCheckDataPartitionExistFalse(client *stream.ExtentClient, partitionID uint64) error {
return errors.New("CheckDataPartitionExist failed")
}
func MockWriteTrue(client *stream.ExtentClient, inode uint64, offset int, data []byte,
flags int, checkFunc func() error, storageClass uint32, isMigration, waitForFlush bool,
) (write int, err error) {
return len(data), nil
}
func MockWriteFalse(client *stream.ExtentClient, inode uint64, offset int, data []byte,
flags int,
) (write int, err error) {
return 0, errors.New("Write failed")
}
func MockPutTrue(bc *bcache.BcacheClient, vol, key string, buf []byte) error {
return nil
}
func MockPutFalse(bc *bcache.BcacheClient, key string, buf []byte) error {
return errors.New("Bcache put failed")
}
func MockGetTrue(bc *bcache.BcacheClient, vol, key string, buf []byte, offset uint64, size uint32) (int, error) {
return int(size), nil
}
func MockGetFalse(bc *bcache.BcacheClient, vol, key string, buf []byte, offset uint64, size uint32) (int, error) {
return 0, errors.New("Bcache get failed")
}