Merge branch '6221-write-trash-list'
[arvados.git] / services / keepstore / volume_unix_test.go
index 278e656066a0adc2ec5d59bb1814c0f04776f00d..6bafa7c1ca03a23ff39037e95a656b27222696c9 100644 (file)
@@ -5,29 +5,31 @@ import (
        "fmt"
        "io/ioutil"
        "os"
+       "syscall"
        "testing"
        "time"
 )
 
-func TempUnixVolume(t *testing.T, serialize boolUnixVolume {
+func TempUnixVolume(t *testing.T, serialize bool, readonly bool) *UnixVolume {
        d, err := ioutil.TempDir("", "volume_test")
        if err != nil {
                t.Fatal(err)
        }
-       return MakeUnixVolume(d, serialize)
+       return &UnixVolume{
+               root:      d,
+               serialize: serialize,
+               readonly:  readonly,
+       }
 }
 
-func _teardown(v UnixVolume) {
-       if v.queue != nil {
-               close(v.queue)
-       }
+func _teardown(v *UnixVolume) {
        os.RemoveAll(v.root)
 }
 
-// store writes a Keep block directly into a UnixVolume, for testing
-// UnixVolume methods.
-//
-func _store(t *testing.T, vol UnixVolume, filename string, block []byte) {
+// _store writes a Keep block directly into a UnixVolume, bypassing
+// the overhead and safeguards of Put(). Useful for storing bogus data
+// and isolating unit tests from Put() behavior.
+func _store(t *testing.T, vol *UnixVolume, filename string, block []byte) {
        blockdir := fmt.Sprintf("%s/%s", vol.root, filename[:3])
        if err := os.MkdirAll(blockdir, 0755); err != nil {
                t.Fatal(err)
@@ -43,7 +45,7 @@ func _store(t *testing.T, vol UnixVolume, filename string, block []byte) {
 }
 
 func TestGet(t *testing.T) {
-       v := TempUnixVolume(t, false)
+       v := TempUnixVolume(t, false, false)
        defer _teardown(v)
        _store(t, v, TEST_HASH, TEST_BLOCK)
 
@@ -57,7 +59,7 @@ func TestGet(t *testing.T) {
 }
 
 func TestGetNotFound(t *testing.T) {
-       v := TempUnixVolume(t, false)
+       v := TempUnixVolume(t, false, false)
        defer _teardown(v)
        _store(t, v, TEST_HASH, TEST_BLOCK)
 
@@ -73,7 +75,7 @@ func TestGetNotFound(t *testing.T) {
 }
 
 func TestPut(t *testing.T) {
-       v := TempUnixVolume(t, false)
+       v := TempUnixVolume(t, false, false)
        defer _teardown(v)
 
        err := v.Put(TEST_HASH, TEST_BLOCK)
@@ -90,7 +92,7 @@ func TestPut(t *testing.T) {
 }
 
 func TestPutBadVolume(t *testing.T) {
-       v := TempUnixVolume(t, false)
+       v := TempUnixVolume(t, false, false)
        defer _teardown(v)
 
        os.Chmod(v.root, 000)
@@ -100,6 +102,87 @@ func TestPutBadVolume(t *testing.T) {
        }
 }
 
+func TestUnixVolumeReadonly(t *testing.T) {
+       v := TempUnixVolume(t, false, false)
+       defer _teardown(v)
+
+       // First write something before marking readonly
+       err := v.Put(TEST_HASH, TEST_BLOCK)
+       if err != nil {
+               t.Error("got err %v, expected nil", err)
+       }
+
+       v.readonly = true
+
+       _, err = v.Get(TEST_HASH)
+       if err != nil {
+               t.Error("got err %v, expected nil", err)
+       }
+
+       err = v.Put(TEST_HASH, TEST_BLOCK)
+       if err != MethodDisabledError {
+               t.Error("got err %v, expected MethodDisabledError", err)
+       }
+
+       err = v.Touch(TEST_HASH)
+       if err != MethodDisabledError {
+               t.Error("got err %v, expected MethodDisabledError", err)
+       }
+
+       err = v.Delete(TEST_HASH)
+       if err != MethodDisabledError {
+               t.Error("got err %v, expected MethodDisabledError", err)
+       }
+}
+
+// TestPutTouch
+//     Test that when applying PUT to a block that already exists,
+//     the block's modification time is updated.
+func TestPutTouch(t *testing.T) {
+       v := TempUnixVolume(t, false, false)
+       defer _teardown(v)
+
+       if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
+               t.Error(err)
+       }
+
+       // We'll verify { t0 < threshold < t1 }, where t0 is the
+       // existing block's timestamp on disk before Put() and t1 is
+       // its timestamp after Put().
+       threshold := time.Now().Add(-time.Second)
+
+       // Set the stored block's mtime far enough in the past that we
+       // can see the difference between "timestamp didn't change"
+       // and "timestamp granularity is too low".
+       {
+               oldtime := time.Now().Add(-20 * time.Second).Unix()
+               if err := syscall.Utime(v.blockPath(TEST_HASH),
+                       &syscall.Utimbuf{oldtime, oldtime}); err != nil {
+                       t.Error(err)
+               }
+
+               // Make sure v.Mtime() agrees the above Utime really worked.
+               if t0, err := v.Mtime(TEST_HASH); err != nil || t0.IsZero() || !t0.Before(threshold) {
+                       t.Errorf("Setting mtime failed: %v, %v", t0, err)
+               }
+       }
+
+       // Write the same block again.
+       if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
+               t.Error(err)
+       }
+
+       // Verify threshold < t1
+       t1, err := v.Mtime(TEST_HASH)
+       if err != nil {
+               t.Error(err)
+       }
+       if t1.Before(threshold) {
+               t.Errorf("t1 %v must be >= threshold %v after v.Put ",
+                       t1, threshold)
+       }
+}
+
 // Serialization tests: launch a bunch of concurrent
 //
 // TODO(twp): show that the underlying Read/Write operations executed
@@ -116,7 +199,7 @@ func TestPutBadVolume(t *testing.T) {
 //
 func TestGetSerialized(t *testing.T) {
        // Create a volume with I/O serialization enabled.
-       v := TempUnixVolume(t, true)
+       v := TempUnixVolume(t, true, false)
        defer _teardown(v)
 
        _store(t, v, TEST_HASH, TEST_BLOCK)
@@ -165,7 +248,7 @@ func TestGetSerialized(t *testing.T) {
 
 func TestPutSerialized(t *testing.T) {
        // Create a volume with I/O serialization enabled.
-       v := TempUnixVolume(t, true)
+       v := TempUnixVolume(t, true, false)
        defer _teardown(v)
 
        sem := make(chan int)
@@ -225,7 +308,7 @@ func TestPutSerialized(t *testing.T) {
 }
 
 func TestIsFull(t *testing.T) {
-       v := TempUnixVolume(t, false)
+       v := TempUnixVolume(t, false, false)
        defer _teardown(v)
 
        full_path := v.root + "/full"
@@ -243,3 +326,23 @@ func TestIsFull(t *testing.T) {
                t.Errorf("%s: should no longer be full", v)
        }
 }
+
+func TestNodeStatus(t *testing.T) {
+       v := TempUnixVolume(t, false, false)
+       defer _teardown(v)
+
+       // Get node status and make a basic sanity check.
+       volinfo := v.Status()
+       if volinfo.MountPoint != v.root {
+               t.Errorf("GetNodeStatus mount_point %s, expected %s", volinfo.MountPoint, v.root)
+       }
+       if volinfo.DeviceNum == 0 {
+               t.Errorf("uninitialized device_num in %v", volinfo)
+       }
+       if volinfo.BytesFree == 0 {
+               t.Errorf("uninitialized bytes_free in %v", volinfo)
+       }
+       if volinfo.BytesUsed == 0 {
+               t.Errorf("uninitialized bytes_used in %v", volinfo)
+       }
+}