github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/seek_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 "errors" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "testing" 29 "time" 30 31 "github.com/m3db/m3/src/dbnode/digest" 32 "github.com/m3db/m3/src/dbnode/persist" 33 "github.com/m3db/m3/src/dbnode/persist/schema" 34 "github.com/m3db/m3/src/x/ident" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 ) 38 39 func newTestSeeker(filePathPrefix string) DataFileSetSeeker { 40 return NewSeeker( 41 filePathPrefix, testReaderBufferSize, testReaderBufferSize, 42 testBytesPool, false, testDefaultOpts) 43 } 44 45 func TestSeekEmptyIndex(t *testing.T) { 46 dir, err := ioutil.TempDir("", "testdb") 47 if err != nil { 48 t.Fatal(err) 49 } 50 filePathPrefix := filepath.Join(dir, "") 51 defer os.RemoveAll(dir) 52 53 w := newTestWriter(t, filePathPrefix) 54 writerOpts := DataWriterOpenOptions{ 55 BlockSize: testBlockSize, 56 Identifier: FileSetFileIdentifier{ 57 Namespace: testNs1ID, 58 Shard: 0, 59 BlockStart: testWriterStart, 60 }, 61 } 62 err = w.Open(writerOpts) 63 assert.NoError(t, err) 64 assert.NoError(t, w.Close()) 65 66 resources := newTestReusableSeekerResources() 67 s := newTestSeeker(filePathPrefix) 68 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 69 assert.NoError(t, err) 70 _, err = s.SeekByID(ident.StringID("foo"), resources) 71 assert.Error(t, err) 72 assert.Equal(t, errSeekIDNotFound, err) 73 assert.NoError(t, s.Close()) 74 } 75 76 func TestSeekDataUnexpectedSize(t *testing.T) { 77 dir, err := ioutil.TempDir("", "testdb") 78 if err != nil { 79 t.Fatal(err) 80 } 81 filePathPrefix := filepath.Join(dir, "") 82 defer os.RemoveAll(dir) 83 84 w := newTestWriter(t, filePathPrefix) 85 writerOpts := DataWriterOpenOptions{ 86 BlockSize: testBlockSize, 87 Identifier: FileSetFileIdentifier{ 88 Namespace: testNs1ID, 89 Shard: 0, 90 BlockStart: testWriterStart, 91 }, 92 } 93 metadata := persist.NewMetadataFromIDAndTags( 94 ident.StringID("foo"), 95 ident.Tags{}, 96 persist.MetadataOptions{}) 97 err = w.Open(writerOpts) 98 assert.NoError(t, err) 99 dataFile := w.(*writer).dataFdWithDigest.Fd().Name() 100 101 assert.NoError(t, w.Write(metadata, 102 bytesRefd([]byte{1, 2, 3}), 103 digest.Checksum([]byte{1, 2, 3}))) 104 assert.NoError(t, w.Close()) 105 106 // Truncate one byte 107 assert.NoError(t, os.Truncate(dataFile, 1)) 108 109 resources := newTestReusableSeekerResources() 110 s := newTestSeeker(filePathPrefix) 111 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 112 assert.NoError(t, err) 113 114 _, err = s.SeekByID(ident.StringID("foo"), resources) 115 assert.Error(t, err) 116 assert.Equal(t, errors.New("unexpected EOF"), err) 117 118 assert.NoError(t, s.Close()) 119 } 120 121 func TestSeekBadChecksum(t *testing.T) { 122 dir, err := ioutil.TempDir("", "testdb") 123 if err != nil { 124 t.Fatal(err) 125 } 126 filePathPrefix := filepath.Join(dir, "") 127 defer os.RemoveAll(dir) 128 129 w := newTestWriter(t, filePathPrefix) 130 writerOpts := DataWriterOpenOptions{ 131 BlockSize: testBlockSize, 132 Identifier: FileSetFileIdentifier{ 133 Namespace: testNs1ID, 134 Shard: 0, 135 BlockStart: testWriterStart, 136 }, 137 } 138 err = w.Open(writerOpts) 139 assert.NoError(t, err) 140 141 // Write data with wrong checksum 142 assert.NoError(t, w.Write( 143 persist.NewMetadataFromIDAndTags( 144 ident.StringID("foo"), 145 ident.Tags{}, 146 persist.MetadataOptions{}), 147 bytesRefd([]byte{1, 2, 3}), 148 digest.Checksum([]byte{1, 2, 4}))) 149 assert.NoError(t, w.Close()) 150 151 resources := newTestReusableSeekerResources() 152 s := newTestSeeker(filePathPrefix) 153 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 154 assert.NoError(t, err) 155 156 _, err = s.SeekByID(ident.StringID("foo"), resources) 157 assert.Error(t, err) 158 assert.Equal(t, errSeekChecksumMismatch, err) 159 160 assert.NoError(t, s.Close()) 161 } 162 163 // TestSeek is a basic sanity test that we can seek IDs that have been written, 164 // as well as received errSeekIDNotFound for IDs that were not written. 165 func TestSeek(t *testing.T) { 166 dir, err := ioutil.TempDir("", "testdb") 167 if err != nil { 168 t.Fatal(err) 169 } 170 filePathPrefix := filepath.Join(dir, "") 171 defer os.RemoveAll(dir) 172 173 w := newTestWriter(t, filePathPrefix) 174 writerOpts := DataWriterOpenOptions{ 175 BlockSize: testBlockSize, 176 Identifier: FileSetFileIdentifier{ 177 Namespace: testNs1ID, 178 Shard: 0, 179 BlockStart: testWriterStart, 180 }, 181 } 182 err = w.Open(writerOpts) 183 assert.NoError(t, err) 184 assert.NoError(t, w.Write( 185 persist.NewMetadataFromIDAndTags( 186 ident.StringID("foo1"), 187 ident.NewTags(ident.StringTag("num", "1")), 188 persist.MetadataOptions{}), 189 bytesRefd([]byte{1, 2, 1}), 190 digest.Checksum([]byte{1, 2, 1}))) 191 assert.NoError(t, w.Write( 192 persist.NewMetadataFromIDAndTags( 193 ident.StringID("foo2"), 194 ident.NewTags(ident.StringTag("num", "2")), 195 persist.MetadataOptions{}), 196 bytesRefd([]byte{1, 2, 2}), 197 digest.Checksum([]byte{1, 2, 2}))) 198 assert.NoError(t, w.Write( 199 persist.NewMetadataFromIDAndTags( 200 ident.StringID("foo3"), 201 ident.NewTags(ident.StringTag("num", "3")), 202 persist.MetadataOptions{}), 203 bytesRefd([]byte{1, 2, 3}), 204 digest.Checksum([]byte{1, 2, 3}))) 205 assert.NoError(t, w.Close()) 206 207 resources := newTestReusableSeekerResources() 208 s := newTestSeeker(filePathPrefix) 209 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 210 assert.NoError(t, err) 211 212 data, err := s.SeekByID(ident.StringID("foo3"), resources) 213 require.NoError(t, err) 214 215 data.IncRef() 216 defer data.DecRef() 217 assert.Equal(t, []byte{1, 2, 3}, data.Bytes()) 218 219 data, err = s.SeekByID(ident.StringID("foo1"), resources) 220 require.NoError(t, err) 221 222 data.IncRef() 223 defer data.DecRef() 224 assert.Equal(t, []byte{1, 2, 1}, data.Bytes()) 225 226 _, err = s.SeekByID(ident.StringID("foo"), resources) 227 assert.Error(t, err) 228 assert.Equal(t, errSeekIDNotFound, err) 229 230 data, err = s.SeekByID(ident.StringID("foo2"), resources) 231 require.NoError(t, err) 232 233 data.IncRef() 234 defer data.DecRef() 235 assert.Equal(t, []byte{1, 2, 2}, data.Bytes()) 236 237 assert.NoError(t, s.Close()) 238 } 239 240 // TestSeekIDNotExists is similar to TestSeek, but it covers more edge cases 241 // around IDs not existing. 242 func TestSeekIDNotExists(t *testing.T) { 243 dir, err := ioutil.TempDir("", "testdb") 244 if err != nil { 245 t.Fatal(err) 246 } 247 filePathPrefix := filepath.Join(dir, "") 248 defer os.RemoveAll(dir) 249 250 w := newTestWriter(t, filePathPrefix) 251 writerOpts := DataWriterOpenOptions{ 252 BlockSize: testBlockSize, 253 Identifier: FileSetFileIdentifier{ 254 Namespace: testNs1ID, 255 Shard: 0, 256 BlockStart: testWriterStart, 257 }, 258 } 259 err = w.Open(writerOpts) 260 assert.NoError(t, err) 261 assert.NoError(t, w.Write( 262 persist.NewMetadataFromIDAndTags( 263 ident.StringID("foo10"), 264 ident.Tags{}, 265 persist.MetadataOptions{}), 266 bytesRefd([]byte{1, 2, 1}), 267 digest.Checksum([]byte{1, 2, 1}))) 268 assert.NoError(t, w.Write( 269 persist.NewMetadataFromIDAndTags( 270 ident.StringID("foo20"), 271 ident.Tags{}, 272 persist.MetadataOptions{}), 273 bytesRefd([]byte{1, 2, 2}), 274 digest.Checksum([]byte{1, 2, 2}))) 275 assert.NoError(t, w.Write( 276 persist.NewMetadataFromIDAndTags( 277 ident.StringID("foo30"), 278 ident.Tags{}, 279 persist.MetadataOptions{}), 280 bytesRefd([]byte{1, 2, 3}), 281 digest.Checksum([]byte{1, 2, 3}))) 282 assert.NoError(t, w.Close()) 283 284 resources := newTestReusableSeekerResources() 285 s := newTestSeeker(filePathPrefix) 286 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 287 assert.NoError(t, err) 288 289 // Test errSeekIDNotFound when we scan far enough into the index file that 290 // we're sure that the ID we're looking for doesn't exist (because the index 291 // file is sorted). In this particular case, we would know foo21 doesn't exist 292 // once we've scanned all the way to foo30 (which does exist). 293 _, err = s.SeekByID(ident.StringID("foo21"), resources) 294 assert.Equal(t, errSeekIDNotFound, err) 295 296 // Test errSeekIDNotFound when we scan to the end of the index file (foo40 297 // would be located at the end of the index file based on the writes we've made) 298 _, err = s.SeekByID(ident.StringID("foo40"), resources) 299 assert.Equal(t, errSeekIDNotFound, err) 300 301 assert.NoError(t, s.Close()) 302 } 303 304 func TestReuseSeeker(t *testing.T) { 305 dir, err := ioutil.TempDir("", "testdb") 306 if err != nil { 307 t.Fatal(err) 308 } 309 filePathPrefix := filepath.Join(dir, "") 310 defer os.RemoveAll(dir) 311 312 w := newTestWriter(t, filePathPrefix) 313 314 writerOpts := DataWriterOpenOptions{ 315 BlockSize: testBlockSize, 316 Identifier: FileSetFileIdentifier{ 317 Namespace: testNs1ID, 318 Shard: 0, 319 BlockStart: testWriterStart.Add(-time.Hour), 320 }, 321 } 322 err = w.Open(writerOpts) 323 assert.NoError(t, err) 324 assert.NoError(t, w.Write( 325 persist.NewMetadataFromIDAndTags( 326 ident.StringID("foo"), 327 ident.Tags{}, 328 persist.MetadataOptions{}), 329 bytesRefd([]byte{1, 2, 1}), 330 digest.Checksum([]byte{1, 2, 1}))) 331 assert.NoError(t, w.Close()) 332 333 writerOpts = DataWriterOpenOptions{ 334 BlockSize: testBlockSize, 335 Identifier: FileSetFileIdentifier{ 336 Namespace: testNs1ID, 337 Shard: 0, 338 BlockStart: testWriterStart, 339 }, 340 } 341 err = w.Open(writerOpts) 342 assert.NoError(t, err) 343 assert.NoError(t, w.Write( 344 persist.NewMetadataFromIDAndTags( 345 ident.StringID("foo"), 346 ident.Tags{}, 347 persist.MetadataOptions{}), 348 bytesRefd([]byte{1, 2, 3}), 349 digest.Checksum([]byte{1, 2, 3}))) 350 assert.NoError(t, w.Close()) 351 352 resources := newTestReusableSeekerResources() 353 s := newTestSeeker(filePathPrefix) 354 err = s.Open(testNs1ID, 0, testWriterStart.Add(-time.Hour), 0, resources) 355 assert.NoError(t, err) 356 357 data, err := s.SeekByID(ident.StringID("foo"), resources) 358 require.NoError(t, err) 359 360 data.IncRef() 361 defer data.DecRef() 362 assert.Equal(t, []byte{1, 2, 1}, data.Bytes()) 363 364 err = s.Open(testNs1ID, 0, testWriterStart, 0, resources) 365 assert.NoError(t, err) 366 367 data, err = s.SeekByID(ident.StringID("foo"), resources) 368 require.NoError(t, err) 369 370 data.IncRef() 371 defer data.DecRef() 372 assert.Equal(t, []byte{1, 2, 3}, data.Bytes()) 373 } 374 375 func TestCloneSeeker(t *testing.T) { 376 dir, err := ioutil.TempDir("", "testdb") 377 if err != nil { 378 t.Fatal(err) 379 } 380 filePathPrefix := filepath.Join(dir, "") 381 defer os.RemoveAll(dir) 382 383 w := newTestWriter(t, filePathPrefix) 384 385 writerOpts := DataWriterOpenOptions{ 386 BlockSize: testBlockSize, 387 Identifier: FileSetFileIdentifier{ 388 Namespace: testNs1ID, 389 Shard: 0, 390 BlockStart: testWriterStart.Add(-time.Hour), 391 }, 392 } 393 err = w.Open(writerOpts) 394 assert.NoError(t, err) 395 assert.NoError(t, w.Write( 396 persist.NewMetadataFromIDAndTags( 397 ident.StringID("foo"), 398 ident.Tags{}, 399 persist.MetadataOptions{}), 400 bytesRefd([]byte{1, 2, 1}), 401 digest.Checksum([]byte{1, 2, 1}))) 402 assert.NoError(t, w.Close()) 403 404 writerOpts = DataWriterOpenOptions{ 405 BlockSize: testBlockSize, 406 Identifier: FileSetFileIdentifier{ 407 Namespace: testNs1ID, 408 Shard: 0, 409 BlockStart: testWriterStart, 410 }, 411 } 412 err = w.Open(writerOpts) 413 assert.NoError(t, err) 414 assert.NoError(t, w.Write( 415 persist.NewMetadataFromIDAndTags( 416 ident.StringID("foo"), 417 ident.Tags{}, 418 persist.MetadataOptions{}), 419 bytesRefd([]byte{1, 2, 3}), 420 digest.Checksum([]byte{1, 2, 3}))) 421 assert.NoError(t, w.Close()) 422 423 resources := newTestReusableSeekerResources() 424 s := newTestSeeker(filePathPrefix) 425 err = s.Open(testNs1ID, 0, testWriterStart.Add(-time.Hour), 0, resources) 426 assert.NoError(t, err) 427 428 clone, err := s.ConcurrentClone() 429 require.NoError(t, err) 430 431 data, err := clone.SeekByID(ident.StringID("foo"), resources) 432 require.NoError(t, err) 433 434 data.IncRef() 435 defer data.DecRef() 436 assert.Equal(t, []byte{1, 2, 1}, data.Bytes()) 437 } 438 439 func TestSeekValidateIndexEntriesFile(t *testing.T) { 440 dir, err := ioutil.TempDir("", "testdb") 441 if err != nil { 442 t.Fatal(err) 443 } 444 filePathPrefix := filepath.Join(dir, "") 445 defer os.RemoveAll(dir) 446 447 w := newTestWriter(t, filePathPrefix) 448 writerOpts := DataWriterOpenOptions{ 449 BlockSize: testBlockSize, 450 Identifier: FileSetFileIdentifier{ 451 Namespace: testNs1ID, 452 Shard: 0, 453 BlockStart: testWriterStart, 454 }, 455 } 456 err = w.Open(writerOpts) 457 assert.NoError(t, err) 458 459 // Write data 460 assert.NoError(t, w.Write( 461 persist.NewMetadataFromIDAndTags( 462 ident.StringID("foo"), 463 ident.Tags{}, 464 persist.MetadataOptions{}), 465 bytesRefd([]byte{1, 2, 3}), 466 digest.Checksum([]byte{1, 2, 3}))) 467 assert.NoError(t, w.Close()) 468 469 shardDir := ShardDataDirPath(filePathPrefix, testNs1ID, 0) 470 471 // With full file validation disabled 472 s := seeker{opts: seekerOpts{ 473 filePathPrefix: filePathPrefix, 474 dataBufferSize: testReaderBufferSize, 475 infoBufferSize: testReaderBufferSize, 476 bytesPool: testBytesPool, 477 keepUnreadBuf: false, 478 opts: testDefaultOpts, 479 }} 480 s.versionChecker = schema.NewVersionChecker(1, 1) 481 482 indexFilePath := dataFilesetPathFromTimeAndIndex(shardDir, testWriterStart, 0, indexFileSuffix, false) 483 indexFd, err := os.Open(indexFilePath) 484 assert.NoError(t, err) 485 indexReader := digest.NewFdWithDigestReader(defaultInfoReaderBufferSize) 486 indexReader.Reset(indexFd) 487 488 assert.NoError(t, s.validateIndexFileDigest(indexReader, 0)) 489 490 // With full file validation enabled 491 s.versionChecker = schema.NewVersionChecker(1, 0) 492 _, err = indexFd.Seek(0, 0) 493 assert.NoError(t, err) 494 indexReader.Reset(indexFd) 495 496 assert.Error(t, s.validateIndexFileDigest(indexReader, 0)) 497 498 // Sanity check -- call seeker#Open and ensure VersionChecker is set correctly 499 err = s.Open(testNs1ID, 0, testWriterStart, 0, newTestReusableSeekerResources()) 500 assert.NoError(t, err) 501 assert.True(t, s.versionChecker.IndexEntryValidationEnabled()) 502 } 503 504 func newTestReusableSeekerResources() ReusableSeekerResources { 505 return NewReusableSeekerResources(testDefaultOpts) 506 }