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