1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
19 "git.curoverse.com/arvados.git/sdk/go/arvados"
20 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21 "github.com/prometheus/client_golang/prometheus"
22 dto "github.com/prometheus/client_model/go"
26 Error(args ...interface{})
27 Errorf(format string, args ...interface{})
31 Fatal(args ...interface{})
32 Fatalf(format string, args ...interface{})
33 Log(args ...interface{})
34 Logf(format string, args ...interface{})
37 // A TestableVolumeFactory returns a new TestableVolume. The factory
38 // function, and the TestableVolume it returns, can use "t" to write
39 // logs, fail the current test, etc.
40 type TestableVolumeFactory func(t TB) TestableVolume
42 // DoGenericVolumeTests runs a set of tests that every TestableVolume
43 // is expected to pass. It calls factory to create a new TestableVolume
44 // for each test case, to avoid leaking state between tests.
45 func DoGenericVolumeTests(t TB, factory TestableVolumeFactory) {
47 testGetNoSuchBlock(t, factory)
49 testCompareNonexistent(t, factory)
50 testCompareSameContent(t, factory, TestHash, TestBlock)
51 testCompareSameContent(t, factory, EmptyHash, EmptyBlock)
52 testCompareWithCollision(t, factory, TestHash, TestBlock, []byte("baddata"))
53 testCompareWithCollision(t, factory, TestHash, TestBlock, EmptyBlock)
54 testCompareWithCollision(t, factory, EmptyHash, EmptyBlock, TestBlock)
55 testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, []byte("baddata"))
56 testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, EmptyBlock)
57 testCompareWithCorruptStoredData(t, factory, EmptyHash, EmptyBlock, []byte("baddata"))
59 testPutBlockWithSameContent(t, factory, TestHash, TestBlock)
60 testPutBlockWithSameContent(t, factory, EmptyHash, EmptyBlock)
61 testPutBlockWithDifferentContent(t, factory, arvadostest.MD5CollisionMD5, arvadostest.MD5CollisionData[0], arvadostest.MD5CollisionData[1])
62 testPutBlockWithDifferentContent(t, factory, arvadostest.MD5CollisionMD5, EmptyBlock, arvadostest.MD5CollisionData[0])
63 testPutBlockWithDifferentContent(t, factory, arvadostest.MD5CollisionMD5, arvadostest.MD5CollisionData[0], EmptyBlock)
64 testPutBlockWithDifferentContent(t, factory, EmptyHash, EmptyBlock, arvadostest.MD5CollisionData[0])
65 testPutMultipleBlocks(t, factory)
67 testPutAndTouch(t, factory)
68 testTouchNoSuchBlock(t, factory)
70 testMtimeNoSuchBlock(t, factory)
72 testIndexTo(t, factory)
74 testDeleteNewBlock(t, factory)
75 testDeleteOldBlock(t, factory)
76 testDeleteNoSuchBlock(t, factory)
78 testStatus(t, factory)
80 testMetrics(t, factory)
82 testString(t, factory)
84 testUpdateReadOnly(t, factory)
86 testGetConcurrent(t, factory)
87 testPutConcurrent(t, factory)
89 testPutFullBlock(t, factory)
91 testTrashUntrash(t, factory)
92 testTrashEmptyTrashUntrash(t, factory)
95 // Put a test block, get it and verify content
96 // Test should pass for both writable and read-only volumes
97 func testGet(t TB, factory TestableVolumeFactory) {
101 v.PutRaw(TestHash, TestBlock)
103 buf := make([]byte, BlockSize)
104 n, err := v.Get(context.Background(), TestHash, buf)
109 if bytes.Compare(buf[:n], TestBlock) != 0 {
110 t.Errorf("expected %s, got %s", string(TestBlock), string(buf))
114 // Invoke get on a block that does not exist in volume; should result in error
115 // Test should pass for both writable and read-only volumes
116 func testGetNoSuchBlock(t TB, factory TestableVolumeFactory) {
120 buf := make([]byte, BlockSize)
121 if _, err := v.Get(context.Background(), TestHash2, buf); err == nil {
122 t.Errorf("Expected error while getting non-existing block %v", TestHash2)
126 // Compare() should return os.ErrNotExist if the block does not exist.
127 // Otherwise, writing new data causes CompareAndTouch() to generate
128 // error logs even though everything is working fine.
129 func testCompareNonexistent(t TB, factory TestableVolumeFactory) {
133 err := v.Compare(context.Background(), TestHash, TestBlock)
134 if err != os.ErrNotExist {
135 t.Errorf("Got err %T %q, expected os.ErrNotExist", err, err)
139 // Put a test block and compare the locator with same content
140 // Test should pass for both writable and read-only volumes
141 func testCompareSameContent(t TB, factory TestableVolumeFactory, testHash string, testData []byte) {
145 v.PutRaw(testHash, testData)
147 // Compare the block locator with same content
148 err := v.Compare(context.Background(), testHash, testData)
150 t.Errorf("Got err %q, expected nil", err)
154 // Test behavior of Compare() when stored data matches expected
155 // checksum but differs from new data we need to store. Requires
156 // testHash = md5(testDataA).
158 // Test should pass for both writable and read-only volumes
159 func testCompareWithCollision(t TB, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
163 v.PutRaw(testHash, testDataA)
165 // Compare the block locator with different content; collision
166 err := v.Compare(context.Background(), TestHash, testDataB)
168 t.Errorf("Got err nil, expected error due to collision")
172 // Test behavior of Compare() when stored data has become
173 // corrupted. Requires testHash = md5(testDataA) != md5(testDataB).
175 // Test should pass for both writable and read-only volumes
176 func testCompareWithCorruptStoredData(t TB, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
180 v.PutRaw(TestHash, testDataB)
182 err := v.Compare(context.Background(), testHash, testDataA)
183 if err == nil || err == CollisionError {
184 t.Errorf("Got err %+v, expected non-collision error", err)
188 // Put a block and put again with same content
189 // Test is intended for only writable volumes
190 func testPutBlockWithSameContent(t TB, factory TestableVolumeFactory, testHash string, testData []byte) {
194 if v.Writable() == false {
198 err := v.Put(context.Background(), testHash, testData)
200 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
203 err = v.Put(context.Background(), testHash, testData)
205 t.Errorf("Got err putting block second time %q: %q, expected nil", TestBlock, err)
209 // Put a block and put again with different content
210 // Test is intended for only writable volumes
211 func testPutBlockWithDifferentContent(t TB, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
215 if v.Writable() == false {
219 v.PutRaw(testHash, testDataA)
221 putErr := v.Put(context.Background(), testHash, testDataB)
222 buf := make([]byte, BlockSize)
223 n, getErr := v.Get(context.Background(), testHash, buf)
225 // Put must not return a nil error unless it has
226 // overwritten the existing data.
227 if bytes.Compare(buf[:n], testDataB) != 0 {
228 t.Errorf("Put succeeded but Get returned %+q, expected %+q", buf[:n], testDataB)
231 // It is permissible for Put to fail, but it must
232 // leave us with either the original data, the new
233 // data, or nothing at all.
234 if getErr == nil && bytes.Compare(buf[:n], testDataA) != 0 && bytes.Compare(buf[:n], testDataB) != 0 {
235 t.Errorf("Put failed but Get returned %+q, which is neither %+q nor %+q", buf[:n], testDataA, testDataB)
240 // Put and get multiple blocks
241 // Test is intended for only writable volumes
242 func testPutMultipleBlocks(t TB, factory TestableVolumeFactory) {
246 if v.Writable() == false {
250 err := v.Put(context.Background(), TestHash, TestBlock)
252 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
255 err = v.Put(context.Background(), TestHash2, TestBlock2)
257 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock2, err)
260 err = v.Put(context.Background(), TestHash3, TestBlock3)
262 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock3, err)
265 data := make([]byte, BlockSize)
266 n, err := v.Get(context.Background(), TestHash, data)
270 if bytes.Compare(data[:n], TestBlock) != 0 {
271 t.Errorf("Block present, but got %+q, expected %+q", data[:n], TestBlock)
275 n, err = v.Get(context.Background(), TestHash2, data)
279 if bytes.Compare(data[:n], TestBlock2) != 0 {
280 t.Errorf("Block present, but got %+q, expected %+q", data[:n], TestBlock2)
284 n, err = v.Get(context.Background(), TestHash3, data)
288 if bytes.Compare(data[:n], TestBlock3) != 0 {
289 t.Errorf("Block present, but to %+q, expected %+q", data[:n], TestBlock3)
295 // Test that when applying PUT to a block that already exists,
296 // the block's modification time is updated.
297 // Test is intended for only writable volumes
298 func testPutAndTouch(t TB, factory TestableVolumeFactory) {
302 if v.Writable() == false {
306 if err := v.Put(context.Background(), TestHash, TestBlock); err != nil {
310 // We'll verify { t0 < threshold < t1 }, where t0 is the
311 // existing block's timestamp on disk before Put() and t1 is
312 // its timestamp after Put().
313 threshold := time.Now().Add(-time.Second)
315 // Set the stored block's mtime far enough in the past that we
316 // can see the difference between "timestamp didn't change"
317 // and "timestamp granularity is too low".
318 v.TouchWithDate(TestHash, time.Now().Add(-20*time.Second))
320 // Make sure v.Mtime() agrees the above Utime really worked.
321 if t0, err := v.Mtime(TestHash); err != nil || t0.IsZero() || !t0.Before(threshold) {
322 t.Errorf("Setting mtime failed: %v, %v", t0, err)
325 // Write the same block again.
326 if err := v.Put(context.Background(), TestHash, TestBlock); err != nil {
330 // Verify threshold < t1
331 if t1, err := v.Mtime(TestHash); err != nil {
333 } else if t1.Before(threshold) {
334 t.Errorf("t1 %v should be >= threshold %v after v.Put ", t1, threshold)
338 // Touching a non-existing block should result in error.
339 // Test should pass for both writable and read-only volumes
340 func testTouchNoSuchBlock(t TB, factory TestableVolumeFactory) {
344 if err := v.Touch(TestHash); err == nil {
345 t.Error("Expected error when attempted to touch a non-existing block")
349 // Invoking Mtime on a non-existing block should result in error.
350 // Test should pass for both writable and read-only volumes
351 func testMtimeNoSuchBlock(t TB, factory TestableVolumeFactory) {
355 if _, err := v.Mtime("12345678901234567890123456789012"); err == nil {
356 t.Error("Expected error when updating Mtime on a non-existing block")
360 // Put a few blocks and invoke IndexTo with:
363 // * with no such prefix
364 // Test should pass for both writable and read-only volumes
365 func testIndexTo(t TB, factory TestableVolumeFactory) {
369 // minMtime and maxMtime are the minimum and maximum
370 // acceptable values the index can report for our test
371 // blocks. 1-second precision is acceptable.
372 minMtime := time.Now().UTC().UnixNano()
373 minMtime -= minMtime % 1e9
375 v.PutRaw(TestHash, TestBlock)
376 v.PutRaw(TestHash2, TestBlock2)
377 v.PutRaw(TestHash3, TestBlock3)
379 maxMtime := time.Now().UTC().UnixNano()
380 if maxMtime%1e9 > 0 {
381 maxMtime -= maxMtime % 1e9
385 // Blocks whose names aren't Keep hashes should be omitted from
387 v.PutRaw("fffffffffnotreallyahashfffffffff", nil)
388 v.PutRaw("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", nil)
389 v.PutRaw("f0000000000000000000000000000000f", nil)
392 buf := new(bytes.Buffer)
394 indexRows := strings.Split(string(buf.Bytes()), "\n")
395 sort.Strings(indexRows)
396 sortedIndex := strings.Join(indexRows, "\n")
397 m := regexp.MustCompile(
398 `^\n` + TestHash + `\+\d+ (\d+)\n` +
399 TestHash3 + `\+\d+ \d+\n` +
400 TestHash2 + `\+\d+ \d+$`,
401 ).FindStringSubmatch(sortedIndex)
403 t.Errorf("Got index %q for empty prefix", sortedIndex)
405 mtime, err := strconv.ParseInt(m[1], 10, 64)
408 } else if mtime < minMtime || mtime > maxMtime {
409 t.Errorf("got %d for TestHash timestamp, expected %d <= t <= %d",
410 mtime, minMtime, maxMtime)
414 for _, prefix := range []string{"f", "f15", "f15ac"} {
415 buf = new(bytes.Buffer)
416 v.IndexTo(prefix, buf)
418 m, err := regexp.MatchString(`^`+TestHash2+`\+\d+ \d+\n$`, string(buf.Bytes()))
422 t.Errorf("Got index %q for prefix %s", string(buf.Bytes()), prefix)
426 for _, prefix := range []string{"zero", "zip", "zilch"} {
427 buf = new(bytes.Buffer)
428 err := v.IndexTo(prefix, buf)
430 t.Errorf("Got error on IndexTo with no such prefix %v", err.Error())
431 } else if buf.Len() != 0 {
432 t.Errorf("Expected empty list for IndexTo with no such prefix %s", prefix)
437 // Calling Delete() for a block immediately after writing it (not old enough)
438 // should neither delete the data nor return an error.
439 // Test is intended for only writable volumes
440 func testDeleteNewBlock(t TB, factory TestableVolumeFactory) {
443 theConfig.BlobSignatureTTL.Set("5m")
445 if v.Writable() == false {
449 v.Put(context.Background(), TestHash, TestBlock)
451 if err := v.Trash(TestHash); err != nil {
454 data := make([]byte, BlockSize)
455 n, err := v.Get(context.Background(), TestHash, data)
458 } else if bytes.Compare(data[:n], TestBlock) != 0 {
459 t.Errorf("Got data %+q, expected %+q", data[:n], TestBlock)
463 // Calling Delete() for a block with a timestamp older than
464 // BlobSignatureTTL seconds in the past should delete the data.
465 // Test is intended for only writable volumes
466 func testDeleteOldBlock(t TB, factory TestableVolumeFactory) {
469 theConfig.BlobSignatureTTL.Set("5m")
471 if v.Writable() == false {
475 v.Put(context.Background(), TestHash, TestBlock)
476 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
478 if err := v.Trash(TestHash); err != nil {
481 data := make([]byte, BlockSize)
482 if _, err := v.Get(context.Background(), TestHash, data); err == nil || !os.IsNotExist(err) {
483 t.Errorf("os.IsNotExist(%v) should have been true", err)
486 _, err := v.Mtime(TestHash)
487 if err == nil || !os.IsNotExist(err) {
488 t.Fatalf("os.IsNotExist(%v) should have been true", err)
491 err = v.Compare(context.Background(), TestHash, TestBlock)
492 if err == nil || !os.IsNotExist(err) {
493 t.Fatalf("os.IsNotExist(%v) should have been true", err)
496 indexBuf := new(bytes.Buffer)
497 v.IndexTo("", indexBuf)
498 if strings.Contains(string(indexBuf.Bytes()), TestHash) {
499 t.Fatalf("Found trashed block in IndexTo")
502 err = v.Touch(TestHash)
503 if err == nil || !os.IsNotExist(err) {
504 t.Fatalf("os.IsNotExist(%v) should have been true", err)
508 // Calling Delete() for a block that does not exist should result in error.
509 // Test should pass for both writable and read-only volumes
510 func testDeleteNoSuchBlock(t TB, factory TestableVolumeFactory) {
514 if err := v.Trash(TestHash2); err == nil {
515 t.Errorf("Expected error when attempting to delete a non-existing block")
519 // Invoke Status and verify that VolumeStatus is returned
520 // Test should pass for both writable and read-only volumes
521 func testStatus(t TB, factory TestableVolumeFactory) {
525 // Get node status and make a basic sanity check.
527 if status.DeviceNum == 0 {
528 t.Errorf("uninitialized device_num in %v", status)
531 if status.BytesFree == 0 {
532 t.Errorf("uninitialized bytes_free in %v", status)
535 if status.BytesUsed == 0 {
536 t.Errorf("uninitialized bytes_used in %v", status)
540 func getValueFrom(cv *prometheus.CounterVec, lbls prometheus.Labels) float64 {
541 c, _ := cv.GetMetricWith(lbls)
544 return pb.GetCounter().GetValue()
547 func testMetrics(t TB, factory TestableVolumeFactory) {
552 reg := prometheus.NewRegistry()
553 vm := newVolumeMetricsVecs(reg)
557 t.Error("Failed Start(): ", err)
559 opsC, _, ioC := vm.getCounterVecsFor(prometheus.Labels{"device_id": v.DeviceID()})
562 t.Error("ioBytes CounterVec is nil")
566 if getValueFrom(ioC, prometheus.Labels{"direction": "out"})+
567 getValueFrom(ioC, prometheus.Labels{"direction": "in"}) > 0 {
568 t.Error("ioBytes counter should be zero")
572 t.Error("opsCounter CounterVec is nil")
576 var c, writeOpCounter, readOpCounter float64
578 readOpType, writeOpType := v.ReadWriteOperationLabelValues()
579 writeOpCounter = getValueFrom(opsC, prometheus.Labels{"operation": writeOpType})
580 readOpCounter = getValueFrom(opsC, prometheus.Labels{"operation": readOpType})
582 // Test Put if volume is writable
584 err = v.Put(context.Background(), TestHash, TestBlock)
586 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
588 // Check that the write operations counter increased
589 c = getValueFrom(opsC, prometheus.Labels{"operation": writeOpType})
590 if c <= writeOpCounter {
591 t.Error("Operation(s) not counted on Put")
593 // Check that bytes counter is > 0
594 if getValueFrom(ioC, prometheus.Labels{"direction": "out"}) == 0 {
595 t.Error("ioBytes{direction=out} counter shouldn't be zero")
598 v.PutRaw(TestHash, TestBlock)
601 buf := make([]byte, BlockSize)
602 _, err = v.Get(context.Background(), TestHash, buf)
607 // Check that the operations counter increased
608 c = getValueFrom(opsC, prometheus.Labels{"operation": readOpType})
609 if c <= readOpCounter {
610 t.Error("Operation(s) not counted on Get")
612 // Check that the bytes "in" counter is > 0
613 if getValueFrom(ioC, prometheus.Labels{"direction": "in"}) == 0 {
614 t.Error("ioBytes{direction=in} counter shouldn't be zero")
618 // Invoke String for the volume; expect non-empty result
619 // Test should pass for both writable and read-only volumes
620 func testString(t TB, factory TestableVolumeFactory) {
624 if id := v.String(); len(id) == 0 {
625 t.Error("Got empty string for v.String()")
629 // Putting, updating, touching, and deleting blocks from a read-only volume result in error.
630 // Test is intended for only read-only volumes
631 func testUpdateReadOnly(t TB, factory TestableVolumeFactory) {
635 if v.Writable() == true {
639 v.PutRaw(TestHash, TestBlock)
640 buf := make([]byte, BlockSize)
642 // Get from read-only volume should succeed
643 _, err := v.Get(context.Background(), TestHash, buf)
645 t.Errorf("got err %v, expected nil", err)
648 // Put a new block to read-only volume should result in error
649 err = v.Put(context.Background(), TestHash2, TestBlock2)
651 t.Errorf("Expected error when putting block in a read-only volume")
653 _, err = v.Get(context.Background(), TestHash2, buf)
655 t.Errorf("Expected error when getting block whose put in read-only volume failed")
658 // Touch a block in read-only volume should result in error
659 err = v.Touch(TestHash)
661 t.Errorf("Expected error when touching block in a read-only volume")
664 // Delete a block from a read-only volume should result in error
665 err = v.Trash(TestHash)
667 t.Errorf("Expected error when deleting block from a read-only volume")
670 // Overwriting an existing block in read-only volume should result in error
671 err = v.Put(context.Background(), TestHash, TestBlock)
673 t.Errorf("Expected error when putting block in a read-only volume")
677 // Launch concurrent Gets
678 // Test should pass for both writable and read-only volumes
679 func testGetConcurrent(t TB, factory TestableVolumeFactory) {
683 v.PutRaw(TestHash, TestBlock)
684 v.PutRaw(TestHash2, TestBlock2)
685 v.PutRaw(TestHash3, TestBlock3)
687 sem := make(chan int)
689 buf := make([]byte, BlockSize)
690 n, err := v.Get(context.Background(), TestHash, buf)
692 t.Errorf("err1: %v", err)
694 if bytes.Compare(buf[:n], TestBlock) != 0 {
695 t.Errorf("buf should be %s, is %s", string(TestBlock), string(buf[:n]))
701 buf := make([]byte, BlockSize)
702 n, err := v.Get(context.Background(), TestHash2, buf)
704 t.Errorf("err2: %v", err)
706 if bytes.Compare(buf[:n], TestBlock2) != 0 {
707 t.Errorf("buf should be %s, is %s", string(TestBlock2), string(buf[:n]))
713 buf := make([]byte, BlockSize)
714 n, err := v.Get(context.Background(), TestHash3, buf)
716 t.Errorf("err3: %v", err)
718 if bytes.Compare(buf[:n], TestBlock3) != 0 {
719 t.Errorf("buf should be %s, is %s", string(TestBlock3), string(buf[:n]))
724 // Wait for all goroutines to finish
725 for done := 0; done < 3; done++ {
730 // Launch concurrent Puts
731 // Test is intended for only writable volumes
732 func testPutConcurrent(t TB, factory TestableVolumeFactory) {
736 if v.Writable() == false {
740 sem := make(chan int)
741 go func(sem chan int) {
742 err := v.Put(context.Background(), TestHash, TestBlock)
744 t.Errorf("err1: %v", err)
749 go func(sem chan int) {
750 err := v.Put(context.Background(), TestHash2, TestBlock2)
752 t.Errorf("err2: %v", err)
757 go func(sem chan int) {
758 err := v.Put(context.Background(), TestHash3, TestBlock3)
760 t.Errorf("err3: %v", err)
765 // Wait for all goroutines to finish
766 for done := 0; done < 3; done++ {
770 // Double check that we actually wrote the blocks we expected to write.
771 buf := make([]byte, BlockSize)
772 n, err := v.Get(context.Background(), TestHash, buf)
774 t.Errorf("Get #1: %v", err)
776 if bytes.Compare(buf[:n], TestBlock) != 0 {
777 t.Errorf("Get #1: expected %s, got %s", string(TestBlock), string(buf[:n]))
780 n, err = v.Get(context.Background(), TestHash2, buf)
782 t.Errorf("Get #2: %v", err)
784 if bytes.Compare(buf[:n], TestBlock2) != 0 {
785 t.Errorf("Get #2: expected %s, got %s", string(TestBlock2), string(buf[:n]))
788 n, err = v.Get(context.Background(), TestHash3, buf)
790 t.Errorf("Get #3: %v", err)
792 if bytes.Compare(buf[:n], TestBlock3) != 0 {
793 t.Errorf("Get #3: expected %s, got %s", string(TestBlock3), string(buf[:n]))
797 // Write and read back a full size block
798 func testPutFullBlock(t TB, factory TestableVolumeFactory) {
806 wdata := make([]byte, BlockSize)
808 wdata[BlockSize-1] = 'z'
809 hash := fmt.Sprintf("%x", md5.Sum(wdata))
810 err := v.Put(context.Background(), hash, wdata)
814 buf := make([]byte, BlockSize)
815 n, err := v.Get(context.Background(), hash, buf)
819 if bytes.Compare(buf[:n], wdata) != 0 {
820 t.Error("buf %+q != wdata %+q", buf[:n], wdata)
824 // With TrashLifetime != 0, perform:
825 // Trash an old block - which either raises ErrNotImplemented or succeeds
826 // Untrash - which either raises ErrNotImplemented or succeeds
827 // Get - which must succeed
828 func testTrashUntrash(t TB, factory TestableVolumeFactory) {
832 theConfig.TrashLifetime = 0
835 theConfig.TrashLifetime.Set("1h")
837 // put block and backdate it
838 v.PutRaw(TestHash, TestBlock)
839 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
841 buf := make([]byte, BlockSize)
842 n, err := v.Get(context.Background(), TestHash, buf)
846 if bytes.Compare(buf[:n], TestBlock) != 0 {
847 t.Errorf("Got data %+q, expected %+q", buf[:n], TestBlock)
851 err = v.Trash(TestHash)
852 if v.Writable() == false {
853 if err != MethodDisabledError {
856 } else if err != nil {
857 if err != ErrNotImplemented {
861 _, err = v.Get(context.Background(), TestHash, buf)
862 if err == nil || !os.IsNotExist(err) {
863 t.Errorf("os.IsNotExist(%v) should have been true", err)
867 err = v.Untrash(TestHash)
873 // Get the block - after trash and untrash sequence
874 n, err = v.Get(context.Background(), TestHash, buf)
878 if bytes.Compare(buf[:n], TestBlock) != 0 {
879 t.Errorf("Got data %+q, expected %+q", buf[:n], TestBlock)
883 func testTrashEmptyTrashUntrash(t TB, factory TestableVolumeFactory) {
886 defer func(orig arvados.Duration) {
887 theConfig.TrashLifetime = orig
888 }(theConfig.TrashLifetime)
890 checkGet := func() error {
891 buf := make([]byte, BlockSize)
892 n, err := v.Get(context.Background(), TestHash, buf)
896 if bytes.Compare(buf[:n], TestBlock) != 0 {
897 t.Fatalf("Got data %+q, expected %+q", buf[:n], TestBlock)
900 _, err = v.Mtime(TestHash)
905 err = v.Compare(context.Background(), TestHash, TestBlock)
910 indexBuf := new(bytes.Buffer)
911 v.IndexTo("", indexBuf)
912 if !strings.Contains(string(indexBuf.Bytes()), TestHash) {
913 return os.ErrNotExist
919 // First set: EmptyTrash before reaching the trash deadline.
921 theConfig.TrashLifetime.Set("1h")
923 v.PutRaw(TestHash, TestBlock)
924 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
932 err = v.Trash(TestHash)
933 if err == MethodDisabledError || err == ErrNotImplemented {
934 // Skip the trash tests for read-only volumes, and
935 // volume types that don't support TrashLifetime>0.
940 if err == nil || !os.IsNotExist(err) {
941 t.Fatalf("os.IsNotExist(%v) should have been true", err)
944 err = v.Touch(TestHash)
945 if err == nil || !os.IsNotExist(err) {
946 t.Fatalf("os.IsNotExist(%v) should have been true", err)
951 // Even after emptying the trash, we can untrash our block
952 // because the deadline hasn't been reached.
953 err = v.Untrash(TestHash)
963 err = v.Touch(TestHash)
968 // Because we Touch'ed, need to backdate again for next set of tests
969 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
971 // If the only block in the trash has already been untrashed,
972 // most volumes will fail a subsequent Untrash with a 404, but
973 // it's also acceptable for Untrash to succeed.
974 err = v.Untrash(TestHash)
975 if err != nil && !os.IsNotExist(err) {
976 t.Fatalf("Expected success or os.IsNotExist(), but got: %v", err)
979 // The additional Untrash should not interfere with our
980 // already-untrashed copy.
986 // Untrash might have updated the timestamp, so backdate again
987 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
989 // Second set: EmptyTrash after the trash deadline has passed.
991 theConfig.TrashLifetime.Set("1ns")
993 err = v.Trash(TestHash)
998 if err == nil || !os.IsNotExist(err) {
999 t.Fatalf("os.IsNotExist(%v) should have been true", err)
1002 // Even though 1ns has passed, we can untrash because we
1003 // haven't called EmptyTrash yet.
1004 err = v.Untrash(TestHash)
1013 // Trash it again, and this time call EmptyTrash so it really
1015 // (In Azure volumes, un/trash changes Mtime, so first backdate again)
1016 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
1017 _ = v.Trash(TestHash)
1019 if err == nil || !os.IsNotExist(err) {
1020 t.Fatalf("os.IsNotExist(%v) should have been true", err)
1024 // Untrash won't find it
1025 err = v.Untrash(TestHash)
1026 if err == nil || !os.IsNotExist(err) {
1027 t.Fatalf("os.IsNotExist(%v) should have been true", err)
1030 // Get block won't find it
1032 if err == nil || !os.IsNotExist(err) {
1033 t.Fatalf("os.IsNotExist(%v) should have been true", err)
1036 // Third set: If the same data block gets written again after
1037 // being trashed, and then the trash gets emptied, the newer
1038 // un-trashed copy doesn't get deleted along with it.
1040 v.PutRaw(TestHash, TestBlock)
1041 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
1043 theConfig.TrashLifetime.Set("1ns")
1044 err = v.Trash(TestHash)
1049 if err == nil || !os.IsNotExist(err) {
1050 t.Fatalf("os.IsNotExist(%v) should have been true", err)
1053 v.PutRaw(TestHash, TestBlock)
1054 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
1056 // EmptyTrash should not delete the untrashed copy.
1063 // Fourth set: If the same data block gets trashed twice with
1064 // different deadlines A and C, and then the trash is emptied
1065 // at intermediate time B (A < B < C), it is still possible to
1066 // untrash the block whose deadline is "C".
1068 v.PutRaw(TestHash, TestBlock)
1069 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
1071 theConfig.TrashLifetime.Set("1ns")
1072 err = v.Trash(TestHash)
1077 v.PutRaw(TestHash, TestBlock)
1078 v.TouchWithDate(TestHash, time.Now().Add(-2*theConfig.BlobSignatureTTL.Duration()))
1080 theConfig.TrashLifetime.Set("1h")
1081 err = v.Trash(TestHash)
1086 // EmptyTrash should not prevent us from recovering the
1087 // time.Hour ("C") trash
1089 err = v.Untrash(TestHash)