Merge branch '8784-dir-listings'
[arvados.git] / services / keepstore / volume_test.go
index c5a7491b3d542951983ad67e7eec29baa972f966..baed6a71b60ae556a28b0cc3478e147ad77f90af 100644 (file)
@@ -1,6 +1,13 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
 package main
 
 import (
+       "bytes"
+       "context"
+       "crypto/md5"
        "errors"
        "fmt"
        "io"
@@ -10,18 +17,39 @@ import (
        "time"
 )
 
+// A TestableVolume allows test suites to manipulate the state of an
+// underlying Volume, in order to test behavior in cases that are
+// impractical to achieve with a sequence of normal Volume operations.
+type TestableVolume interface {
+       Volume
+       // [Over]write content for a locator with the given data,
+       // bypassing all constraints like readonly and serialize.
+       PutRaw(locator string, data []byte)
+
+       // Specify the value Mtime() should return, until the next
+       // call to Touch, TouchWithDate, or Put.
+       TouchWithDate(locator string, lastPut time.Time)
+
+       // Clean up, delete temporary files.
+       Teardown()
+}
+
 // MockVolumes are test doubles for Volumes, used to test handlers.
 type MockVolume struct {
        Store      map[string][]byte
        Timestamps map[string]time.Time
+
        // Bad volumes return an error for every operation.
        Bad bool
+
        // Touchable volumes' Touch() method succeeds for a locator
        // that has been Put().
        Touchable bool
+
        // Readonly volumes return an error for Put, Delete, and
        // Touch.
        Readonly bool
+
        // Gate is a "starting gate", allowing test cases to pause
        // volume operations long enough to inspect state. Every
        // operation (except Status) starts by receiving from
@@ -29,7 +57,8 @@ type MockVolume struct {
        // channel unblocks all operations. By default, Gate is a
        // closed channel, so all operations proceed without
        // blocking. See trash_worker_test.go for an example.
-       Gate   chan struct{}
+       Gate chan struct{}
+
        called map[string]int
        mutex  sync.Mutex
 }
@@ -54,11 +83,11 @@ func CreateMockVolume() *MockVolume {
 func (v *MockVolume) CallCount(method string) int {
        v.mutex.Lock()
        defer v.mutex.Unlock()
-       if c, ok := v.called[method]; !ok {
+       c, ok := v.called[method]
+       if !ok {
                return 0
-       } else {
-               return c
        }
+       return c
 }
 
 func (v *MockVolume) gotCall(method string) {
@@ -71,20 +100,37 @@ func (v *MockVolume) gotCall(method string) {
        }
 }
 
-func (v *MockVolume) Get(loc string) ([]byte, error) {
+func (v *MockVolume) Compare(ctx context.Context, loc string, buf []byte) error {
+       v.gotCall("Compare")
+       <-v.Gate
+       if v.Bad {
+               return errors.New("Bad volume")
+       } else if block, ok := v.Store[loc]; ok {
+               if fmt.Sprintf("%x", md5.Sum(block)) != loc {
+                       return DiskHashError
+               }
+               if bytes.Compare(buf, block) != 0 {
+                       return CollisionError
+               }
+               return nil
+       } else {
+               return NotFoundError
+       }
+}
+
+func (v *MockVolume) Get(ctx context.Context, loc string, buf []byte) (int, error) {
        v.gotCall("Get")
        <-v.Gate
        if v.Bad {
-               return nil, errors.New("Bad volume")
+               return 0, errors.New("Bad volume")
        } else if block, ok := v.Store[loc]; ok {
-               buf := bufs.Get(len(block))
-               copy(buf, block)
-               return buf, nil
+               copy(buf[:len(block)], block)
+               return len(block), nil
        }
-       return nil, os.ErrNotExist
+       return 0, os.ErrNotExist
 }
 
-func (v *MockVolume) Put(loc string, block []byte) error {
+func (v *MockVolume) Put(ctx context.Context, loc string, block []byte) error {
        v.gotCall("Put")
        <-v.Gate
        if v.Bad {
@@ -141,14 +187,14 @@ func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
        return nil
 }
 
-func (v *MockVolume) Delete(loc string) error {
+func (v *MockVolume) Trash(loc string) error {
        v.gotCall("Delete")
        <-v.Gate
        if v.Readonly {
                return MethodDisabledError
        }
        if _, ok := v.Store[loc]; ok {
-               if time.Since(v.Timestamps[loc]) < blob_signature_ttl {
+               if time.Since(v.Timestamps[loc]) < time.Duration(theConfig.BlobSignatureTTL) {
                        return nil
                }
                delete(v.Store, loc)
@@ -157,6 +203,22 @@ func (v *MockVolume) Delete(loc string) error {
        return os.ErrNotExist
 }
 
+func (v *MockVolume) DeviceID() string {
+       return "mock-device-id"
+}
+
+func (v *MockVolume) Type() string {
+       return "Mock"
+}
+
+func (v *MockVolume) Start() error {
+       return nil
+}
+
+func (v *MockVolume) Untrash(loc string) error {
+       return nil
+}
+
 func (v *MockVolume) Status() *VolumeStatus {
        var used uint64
        for _, block := range v.Store {
@@ -172,3 +234,10 @@ func (v *MockVolume) String() string {
 func (v *MockVolume) Writable() bool {
        return !v.Readonly
 }
+
+func (v *MockVolume) Replication() int {
+       return 1
+}
+
+func (v *MockVolume) EmptyTrash() {
+}