1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
19 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
22 var TestBlock = []byte("The quick brown fox jumps over the lazy dog.")
23 var TestHash = "e4d909c290d0fb1ca068ffaddf22cbd0"
24 var TestHashPutResp = "e4d909c290d0fb1ca068ffaddf22cbd0+44\n"
26 var TestBlock2 = []byte("Pack my box with five dozen liquor jugs.")
27 var TestHash2 = "f15ac516f788aec4f30932ffb6395c39"
29 var TestBlock3 = []byte("Now is the time for all good men to come to the aid of their country.")
30 var TestHash3 = "eed29bbffbc2dbe5e5ee0bb71888e61f"
32 // BadBlock is used to test collisions and corruption.
33 // It must not match any test hashes.
34 var BadBlock = []byte("The magic words are squeamish ossifrage.")
37 var EmptyHash = "d41d8cd98f00b204e9800998ecf8427e"
38 var EmptyBlock = []byte("")
40 // TODO(twp): Tests still to be written
43 // - test that PutBlock returns 503 Full if the filesystem is full.
44 // (must mock FreeDiskSpace or Statfs? use a tmpfs?)
46 // * TestPutBlockWriteErr
47 // - test the behavior when Write returns an error.
48 // - Possible solutions: use a small tmpfs and a high
49 // MIN_FREE_KILOBYTES to trick PutBlock into attempting
50 // to write a block larger than the amount of space left
51 // - use an interface to mock ioutil.TempFile with a File
52 // object that always returns an error on write
54 // ========================================
56 // ========================================
59 // Test that simple block reads succeed.
61 func TestGetBlock(t *testing.T) {
64 // Prepare two test Keep volumes. Our block is stored on the second volume.
65 KeepVM = MakeTestVolumeManager(2)
68 vols := KeepVM.AllReadable()
69 if err := vols[1].Put(context.Background(), TestHash, TestBlock); err != nil {
73 // Check that GetBlock returns success.
74 buf := make([]byte, BlockSize)
75 size, err := GetBlock(context.Background(), TestHash, buf, nil)
77 t.Errorf("GetBlock error: %s", err)
79 if bytes.Compare(buf[:size], TestBlock) != 0 {
80 t.Errorf("got %v, expected %v", buf[:size], TestBlock)
84 // TestGetBlockMissing
85 // GetBlock must return an error when the block is not found.
87 func TestGetBlockMissing(t *testing.T) {
90 // Create two empty test Keep volumes.
91 KeepVM = MakeTestVolumeManager(2)
94 // Check that GetBlock returns failure.
95 buf := make([]byte, BlockSize)
96 size, err := GetBlock(context.Background(), TestHash, buf, nil)
97 if err != NotFoundError {
98 t.Errorf("Expected NotFoundError, got %v, err %v", buf[:size], err)
102 // TestGetBlockCorrupt
103 // GetBlock must return an error when a corrupted block is requested
104 // (the contents of the file do not checksum to its hash).
106 func TestGetBlockCorrupt(t *testing.T) {
109 // Create two test Keep volumes and store a corrupt block in one.
110 KeepVM = MakeTestVolumeManager(2)
113 vols := KeepVM.AllReadable()
114 vols[0].Put(context.Background(), TestHash, BadBlock)
116 // Check that GetBlock returns failure.
117 buf := make([]byte, BlockSize)
118 size, err := GetBlock(context.Background(), TestHash, buf, nil)
119 if err != DiskHashError {
120 t.Errorf("Expected DiskHashError, got %v (buf: %v)", err, buf[:size])
124 // ========================================
126 // ========================================
129 // PutBlock can perform a simple block write and returns success.
131 func TestPutBlockOK(t *testing.T) {
134 // Create two test Keep volumes.
135 KeepVM = MakeTestVolumeManager(2)
138 // Check that PutBlock stores the data as expected.
139 if n, err := PutBlock(context.Background(), TestBlock, TestHash); err != nil || n < 1 {
140 t.Fatalf("PutBlock: n %d err %v", n, err)
143 vols := KeepVM.AllReadable()
144 buf := make([]byte, BlockSize)
145 n, err := vols[1].Get(context.Background(), TestHash, buf)
147 t.Fatalf("Volume #0 Get returned error: %v", err)
149 if string(buf[:n]) != string(TestBlock) {
150 t.Fatalf("PutBlock stored '%s', Get retrieved '%s'",
151 string(TestBlock), string(buf[:n]))
155 // TestPutBlockOneVol
156 // PutBlock still returns success even when only one of the known
157 // volumes is online.
159 func TestPutBlockOneVol(t *testing.T) {
162 // Create two test Keep volumes, but cripple one of them.
163 KeepVM = MakeTestVolumeManager(2)
166 vols := KeepVM.AllWritable()
167 vols[0].(*MockVolume).Bad = true
169 // Check that PutBlock stores the data as expected.
170 if n, err := PutBlock(context.Background(), TestBlock, TestHash); err != nil || n < 1 {
171 t.Fatalf("PutBlock: n %d err %v", n, err)
174 buf := make([]byte, BlockSize)
175 size, err := GetBlock(context.Background(), TestHash, buf, nil)
177 t.Fatalf("GetBlock: %v", err)
179 if bytes.Compare(buf[:size], TestBlock) != 0 {
180 t.Fatalf("PutBlock stored %+q, GetBlock retrieved %+q",
181 TestBlock, buf[:size])
185 // TestPutBlockMD5Fail
186 // Check that PutBlock returns an error if passed a block and hash that
189 func TestPutBlockMD5Fail(t *testing.T) {
192 // Create two test Keep volumes.
193 KeepVM = MakeTestVolumeManager(2)
196 // Check that PutBlock returns the expected error when the hash does
197 // not match the block.
198 if _, err := PutBlock(context.Background(), BadBlock, TestHash); err != RequestHashError {
199 t.Errorf("Expected RequestHashError, got %v", err)
202 // Confirm that GetBlock fails to return anything.
203 if result, err := GetBlock(context.Background(), TestHash, make([]byte, BlockSize), nil); err != NotFoundError {
204 t.Errorf("GetBlock succeeded after a corrupt block store (result = %s, err = %v)",
209 // TestPutBlockCorrupt
210 // PutBlock should overwrite corrupt blocks on disk when given
211 // a PUT request with a good block.
213 func TestPutBlockCorrupt(t *testing.T) {
216 // Create two test Keep volumes.
217 KeepVM = MakeTestVolumeManager(2)
220 // Store a corrupted block under TestHash.
221 vols := KeepVM.AllWritable()
222 vols[0].Put(context.Background(), TestHash, BadBlock)
223 if n, err := PutBlock(context.Background(), TestBlock, TestHash); err != nil || n < 1 {
224 t.Errorf("PutBlock: n %d err %v", n, err)
227 // The block on disk should now match TestBlock.
228 buf := make([]byte, BlockSize)
229 if size, err := GetBlock(context.Background(), TestHash, buf, nil); err != nil {
230 t.Errorf("GetBlock: %v", err)
231 } else if bytes.Compare(buf[:size], TestBlock) != 0 {
232 t.Errorf("Got %+q, expected %+q", buf[:size], TestBlock)
236 // TestPutBlockCollision
237 // PutBlock returns a 400 Collision error when attempting to
238 // store a block that collides with another block on disk.
240 func TestPutBlockCollision(t *testing.T) {
243 // These blocks both hash to the MD5 digest cee9a457e790cf20d4bdaa6d69f01e41.
244 b1 := arvadostest.MD5CollisionData[0]
245 b2 := arvadostest.MD5CollisionData[1]
246 locator := arvadostest.MD5CollisionMD5
248 // Prepare two test Keep volumes.
249 KeepVM = MakeTestVolumeManager(2)
252 // Store one block, then attempt to store the other. Confirm that
253 // PutBlock reported a CollisionError.
254 if _, err := PutBlock(context.Background(), b1, locator); err != nil {
257 if _, err := PutBlock(context.Background(), b2, locator); err == nil {
258 t.Error("PutBlock did not report a collision")
259 } else if err != CollisionError {
260 t.Errorf("PutBlock returned %v", err)
264 // TestPutBlockTouchFails
265 // When PutBlock is asked to PUT an existing block, but cannot
266 // modify the timestamp, it should write a second block.
268 func TestPutBlockTouchFails(t *testing.T) {
271 // Prepare two test Keep volumes.
272 KeepVM = MakeTestVolumeManager(2)
274 vols := KeepVM.AllWritable()
276 // Store a block and then make the underlying volume bad,
277 // so a subsequent attempt to update the file timestamp
279 vols[0].Put(context.Background(), TestHash, BadBlock)
280 oldMtime, err := vols[0].Mtime(TestHash)
282 t.Fatalf("vols[0].Mtime(%s): %s\n", TestHash, err)
285 // vols[0].Touch will fail on the next call, so the volume
286 // manager will store a copy on vols[1] instead.
287 vols[0].(*MockVolume).Touchable = false
288 if n, err := PutBlock(context.Background(), TestBlock, TestHash); err != nil || n < 1 {
289 t.Fatalf("PutBlock: n %d err %v", n, err)
291 vols[0].(*MockVolume).Touchable = true
293 // Now the mtime on the block on vols[0] should be unchanged, and
294 // there should be a copy of the block on vols[1].
295 newMtime, err := vols[0].Mtime(TestHash)
297 t.Fatalf("vols[0].Mtime(%s): %s\n", TestHash, err)
299 if !newMtime.Equal(oldMtime) {
300 t.Errorf("mtime was changed on vols[0]:\noldMtime = %v\nnewMtime = %v\n",
303 buf := make([]byte, BlockSize)
304 n, err := vols[1].Get(context.Background(), TestHash, buf)
306 t.Fatalf("vols[1]: %v", err)
308 if bytes.Compare(buf[:n], TestBlock) != 0 {
309 t.Errorf("new block does not match test block\nnew block = %v\n", buf[:n])
313 func TestDiscoverTmpfs(t *testing.T) {
314 var tempVols [4]string
317 // Create some directories suitable for using as keep volumes.
318 for i := range tempVols {
319 if tempVols[i], err = ioutil.TempDir("", "findvol"); err != nil {
322 defer os.RemoveAll(tempVols[i])
323 tempVols[i] = tempVols[i] + "/keep"
324 if err = os.Mkdir(tempVols[i], 0755); err != nil {
329 // Set up a bogus ProcMounts file.
330 f, err := ioutil.TempFile("", "keeptest")
334 defer os.Remove(f.Name())
335 for i, vol := range tempVols {
336 // Add readonly mount points at odd indexes.
340 opts = "rw,nosuid,nodev,noexec"
342 opts = "nosuid,nodev,noexec,ro"
344 fmt.Fprintf(f, "tmpfs %s tmpfs %s 0 0\n", path.Dir(vol), opts)
347 ProcMounts = f.Name()
350 added := (&unixVolumeAdder{cfg}).Discover()
352 if added != len(cfg.Volumes) {
353 t.Errorf("Discover returned %d, but added %d volumes",
354 added, len(cfg.Volumes))
356 if added != len(tempVols) {
357 t.Errorf("Discover returned %d but we set up %d volumes",
358 added, len(tempVols))
360 for i, tmpdir := range tempVols {
361 if tmpdir != cfg.Volumes[i].(*UnixVolume).Root {
362 t.Errorf("Discover returned %s, expected %s\n",
363 cfg.Volumes[i].(*UnixVolume).Root, tmpdir)
365 if expectReadonly := i%2 == 1; expectReadonly != cfg.Volumes[i].(*UnixVolume).ReadOnly {
366 t.Errorf("Discover added %s with readonly=%v, should be %v",
367 tmpdir, !expectReadonly, expectReadonly)
372 func TestDiscoverNone(t *testing.T) {
375 // Set up a bogus ProcMounts file with no Keep vols.
376 f, err := ioutil.TempFile("", "keeptest")
380 defer os.Remove(f.Name())
381 fmt.Fprintln(f, "rootfs / rootfs opts 0 0")
382 fmt.Fprintln(f, "sysfs /sys sysfs opts 0 0")
383 fmt.Fprintln(f, "proc /proc proc opts 0 0")
384 fmt.Fprintln(f, "udev /dev devtmpfs opts 0 0")
385 fmt.Fprintln(f, "devpts /dev/pts devpts opts 0 0")
387 ProcMounts = f.Name()
390 added := (&unixVolumeAdder{cfg}).Discover()
391 if added != 0 || len(cfg.Volumes) != 0 {
392 t.Fatalf("got %d, %v; expected 0, []", added, cfg.Volumes)
397 // Test an /index request.
398 func TestIndex(t *testing.T) {
401 // Set up Keep volumes and populate them.
402 // Include multiple blocks on different volumes, and
403 // some metadata files.
404 KeepVM = MakeTestVolumeManager(2)
407 vols := KeepVM.AllReadable()
408 vols[0].Put(context.Background(), TestHash, TestBlock)
409 vols[1].Put(context.Background(), TestHash2, TestBlock2)
410 vols[0].Put(context.Background(), TestHash3, TestBlock3)
411 vols[0].Put(context.Background(), TestHash+".meta", []byte("metadata"))
412 vols[1].Put(context.Background(), TestHash2+".meta", []byte("metadata"))
414 buf := new(bytes.Buffer)
415 vols[0].IndexTo("", buf)
416 vols[1].IndexTo("", buf)
417 indexRows := strings.Split(string(buf.Bytes()), "\n")
418 sort.Strings(indexRows)
419 sortedIndex := strings.Join(indexRows, "\n")
420 expected := `^\n` + TestHash + `\+\d+ \d+\n` +
421 TestHash3 + `\+\d+ \d+\n` +
422 TestHash2 + `\+\d+ \d+$`
424 match, err := regexp.MatchString(expected, sortedIndex)
427 t.Errorf("IndexLocators returned:\n%s", string(buf.Bytes()))
430 t.Errorf("regexp.MatchString: %s", err)
434 // ========================================
435 // Helper functions for unit tests.
436 // ========================================
438 // MakeTestVolumeManager returns a RRVolumeManager with the specified
439 // number of MockVolumes.
440 func MakeTestVolumeManager(numVolumes int) VolumeManager {
441 vols := make([]Volume, numVolumes)
442 for i := range vols {
443 vols[i] = CreateMockVolume()
445 return MakeRRVolumeManager(vols)
448 // teardown cleans up after each test.
450 theConfig.systemAuthToken = ""
451 theConfig.RequireSignatures = false
452 theConfig.blobSigningKey = nil