Merge branch '7181-crunch-missing-git' closes #7181
authorPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 24 Sep 2015 20:36:59 +0000 (16:36 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 24 Sep 2015 20:36:59 +0000 (16:36 -0400)
docker/jobs/Dockerfile
services/api/script/crunch-dispatch.rb
services/keepstore/collision.go
services/keepstore/handlers_with_generic_volume_test.go [new file with mode: 0644]
services/keepstore/keepstore.go
services/keepstore/keepstore_test.go
services/keepstore/mock_mutex_for_test.go
services/keepstore/volume_generic_test.go
services/keepstore/volume_unix_test.go

index 41e4aea1ddcf0f4b69a6735b72fcbecf7b942a72..0d7295873f723e637cf76413e01c16c6a2be5d95 100644 (file)
@@ -8,7 +8,7 @@ ADD apt.arvados.org.list /etc/apt/sources.list.d/
 RUN apt-key adv --keyserver pool.sks-keyservers.net --recv 1078ECD7
 RUN apt-get update -q
 
-RUN apt-get install -qy git python-minimal python-virtualenv python-arvados-python-client
+RUN apt-get install -qy git python-pip python-virtualenv python-arvados-python-client python-dev libcurl4-gnutls-dev
 
 RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3
 
index 27cb82115b2f7993bce20b7a589bdb62eccad619..4a1fdbce758d7b552f529419f7c37f970299d298 100755 (executable)
@@ -1,5 +1,9 @@
 #!/usr/bin/env ruby
 
+# We want files written by crunch-dispatch to be writable by other processes
+# with the same GID, see bug #7228
+File.umask(0002)
+
 require 'shellwords'
 include Process
 
@@ -747,6 +751,7 @@ class Dispatcher
 
   def run
     act_as_system_user
+    User.first.group_permissions
     $stderr.puts "dispatch: ready"
     while !$signal[:term] or @running.size > 0
       read_pipes
index 210286ad75ab3869aaf6a9690f5ef341eb15b549..be26514a00ce6ae9092bf12981f5f55818a083f1 100644 (file)
@@ -35,7 +35,7 @@ func collisionOrCorrupt(expectMD5 string, buf1, buf2 []byte, rdr io.Reader) erro
        }
        var err error
        for rdr != nil && err == nil {
-               buf := make([]byte, 1 << 18)
+               buf := make([]byte, 1<<18)
                var n int
                n, err = rdr.Read(buf)
                data <- buf[:n]
diff --git a/services/keepstore/handlers_with_generic_volume_test.go b/services/keepstore/handlers_with_generic_volume_test.go
new file mode 100644 (file)
index 0000000..90094f3
--- /dev/null
@@ -0,0 +1,119 @@
+package main
+
+import (
+       "bytes"
+       "testing"
+)
+
+// A TestableVolumeManagerFactory creates a volume manager with at least two TestableVolume instances.
+// The factory function, and the TestableVolume instances it returns, can use "t" to write
+// logs, fail the current test, etc.
+type TestableVolumeManagerFactory func(t *testing.T) (*RRVolumeManager, []TestableVolume)
+
+// DoHandlersWithGenericVolumeTests runs a set of handler tests with a
+// Volume Manager comprised of TestableVolume instances.
+// It calls factory to create a volume manager with TestableVolume
+// instances for each test case, to avoid leaking state between tests.
+func DoHandlersWithGenericVolumeTests(t *testing.T, factory TestableVolumeManagerFactory) {
+       testGetBlock(t, factory, TestHash, TestBlock)
+       testGetBlock(t, factory, EmptyHash, EmptyBlock)
+       testPutRawBadDataGetBlock(t, factory, TestHash, TestBlock, []byte("baddata"))
+       testPutRawBadDataGetBlock(t, factory, EmptyHash, EmptyBlock, []byte("baddata"))
+       testPutBlock(t, factory, TestHash, TestBlock)
+       testPutBlock(t, factory, EmptyHash, EmptyBlock)
+       testPutBlockCorrupt(t, factory, TestHash, TestBlock, []byte("baddata"))
+       testPutBlockCorrupt(t, factory, EmptyHash, EmptyBlock, []byte("baddata"))
+}
+
+// Setup RRVolumeManager with TestableVolumes
+func setupHandlersWithGenericVolumeTest(t *testing.T, factory TestableVolumeManagerFactory) []TestableVolume {
+       vm, testableVolumes := factory(t)
+       KeepVM = vm
+
+       for _, v := range testableVolumes {
+               defer v.Teardown()
+       }
+       defer KeepVM.Close()
+
+       return testableVolumes
+}
+
+// Put a block using PutRaw in just one volume and Get it using GetBlock
+func testGetBlock(t *testing.T, factory TestableVolumeManagerFactory, testHash string, testBlock []byte) {
+       testableVolumes := setupHandlersWithGenericVolumeTest(t, factory)
+
+       // Put testBlock in one volume
+       testableVolumes[1].PutRaw(testHash, testBlock)
+
+       // Get should pass
+       buf, err := GetBlock(testHash)
+       if err != nil {
+               t.Fatalf("Error while getting block %s", err)
+       }
+       if bytes.Compare(buf, testBlock) != 0 {
+               t.Errorf("Put succeeded but Get returned %+v, expected %+v", buf, testBlock)
+       }
+}
+
+// Put a bad block using PutRaw and get it.
+func testPutRawBadDataGetBlock(t *testing.T, factory TestableVolumeManagerFactory,
+       testHash string, testBlock []byte, badData []byte) {
+       testableVolumes := setupHandlersWithGenericVolumeTest(t, factory)
+
+       // Put bad data for testHash in both volumes
+       testableVolumes[0].PutRaw(testHash, badData)
+       testableVolumes[1].PutRaw(testHash, badData)
+
+       // Get should fail
+       _, err := GetBlock(testHash)
+       if err == nil {
+               t.Fatalf("Expected error while getting corrupt block %v", testHash)
+       }
+}
+
+// Invoke PutBlock twice to ensure CompareAndTouch path is tested.
+func testPutBlock(t *testing.T, factory TestableVolumeManagerFactory, testHash string, testBlock []byte) {
+       setupHandlersWithGenericVolumeTest(t, factory)
+
+       // PutBlock
+       if err := PutBlock(testBlock, testHash); err != nil {
+               t.Fatalf("Error during PutBlock: %s", err)
+       }
+
+       // Check that PutBlock succeeds again even after CompareAndTouch
+       if err := PutBlock(testBlock, testHash); err != nil {
+               t.Fatalf("Error during PutBlock: %s", err)
+       }
+
+       // Check that PutBlock stored the data as expected
+       buf, err := GetBlock(testHash)
+       if err != nil {
+               t.Fatalf("Error during GetBlock for %q: %s", testHash, err)
+       } else if bytes.Compare(buf, testBlock) != 0 {
+               t.Errorf("Get response incorrect. Expected %q; found %q", testBlock, buf)
+       }
+}
+
+// Put a bad block using PutRaw, overwrite it using PutBlock and get it.
+func testPutBlockCorrupt(t *testing.T, factory TestableVolumeManagerFactory,
+       testHash string, testBlock []byte, badData []byte) {
+       testableVolumes := setupHandlersWithGenericVolumeTest(t, factory)
+
+       // Put bad data for testHash in both volumes
+       testableVolumes[0].PutRaw(testHash, badData)
+       testableVolumes[1].PutRaw(testHash, badData)
+
+       // Check that PutBlock with good data succeeds
+       if err := PutBlock(testBlock, testHash); err != nil {
+               t.Fatalf("Error during PutBlock for %q: %s", testHash, err)
+       }
+
+       // Put succeeded and overwrote the badData in one volume,
+       // and Get should return the testBlock now, ignoring the bad data.
+       buf, err := GetBlock(testHash)
+       if err != nil {
+               t.Fatalf("Error during GetBlock for %q: %s", testHash, err)
+       } else if bytes.Compare(buf, testBlock) != 0 {
+               t.Errorf("Get response incorrect. Expected %q; found %q", testBlock, buf)
+       }
+}
index ec11af5cf51c8c1a65bdf4827962a74aa90a03fe..3e360e1799117e80e773e1e5c58fa3b5560b07ef 100644 (file)
@@ -37,8 +37,8 @@ const BlockSize = 64 * 1024 * 1024
 const MinFreeKilobytes = BlockSize / 1024
 
 // Until #6221 is resolved, never_delete must be true.
-// However, allow it to be false in testing with TEST_DATA_MANAGER_TOKEN
-const TEST_DATA_MANAGER_TOKEN = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
+// However, allow it to be false in testing with TestDataManagerToken
+const TestDataManagerToken = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
 
 // ProcMounts /proc/mounts
 var ProcMounts = "/proc/mounts"
@@ -348,7 +348,7 @@ func main() {
                }
        }
 
-       if neverDelete != true && dataManagerToken != TEST_DATA_MANAGER_TOKEN {
+       if neverDelete != true && dataManagerToken != TestDataManagerToken {
                log.Fatal("never_delete must be true, see #6221")
        }
 
index 380731770dc7e28dd2b6963b833d2fdeaf3dcd32..0e2129cc848d4f4bc7b2b91c17d2fa3115ae7732 100644 (file)
@@ -26,6 +26,10 @@ var TestHash3 = "eed29bbffbc2dbe5e5ee0bb71888e61f"
 // It must not match any test hashes.
 var BadBlock = []byte("The magic words are squeamish ossifrage.")
 
+// Empty block
+var EmptyHash = "d41d8cd98f00b204e9800998ecf8427e"
+var EmptyBlock = []byte("")
+
 // TODO(twp): Tests still to be written
 //
 //   * TestPutBlockFull
index e75d91058785e018a064f3255d2f27b4d005ca41..24b549cd656d5eb28cd720be6810ba6c5d2a0192 100644 (file)
@@ -7,17 +7,17 @@ type MockMutex struct {
 
 func NewMockMutex() *MockMutex {
        return &MockMutex{
-               AllowLock: make(chan struct{}),
+               AllowLock:   make(chan struct{}),
                AllowUnlock: make(chan struct{}),
        }
 }
 
 // Lock waits for someone to send to AllowLock.
 func (m *MockMutex) Lock() {
-       <- m.AllowLock
+       <-m.AllowLock
 }
 
 // Unlock waits for someone to send to AllowUnlock.
 func (m *MockMutex) Unlock() {
-       <- m.AllowUnlock
+       <-m.AllowUnlock
 }
index 193d9d2c2cadfd0d2a583c404562a5be65d9754e..c08c3f5f0007ac483486cb01d1e6a4bd68253d42 100644 (file)
@@ -22,12 +22,21 @@ func DoGenericVolumeTests(t *testing.T, factory TestableVolumeFactory) {
        testGet(t, factory)
        testGetNoSuchBlock(t, factory)
 
-       testCompareSameContent(t, factory)
-       testCompareWithDifferentContent(t, factory)
-       testCompareWithBadData(t, factory)
-
-       testPutBlockWithSameContent(t, factory)
-       testPutBlockWithDifferentContent(t, factory)
+       testCompareSameContent(t, factory, TestHash, TestBlock)
+       testCompareSameContent(t, factory, EmptyHash, EmptyBlock)
+       testCompareWithCollision(t, factory, TestHash, TestBlock, []byte("baddata"))
+       testCompareWithCollision(t, factory, TestHash, TestBlock, EmptyBlock)
+       testCompareWithCollision(t, factory, EmptyHash, EmptyBlock, TestBlock)
+       testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, []byte("baddata"))
+       testCompareWithCorruptStoredData(t, factory, TestHash, TestBlock, EmptyBlock)
+       testCompareWithCorruptStoredData(t, factory, EmptyHash, EmptyBlock, []byte("baddata"))
+
+       testPutBlockWithSameContent(t, factory, TestHash, TestBlock)
+       testPutBlockWithSameContent(t, factory, EmptyHash, EmptyBlock)
+       testPutBlockWithDifferentContent(t, factory, TestHash, TestBlock, TestBlock2)
+       testPutBlockWithDifferentContent(t, factory, TestHash, EmptyBlock, TestBlock)
+       testPutBlockWithDifferentContent(t, factory, TestHash, TestBlock, EmptyBlock)
+       testPutBlockWithDifferentContent(t, factory, EmptyHash, EmptyBlock, TestBlock)
        testPutMultipleBlocks(t, factory)
 
        testPutAndTouch(t, factory)
@@ -84,54 +93,56 @@ func testGetNoSuchBlock(t *testing.T, factory TestableVolumeFactory) {
 
 // Put a test block and compare the locator with same content
 // Test should pass for both writable and read-only volumes
-func testCompareSameContent(t *testing.T, factory TestableVolumeFactory) {
+func testCompareSameContent(t *testing.T, factory TestableVolumeFactory, testHash string, testData []byte) {
        v := factory(t)
        defer v.Teardown()
 
-       v.PutRaw(TestHash, TestBlock)
+       v.PutRaw(testHash, testData)
 
        // Compare the block locator with same content
-       err := v.Compare(TestHash, TestBlock)
+       err := v.Compare(testHash, testData)
        if err != nil {
                t.Errorf("Got err %q, expected nil", err)
        }
 }
 
-// Put a test block and compare the locator with a different content
-// Expect error due to collision
+// Test behavior of Compare() when stored data matches expected
+// checksum but differs from new data we need to store. Requires
+// testHash = md5(testDataA).
+//
 // Test should pass for both writable and read-only volumes
-func testCompareWithDifferentContent(t *testing.T, factory TestableVolumeFactory) {
+func testCompareWithCollision(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
        v := factory(t)
        defer v.Teardown()
 
-       v.PutRaw(TestHash, TestBlock)
+       v.PutRaw(testHash, testDataA)
 
        // Compare the block locator with different content; collision
-       err := v.Compare(TestHash, []byte("baddata"))
+       err := v.Compare(TestHash, testDataB)
        if err == nil {
-               t.Errorf("Expected error due to collision")
+               t.Errorf("Got err nil, expected error due to collision")
        }
 }
 
-// Put a test block with bad data (hash does not match, but Put does not verify)
-// Compare the locator with good data whose hash matches with locator
-// Expect error due to corruption.
+// Test behavior of Compare() when stored data has become
+// corrupted. Requires testHash = md5(testDataA) != md5(testDataB).
+//
 // Test should pass for both writable and read-only volumes
-func testCompareWithBadData(t *testing.T, factory TestableVolumeFactory) {
+func testCompareWithCorruptStoredData(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
        v := factory(t)
        defer v.Teardown()
 
-       v.PutRaw(TestHash, []byte("baddata"))
+       v.PutRaw(TestHash, testDataB)
 
-       err := v.Compare(TestHash, TestBlock)
-       if err == nil {
-               t.Errorf("Expected error due to corruption")
+       err := v.Compare(testHash, testDataA)
+       if err == nil || err == CollisionError {
+               t.Errorf("Got err %+v, expected non-collision error", err)
        }
 }
 
 // Put a block and put again with same content
 // Test is intended for only writable volumes
-func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory) {
+func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory, testHash string, testData []byte) {
        v := factory(t)
        defer v.Teardown()
 
@@ -139,12 +150,12 @@ func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory) {
                return
        }
 
-       err := v.Put(TestHash, TestBlock)
+       err := v.Put(testHash, testData)
        if err != nil {
                t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
        }
 
-       err = v.Put(TestHash, TestBlock)
+       err = v.Put(testHash, testData)
        if err != nil {
                t.Errorf("Got err putting block second time %q: %q, expected nil", TestBlock, err)
        }
@@ -152,7 +163,7 @@ func testPutBlockWithSameContent(t *testing.T, factory TestableVolumeFactory) {
 
 // Put a block and put again with different content
 // Test is intended for only writable volumes
-func testPutBlockWithDifferentContent(t *testing.T, factory TestableVolumeFactory) {
+func testPutBlockWithDifferentContent(t *testing.T, factory TestableVolumeFactory, testHash string, testDataA, testDataB []byte) {
        v := factory(t)
        defer v.Teardown()
 
@@ -160,25 +171,25 @@ func testPutBlockWithDifferentContent(t *testing.T, factory TestableVolumeFactor
                return
        }
 
-       err := v.Put(TestHash, TestBlock)
+       err := v.Put(testHash, testDataA)
        if err != nil {
-               t.Errorf("Got err putting block %q: %q, expected nil", TestBlock, err)
+               t.Errorf("Got err putting block %q: %q, expected nil", testDataA, err)
        }
 
-       putErr := v.Put(TestHash, TestBlock2)
-       buf, getErr := v.Get(TestHash)
+       putErr := v.Put(testHash, testDataB)
+       buf, getErr := v.Get(testHash)
        if putErr == nil {
                // Put must not return a nil error unless it has
                // overwritten the existing data.
-               if bytes.Compare(buf, TestBlock2) != 0 {
-                       t.Errorf("Put succeeded but Get returned %+v, expected %+v", buf, TestBlock2)
+               if bytes.Compare(buf, testDataB) != 0 {
+                       t.Errorf("Put succeeded but Get returned %+v, expected %+v", buf, testDataB)
                }
        } else {
                // It is permissible for Put to fail, but it must
                // leave us with either the original data, the new
                // data, or nothing at all.
-               if getErr == nil && bytes.Compare(buf, TestBlock) != 0 && bytes.Compare(buf, TestBlock2) != 0 {
-                       t.Errorf("Put failed but Get returned %+v, which is neither %+v nor %+v", buf, TestBlock, TestBlock2)
+               if getErr == nil && bytes.Compare(buf, testDataA) != 0 && bytes.Compare(buf, testDataB) != 0 {
+                       t.Errorf("Put failed but Get returned %+v, which is neither %+v nor %+v", buf, testDataA, testDataB)
                }
        }
        if getErr == nil {
index 4f1e84c0dd998d59f4ea2449c73d2d5d53a4074c..924637f58e5004f1cec307266c87c0b53ae81d03 100644 (file)
@@ -85,6 +85,22 @@ func TestUnixVolumeWithGenericTestsSerialized(t *testing.T) {
        })
 }
 
+// serialize = false; readonly = false
+func TestUnixVolumeHandlersWithGenericVolumeTests(t *testing.T) {
+       DoHandlersWithGenericVolumeTests(t, func(t *testing.T) (*RRVolumeManager, []TestableVolume) {
+               vols := make([]Volume, 2)
+               testableUnixVols := make([]TestableVolume, 2)
+
+               for i := range vols {
+                       v := NewTestableUnixVolume(t, false, false)
+                       vols[i] = v
+                       testableUnixVols[i] = v
+               }
+
+               return MakeRRVolumeManager(vols), testableUnixVols
+       })
+}
+
 func TestGetNotFound(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()