Merge branch '9068-drop-abandoned-conns'
[arvados.git] / services / keepstore / volume_test.go
1 package main
2
3 import (
4         "bytes"
5         "crypto/md5"
6         "errors"
7         "fmt"
8         "io"
9         "os"
10         "strings"
11         "sync"
12         "time"
13 )
14
15 // A TestableVolume allows test suites to manipulate the state of an
16 // underlying Volume, in order to test behavior in cases that are
17 // impractical to achieve with a sequence of normal Volume operations.
18 type TestableVolume interface {
19         Volume
20         // [Over]write content for a locator with the given data,
21         // bypassing all constraints like readonly and serialize.
22         PutRaw(locator string, data []byte)
23
24         // Specify the value Mtime() should return, until the next
25         // call to Touch, TouchWithDate, or Put.
26         TouchWithDate(locator string, lastPut time.Time)
27
28         // Clean up, delete temporary files.
29         Teardown()
30 }
31
32 // MockVolumes are test doubles for Volumes, used to test handlers.
33 type MockVolume struct {
34         Store      map[string][]byte
35         Timestamps map[string]time.Time
36
37         // Bad volumes return an error for every operation.
38         Bad bool
39
40         // Touchable volumes' Touch() method succeeds for a locator
41         // that has been Put().
42         Touchable bool
43
44         // Readonly volumes return an error for Put, Delete, and
45         // Touch.
46         Readonly bool
47
48         // Gate is a "starting gate", allowing test cases to pause
49         // volume operations long enough to inspect state. Every
50         // operation (except Status) starts by receiving from
51         // Gate. Sending one value unblocks one operation; closing the
52         // channel unblocks all operations. By default, Gate is a
53         // closed channel, so all operations proceed without
54         // blocking. See trash_worker_test.go for an example.
55         Gate chan struct{}
56
57         called map[string]int
58         mutex  sync.Mutex
59 }
60
61 // CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock
62 // volume.
63 func CreateMockVolume() *MockVolume {
64         gate := make(chan struct{})
65         close(gate)
66         return &MockVolume{
67                 Store:      make(map[string][]byte),
68                 Timestamps: make(map[string]time.Time),
69                 Bad:        false,
70                 Touchable:  true,
71                 Readonly:   false,
72                 called:     map[string]int{},
73                 Gate:       gate,
74         }
75 }
76
77 // CallCount returns how many times the named method has been called.
78 func (v *MockVolume) CallCount(method string) int {
79         v.mutex.Lock()
80         defer v.mutex.Unlock()
81         c, ok := v.called[method]
82         if !ok {
83                 return 0
84         }
85         return c
86 }
87
88 func (v *MockVolume) gotCall(method string) {
89         v.mutex.Lock()
90         defer v.mutex.Unlock()
91         if _, ok := v.called[method]; !ok {
92                 v.called[method] = 1
93         } else {
94                 v.called[method]++
95         }
96 }
97
98 func (v *MockVolume) Compare(loc string, buf []byte) error {
99         v.gotCall("Compare")
100         <-v.Gate
101         if v.Bad {
102                 return errors.New("Bad volume")
103         } else if block, ok := v.Store[loc]; ok {
104                 if fmt.Sprintf("%x", md5.Sum(block)) != loc {
105                         return DiskHashError
106                 }
107                 if bytes.Compare(buf, block) != 0 {
108                         return CollisionError
109                 }
110                 return nil
111         } else {
112                 return NotFoundError
113         }
114 }
115
116 func (v *MockVolume) Get(loc string, buf []byte) (int, error) {
117         v.gotCall("Get")
118         <-v.Gate
119         if v.Bad {
120                 return 0, errors.New("Bad volume")
121         } else if block, ok := v.Store[loc]; ok {
122                 copy(buf[:len(block)], block)
123                 return len(block), nil
124         }
125         return 0, os.ErrNotExist
126 }
127
128 func (v *MockVolume) Put(loc string, block []byte) error {
129         v.gotCall("Put")
130         <-v.Gate
131         if v.Bad {
132                 return errors.New("Bad volume")
133         }
134         if v.Readonly {
135                 return MethodDisabledError
136         }
137         v.Store[loc] = block
138         return v.Touch(loc)
139 }
140
141 func (v *MockVolume) Touch(loc string) error {
142         v.gotCall("Touch")
143         <-v.Gate
144         if v.Readonly {
145                 return MethodDisabledError
146         }
147         if v.Touchable {
148                 v.Timestamps[loc] = time.Now()
149                 return nil
150         }
151         return errors.New("Touch failed")
152 }
153
154 func (v *MockVolume) Mtime(loc string) (time.Time, error) {
155         v.gotCall("Mtime")
156         <-v.Gate
157         var mtime time.Time
158         var err error
159         if v.Bad {
160                 err = errors.New("Bad volume")
161         } else if t, ok := v.Timestamps[loc]; ok {
162                 mtime = t
163         } else {
164                 err = os.ErrNotExist
165         }
166         return mtime, err
167 }
168
169 func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
170         v.gotCall("IndexTo")
171         <-v.Gate
172         for loc, block := range v.Store {
173                 if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) {
174                         continue
175                 }
176                 _, err := fmt.Fprintf(w, "%s+%d %d\n",
177                         loc, len(block), 123456789)
178                 if err != nil {
179                         return err
180                 }
181         }
182         return nil
183 }
184
185 func (v *MockVolume) Trash(loc string) error {
186         v.gotCall("Delete")
187         <-v.Gate
188         if v.Readonly {
189                 return MethodDisabledError
190         }
191         if _, ok := v.Store[loc]; ok {
192                 if time.Since(v.Timestamps[loc]) < blobSignatureTTL {
193                         return nil
194                 }
195                 delete(v.Store, loc)
196                 return nil
197         }
198         return os.ErrNotExist
199 }
200
201 // TBD
202 func (v *MockVolume) Untrash(loc string) error {
203         return nil
204 }
205
206 func (v *MockVolume) Status() *VolumeStatus {
207         var used uint64
208         for _, block := range v.Store {
209                 used = used + uint64(len(block))
210         }
211         return &VolumeStatus{"/bogo", 123, 1000000 - used, used}
212 }
213
214 func (v *MockVolume) String() string {
215         return "[MockVolume]"
216 }
217
218 func (v *MockVolume) Writable() bool {
219         return !v.Readonly
220 }
221
222 func (v *MockVolume) Replication() int {
223         return 1
224 }
225
226 func (v *MockVolume) EmptyTrash() {
227 }