Merge branch '7179-generic-volume-tests' of git.curoverse.com:arvados into 7179-gener...
[arvados.git] / services / keepstore / volume_generic_test.go
1 package main
2
3 import (
4         "bytes"
5         "os"
6         "regexp"
7         "sort"
8         "strings"
9         "testing"
10         "time"
11 )
12
13 // A TestableVolumeFactory returns a new TestableVolume. The factory
14 // function, and the TestableVolume it returns, can use t to write
15 // logs, fail the current test, etc.
16 type TestableVolumeFactory func(t *testing.T) TestableVolume
17
18 // DoGenericVolumeTests runs a set of tests that every TestableVolume
19 // is expected to pass. It calls factory to create a new writable
20 // TestableVolume for each test case, to avoid leaking state between
21 // tests.
22 func DoGenericVolumeTests(t *testing.T, factory TestableVolumeFactory) {
23         testGet(t, factory)
24         testGetNoSuchBlock(t, factory)
25
26         testCompareSameContent(t, factory)
27         testCompareWithDifferentContent(t, factory)
28         testCompareWithBadData(t, factory)
29
30         testPutBlockWithSameContent(t, factory)
31         testPutBlockWithDifferentContent(t, factory)
32         testPutMultipleBlocks(t, factory)
33
34         testPutAndTouch(t, factory)
35         testTouchNoSuchBlock(t, factory)
36
37         testMtimeNoSuchBlock(t, factory)
38
39         testIndexTo(t, factory)
40
41         testDeleteNewBlock(t, factory)
42         testDeleteOldBlock(t, factory)
43         testDeleteNoSuchBlock(t, factory)
44
45         testStatus(t, factory)
46
47         testString(t, factory)
48
49         testWritableTrue(t, factory)
50
51         testGetSerialized(t, factory)
52         testPutSerialized(t, factory)
53 }
54
55 // DoGenericReadOnlyVolumeTests runs a set of tests that every
56 // read-only TestableVolume is expected to pass. It calls factory
57 // to create a new read-only TestableVolume for each test case,
58 // to avoid leaking state between tests.
59 func DoGenericReadOnlyVolumeTests(t *testing.T, factory TestableVolumeFactory) {
60         testWritableFalse(t, factory)
61         testUpdateReadOnly(t, factory)
62 }
63
64 // Put a test block, get it and verify content
65 func testGet(t *testing.T, factory TestableVolumeFactory) {
66         v := factory(t)
67         defer v.Teardown()
68         v.Put(TEST_HASH, TEST_BLOCK)
69
70         buf, err := v.Get(TEST_HASH)
71         if err != nil {
72                 t.Error(err)
73         }
74         if bytes.Compare(buf, TEST_BLOCK) != 0 {
75                 t.Errorf("expected %s, got %s", string(TEST_BLOCK), string(buf))
76         }
77 }
78
79 // Invoke get on a block that does not exist in volume; should result in error
80 func testGetNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
81         v := factory(t)
82         defer v.Teardown()
83         v.Put(TEST_HASH, TEST_BLOCK)
84
85         if _, err := v.Get(TEST_HASH_2); err == nil {
86                 t.Errorf("Expected error while getting non-existing block %v", TEST_HASH_2)
87         }
88 }
89
90 // Put a test block and compare the locator with same content
91 func testCompareSameContent(t *testing.T, factory TestableVolumeFactory) {
92         v := factory(t)
93         defer v.Teardown()
94
95         v.Put(TEST_HASH, TEST_BLOCK)
96
97         // Compare the block locator with same content
98         err := v.Compare(TEST_HASH, TEST_BLOCK)
99         if err != nil {
100                 t.Errorf("Got err %q, expected nil", err)
101         }
102 }
103
104 // Put a test block and compare the locator with a different content
105 // Expect error due to collision
106 func testCompareWithDifferentContent(t *testing.T, factory TestableVolumeFactory) {
107         v := factory(t)
108         defer v.Teardown()
109
110         v.Put(TEST_HASH, TEST_BLOCK)
111
112         // Compare the block locator with different content; collision
113         err := v.Compare(TEST_HASH, []byte("baddata"))
114         if err == nil {
115                 t.Errorf("Expected error due to collision")
116         }
117 }
118
119 // Put a test block with bad data (hash does not match, but Put does not verify)
120 // Compare the locator with good data whose has matches with locator
121 // Expect error due to corruption.
122 func testCompareWithBadData(t *testing.T, factory TestableVolumeFactory) {
123         v := factory(t)
124         defer v.Teardown()
125
126         v.Put(TEST_HASH, []byte("baddata"))
127
128         err := v.Compare(TEST_HASH, TEST_BLOCK)
129         if err == nil {
130                 t.Errorf("Expected error due to corruption")
131         }
132 }
133
134 // Put a block and put again with same content
135 func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory) {
136         v := factory(t)
137         defer v.Teardown()
138
139         err := v.Put(TEST_HASH, TEST_BLOCK)
140         if err != nil {
141                 t.Errorf("Got err putting block %q: %q, expected nil", TEST_BLOCK, err)
142         }
143
144         err = v.Put(TEST_HASH, TEST_BLOCK)
145         if err != nil {
146                 t.Errorf("Got err putting block second time %q: %q, expected nil", TEST_BLOCK, err)
147         }
148 }
149
150 // Put a block and put again with different content
151 func testPutBlockWithDifferentContent(t *testing.T, factory TestableVolumeFactory) {
152         v := factory(t)
153         defer v.Teardown()
154
155         err := v.Put(TEST_HASH, TEST_BLOCK)
156         if err != nil {
157                 t.Errorf("Got err putting block %q: %q, expected nil", TEST_BLOCK, err)
158         }
159
160         putErr := v.Put(TEST_HASH, TEST_BLOCK_2)
161         buf, getErr := v.Get(TEST_HASH)
162         if putErr == nil {
163                 // Put must not return a nil error unless it has
164                 // overwritten the existing data.
165                 if bytes.Compare(buf, TEST_BLOCK_2) != 0 {
166                         t.Errorf("Put succeeded but Get returned %+v, expected %+v", buf, TEST_BLOCK_2)
167                 }
168         } else {
169                 // It is permissible for Put to fail, but it must
170                 // leave us with either the original data, the new
171                 // data, or nothing at all.
172                 if getErr == nil && bytes.Compare(buf, TEST_BLOCK) != 0 && bytes.Compare(buf, TEST_BLOCK_2) != 0 {
173                         t.Errorf("Put failed but Get returned %+v, which is neither %+v nor %+v", buf, TEST_BLOCK, TEST_BLOCK_2)
174                 }
175         }
176         if getErr == nil {
177                 bufs.Put(buf)
178         }
179 }
180
181 // Put and get multiple blocks
182 func testPutMultipleBlocks(t *testing.T, factory TestableVolumeFactory) {
183         v := factory(t)
184         defer v.Teardown()
185
186         err := v.Put(TEST_HASH, TEST_BLOCK)
187         if err != nil {
188                 t.Errorf("Got err putting block %q: %q, expected nil", TEST_BLOCK, err)
189         }
190
191         err = v.Put(TEST_HASH_2, TEST_BLOCK_2)
192         if err != nil {
193                 t.Errorf("Got err putting block %q: %q, expected nil", TEST_BLOCK_2, err)
194         }
195
196         err = v.Put(TEST_HASH_3, TEST_BLOCK_3)
197         if err != nil {
198                 t.Errorf("Got err putting block %q: %q, expected nil", TEST_BLOCK_3, err)
199         }
200
201         if data, err := v.Get(TEST_HASH); err != nil {
202                 t.Error(err)
203         } else if bytes.Compare(data, TEST_BLOCK) != 0 {
204                 t.Errorf("Block present, but content is incorrect: Expected: %v  Found: %v", data, TEST_BLOCK)
205         }
206
207         if data, err := v.Get(TEST_HASH_2); err != nil {
208                 t.Error(err)
209         } else if bytes.Compare(data, TEST_BLOCK_2) != 0 {
210                 t.Errorf("Block present, but content is incorrect: Expected: %v  Found: %v", data, TEST_BLOCK_2)
211         }
212
213         if data, err := v.Get(TEST_HASH_3); err != nil {
214                 t.Error(err)
215         } else if bytes.Compare(data, TEST_BLOCK_3) != 0 {
216                 t.Errorf("Block present, but content is incorrect: Expected: %v  Found: %v", data, TEST_BLOCK_3)
217         }
218 }
219
220 // testPutAndTouch
221 //   Test that when applying PUT to a block that already exists,
222 //   the block's modification time is updated.
223 func testPutAndTouch(t *testing.T, factory TestableVolumeFactory) {
224         v := factory(t)
225         defer v.Teardown()
226
227         if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
228                 t.Error(err)
229         }
230
231         // We'll verify { t0 < threshold < t1 }, where t0 is the
232         // existing block's timestamp on disk before Put() and t1 is
233         // its timestamp after Put().
234         threshold := time.Now().Add(-time.Second)
235
236         // Set the stored block's mtime far enough in the past that we
237         // can see the difference between "timestamp didn't change"
238         // and "timestamp granularity is too low".
239         v.TouchWithDate(TEST_HASH, time.Now().Add(-20*time.Second))
240
241         // Make sure v.Mtime() agrees the above Utime really worked.
242         if t0, err := v.Mtime(TEST_HASH); err != nil || t0.IsZero() || !t0.Before(threshold) {
243                 t.Errorf("Setting mtime failed: %v, %v", t0, err)
244         }
245
246         // Write the same block again.
247         if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
248                 t.Error(err)
249         }
250
251         // Verify threshold < t1
252         if t1, err := v.Mtime(TEST_HASH); err != nil {
253                 t.Error(err)
254         } else if t1.Before(threshold) {
255                 t.Errorf("t1 %v should be >= threshold %v after v.Put ", t1, threshold)
256         }
257 }
258
259 // Touching a non-existing block should result in error.
260 func testTouchNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
261         v := factory(t)
262         defer v.Teardown()
263
264         if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
265                 t.Error(err)
266         }
267
268         if err := v.Touch(TEST_HASH); err != nil {
269                 t.Error("Expected error when attempted to touch a non-existing block")
270         }
271 }
272
273 // Invoking Mtime on a non-existing block should result in error.
274 func testMtimeNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
275         v := factory(t)
276         defer v.Teardown()
277
278         if _, err := v.Mtime("12345678901234567890123456789012"); err == nil {
279                 t.Error("Expected error when updating Mtime on a non-existing block")
280         }
281 }
282
283 // Put a few blocks and invoke IndexTo with:
284 // * no prefix
285 // * with a prefix
286 // * with no such prefix
287 func testIndexTo(t *testing.T, factory TestableVolumeFactory) {
288         v := factory(t)
289         defer v.Teardown()
290
291         v.Put(TEST_HASH, TEST_BLOCK)
292         v.Put(TEST_HASH_2, TEST_BLOCK_2)
293         v.Put(TEST_HASH_3, TEST_BLOCK_3)
294
295         buf := new(bytes.Buffer)
296         v.IndexTo("", buf)
297         index_rows := strings.Split(string(buf.Bytes()), "\n")
298         sort.Strings(index_rows)
299         sorted_index := strings.Join(index_rows, "\n")
300         m, err := regexp.MatchString(
301                 `^\n`+TEST_HASH+`\+\d+ \d+\n`+
302                         TEST_HASH_3+`\+\d+ \d+\n`+
303                         TEST_HASH_2+`\+\d+ \d+$`,
304                 sorted_index)
305         if err != nil {
306                 t.Error(err)
307         } else if !m {
308                 t.Errorf("Got index %q for empty prefix", sorted_index)
309         }
310
311         for _, prefix := range []string{"f", "f15", "f15ac"} {
312                 buf = new(bytes.Buffer)
313                 v.IndexTo(prefix, buf)
314
315                 m, err := regexp.MatchString(`^`+TEST_HASH_2+`\+\d+ \d+\n$`, string(buf.Bytes()))
316                 if err != nil {
317                         t.Error(err)
318                 } else if !m {
319                         t.Errorf("Got index %q for prefix %s", string(buf.Bytes()), prefix)
320                 }
321         }
322
323         for _, prefix := range []string{"zero", "zip", "zilch"} {
324                 buf = new(bytes.Buffer)
325                 v.IndexTo(prefix, buf)
326                 if err != nil {
327                         t.Errorf("Got error on IndexTo with no such prefix %v", err.Error())
328                 } else if buf.Len() != 0 {
329                         t.Errorf("Expected empty list for IndexTo with no such prefix %s", prefix)
330                 }
331         }
332 }
333
334 // Calling Delete() for a block immediately after writing it (not old enough)
335 // should neither delete the data nor return an error.
336 func testDeleteNewBlock(t *testing.T, factory TestableVolumeFactory) {
337         v := factory(t)
338         defer v.Teardown()
339         v.Put(TEST_HASH, TEST_BLOCK)
340
341         if err := v.Delete(TEST_HASH); err != nil {
342                 t.Error(err)
343         }
344         if data, err := v.Get(TEST_HASH); err != nil {
345                 t.Error(err)
346         } else if bytes.Compare(data, TEST_BLOCK) != 0 {
347                 t.Error("Block still present, but content is incorrect: %+v != %+v", data, TEST_BLOCK)
348         }
349 }
350
351 // Calling Delete() for a block with a timestamp older than
352 // blob_signature_ttl seconds in the past should delete the data.
353 func testDeleteOldBlock(t *testing.T, factory TestableVolumeFactory) {
354         v := factory(t)
355         defer v.Teardown()
356         v.Put(TEST_HASH, TEST_BLOCK)
357         v.TouchWithDate(TEST_HASH, time.Now().Add(-2*blob_signature_ttl*time.Second))
358
359         if err := v.Delete(TEST_HASH); err != nil {
360                 t.Error(err)
361         }
362         if _, err := v.Get(TEST_HASH); err == nil || !os.IsNotExist(err) {
363                 t.Errorf("os.IsNotExist(%v) should have been true", err.Error())
364         }
365 }
366
367 // Calling Delete() for a block that does not exist should result in error.
368 func testDeleteNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
369         v := factory(t)
370         defer v.Teardown()
371         v.Put(TEST_HASH, TEST_BLOCK)
372
373         if err := v.Delete(TEST_HASH_2); err == nil {
374                 t.Errorf("Expected error when attempting to delete a non-existing block")
375         }
376 }
377
378 // Invoke Status and verify that VolumeStatus is returned
379 func testStatus(t *testing.T, factory TestableVolumeFactory) {
380         v := factory(t)
381         defer v.Teardown()
382
383         // Get node status and make a basic sanity check.
384         status := v.Status()
385         if status.DeviceNum == 0 {
386                 t.Errorf("uninitialized device_num in %v", status)
387         }
388
389         if status.BytesFree == 0 {
390                 t.Errorf("uninitialized bytes_free in %v", status)
391         }
392
393         if status.BytesUsed == 0 {
394                 t.Errorf("uninitialized bytes_used in %v", status)
395         }
396 }
397
398 // Invoke String for the volume; expect non-empty result
399 func testString(t *testing.T, factory TestableVolumeFactory) {
400         v := factory(t)
401         defer v.Teardown()
402
403         if id := v.String(); len(id) == 0 {
404                 t.Error("Got empty string for v.String()")
405         }
406 }
407
408 // Verify Writable is true on a writable volume
409 func testWritableTrue(t *testing.T, factory TestableVolumeFactory) {
410         v := factory(t)
411         defer v.Teardown()
412
413         if v.Writable() == false {
414                 t.Errorf("Expected writable to be true on a writable volume")
415         }
416 }
417
418 // Verify Writable is false on a read-only volume
419 func testWritableFalse(t *testing.T, factory TestableVolumeFactory) {
420         v := factory(t)
421         defer v.Teardown()
422
423         if v.Writable() != false {
424                 t.Errorf("Expected writable to be false on a read-only volume")
425         }
426 }
427
428 // Updating, touching, and deleting blocks from a read-only volume result in error.
429 func testUpdateReadOnly(t *testing.T, factory TestableVolumeFactory) {
430         v := factory(t)
431         defer v.Teardown()
432
433         v.PutRaw(TEST_HASH, TEST_BLOCK)
434
435         _, err := v.Get(TEST_HASH)
436         if err != nil {
437                 t.Errorf("got err %v, expected nil", err)
438         }
439
440         err = v.Put(TEST_HASH, TEST_BLOCK)
441         if err == nil {
442                 t.Errorf("Expected error when putting block in a read-only volume")
443         }
444
445         err = v.Touch(TEST_HASH)
446         if err == nil {
447                 t.Errorf("Expected error when touching block in a read-only volume")
448         }
449
450         err = v.Delete(TEST_HASH)
451         if err == nil {
452                 t.Errorf("Expected error when deleting block from a read-only volume")
453         }
454 }
455
456 // Serialization tests: launch a bunch of concurrent
457 //
458 // TODO(twp): show that the underlying Read/Write operations executed
459 // serially and not concurrently. The easiest way to do this is
460 // probably to activate verbose or debug logging, capture log output
461 // and examine it to confirm that Reads and Writes did not overlap.
462 //
463 // TODO(twp): a proper test of I/O serialization requires that a
464 // second request start while the first one is still underway.
465 // Guaranteeing that the test behaves this way requires some tricky
466 // synchronization and mocking.  For now we'll just launch a bunch of
467 // requests simultaenously in goroutines and demonstrate that they
468 // return accurate results.
469 //
470
471 func testGetSerialized(t *testing.T, factory TestableVolumeFactory) {
472         v := factory(t)
473         defer v.Teardown()
474
475         v.Put(TEST_HASH, TEST_BLOCK)
476         v.Put(TEST_HASH_2, TEST_BLOCK_2)
477         v.Put(TEST_HASH_3, TEST_BLOCK_3)
478
479         sem := make(chan int)
480         go func(sem chan int) {
481                 buf, err := v.Get(TEST_HASH)
482                 if err != nil {
483                         t.Errorf("err1: %v", err)
484                 }
485                 if bytes.Compare(buf, TEST_BLOCK) != 0 {
486                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK), string(buf))
487                 }
488                 sem <- 1
489         }(sem)
490
491         go func(sem chan int) {
492                 buf, err := v.Get(TEST_HASH_2)
493                 if err != nil {
494                         t.Errorf("err2: %v", err)
495                 }
496                 if bytes.Compare(buf, TEST_BLOCK_2) != 0 {
497                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK_2), string(buf))
498                 }
499                 sem <- 1
500         }(sem)
501
502         go func(sem chan int) {
503                 buf, err := v.Get(TEST_HASH_3)
504                 if err != nil {
505                         t.Errorf("err3: %v", err)
506                 }
507                 if bytes.Compare(buf, TEST_BLOCK_3) != 0 {
508                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK_3), string(buf))
509                 }
510                 sem <- 1
511         }(sem)
512
513         // Wait for all goroutines to finish
514         for done := 0; done < 3; {
515                 done += <-sem
516         }
517 }
518
519 func testPutSerialized(t *testing.T, factory TestableVolumeFactory) {
520         v := factory(t)
521         defer v.Teardown()
522
523         sem := make(chan int)
524         go func(sem chan int) {
525                 err := v.Put(TEST_HASH, TEST_BLOCK)
526                 if err != nil {
527                         t.Errorf("err1: %v", err)
528                 }
529                 sem <- 1
530         }(sem)
531
532         go func(sem chan int) {
533                 err := v.Put(TEST_HASH_2, TEST_BLOCK_2)
534                 if err != nil {
535                         t.Errorf("err2: %v", err)
536                 }
537                 sem <- 1
538         }(sem)
539
540         go func(sem chan int) {
541                 err := v.Put(TEST_HASH_3, TEST_BLOCK_3)
542                 if err != nil {
543                         t.Errorf("err3: %v", err)
544                 }
545                 sem <- 1
546         }(sem)
547
548         // Wait for all goroutines to finish
549         for done := 0; done < 3; {
550                 done += <-sem
551         }
552
553         // Double check that we actually wrote the blocks we expected to write.
554         buf, err := v.Get(TEST_HASH)
555         if err != nil {
556                 t.Errorf("Get #1: %v", err)
557         }
558         if bytes.Compare(buf, TEST_BLOCK) != 0 {
559                 t.Errorf("Get #1: expected %s, got %s", string(TEST_BLOCK), string(buf))
560         }
561
562         buf, err = v.Get(TEST_HASH_2)
563         if err != nil {
564                 t.Errorf("Get #2: %v", err)
565         }
566         if bytes.Compare(buf, TEST_BLOCK_2) != 0 {
567                 t.Errorf("Get #2: expected %s, got %s", string(TEST_BLOCK_2), string(buf))
568         }
569
570         buf, err = v.Get(TEST_HASH_3)
571         if err != nil {
572                 t.Errorf("Get #3: %v", err)
573         }
574         if bytes.Compare(buf, TEST_BLOCK_3) != 0 {
575                 t.Errorf("Get #3: expected %s, got %s", string(TEST_BLOCK_3), string(buf))
576         }
577 }