github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/index_read_write_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package fs 22 23 import ( 24 "bufio" 25 "bytes" 26 "crypto/rand" 27 "encoding/json" 28 "io" 29 "io/ioutil" 30 "os" 31 "path/filepath" 32 "sync" 33 "testing" 34 "time" 35 36 "github.com/m3db/m3/src/dbnode/persist" 37 idxpersist "github.com/m3db/m3/src/m3ninx/persist" 38 "github.com/m3db/m3/src/x/ident" 39 xtime "github.com/m3db/m3/src/x/time" 40 41 "github.com/golang/mock/gomock" 42 "github.com/stretchr/testify/assert" 43 "github.com/stretchr/testify/require" 44 ) 45 46 func shardsSet(shards ...uint32) map[uint32]struct{} { 47 r := make(map[uint32]struct{}) 48 for _, shard := range shards { 49 r[shard] = struct{}{} 50 } 51 return r 52 } 53 54 type indexWriteTestSetup struct { 55 now xtime.UnixNano 56 rootDir string 57 filePathPrefix string 58 blockSize time.Duration 59 blockStart xtime.UnixNano 60 fileSetID FileSetFileIdentifier 61 } 62 63 func newIndexWriteTestSetup(t *testing.T) indexWriteTestSetup { 64 now := xtime.ToUnixNano(time.Now().UTC()) 65 dir := createTempDir(t) 66 filePathPrefix := filepath.Join(dir, "") 67 blockSize := 12 * time.Hour 68 blockStart := now.Truncate(blockSize) 69 fileSetID := FileSetFileIdentifier{ 70 FileSetContentType: persist.FileSetIndexContentType, 71 Namespace: ident.StringID("metrics"), 72 BlockStart: blockStart, 73 } 74 return indexWriteTestSetup{ 75 now: now, 76 rootDir: dir, 77 filePathPrefix: filePathPrefix, 78 blockSize: blockSize, 79 blockStart: blockStart, 80 fileSetID: fileSetID, 81 } 82 } 83 84 func (s indexWriteTestSetup) cleanup() { 85 os.RemoveAll(s.rootDir) 86 } 87 88 type testIndexReadWriteOptions struct { 89 IndexReaderOptions testIndexReaderOptions 90 } 91 92 func TestIndexSimpleReadWrite(t *testing.T) { 93 tests := []struct { 94 TestOptions testIndexReadWriteOptions 95 }{ 96 { 97 TestOptions: testIndexReadWriteOptions{ 98 IndexReaderOptions: testIndexReaderOptions{ 99 AutovalidateIndexSegments: true, 100 }, 101 }, 102 }, 103 { 104 TestOptions: testIndexReadWriteOptions{ 105 IndexReaderOptions: testIndexReaderOptions{ 106 AutovalidateIndexSegments: true, 107 }, 108 }, 109 }, 110 } 111 112 for _, test := range tests { 113 test := test 114 name, err := json.Marshal(test) 115 require.NoError(t, err) 116 t.Run(string(name), func(t *testing.T) { 117 testIndexSimpleReadWrite(t, test.TestOptions) 118 }) 119 } 120 } 121 122 func testIndexSimpleReadWrite(t *testing.T, testOpts testIndexReadWriteOptions) { 123 ctrl := gomock.NewController(t) 124 defer ctrl.Finish() 125 126 test := newIndexWriteTestSetup(t) 127 defer test.cleanup() 128 129 writer := newTestIndexWriter(t, test.filePathPrefix) 130 err := writer.Open(IndexWriterOpenOptions{ 131 Identifier: test.fileSetID, 132 BlockSize: test.blockSize, 133 FileSetType: persist.FileSetFlushType, 134 Shards: shardsSet(1, 3, 5), 135 }) 136 require.NoError(t, err) 137 138 testSegments := []testIndexSegment{ 139 { 140 segmentType: idxpersist.IndexSegmentType("fst"), 141 majorVersion: 1, 142 minorVersion: 2, 143 files: []testIndexSegmentFile{ 144 {idxpersist.IndexSegmentFileType("first"), randDataFactorOfBuffSize(t, 1.5)}, 145 {idxpersist.IndexSegmentFileType("second"), randDataFactorOfBuffSize(t, 2.5)}, 146 }, 147 }, 148 { 149 segmentType: idxpersist.IndexSegmentType("trie"), 150 majorVersion: 3, 151 minorVersion: 4, 152 files: []testIndexSegmentFile{ 153 {idxpersist.IndexSegmentFileType("first"), randDataFactorOfBuffSize(t, 1.5)}, 154 {idxpersist.IndexSegmentFileType("second"), randDataFactorOfBuffSize(t, 2.5)}, 155 {idxpersist.IndexSegmentFileType("third"), randDataFactorOfBuffSize(t, 3)}, 156 }, 157 }, 158 } 159 writeTestIndexSegments(t, ctrl, writer, testSegments) 160 161 err = writer.Close() 162 require.NoError(t, err) 163 164 reader := newTestIndexReader(t, test.filePathPrefix, 165 testOpts.IndexReaderOptions) 166 result, err := reader.Open(IndexReaderOpenOptions{ 167 Identifier: test.fileSetID, 168 FileSetType: persist.FileSetFlushType, 169 }) 170 require.NoError(t, err) 171 require.Equal(t, shardsSet(1, 3, 5), result.Shards) 172 173 readTestIndexSegments(t, ctrl, reader, testSegments) 174 175 err = reader.Validate() 176 require.NoError(t, err) 177 178 err = reader.Close() 179 require.NoError(t, err) 180 } 181 182 func newTestIndexWriter(t *testing.T, filePathPrefix string) IndexFileSetWriter { 183 writer, err := NewIndexWriter(testDefaultOpts. 184 SetFilePathPrefix(filePathPrefix). 185 SetWriterBufferSize(testWriterBufferSize)) 186 require.NoError(t, err) 187 return writer 188 } 189 190 type testIndexReaderOptions struct { 191 AutovalidateIndexSegments bool 192 } 193 194 func newTestIndexReader( 195 t *testing.T, 196 filePathPrefix string, 197 opts testIndexReaderOptions, 198 ) IndexFileSetReader { 199 reader, err := NewIndexReader(testDefaultOpts. 200 SetFilePathPrefix(filePathPrefix). 201 SetIndexReaderAutovalidateIndexSegments(opts.AutovalidateIndexSegments)) 202 require.NoError(t, err) 203 return reader 204 } 205 206 func randDataFactorOfBuffSize(t *testing.T, factor float64) []byte { 207 length := int(factor * float64(defaultBufferedReaderSize())) 208 buffer := bytes.NewBuffer(nil) 209 src := io.LimitReader(rand.Reader, int64(length)) 210 _, err := io.Copy(buffer, src) 211 require.NoError(t, err) 212 return buffer.Bytes() 213 } 214 215 type testIndexSegment struct { 216 segmentType idxpersist.IndexSegmentType 217 majorVersion int 218 minorVersion int 219 metadata []byte 220 files []testIndexSegmentFile 221 } 222 223 type testIndexSegmentFile struct { 224 segmentFileType idxpersist.IndexSegmentFileType 225 data []byte 226 } 227 228 func writeTestIndexSegments( 229 t *testing.T, 230 ctrl *gomock.Controller, 231 writer IndexFileSetWriter, 232 v []testIndexSegment, 233 ) { 234 for _, s := range v { 235 fileSet := NewMockIndexSegmentFileSetWriter(ctrl) 236 fileSet.EXPECT().SegmentType().Return(s.segmentType).AnyTimes() 237 fileSet.EXPECT().MajorVersion().Return(s.majorVersion) 238 fileSet.EXPECT().MinorVersion().Return(s.minorVersion) 239 fileSet.EXPECT().SegmentMetadata().Return(s.metadata) 240 241 var files []idxpersist.IndexSegmentFileType 242 for _, f := range s.files { 243 files = append(files, f.segmentFileType) 244 } 245 fileSet.EXPECT().Files().Return(files).AnyTimes() 246 247 for _, f := range s.files { 248 f := f 249 // Make sure we're actually trying to test writing out file contents 250 require.True(t, len(f.data) > 0) 251 252 fileSet.EXPECT(). 253 WriteFile(f.segmentFileType, gomock.Any()). 254 DoAndReturn(func(_ idxpersist.IndexSegmentFileType, w io.Writer) error { 255 _, err := w.Write(f.data) 256 return err 257 }) 258 } 259 260 err := writer.WriteSegmentFileSet(fileSet) 261 require.NoError(t, err) 262 } 263 } 264 265 func readTestIndexSegments( 266 t *testing.T, 267 ctrl *gomock.Controller, 268 reader IndexFileSetReader, 269 v []testIndexSegment, 270 ) { 271 require.Equal(t, len(v), reader.SegmentFileSets()) 272 273 for _, s := range v { 274 result, err := reader.ReadSegmentFileSet() 275 require.NoError(t, err) 276 277 assert.Equal(t, s.segmentType, result.SegmentType()) 278 assert.Equal(t, s.majorVersion, result.MajorVersion()) 279 assert.Equal(t, s.minorVersion, result.MinorVersion()) 280 assert.Equal(t, s.metadata, result.SegmentMetadata()) 281 282 require.Equal(t, len(s.files), len(result.Files())) 283 284 for i, expected := range s.files { 285 actual := result.Files()[i] 286 287 require.Equal(t, expected.segmentFileType, actual.SegmentFileType()) 288 289 // Assert read data is correct 290 actualData, err := ioutil.ReadAll(actual) 291 require.NoError(t, err) 292 assert.Equal(t, expected.data, actualData) 293 294 // Assert bytes data (should be mmap'd byte slice) is also correct 295 directData, err := actual.Mmap() 296 require.NoError(t, err) 297 assert.True(t, bytes.Equal(expected.data, directData.Bytes)) 298 299 err = actual.Close() 300 require.NoError(t, err) 301 } 302 } 303 304 // Ensure last read is io.EOF 305 _, err := reader.ReadSegmentFileSet() 306 require.Equal(t, io.EOF, err) 307 } 308 309 var ( 310 defaultBufferedReaderLock sync.Mutex 311 defaultBufferedReader *bufio.Reader 312 ) 313 314 func defaultBufferedReaderSize() int { 315 // Pre go1.10 it wasn't possible to query the size of the buffered reader 316 // however in go1.10 it is possible 317 defaultBufferedReaderLock.Lock() 318 defer defaultBufferedReaderLock.Unlock() 319 320 if defaultBufferedReader == nil { 321 defaultBufferedReader = bufio.NewReader(nil) 322 } 323 return defaultBufferedReader.Size() 324 }