1
// Copyright 2023 The CubeFS Authors.
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
// implied. See the License for the specific language governing
13
// permissions and limitations under the License.
24
"github.com/cubefs/cubefs/blobstore/blobnode/sys"
25
"github.com/cubefs/cubefs/storage"
26
"github.com/cubefs/cubefs/util"
27
"github.com/stretchr/testify/require"
32
testNormalExtentID = 65
34
dataStr = "hello world"
35
dataSize = int64(len(dataStr))
38
func getTestPathExtentName(id uint64) (string, func(), error) {
39
dir, err := os.MkdirTemp(os.TempDir(), "cfs_storage_extent_")
43
return fmt.Sprintf("%s/%d", dir, id), func() { os.RemoveAll(dir) }, nil
46
func mockCrcPersist(t *testing.T, e *storage.Extent, blockNo int, blockCrc uint32) (err error) {
47
t.Logf("persist crc extent blockNo: %v blockCrc:%v", blockNo, blockCrc)
51
func getMockCrcPersist(t *testing.T) storage.UpdateCrcFunc {
52
return func(e *storage.Extent, blockNo int, crc uint32) (err error) {
53
return mockCrcPersist(t, e, blockNo, crc)
57
func normalExtentRwTest(t *testing.T, e *storage.Extent) {
58
data := []byte(dataStr)
59
_, err := e.Write(data, 0, 0, 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
62
_, err = e.Write(data, 0, int64(len(data)), 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
63
require.NoError(t, err)
64
require.EqualValues(t, e.Size(), len(data))
65
_, err = e.Read(data, 0, int64(len(data)), false)
66
require.NoError(t, err)
67
require.Equal(t, string(data), dataStr)
68
// failed append write
69
_, err = e.Write(data, 0, int64(len(data)), 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
71
// random append write
73
_, err = e.Write(data, 0, int64(len(data)), 0, storage.RandomWriteType, true, getMockCrcPersist(t), nil)
74
require.NoError(t, err)
75
require.Equal(t, e.Size(), oldSize)
76
_, err = e.Read(data, 0, int64(len(data)), false)
77
require.NoError(t, err)
78
require.Equal(t, string(data), dataStr)
79
_, err = e.Write(data, util.BlockSize, dataSize, 0, storage.RandomWriteType, true, getMockCrcPersist(t), nil)
80
require.NoError(t, err)
81
_, err = e.Write(data, util.ExtentSize, dataSize, 0, storage.RandomWriteType, true, getMockCrcPersist(t), nil)
82
require.NoError(t, err)
83
// TODO: append random write test
86
func tinyExtentRwTest(t *testing.T, e *storage.Extent) {
87
data := []byte(dataStr)
89
_, err := e.Write(data, storage.ExtentMaxSize, dataSize, 0, storage.RandomWriteType, true, getMockCrcPersist(t), nil)
90
require.ErrorIs(t, err, storage.ExtentIsFullError)
92
_, err = e.Write(data, 0, int64(len(data)), 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
93
require.NoError(t, err)
94
require.EqualValues(t, e.Size()%util.PageSize, 0)
95
_, err = e.Read(data, 0, int64(len(data)), false)
96
require.NoError(t, err)
97
require.Equal(t, string(data), dataStr)
98
// failed append write
99
_, err = e.Write(data, 0, int64(len(data)), 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
100
require.Error(t, err)
103
_, err = e.Write(data, int64(len(data)), int64(len(data)), 0, storage.RandomWriteType, true, getMockCrcPersist(t), nil)
104
require.NoError(t, err)
105
require.Equal(t, e.Size(), oldSize)
106
_, err = e.Read(data, int64(len(data)), int64(len(data)), false)
107
require.NoError(t, err)
108
require.Equal(t, string(data), dataStr)
111
func normalExtentCreateTest(t *testing.T, name string) {
112
e := storage.NewExtentInCore(name, testNormalExtentID)
113
t.Log("normal-extent:", e)
114
require.False(t, e.Exist())
116
require.NoError(t, err)
118
normalExtentRwTest(t, e)
121
func normalExtentRecoveryTest(t *testing.T, name string) {
122
e := storage.NewExtentInCore(name, testNormalExtentID)
123
require.Equal(t, e.Exist(), true)
124
t.Log("normal-extent:", e.String())
125
err := e.RestoreFromFS()
126
require.NoError(t, err)
128
for _, offset := range []int64{0, util.BlockSize, util.ExtentSize} {
129
data := make([]byte, dataSize)
130
_, err = e.Read(data, offset, dataSize, false)
131
require.NoError(t, err)
132
require.Equal(t, string(data), dataStr)
136
func tinyExtentCreateTest(t *testing.T, name string) {
137
e := storage.NewExtentInCore(name, testTinyExtentID)
138
t.Log("tiny-extent:", e)
139
require.False(t, e.Exist())
140
require.ErrorIs(t, e.RestoreFromFS(), storage.ExtentNotFoundError)
141
require.NoError(t, e.InitToFS())
143
tinyExtentRwTest(t, e)
146
func tinyExtentRecoveryTest(t *testing.T, name string) {
147
e := storage.NewExtentInCore(name, testTinyExtentID)
148
require.Equal(t, e.Exist(), true)
149
err := e.RestoreFromFS()
150
require.NoError(t, err)
152
data := make([]byte, dataSize)
153
_, err = e.ReadTiny(data, 0, int64(len(data)), false)
154
require.NoError(t, err)
155
require.Equal(t, string(data), dataStr)
156
_, err = e.Read(data, int64(len(data)), int64(len(data)), false)
157
require.NoError(t, err)
158
require.Equal(t, string(data), dataStr)
161
func tinyExtentRepairTest(t *testing.T, name string) {
162
e := storage.NewExtentInCore(name, testTinyExtentID)
163
require.Equal(t, e.Exist(), true)
164
err := e.RestoreFromFS()
165
require.NoError(t, err)
167
data := []byte(dataStr)
169
err = e.TinyExtentRecover(nil, size, int64(len(data)), 0, true)
170
require.NoError(t, err)
171
t.Logf("extent data size is %v", e.Size())
172
_, err = e.Read(data, size, int64(len(data)), true)
173
require.NoError(t, err)
174
for _, v := range data {
175
require.EqualValues(t, v, 0)
178
data = []byte(dataStr)
179
err = e.TinyExtentRecover(data, size, int64(len(data)), 0, false)
180
require.NoError(t, err)
181
_, err = e.Read(data, size, int64(len(data)), false)
182
require.NoError(t, err)
183
require.Equal(t, string(data), dataStr)
186
func TestTinyExtent(t *testing.T) {
187
name, clean, err := getTestPathExtentName(testTinyExtentID)
188
require.NoError(t, err)
190
tinyExtentCreateTest(t, name)
191
tinyExtentRecoveryTest(t, name)
192
tinyExtentRepairTest(t, name)
195
func TestNormalExtent(t *testing.T) {
196
name, clean, err := getTestPathExtentName(testNormalExtentID)
197
require.NoError(t, err)
199
normalExtentCreateTest(t, name)
200
normalExtentRecoveryTest(t, name)
203
func TestSeekHole(t *testing.T) {
206
filePath = "./filename"
211
defer os.Remove(filePath)
212
e := storage.NewExtentInCore(filePath, 0)
214
require.NoError(t, err)
217
info, err = file.Stat()
218
require.NoError(t, err)
220
size = e.GetDataSize(info.Size())
221
t.Logf("data size %v, file stat size %v", size, info.Size())
222
blockSize := info.Sys().(*syscall.Stat_t).Blksize
223
t.Logf("blockSize %v", blockSize)
224
headSize := 10 * 1024 * 1024
225
file.Truncate(util.ExtentSize) // this necessary or else hole position not stable
228
data := bytes.Repeat([]byte("s"), headSize)
231
size_, err = file.Write(data)
232
require.NoError(t, err)
233
info, err = file.Stat()
234
require.NoError(t, err)
235
t.Logf("err %v,size %v, file stat size %v", err, size_, info.Size())
237
// puch hole at the begin
238
err = sys.Fallocate(file.Fd(), util.FallocFLPunchHole|util.FallocFLKeepSize, 4*1024, blockSize)
239
require.NoError(t, err)
241
midDataOffset := int64(util.ExtentSize / 4)
242
_, err = file.WriteAt(data[:1024], midDataOffset)
243
require.NoError(t, err)
246
midDataOffset = int64(util.ExtentSize / 2)
247
size_, err = file.WriteAt(data[:1024], midDataOffset)
248
require.NoError(t, err)
249
info, err = file.Stat()
250
t.Logf("err %v,size %v, file stat size %v", err, size_, info.Size())
252
// calc last hole in 128M
253
lastHole := midDataOffset + 1024
254
alignlastHole := lastHole + (blockSize - lastHole%blockSize)
255
t.Logf("write at %v size %v last hole off %v ,aligned off %v", midDataOffset, size_, lastHole, alignlastHole)
258
_, err = file.WriteAt(data[:1024], int64(util.ExtentSize))
259
require.NoError(t, err)
261
// seek last hole in 128M
262
info, err = file.Stat()
263
size = e.GetDataSize(info.Size())
264
t.Logf("datasize %v alignLastOff %v lastHoleOfData %v size %v", size, alignlastHole, lastHole, info.Size())
265
require.NoError(t, err)
269
err = e.RestoreFromFS()
270
require.NoError(t, err)
272
dataSize, snapSize := e.GetSize()
273
t.Logf("dataSize %v, snapSize %v", dataSize, snapSize)
274
require.True(t, dataSize == alignlastHole)
277
func TestExtentRecovery(t *testing.T) {
280
defer os.Remove(filePath)
281
e := storage.NewExtentInCore(filePath, 1025)
283
require.NoError(t, err)
285
headSize := 128 * 1024
287
data := bytes.Repeat([]byte("s"), headSize)
288
for i := 0; i < 10; i++ {
289
_, err := e.Write(data, int64(i)*util.BlockSize, int64(headSize), 0, storage.AppendWriteType, true, getMockCrcPersist(t), nil)
290
require.NoError(t, err)
292
for i := 0; i < 10; i++ {
293
_, err := e.Write(data, int64(i)*util.BlockSize+util.ExtentSize, int64(headSize), 0, storage.AppendRandomWriteType, true, getMockCrcPersist(t), nil)
294
require.NoError(t, err)
297
err = e.RestoreFromFS()
298
require.NoError(t, err)
299
dataSize, snapSize := e.GetSize()
300
t.Logf("dataSize %v, snapSize %v", dataSize, snapSize)
301
require.True(t, util.BlockSize*10 == dataSize)