Arvados-DCO-1.1-Signed-off-by: Radhika Chippada <radhika@curoverse.com>
[arvados.git] / services / keepstore / volume_unix_test.go
index 0775e89ed275d14f7e2be510084a52e39af84472..ea3d91d98c2e42deed99417c87254af42b8148e8 100644 (file)
@@ -1,7 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 package main
 
 import (
        "bytes"
+       "context"
+       "encoding/json"
        "errors"
        "fmt"
        "io"
@@ -12,6 +18,8 @@ import (
        "syscall"
        "testing"
        "time"
+
+       check "gopkg.in/check.v1"
 )
 
 type TestableUnixVolume struct {
@@ -30,9 +38,9 @@ func NewTestableUnixVolume(t TB, serialize bool, readonly bool) *TestableUnixVol
        }
        return &TestableUnixVolume{
                UnixVolume: UnixVolume{
-                       root:     d,
+                       Root:     d,
+                       ReadOnly: readonly,
                        locker:   locker,
-                       readonly: readonly,
                },
                t: t,
        }
@@ -42,10 +50,10 @@ func NewTestableUnixVolume(t TB, serialize bool, readonly bool) *TestableUnixVol
 // the volume is readonly.
 func (v *TestableUnixVolume) PutRaw(locator string, data []byte) {
        defer func(orig bool) {
-               v.readonly = orig
-       }(v.readonly)
-       v.readonly = false
-       err := v.Put(locator, data)
+               v.ReadOnly = orig
+       }(v.ReadOnly)
+       v.ReadOnly = false
+       err := v.Put(context.Background(), locator, data)
        if err != nil {
                v.t.Fatal(err)
        }
@@ -59,7 +67,7 @@ func (v *TestableUnixVolume) TouchWithDate(locator string, lastPut time.Time) {
 }
 
 func (v *TestableUnixVolume) Teardown() {
-       if err := os.RemoveAll(v.root); err != nil {
+       if err := os.RemoveAll(v.Root); err != nil {
                v.t.Fatal(err)
        }
 }
@@ -101,17 +109,31 @@ func TestUnixVolumeHandlersWithGenericVolumeTests(t *testing.T) {
        })
 }
 
+func TestReplicationDefault1(t *testing.T) {
+       v := &UnixVolume{
+               Root:     "/",
+               ReadOnly: true,
+       }
+       if err := v.Start(); err != nil {
+               t.Error(err)
+       }
+       if got := v.Replication(); got != 1 {
+               t.Errorf("Replication() returned %d, expected 1 if no config given", got)
+       }
+}
+
 func TestGetNotFound(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
-       v.Put(TestHash, TestBlock)
+       v.Put(context.Background(), TestHash, TestBlock)
 
-       buf, err := v.Get(TestHash2)
+       buf := make([]byte, BlockSize)
+       n, err := v.Get(context.Background(), TestHash2, buf)
        switch {
        case os.IsNotExist(err):
                break
        case err == nil:
-               t.Errorf("Read should have failed, returned %s", string(buf))
+               t.Errorf("Read should have failed, returned %+q", buf[:n])
        default:
                t.Errorf("Read expected ErrNotExist, got: %s", err)
        }
@@ -121,11 +143,11 @@ func TestPut(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       err := v.Put(TestHash, TestBlock)
+       err := v.Put(context.Background(), TestHash, TestBlock)
        if err != nil {
                t.Error(err)
        }
-       p := fmt.Sprintf("%s/%s/%s", v.root, TestHash[:3], TestHash)
+       p := fmt.Sprintf("%s/%s/%s", v.Root, TestHash[:3], TestHash)
        if buf, err := ioutil.ReadFile(p); err != nil {
                t.Error(err)
        } else if bytes.Compare(buf, TestBlock) != 0 {
@@ -138,8 +160,8 @@ func TestPutBadVolume(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       os.Chmod(v.root, 000)
-       err := v.Put(TestHash, TestBlock)
+       os.Chmod(v.Root, 000)
+       err := v.Put(context.Background(), TestHash, TestBlock)
        if err == nil {
                t.Error("Write should have failed")
        }
@@ -151,12 +173,13 @@ func TestUnixVolumeReadonly(t *testing.T) {
 
        v.PutRaw(TestHash, TestBlock)
 
-       _, err := v.Get(TestHash)
+       buf := make([]byte, BlockSize)
+       _, err := v.Get(context.Background(), TestHash, buf)
        if err != nil {
                t.Errorf("got err %v, expected nil", err)
        }
 
-       err = v.Put(TestHash, TestBlock)
+       err = v.Put(context.Background(), TestHash, TestBlock)
        if err != MethodDisabledError {
                t.Errorf("got err %v, expected MethodDisabledError", err)
        }
@@ -176,7 +199,7 @@ func TestIsFull(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       fullPath := v.root + "/full"
+       fullPath := v.Root + "/full"
        now := fmt.Sprintf("%d", time.Now().Unix())
        os.Symlink(now, fullPath)
        if !v.IsFull() {
@@ -198,8 +221,8 @@ func TestNodeStatus(t *testing.T) {
 
        // 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.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)
@@ -216,9 +239,9 @@ func TestUnixVolumeGetFuncWorkerError(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       v.Put(TestHash, TestBlock)
+       v.Put(context.Background(), TestHash, TestBlock)
        mockErr := errors.New("Mock error")
-       err := v.getFunc(v.blockPath(TestHash), func(rdr io.Reader) error {
+       err := v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
                return mockErr
        })
        if err != mockErr {
@@ -231,7 +254,7 @@ func TestUnixVolumeGetFuncFileError(t *testing.T) {
        defer v.Teardown()
 
        funcCalled := false
-       err := v.getFunc(v.blockPath(TestHash), func(rdr io.Reader) error {
+       err := v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
                funcCalled = true
                return nil
        })
@@ -247,13 +270,13 @@ func TestUnixVolumeGetFuncWorkerWaitsOnMutex(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       v.Put(TestHash, TestBlock)
+       v.Put(context.Background(), TestHash, TestBlock)
 
        mtx := NewMockMutex()
        v.locker = mtx
 
        funcCalled := make(chan struct{})
-       go v.getFunc(v.blockPath(TestHash), func(rdr io.Reader) error {
+       go v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
                funcCalled <- struct{}{}
                return nil
        })
@@ -282,39 +305,125 @@ func TestUnixVolumeCompare(t *testing.T) {
        v := NewTestableUnixVolume(t, false, false)
        defer v.Teardown()
 
-       v.Put(TestHash, TestBlock)
-       err := v.Compare(TestHash, TestBlock)
+       v.Put(context.Background(), TestHash, TestBlock)
+       err := v.Compare(context.Background(), TestHash, TestBlock)
        if err != nil {
                t.Errorf("Got err %q, expected nil", err)
        }
 
-       err = v.Compare(TestHash, []byte("baddata"))
+       err = v.Compare(context.Background(), TestHash, []byte("baddata"))
        if err != CollisionError {
                t.Errorf("Got err %q, expected %q", err, CollisionError)
        }
 
-       v.Put(TestHash, []byte("baddata"))
-       err = v.Compare(TestHash, TestBlock)
+       v.Put(context.Background(), TestHash, []byte("baddata"))
+       err = v.Compare(context.Background(), TestHash, TestBlock)
        if err != DiskHashError {
                t.Errorf("Got err %q, expected %q", err, DiskHashError)
        }
 
-       p := fmt.Sprintf("%s/%s/%s", v.root, TestHash[:3], TestHash)
+       p := fmt.Sprintf("%s/%s/%s", v.Root, TestHash[:3], TestHash)
        os.Chmod(p, 000)
-       err = v.Compare(TestHash, TestBlock)
+       err = v.Compare(context.Background(), TestHash, TestBlock)
        if err == nil || strings.Index(err.Error(), "permission denied") < 0 {
                t.Errorf("Got err %q, expected %q", err, "permission denied")
        }
 }
 
-// TODO(twp): show that the underlying Read/Write operations executed
-// serially and not concurrently. The easiest way to do this is
-// probably to activate verbose or debug logging, capture log output
-// and examine it to confirm that Reads and Writes did not overlap.
-//
-// TODO(twp): a proper test of I/O serialization requires that a
-// second request start while the first one is still underway.
-// Guaranteeing that the test behaves this way requires some tricky
-// synchronization and mocking.  For now we'll just launch a bunch of
-// requests simultaenously in goroutines and demonstrate that they
-// return accurate results.
+func TestUnixVolumeContextCancelPut(t *testing.T) {
+       v := NewTestableUnixVolume(t, true, false)
+       defer v.Teardown()
+       v.locker.Lock()
+       ctx, cancel := context.WithCancel(context.Background())
+       go func() {
+               time.Sleep(50 * time.Millisecond)
+               cancel()
+               time.Sleep(50 * time.Millisecond)
+               v.locker.Unlock()
+       }()
+       err := v.Put(ctx, TestHash, TestBlock)
+       if err != context.Canceled {
+               t.Errorf("Put() returned %s -- expected short read / canceled", err)
+       }
+}
+
+func TestUnixVolumeContextCancelGet(t *testing.T) {
+       v := NewTestableUnixVolume(t, false, false)
+       defer v.Teardown()
+       bpath := v.blockPath(TestHash)
+       v.PutRaw(TestHash, TestBlock)
+       os.Remove(bpath)
+       err := syscall.Mkfifo(bpath, 0600)
+       if err != nil {
+               t.Fatalf("Mkfifo %s: %s", bpath, err)
+       }
+       defer os.Remove(bpath)
+       ctx, cancel := context.WithCancel(context.Background())
+       go func() {
+               time.Sleep(50 * time.Millisecond)
+               cancel()
+       }()
+       buf := make([]byte, len(TestBlock))
+       n, err := v.Get(ctx, TestHash, buf)
+       if n == len(TestBlock) || err != context.Canceled {
+               t.Errorf("Get() returned %d, %s -- expected short read / canceled", n, err)
+       }
+}
+
+var _ = check.Suite(&UnixVolumeSuite{})
+
+type UnixVolumeSuite struct {
+       volume *TestableUnixVolume
+}
+
+func (s *UnixVolumeSuite) TearDownTest(c *check.C) {
+       if s.volume != nil {
+               s.volume.Teardown()
+       }
+}
+
+func (s *UnixVolumeSuite) TestStats(c *check.C) {
+       s.volume = NewTestableUnixVolume(c, false, false)
+       stats := func() string {
+               buf, err := json.Marshal(s.volume.InternalStats())
+               c.Check(err, check.IsNil)
+               return string(buf)
+       }
+
+       c.Check(stats(), check.Matches, `.*"StatOps":0,.*`)
+       c.Check(stats(), check.Matches, `.*"Errors":0,.*`)
+
+       loc := "acbd18db4cc2f85cedef654fccc4a4d8"
+       _, err := s.volume.Get(context.Background(), loc, make([]byte, 3))
+       c.Check(err, check.NotNil)
+       c.Check(stats(), check.Matches, `.*"StatOps":[^0],.*`)
+       c.Check(stats(), check.Matches, `.*"Errors":[^0],.*`)
+       c.Check(stats(), check.Matches, `.*"\*os\.PathError":[^0].*`)
+       c.Check(stats(), check.Matches, `.*"InBytes":0,.*`)
+       c.Check(stats(), check.Matches, `.*"OpenOps":0,.*`)
+       c.Check(stats(), check.Matches, `.*"CreateOps":0,.*`)
+
+       err = s.volume.Put(context.Background(), loc, []byte("foo"))
+       c.Check(err, check.IsNil)
+       c.Check(stats(), check.Matches, `.*"OutBytes":3,.*`)
+       c.Check(stats(), check.Matches, `.*"CreateOps":1,.*`)
+       c.Check(stats(), check.Matches, `.*"OpenOps":0,.*`)
+       c.Check(stats(), check.Matches, `.*"UtimesOps":0,.*`)
+
+       err = s.volume.Touch(loc)
+       c.Check(err, check.IsNil)
+       c.Check(stats(), check.Matches, `.*"FlockOps":1,.*`)
+       c.Check(stats(), check.Matches, `.*"OpenOps":1,.*`)
+       c.Check(stats(), check.Matches, `.*"UtimesOps":1,.*`)
+
+       _, err = s.volume.Get(context.Background(), loc, make([]byte, 3))
+       c.Check(err, check.IsNil)
+       err = s.volume.Compare(context.Background(), loc, []byte("foo"))
+       c.Check(err, check.IsNil)
+       c.Check(stats(), check.Matches, `.*"InBytes":6,.*`)
+       c.Check(stats(), check.Matches, `.*"OpenOps":3,.*`)
+
+       err = s.volume.Trash(loc)
+       c.Check(err, check.IsNil)
+       c.Check(stats(), check.Matches, `.*"FlockOps":2,.*`)
+}