github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/block_store_test.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 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 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 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 implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // This file incorporates work covered by the following copyright and 16 // permission notice: 17 // 18 // Copyright 2016 Attic Labs, Inc. All rights reserved. 19 // Licensed under the Apache License, version 2.0: 20 // http://www.apache.org/licenses/LICENSE-2.0 21 22 package nbs 23 24 import ( 25 "bytes" 26 "context" 27 "crypto/rand" 28 "errors" 29 "io/ioutil" 30 "os" 31 "path/filepath" 32 "sort" 33 "testing" 34 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 "github.com/stretchr/testify/suite" 38 39 "github.com/dolthub/dolt/go/libraries/utils/osutil" 40 "github.com/dolthub/dolt/go/store/chunks" 41 "github.com/dolthub/dolt/go/store/constants" 42 "github.com/dolthub/dolt/go/store/d" 43 "github.com/dolthub/dolt/go/store/hash" 44 ) 45 46 const testMemTableSize = 1 << 8 47 48 func TestBlockStoreSuite(t *testing.T) { 49 suite.Run(t, &BlockStoreSuite{}) 50 } 51 52 type BlockStoreSuite struct { 53 suite.Suite 54 dir string 55 store *NomsBlockStore 56 putCountFn func() int 57 } 58 59 func (suite *BlockStoreSuite) SetupTest() { 60 var err error 61 suite.dir, err = ioutil.TempDir("", "") 62 suite.NoError(err) 63 suite.store, err = NewLocalStore(context.Background(), constants.FormatDefaultString, suite.dir, testMemTableSize) 64 suite.NoError(err) 65 suite.putCountFn = func() int { 66 return int(suite.store.putCount) 67 } 68 } 69 70 func (suite *BlockStoreSuite) TearDownTest() { 71 err := suite.store.Close() 72 suite.NoError(err) 73 err = os.RemoveAll(suite.dir) 74 if !osutil.IsWindowsSharingViolation(err) { 75 suite.NoError(err) 76 } 77 } 78 79 func (suite *BlockStoreSuite) TestChunkStoreMissingDir() { 80 newDir := filepath.Join(suite.dir, "does-not-exist") 81 _, err := NewLocalStore(context.Background(), constants.FormatDefaultString, newDir, testMemTableSize) 82 suite.Error(err) 83 } 84 85 func (suite *BlockStoreSuite) TestChunkStoreNotDir() { 86 existingFile := filepath.Join(suite.dir, "path-exists-but-is-a-file") 87 _, err := os.Create(existingFile) 88 suite.NoError(err) 89 90 _, err = NewLocalStore(context.Background(), constants.FormatDefaultString, existingFile, testMemTableSize) 91 suite.Error(err) 92 } 93 94 func (suite *BlockStoreSuite) TestChunkStorePut() { 95 input := []byte("abc") 96 c := chunks.NewChunk(input) 97 err := suite.store.Put(context.Background(), c) 98 suite.NoError(err) 99 h := c.Hash() 100 101 // See http://www.di-mgt.com.au/sha_testvectors.html 102 suite.Equal("rmnjb8cjc5tblj21ed4qs821649eduie", h.String()) 103 104 rt, err := suite.store.Root(context.Background()) 105 suite.NoError(err) 106 success, err := suite.store.Commit(context.Background(), h, rt) // Commit writes 107 suite.NoError(err) 108 suite.True(success) 109 110 // And reading it via the API should work... 111 assertInputInStore(input, h, suite.store, suite.Assert()) 112 if suite.putCountFn != nil { 113 suite.Equal(1, suite.putCountFn()) 114 } 115 116 // Re-writing the same data should cause a second put 117 c = chunks.NewChunk(input) 118 err = suite.store.Put(context.Background(), c) 119 suite.NoError(err) 120 suite.Equal(h, c.Hash()) 121 assertInputInStore(input, h, suite.store, suite.Assert()) 122 rt, err = suite.store.Root(context.Background()) 123 suite.NoError(err) 124 _, err = suite.store.Commit(context.Background(), h, rt) // Commit writes 125 suite.NoError(err) 126 127 if suite.putCountFn != nil { 128 suite.Equal(2, suite.putCountFn()) 129 } 130 } 131 132 func (suite *BlockStoreSuite) TestChunkStorePutMany() { 133 input1, input2 := []byte("abc"), []byte("def") 134 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 135 err := suite.store.Put(context.Background(), c1) 136 suite.NoError(err) 137 err = suite.store.Put(context.Background(), c2) 138 suite.NoError(err) 139 140 rt, err := suite.store.Root(context.Background()) 141 suite.NoError(err) 142 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 143 suite.NoError(err) 144 suite.True(success) 145 146 // And reading it via the API should work... 147 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 148 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 149 if suite.putCountFn != nil { 150 suite.Equal(2, suite.putCountFn()) 151 } 152 } 153 154 func (suite *BlockStoreSuite) TestChunkStoreStatsSummary() { 155 input1, input2 := []byte("abc"), []byte("def") 156 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 157 err := suite.store.Put(context.Background(), c1) 158 suite.NoError(err) 159 err = suite.store.Put(context.Background(), c2) 160 suite.NoError(err) 161 162 rt, err := suite.store.Root(context.Background()) 163 suite.NoError(err) 164 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 165 suite.True(success) 166 suite.NoError(err) 167 168 summary := suite.store.StatsSummary() 169 suite.Contains(summary, c1.Hash().String()) 170 suite.NotEqual("Unsupported", summary) 171 } 172 173 func (suite *BlockStoreSuite) TestChunkStorePutMoreThanMemTable() { 174 input1, input2 := make([]byte, testMemTableSize/2+1), make([]byte, testMemTableSize/2+1) 175 _, err := rand.Read(input1) 176 suite.NoError(err) 177 _, err = rand.Read(input2) 178 suite.NoError(err) 179 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 180 err = suite.store.Put(context.Background(), c1) 181 suite.NoError(err) 182 err = suite.store.Put(context.Background(), c2) 183 suite.NoError(err) 184 185 rt, err := suite.store.Root(context.Background()) 186 suite.NoError(err) 187 success, err := suite.store.Commit(context.Background(), c1.Hash(), rt) // Commit writes 188 suite.NoError(err) 189 suite.True(success) 190 191 // And reading it via the API should work... 192 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 193 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 194 if suite.putCountFn != nil { 195 suite.Equal(2, suite.putCountFn()) 196 } 197 specs, err := suite.store.tables.ToSpecs() 198 suite.NoError(err) 199 suite.Len(specs, 2) 200 } 201 202 func (suite *BlockStoreSuite) TestChunkStoreGetMany() { 203 inputs := [][]byte{make([]byte, testMemTableSize/2+1), make([]byte, testMemTableSize/2+1), []byte("abc")} 204 _, err := rand.Read(inputs[0]) 205 suite.NoError(err) 206 _, err = rand.Read(inputs[1]) 207 suite.NoError(err) 208 chnx := make([]chunks.Chunk, len(inputs)) 209 for i, data := range inputs { 210 chnx[i] = chunks.NewChunk(data) 211 err = suite.store.Put(context.Background(), chnx[i]) 212 suite.NoError(err) 213 } 214 215 rt, err := suite.store.Root(context.Background()) 216 suite.NoError(err) 217 _, err = suite.store.Commit(context.Background(), chnx[0].Hash(), rt) // Commit writes 218 suite.NoError(err) 219 220 hashes := make(hash.HashSlice, len(chnx)) 221 for i, c := range chnx { 222 hashes[i] = c.Hash() 223 } 224 225 chunkChan := make(chan *chunks.Chunk, len(hashes)) 226 err = suite.store.GetMany(context.Background(), hashes.HashSet(), func(c *chunks.Chunk) { chunkChan <- c }) 227 suite.NoError(err) 228 close(chunkChan) 229 230 found := make(hash.HashSlice, 0) 231 for c := range chunkChan { 232 found = append(found, c.Hash()) 233 } 234 235 sort.Sort(found) 236 sort.Sort(hashes) 237 suite.True(found.Equals(hashes)) 238 } 239 240 func (suite *BlockStoreSuite) TestChunkStoreHasMany() { 241 chnx := []chunks.Chunk{ 242 chunks.NewChunk([]byte("abc")), 243 chunks.NewChunk([]byte("def")), 244 } 245 for _, c := range chnx { 246 err := suite.store.Put(context.Background(), c) 247 suite.NoError(err) 248 } 249 250 rt, err := suite.store.Root(context.Background()) 251 suite.NoError(err) 252 success, err := suite.store.Commit(context.Background(), chnx[0].Hash(), rt) // Commit writes 253 suite.NoError(err) 254 suite.True(success) 255 notPresent := chunks.NewChunk([]byte("ghi")).Hash() 256 257 hashes := hash.NewHashSet(chnx[0].Hash(), chnx[1].Hash(), notPresent) 258 absent, err := suite.store.HasMany(context.Background(), hashes) 259 suite.NoError(err) 260 261 suite.Len(absent, 1) 262 for _, c := range chnx { 263 suite.False(absent.Has(c.Hash()), "%s present in %v", c.Hash(), absent) 264 } 265 suite.True(absent.Has(notPresent)) 266 } 267 268 func (suite *BlockStoreSuite) TestChunkStoreFlushOptimisticLockFail() { 269 input1, input2 := []byte("abc"), []byte("def") 270 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 271 root, err := suite.store.Root(context.Background()) 272 suite.NoError(err) 273 274 interloper, err := NewLocalStore(context.Background(), constants.FormatDefaultString, suite.dir, testMemTableSize) 275 suite.NoError(err) 276 err = interloper.Put(context.Background(), c1) 277 suite.NoError(err) 278 h, err := interloper.Root(context.Background()) 279 suite.NoError(err) 280 success, err := interloper.Commit(context.Background(), h, h) 281 suite.NoError(err) 282 suite.True(success) 283 284 err = suite.store.Put(context.Background(), c2) 285 suite.NoError(err) 286 h, err = suite.store.Root(context.Background()) 287 suite.NoError(err) 288 success, err = suite.store.Commit(context.Background(), h, h) 289 suite.NoError(err) 290 suite.True(success) 291 292 // Reading c2 via the API should work... 293 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 294 // And so should reading c1 via the API 295 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 296 297 h, err = interloper.Root(context.Background()) 298 suite.NoError(err) 299 success, err = interloper.Commit(context.Background(), c1.Hash(), h) // Commit root 300 suite.NoError(err) 301 suite.True(success) 302 303 // Updating from stale root should fail... 304 success, err = suite.store.Commit(context.Background(), c2.Hash(), root) 305 suite.NoError(err) 306 suite.False(success) 307 308 // ...but new root should succeed 309 h, err = suite.store.Root(context.Background()) 310 suite.NoError(err) 311 success, err = suite.store.Commit(context.Background(), c2.Hash(), h) 312 suite.NoError(err) 313 suite.True(success) 314 } 315 316 func (suite *BlockStoreSuite) TestChunkStoreRebaseOnNoOpFlush() { 317 input1 := []byte("abc") 318 c1 := chunks.NewChunk(input1) 319 320 interloper, err := NewLocalStore(context.Background(), constants.FormatDefaultString, suite.dir, testMemTableSize) 321 suite.NoError(err) 322 err = interloper.Put(context.Background(), c1) 323 suite.NoError(err) 324 root, err := interloper.Root(context.Background()) 325 suite.NoError(err) 326 success, err := interloper.Commit(context.Background(), c1.Hash(), root) 327 suite.NoError(err) 328 suite.True(success) 329 330 has, err := suite.store.Has(context.Background(), c1.Hash()) 331 suite.NoError(err) 332 suite.False(has) 333 334 root, err = suite.store.Root(context.Background()) 335 suite.NoError(err) 336 suite.Equal(hash.Hash{}, root) 337 338 // Should Rebase, even though there's no work to do. 339 root, err = suite.store.Root(context.Background()) 340 suite.NoError(err) 341 success, err = suite.store.Commit(context.Background(), root, root) 342 suite.NoError(err) 343 suite.True(success) 344 345 // Reading c1 via the API should work 346 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 347 suite.True(suite.store.Has(context.Background(), c1.Hash())) 348 } 349 350 func (suite *BlockStoreSuite) TestChunkStorePutWithRebase() { 351 input1, input2 := []byte("abc"), []byte("def") 352 c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2) 353 root, err := suite.store.Root(context.Background()) 354 suite.NoError(err) 355 356 interloper, err := NewLocalStore(context.Background(), constants.FormatDefaultString, suite.dir, testMemTableSize) 357 suite.NoError(err) 358 err = interloper.Put(context.Background(), c1) 359 suite.NoError(err) 360 h, err := interloper.Root(context.Background()) 361 suite.NoError(err) 362 success, err := interloper.Commit(context.Background(), h, h) 363 suite.NoError(err) 364 suite.True(success) 365 366 err = suite.store.Put(context.Background(), c2) 367 suite.NoError(err) 368 369 // Reading c2 via the API should work pre-rebase 370 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 371 // Shouldn't have c1 yet. 372 suite.False(suite.store.Has(context.Background(), c1.Hash())) 373 374 err = suite.store.Rebase(context.Background()) 375 suite.NoError(err) 376 377 // Reading c2 via the API should work post-rebase 378 assertInputInStore(input2, c2.Hash(), suite.store, suite.Assert()) 379 // And so should reading c1 via the API 380 assertInputInStore(input1, c1.Hash(), suite.store, suite.Assert()) 381 382 // Commit interloper root 383 h, err = interloper.Root(context.Background()) 384 suite.NoError(err) 385 success, err = interloper.Commit(context.Background(), c1.Hash(), h) 386 suite.NoError(err) 387 suite.True(success) 388 389 // suite.store should still have its initial root 390 h, err = suite.store.Root(context.Background()) 391 suite.NoError(err) 392 suite.EqualValues(root, h) 393 err = suite.store.Rebase(context.Background()) 394 suite.NoError(err) 395 396 // Rebase grabbed the new root, so updating should now succeed! 397 h, err = suite.store.Root(context.Background()) 398 suite.NoError(err) 399 success, err = suite.store.Commit(context.Background(), c2.Hash(), h) 400 suite.NoError(err) 401 suite.True(success) 402 403 // Interloper shouldn't see c2 yet.... 404 suite.False(interloper.Has(context.Background(), c2.Hash())) 405 err = interloper.Rebase(context.Background()) 406 suite.NoError(err) 407 // ...but post-rebase it must 408 assertInputInStore(input2, c2.Hash(), interloper, suite.Assert()) 409 } 410 411 func TestBlockStoreConjoinOnCommit(t *testing.T) { 412 stats := &Stats{} 413 assertContainAll := func(t *testing.T, store chunks.ChunkStore, srcs ...chunkSource) { 414 rdrs := make(chunkReaderGroup, len(srcs)) 415 for i, src := range srcs { 416 rdrs[i] = src.Clone() 417 } 418 chunkChan := make(chan extractRecord, mustUint32(rdrs.count())) 419 err := rdrs.extract(context.Background(), chunkChan) 420 require.NoError(t, err) 421 close(chunkChan) 422 423 for rec := range chunkChan { 424 ok, err := store.Has(context.Background(), hash.Hash(rec.a)) 425 require.NoError(t, err) 426 assert.True(t, ok) 427 } 428 } 429 430 makeManifestManager := func(m manifest) manifestManager { 431 return manifestManager{m, newManifestCache(0), newManifestLocks()} 432 } 433 434 newChunk := chunks.NewChunk([]byte("gnu")) 435 436 t.Run("NoConjoin", func(t *testing.T) { 437 mm := makeManifestManager(&fakeManifest{}) 438 p := newFakeTablePersister() 439 c := &fakeConjoiner{} 440 441 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, mm, p, c, testMemTableSize) 442 require.NoError(t, err) 443 444 root, err := smallTableStore.Root(context.Background()) 445 require.NoError(t, err) 446 err = smallTableStore.Put(context.Background(), newChunk) 447 require.NoError(t, err) 448 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 449 require.NoError(t, err) 450 assert.True(t, success) 451 452 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 453 require.NoError(t, err) 454 assert.True(t, ok) 455 }) 456 457 makeCanned := func(conjoinees, keepers []tableSpec, p tablePersister) cannedConjoin { 458 srcs := chunkSources{} 459 for _, sp := range conjoinees { 460 cs, err := p.Open(context.Background(), sp.name, sp.chunkCount, nil) 461 require.NoError(t, err) 462 srcs = append(srcs, cs) 463 } 464 conjoined, err := p.ConjoinAll(context.Background(), srcs, stats) 465 require.NoError(t, err) 466 cannedSpecs := []tableSpec{{mustAddr(conjoined.hash()), mustUint32(conjoined.count())}} 467 return cannedConjoin{true, append(cannedSpecs, keepers...)} 468 } 469 470 t.Run("ConjoinSuccess", func(t *testing.T) { 471 fm := &fakeManifest{} 472 p := newFakeTablePersister() 473 474 srcs := makeTestSrcs(t, []uint32{1, 1, 3, 7}, p) 475 upstream, err := toSpecs(srcs) 476 require.NoError(t, err) 477 fm.set(constants.NomsVersion, computeAddr([]byte{0xbe}), hash.Of([]byte{0xef}), upstream, nil) 478 c := &fakeConjoiner{ 479 []cannedConjoin{makeCanned(upstream[:2], upstream[2:], p)}, 480 } 481 482 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, makeManifestManager(fm), p, c, testMemTableSize) 483 require.NoError(t, err) 484 485 root, err := smallTableStore.Root(context.Background()) 486 require.NoError(t, err) 487 err = smallTableStore.Put(context.Background(), newChunk) 488 require.NoError(t, err) 489 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 490 require.NoError(t, err) 491 assert.True(t, success) 492 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 493 require.NoError(t, err) 494 assert.True(t, ok) 495 assertContainAll(t, smallTableStore, srcs...) 496 for _, src := range srcs { 497 err := src.Close() 498 require.NoError(t, err) 499 } 500 }) 501 502 t.Run("ConjoinRetry", func(t *testing.T) { 503 fm := &fakeManifest{} 504 p := newFakeTablePersister() 505 506 srcs := makeTestSrcs(t, []uint32{1, 1, 3, 7, 13}, p) 507 upstream, err := toSpecs(srcs) 508 require.NoError(t, err) 509 fm.set(constants.NomsVersion, computeAddr([]byte{0xbe}), hash.Of([]byte{0xef}), upstream, nil) 510 c := &fakeConjoiner{ 511 []cannedConjoin{ 512 makeCanned(upstream[:2], upstream[2:], p), 513 makeCanned(upstream[:4], upstream[4:], p), 514 }, 515 } 516 517 smallTableStore, err := newNomsBlockStore(context.Background(), constants.FormatDefaultString, makeManifestManager(fm), p, c, testMemTableSize) 518 require.NoError(t, err) 519 520 root, err := smallTableStore.Root(context.Background()) 521 require.NoError(t, err) 522 err = smallTableStore.Put(context.Background(), newChunk) 523 require.NoError(t, err) 524 success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root) 525 require.NoError(t, err) 526 assert.True(t, success) 527 ok, err := smallTableStore.Has(context.Background(), newChunk.Hash()) 528 require.NoError(t, err) 529 assert.True(t, ok) 530 assertContainAll(t, smallTableStore, srcs...) 531 for _, src := range srcs { 532 err := src.Close() 533 require.NoError(t, err) 534 } 535 }) 536 } 537 538 type cannedConjoin struct { 539 should bool 540 specs []tableSpec // Must name tables that are already persisted 541 } 542 543 type fakeConjoiner struct { 544 canned []cannedConjoin 545 } 546 547 func (fc *fakeConjoiner) ConjoinRequired(ts tableSet) bool { 548 if len(fc.canned) == 0 { 549 return false 550 } 551 return fc.canned[0].should 552 } 553 554 func (fc *fakeConjoiner) Conjoin(ctx context.Context, upstream manifestContents, mm manifestUpdater, p tablePersister, stats *Stats) (manifestContents, error) { 555 d.PanicIfTrue(len(fc.canned) == 0) 556 canned := fc.canned[0] 557 fc.canned = fc.canned[1:] 558 559 newContents := manifestContents{ 560 vers: constants.NomsVersion, 561 root: upstream.root, 562 specs: canned.specs, 563 lock: generateLockHash(upstream.root, canned.specs), 564 } 565 566 var err error 567 upstream, err = mm.Update(context.Background(), upstream.lock, newContents, stats, nil) 568 569 if err != nil { 570 return manifestContents{}, err 571 } 572 573 if upstream.lock != newContents.lock { 574 return manifestContents{}, errors.New("lock failed") 575 } 576 577 return upstream, err 578 } 579 580 func assertInputInStore(input []byte, h hash.Hash, s chunks.ChunkStore, assert *assert.Assertions) { 581 c, err := s.Get(context.Background(), h) 582 assert.NoError(err) 583 assert.False(c.IsEmpty(), "Shouldn't get empty chunk for %s", h.String()) 584 assert.Zero(bytes.Compare(input, c.Data()), "%s != %s", string(input), string(c.Data())) 585 } 586 587 func (suite *BlockStoreSuite) TestChunkStoreGetNonExisting() { 588 h := hash.Parse("11111111111111111111111111111111") 589 c, err := suite.store.Get(context.Background(), h) 590 suite.NoError(err) 591 suite.True(c.IsEmpty()) 592 }