X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/dc0237c289882a058a0d5c91b720a4b37917bc9d..64efd1030538d59821ce288a7674e29d49c35744:/services/keepstore/volume_test.go diff --git a/services/keepstore/volume_test.go b/services/keepstore/volume_test.go index 261501992f..6ab386aec4 100644 --- a/services/keepstore/volume_test.go +++ b/services/keepstore/volume_test.go @@ -1,6 +1,8 @@ package main import ( + "bytes" + "crypto/md5" "errors" "fmt" "io" @@ -10,25 +12,57 @@ 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 - called map[string]int - mutex sync.Mutex + + // 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 + // Gate. Sending one value unblocks one operation; closing the + // 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{} + + called map[string]int + mutex sync.Mutex } // CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock // volume. func CreateMockVolume() *MockVolume { + gate := make(chan struct{}) + close(gate) return &MockVolume{ Store: make(map[string][]byte), Timestamps: make(map[string]time.Time), @@ -36,6 +70,7 @@ func CreateMockVolume() *MockVolume { Touchable: true, Readonly: false, called: map[string]int{}, + Gate: gate, } } @@ -43,11 +78,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) { @@ -60,20 +95,39 @@ func (v *MockVolume) gotCall(method string) { } } -func (v *MockVolume) Get(loc string) ([]byte, error) { +func (v *MockVolume) Compare(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(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 { v.gotCall("Put") + <-v.Gate if v.Bad { return errors.New("Bad volume") } @@ -86,6 +140,7 @@ func (v *MockVolume) Put(loc string, block []byte) error { func (v *MockVolume) Touch(loc string) error { v.gotCall("Touch") + <-v.Gate if v.Readonly { return MethodDisabledError } @@ -98,6 +153,7 @@ func (v *MockVolume) Touch(loc string) error { func (v *MockVolume) Mtime(loc string) (time.Time, error) { v.gotCall("Mtime") + <-v.Gate var mtime time.Time var err error if v.Bad { @@ -112,6 +168,7 @@ func (v *MockVolume) Mtime(loc string) (time.Time, error) { func (v *MockVolume) IndexTo(prefix string, w io.Writer) error { v.gotCall("IndexTo") + <-v.Gate for loc, block := range v.Store { if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) { continue @@ -125,13 +182,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) @@ -140,6 +198,18 @@ func (v *MockVolume) Delete(loc string) error { return os.ErrNotExist } +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 { @@ -155,3 +225,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() { +}