Merge branch 'pr/28'
[arvados.git] / services / keepstore / volume_generic_test.go
1 package main
2
3 import (
4         "bytes"
5         "crypto/md5"
6         "fmt"
7         "os"
8         "regexp"
9         "sort"
10         "strings"
11         "testing"
12         "time"
13 )
14
15 // A TestableVolumeFactory returns a new TestableVolume. The factory
16 // function, and the TestableVolume it returns, can use "t" to write
17 // logs, fail the current test, etc.
18 type TestableVolumeFactory func(t *testing.T) TestableVolume
19
20 // DoGenericVolumeTests runs a set of tests that every TestableVolume
21 // is expected to pass. It calls factory to create a new TestableVolume
22 // for each test case, to avoid leaking state between tests.
23 func DoGenericVolumeTests(t *testing.T, factory TestableVolumeFactory) {
24         testGet(t, factory)
25         testGetNoSuchBlock(t, factory)
26
27         testCompareNonexistent(t, factory)
28         testCompareSameContent(t, factory, TestHash, TestBlock)
29         testCompareSameContent(t, factory, EmptyHash, EmptyBlock)
30         testCompareWithCollision(t, factory, TestHash, TestBlock, []byte("baddata"))
31         testCompareWithCollision(t, factory, TestHash, TestBlock, EmptyBlock)
32         testCompareWithCollision(t, factory, EmptyHash, EmptyBlock, TestBlock)
33         testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, []byte("baddata"))
34         testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, EmptyBlock)
35         testCompareWithCorruptStoredData(t, factory, EmptyHash, EmptyBlock, []byte("baddata"))
36
37         testPutBlockWithSameContent(t, factory, TestHash, TestBlock)
38         testPutBlockWithSameContent(t, factory, EmptyHash, EmptyBlock)
39         testPutBlockWithDifferentContent(t, factory, TestHash, TestBlock, TestBlock2)
40         testPutBlockWithDifferentContent(t, factory, TestHash, EmptyBlock, TestBlock)
41         testPutBlockWithDifferentContent(t, factory, TestHash, TestBlock, EmptyBlock)
42         testPutBlockWithDifferentContent(t, factory, EmptyHash, EmptyBlock, TestBlock)
43         testPutMultipleBlocks(t, factory)
44
45         testPutAndTouch(t, factory)
46         testTouchNoSuchBlock(t, factory)
47
48         testMtimeNoSuchBlock(t, factory)
49
50         testIndexTo(t, factory)
51
52         testDeleteNewBlock(t, factory)
53         testDeleteOldBlock(t, factory)
54         testDeleteNoSuchBlock(t, factory)
55
56         testStatus(t, factory)
57
58         testString(t, factory)
59
60         testUpdateReadOnly(t, factory)
61
62         testGetConcurrent(t, factory)
63         testPutConcurrent(t, factory)
64
65         testPutFullBlock(t, factory)
66 }
67
68 // Put a test block, get it and verify content
69 // Test should pass for both writable and read-only volumes
70 func testGet(t *testing.T, factory TestableVolumeFactory) {
71         v := factory(t)
72         defer v.Teardown()
73
74         v.PutRaw(TestHash, TestBlock)
75
76         buf, err := v.Get(TestHash)
77         if err != nil {
78                 t.Fatal(err)
79         }
80
81         bufs.Put(buf)
82
83         if bytes.Compare(buf, TestBlock) != 0 {
84                 t.Errorf("expected %s, got %s", string(TestBlock), string(buf))
85         }
86 }
87
88 // Invoke get on a block that does not exist in volume; should result in error
89 // Test should pass for both writable and read-only volumes
90 func testGetNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
91         v := factory(t)
92         defer v.Teardown()
93
94         if _, err := v.Get(TestHash2); err == nil {
95                 t.Errorf("Expected error while getting non-existing block %v", TestHash2)
96         }
97 }
98
99 // Compare() should return os.ErrNotExist if the block does not exist.
100 // Otherwise, writing new data causes CompareAndTouch() to generate
101 // error logs even though everything is working fine.
102 func testCompareNonexistent(t *testing.T, factory TestableVolumeFactory) {
103         v := factory(t)
104         defer v.Teardown()
105
106         err := v.Compare(TestHash, TestBlock)
107         if err != os.ErrNotExist {
108                 t.Errorf("Got err %T %q, expected os.ErrNotExist", err, err)
109         }
110 }
111
112 // Put a test block and compare the locator with same content
113 // Test should pass for both writable and read-only volumes
114 func testCompareSameContent(t *testing.T, factory TestableVolumeFactory, testHash string, testData []byte) {
115         v := factory(t)
116         defer v.Teardown()
117
118         v.PutRaw(testHash, testData)
119
120         // Compare the block locator with same content
121         err := v.Compare(testHash, testData)
122         if err != nil {
123                 t.Errorf("Got err %q, expected nil", err)
124         }
125 }
126
127 // Test behavior of Compare() when stored data matches expected
128 // checksum but differs from new data we need to store. Requires
129 // testHash = md5(testDataA).
130 //
131 // Test should pass for both writable and read-only volumes
132 func testCompareWithCollision(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
133         v := factory(t)
134         defer v.Teardown()
135
136         v.PutRaw(testHash, testDataA)
137
138         // Compare the block locator with different content; collision
139         err := v.Compare(TestHash, testDataB)
140         if err == nil {
141                 t.Errorf("Got err nil, expected error due to collision")
142         }
143 }
144
145 // Test behavior of Compare() when stored data has become
146 // corrupted. Requires testHash = md5(testDataA) != md5(testDataB).
147 //
148 // Test should pass for both writable and read-only volumes
149 func testCompareWithCorruptStoredData(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
150         v := factory(t)
151         defer v.Teardown()
152
153         v.PutRaw(TestHash, testDataB)
154
155         err := v.Compare(testHash, testDataA)
156         if err == nil || err == CollisionError {
157                 t.Errorf("Got err %+v, expected non-collision error", err)
158         }
159 }
160
161 // Put a block and put again with same content
162 // Test is intended for only writable volumes
163 func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory, testHash string, testData []byte) {
164         v := factory(t)
165         defer v.Teardown()
166
167         if v.Writable() == false {
168                 return
169         }
170
171         err := v.Put(testHash, testData)
172         if err != nil {
173                 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
174         }
175
176         err = v.Put(testHash, testData)
177         if err != nil {
178                 t.Errorf("Got err putting block second time %q: %q, expected nil", TestBlock, err)
179         }
180 }
181
182 // Put a block and put again with different content
183 // Test is intended for only writable volumes
184 func testPutBlockWithDifferentContent(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
185         v := factory(t)
186         defer v.Teardown()
187
188         if v.Writable() == false {
189                 return
190         }
191
192         err := v.Put(testHash, testDataA)
193         if err != nil {
194                 t.Errorf("Got err putting block %q: %q, expected nil", testDataA, err)
195         }
196
197         putErr := v.Put(testHash, testDataB)
198         buf, getErr := v.Get(testHash)
199         if putErr == nil {
200                 // Put must not return a nil error unless it has
201                 // overwritten the existing data.
202                 if bytes.Compare(buf, testDataB) != 0 {
203                         t.Errorf("Put succeeded but Get returned %+q, expected %+q", buf, testDataB)
204                 }
205         } else {
206                 // It is permissible for Put to fail, but it must
207                 // leave us with either the original data, the new
208                 // data, or nothing at all.
209                 if getErr == nil && bytes.Compare(buf, testDataA) != 0 && bytes.Compare(buf, testDataB) != 0 {
210                         t.Errorf("Put failed but Get returned %+q, which is neither %+q nor %+q", buf, testDataA, testDataB)
211                 }
212         }
213         if getErr == nil {
214                 bufs.Put(buf)
215         }
216 }
217
218 // Put and get multiple blocks
219 // Test is intended for only writable volumes
220 func testPutMultipleBlocks(t *testing.T, factory TestableVolumeFactory) {
221         v := factory(t)
222         defer v.Teardown()
223
224         if v.Writable() == false {
225                 return
226         }
227
228         err := v.Put(TestHash, TestBlock)
229         if err != nil {
230                 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
231         }
232
233         err = v.Put(TestHash2, TestBlock2)
234         if err != nil {
235                 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock2, err)
236         }
237
238         err = v.Put(TestHash3, TestBlock3)
239         if err != nil {
240                 t.Errorf("Got err putting block %q: %q, expected nil", TestBlock3, err)
241         }
242
243         data, err := v.Get(TestHash)
244         if err != nil {
245                 t.Error(err)
246         } else {
247                 if bytes.Compare(data, TestBlock) != 0 {
248                         t.Errorf("Block present, but got %+q, expected %+q", data, TestBlock)
249                 }
250                 bufs.Put(data)
251         }
252
253         data, err = v.Get(TestHash2)
254         if err != nil {
255                 t.Error(err)
256         } else {
257                 if bytes.Compare(data, TestBlock2) != 0 {
258                         t.Errorf("Block present, but got %+q, expected %+q", data, TestBlock2)
259                 }
260                 bufs.Put(data)
261         }
262
263         data, err = v.Get(TestHash3)
264         if err != nil {
265                 t.Error(err)
266         } else {
267                 if bytes.Compare(data, TestBlock3) != 0 {
268                         t.Errorf("Block present, but to %+q, expected %+q", data, TestBlock3)
269                 }
270                 bufs.Put(data)
271         }
272 }
273
274 // testPutAndTouch
275 //   Test that when applying PUT to a block that already exists,
276 //   the block's modification time is updated.
277 // Test is intended for only writable volumes
278 func testPutAndTouch(t *testing.T, factory TestableVolumeFactory) {
279         v := factory(t)
280         defer v.Teardown()
281
282         if v.Writable() == false {
283                 return
284         }
285
286         if err := v.Put(TestHash, TestBlock); err != nil {
287                 t.Error(err)
288         }
289
290         // We'll verify { t0 < threshold < t1 }, where t0 is the
291         // existing block's timestamp on disk before Put() and t1 is
292         // its timestamp after Put().
293         threshold := time.Now().Add(-time.Second)
294
295         // Set the stored block's mtime far enough in the past that we
296         // can see the difference between "timestamp didn't change"
297         // and "timestamp granularity is too low".
298         v.TouchWithDate(TestHash, time.Now().Add(-20*time.Second))
299
300         // Make sure v.Mtime() agrees the above Utime really worked.
301         if t0, err := v.Mtime(TestHash); err != nil || t0.IsZero() || !t0.Before(threshold) {
302                 t.Errorf("Setting mtime failed: %v, %v", t0, err)
303         }
304
305         // Write the same block again.
306         if err := v.Put(TestHash, TestBlock); err != nil {
307                 t.Error(err)
308         }
309
310         // Verify threshold < t1
311         if t1, err := v.Mtime(TestHash); err != nil {
312                 t.Error(err)
313         } else if t1.Before(threshold) {
314                 t.Errorf("t1 %v should be >= threshold %v after v.Put ", t1, threshold)
315         }
316 }
317
318 // Touching a non-existing block should result in error.
319 // Test should pass for both writable and read-only volumes
320 func testTouchNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
321         v := factory(t)
322         defer v.Teardown()
323
324         if err := v.Touch(TestHash); err == nil {
325                 t.Error("Expected error when attempted to touch a non-existing block")
326         }
327 }
328
329 // Invoking Mtime on a non-existing block should result in error.
330 // Test should pass for both writable and read-only volumes
331 func testMtimeNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
332         v := factory(t)
333         defer v.Teardown()
334
335         if _, err := v.Mtime("12345678901234567890123456789012"); err == nil {
336                 t.Error("Expected error when updating Mtime on a non-existing block")
337         }
338 }
339
340 // Put a few blocks and invoke IndexTo with:
341 // * no prefix
342 // * with a prefix
343 // * with no such prefix
344 // Test should pass for both writable and read-only volumes
345 func testIndexTo(t *testing.T, factory TestableVolumeFactory) {
346         v := factory(t)
347         defer v.Teardown()
348
349         v.PutRaw(TestHash, TestBlock)
350         v.PutRaw(TestHash2, TestBlock2)
351         v.PutRaw(TestHash3, TestBlock3)
352
353         // Blocks whose names aren't Keep hashes should be omitted from
354         // index
355         v.PutRaw("fffffffffnotreallyahashfffffffff", nil)
356         v.PutRaw("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", nil)
357         v.PutRaw("f0000000000000000000000000000000f", nil)
358         v.PutRaw("f00", nil)
359
360         buf := new(bytes.Buffer)
361         v.IndexTo("", buf)
362         indexRows := strings.Split(string(buf.Bytes()), "\n")
363         sort.Strings(indexRows)
364         sortedIndex := strings.Join(indexRows, "\n")
365         m, err := regexp.MatchString(
366                 `^\n`+TestHash+`\+\d+ \d+\n`+
367                         TestHash3+`\+\d+ \d+\n`+
368                         TestHash2+`\+\d+ \d+$`,
369                 sortedIndex)
370         if err != nil {
371                 t.Error(err)
372         } else if !m {
373                 t.Errorf("Got index %q for empty prefix", sortedIndex)
374         }
375
376         for _, prefix := range []string{"f", "f15", "f15ac"} {
377                 buf = new(bytes.Buffer)
378                 v.IndexTo(prefix, buf)
379
380                 m, err := regexp.MatchString(`^`+TestHash2+`\+\d+ \d+\n$`, string(buf.Bytes()))
381                 if err != nil {
382                         t.Error(err)
383                 } else if !m {
384                         t.Errorf("Got index %q for prefix %s", string(buf.Bytes()), prefix)
385                 }
386         }
387
388         for _, prefix := range []string{"zero", "zip", "zilch"} {
389                 buf = new(bytes.Buffer)
390                 v.IndexTo(prefix, buf)
391                 if err != nil {
392                         t.Errorf("Got error on IndexTo with no such prefix %v", err.Error())
393                 } else if buf.Len() != 0 {
394                         t.Errorf("Expected empty list for IndexTo with no such prefix %s", prefix)
395                 }
396         }
397 }
398
399 // Calling Delete() for a block immediately after writing it (not old enough)
400 // should neither delete the data nor return an error.
401 // Test is intended for only writable volumes
402 func testDeleteNewBlock(t *testing.T, factory TestableVolumeFactory) {
403         v := factory(t)
404         defer v.Teardown()
405         blobSignatureTTL = 300 * time.Second
406
407         if v.Writable() == false {
408                 return
409         }
410
411         v.Put(TestHash, TestBlock)
412
413         if err := v.Delete(TestHash); err != nil {
414                 t.Error(err)
415         }
416         data, err := v.Get(TestHash)
417         if err != nil {
418                 t.Error(err)
419         } else {
420                 if bytes.Compare(data, TestBlock) != 0 {
421                         t.Errorf("Got data %+q, expected %+q", data, TestBlock)
422                 }
423                 bufs.Put(data)
424         }
425 }
426
427 // Calling Delete() for a block with a timestamp older than
428 // blobSignatureTTL seconds in the past should delete the data.
429 // Test is intended for only writable volumes
430 func testDeleteOldBlock(t *testing.T, factory TestableVolumeFactory) {
431         v := factory(t)
432         defer v.Teardown()
433         blobSignatureTTL = 300 * time.Second
434
435         if v.Writable() == false {
436                 return
437         }
438
439         v.Put(TestHash, TestBlock)
440         v.TouchWithDate(TestHash, time.Now().Add(-2*blobSignatureTTL))
441
442         if err := v.Delete(TestHash); err != nil {
443                 t.Error(err)
444         }
445         if _, err := v.Get(TestHash); err == nil || !os.IsNotExist(err) {
446                 t.Errorf("os.IsNotExist(%v) should have been true", err)
447         }
448 }
449
450 // Calling Delete() for a block that does not exist should result in error.
451 // Test should pass for both writable and read-only volumes
452 func testDeleteNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
453         v := factory(t)
454         defer v.Teardown()
455
456         if err := v.Delete(TestHash2); err == nil {
457                 t.Errorf("Expected error when attempting to delete a non-existing block")
458         }
459 }
460
461 // Invoke Status and verify that VolumeStatus is returned
462 // Test should pass for both writable and read-only volumes
463 func testStatus(t *testing.T, factory TestableVolumeFactory) {
464         v := factory(t)
465         defer v.Teardown()
466
467         // Get node status and make a basic sanity check.
468         status := v.Status()
469         if status.DeviceNum == 0 {
470                 t.Errorf("uninitialized device_num in %v", status)
471         }
472
473         if status.BytesFree == 0 {
474                 t.Errorf("uninitialized bytes_free in %v", status)
475         }
476
477         if status.BytesUsed == 0 {
478                 t.Errorf("uninitialized bytes_used in %v", status)
479         }
480 }
481
482 // Invoke String for the volume; expect non-empty result
483 // Test should pass for both writable and read-only volumes
484 func testString(t *testing.T, factory TestableVolumeFactory) {
485         v := factory(t)
486         defer v.Teardown()
487
488         if id := v.String(); len(id) == 0 {
489                 t.Error("Got empty string for v.String()")
490         }
491 }
492
493 // Putting, updating, touching, and deleting blocks from a read-only volume result in error.
494 // Test is intended for only read-only volumes
495 func testUpdateReadOnly(t *testing.T, factory TestableVolumeFactory) {
496         v := factory(t)
497         defer v.Teardown()
498
499         if v.Writable() == true {
500                 return
501         }
502
503         v.PutRaw(TestHash, TestBlock)
504
505         // Get from read-only volume should succeed
506         _, err := v.Get(TestHash)
507         if err != nil {
508                 t.Errorf("got err %v, expected nil", err)
509         }
510
511         // Put a new block to read-only volume should result in error
512         err = v.Put(TestHash2, TestBlock2)
513         if err == nil {
514                 t.Errorf("Expected error when putting block in a read-only volume")
515         }
516         _, err = v.Get(TestHash2)
517         if err == nil {
518                 t.Errorf("Expected error when getting block whose put in read-only volume failed")
519         }
520
521         // Touch a block in read-only volume should result in error
522         err = v.Touch(TestHash)
523         if err == nil {
524                 t.Errorf("Expected error when touching block in a read-only volume")
525         }
526
527         // Delete a block from a read-only volume should result in error
528         err = v.Delete(TestHash)
529         if err == nil {
530                 t.Errorf("Expected error when deleting block from a read-only volume")
531         }
532
533         // Overwriting an existing block in read-only volume should result in error
534         err = v.Put(TestHash, TestBlock)
535         if err == nil {
536                 t.Errorf("Expected error when putting block in a read-only volume")
537         }
538 }
539
540 // Launch concurrent Gets
541 // Test should pass for both writable and read-only volumes
542 func testGetConcurrent(t *testing.T, factory TestableVolumeFactory) {
543         v := factory(t)
544         defer v.Teardown()
545
546         v.PutRaw(TestHash, TestBlock)
547         v.PutRaw(TestHash2, TestBlock2)
548         v.PutRaw(TestHash3, TestBlock3)
549
550         sem := make(chan int)
551         go func(sem chan int) {
552                 buf, err := v.Get(TestHash)
553                 if err != nil {
554                         t.Errorf("err1: %v", err)
555                 }
556                 bufs.Put(buf)
557                 if bytes.Compare(buf, TestBlock) != 0 {
558                         t.Errorf("buf should be %s, is %s", string(TestBlock), string(buf))
559                 }
560                 sem <- 1
561         }(sem)
562
563         go func(sem chan int) {
564                 buf, err := v.Get(TestHash2)
565                 if err != nil {
566                         t.Errorf("err2: %v", err)
567                 }
568                 bufs.Put(buf)
569                 if bytes.Compare(buf, TestBlock2) != 0 {
570                         t.Errorf("buf should be %s, is %s", string(TestBlock2), string(buf))
571                 }
572                 sem <- 1
573         }(sem)
574
575         go func(sem chan int) {
576                 buf, err := v.Get(TestHash3)
577                 if err != nil {
578                         t.Errorf("err3: %v", err)
579                 }
580                 bufs.Put(buf)
581                 if bytes.Compare(buf, TestBlock3) != 0 {
582                         t.Errorf("buf should be %s, is %s", string(TestBlock3), string(buf))
583                 }
584                 sem <- 1
585         }(sem)
586
587         // Wait for all goroutines to finish
588         for done := 0; done < 3; {
589                 done += <-sem
590         }
591 }
592
593 // Launch concurrent Puts
594 // Test is intended for only writable volumes
595 func testPutConcurrent(t *testing.T, factory TestableVolumeFactory) {
596         v := factory(t)
597         defer v.Teardown()
598
599         if v.Writable() == false {
600                 return
601         }
602
603         sem := make(chan int)
604         go func(sem chan int) {
605                 err := v.Put(TestHash, TestBlock)
606                 if err != nil {
607                         t.Errorf("err1: %v", err)
608                 }
609                 sem <- 1
610         }(sem)
611
612         go func(sem chan int) {
613                 err := v.Put(TestHash2, TestBlock2)
614                 if err != nil {
615                         t.Errorf("err2: %v", err)
616                 }
617                 sem <- 1
618         }(sem)
619
620         go func(sem chan int) {
621                 err := v.Put(TestHash3, TestBlock3)
622                 if err != nil {
623                         t.Errorf("err3: %v", err)
624                 }
625                 sem <- 1
626         }(sem)
627
628         // Wait for all goroutines to finish
629         for done := 0; done < 3; {
630                 done += <-sem
631         }
632
633         // Double check that we actually wrote the blocks we expected to write.
634         buf, err := v.Get(TestHash)
635         if err != nil {
636                 t.Errorf("Get #1: %v", err)
637         }
638         bufs.Put(buf)
639         if bytes.Compare(buf, TestBlock) != 0 {
640                 t.Errorf("Get #1: expected %s, got %s", string(TestBlock), string(buf))
641         }
642
643         buf, err = v.Get(TestHash2)
644         if err != nil {
645                 t.Errorf("Get #2: %v", err)
646         }
647         bufs.Put(buf)
648         if bytes.Compare(buf, TestBlock2) != 0 {
649                 t.Errorf("Get #2: expected %s, got %s", string(TestBlock2), string(buf))
650         }
651
652         buf, err = v.Get(TestHash3)
653         if err != nil {
654                 t.Errorf("Get #3: %v", err)
655         }
656         bufs.Put(buf)
657         if bytes.Compare(buf, TestBlock3) != 0 {
658                 t.Errorf("Get #3: expected %s, got %s", string(TestBlock3), string(buf))
659         }
660 }
661
662 // Write and read back a full size block
663 func testPutFullBlock(t *testing.T, factory TestableVolumeFactory) {
664         v := factory(t)
665         defer v.Teardown()
666
667         if !v.Writable() {
668                 return
669         }
670
671         wdata := make([]byte, BlockSize)
672         wdata[0] = 'a'
673         wdata[BlockSize-1] = 'z'
674         hash := fmt.Sprintf("%x", md5.Sum(wdata))
675         err := v.Put(hash, wdata)
676         if err != nil {
677                 t.Fatal(err)
678         }
679         rdata, err := v.Get(hash)
680         if err != nil {
681                 t.Error(err)
682         } else {
683                 defer bufs.Put(rdata)
684         }
685         if bytes.Compare(rdata, wdata) != 0 {
686                 t.Error("rdata != wdata")
687         }
688 }