Merge branch '7121-fix-deadlock' closes #7121
[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 // MockVolumes are test doubles for Volumes, used to test handlers.
16 type MockVolume struct {
17         Store      map[string][]byte
18         Timestamps map[string]time.Time
19         // Bad volumes return an error for every operation.
20         Bad bool
21         // Touchable volumes' Touch() method succeeds for a locator
22         // that has been Put().
23         Touchable bool
24         // Readonly volumes return an error for Put, Delete, and
25         // Touch.
26         Readonly bool
27         // Gate is a "starting gate", allowing test cases to pause
28         // volume operations long enough to inspect state. Every
29         // operation (except Status) starts by receiving from
30         // Gate. Sending one value unblocks one operation; closing the
31         // channel unblocks all operations. By default, Gate is a
32         // closed channel, so all operations proceed without
33         // blocking. See trash_worker_test.go for an example.
34         Gate   chan struct{}
35         called map[string]int
36         mutex  sync.Mutex
37 }
38
39 // CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock
40 // volume.
41 func CreateMockVolume() *MockVolume {
42         gate := make(chan struct{})
43         close(gate)
44         return &MockVolume{
45                 Store:      make(map[string][]byte),
46                 Timestamps: make(map[string]time.Time),
47                 Bad:        false,
48                 Touchable:  true,
49                 Readonly:   false,
50                 called:     map[string]int{},
51                 Gate:       gate,
52         }
53 }
54
55 // CallCount returns how many times the named method has been called.
56 func (v *MockVolume) CallCount(method string) int {
57         v.mutex.Lock()
58         defer v.mutex.Unlock()
59         if c, ok := v.called[method]; !ok {
60                 return 0
61         } else {
62                 return c
63         }
64 }
65
66 func (v *MockVolume) gotCall(method string) {
67         v.mutex.Lock()
68         defer v.mutex.Unlock()
69         if _, ok := v.called[method]; !ok {
70                 v.called[method] = 1
71         } else {
72                 v.called[method]++
73         }
74 }
75
76 func (v *MockVolume) Compare(loc string, buf []byte) error {
77         v.gotCall("Compare")
78         <-v.Gate
79         if v.Bad {
80                 return errors.New("Bad volume")
81         } else if block, ok := v.Store[loc]; ok {
82                 if fmt.Sprintf("%x", md5.Sum(block)) != loc {
83                         return DiskHashError
84                 }
85                 if bytes.Compare(buf, block) != 0 {
86                         return CollisionError
87                 }
88                 return nil
89         } else {
90                 return NotFoundError
91         }
92 }
93
94 func (v *MockVolume) Get(loc string) ([]byte, error) {
95         v.gotCall("Get")
96         <-v.Gate
97         if v.Bad {
98                 return nil, errors.New("Bad volume")
99         } else if block, ok := v.Store[loc]; ok {
100                 buf := bufs.Get(len(block))
101                 copy(buf, block)
102                 return buf, nil
103         }
104         return nil, os.ErrNotExist
105 }
106
107 func (v *MockVolume) Put(loc string, block []byte) error {
108         v.gotCall("Put")
109         <-v.Gate
110         if v.Bad {
111                 return errors.New("Bad volume")
112         }
113         if v.Readonly {
114                 return MethodDisabledError
115         }
116         v.Store[loc] = block
117         return v.Touch(loc)
118 }
119
120 func (v *MockVolume) Touch(loc string) error {
121         v.gotCall("Touch")
122         <-v.Gate
123         if v.Readonly {
124                 return MethodDisabledError
125         }
126         if v.Touchable {
127                 v.Timestamps[loc] = time.Now()
128                 return nil
129         }
130         return errors.New("Touch failed")
131 }
132
133 func (v *MockVolume) Mtime(loc string) (time.Time, error) {
134         v.gotCall("Mtime")
135         <-v.Gate
136         var mtime time.Time
137         var err error
138         if v.Bad {
139                 err = errors.New("Bad volume")
140         } else if t, ok := v.Timestamps[loc]; ok {
141                 mtime = t
142         } else {
143                 err = os.ErrNotExist
144         }
145         return mtime, err
146 }
147
148 func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
149         v.gotCall("IndexTo")
150         <-v.Gate
151         for loc, block := range v.Store {
152                 if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) {
153                         continue
154                 }
155                 _, err := fmt.Fprintf(w, "%s+%d %d\n",
156                         loc, len(block), 123456789)
157                 if err != nil {
158                         return err
159                 }
160         }
161         return nil
162 }
163
164 func (v *MockVolume) Delete(loc string) error {
165         v.gotCall("Delete")
166         <-v.Gate
167         if v.Readonly {
168                 return MethodDisabledError
169         }
170         if _, ok := v.Store[loc]; ok {
171                 if time.Since(v.Timestamps[loc]) < blob_signature_ttl {
172                         return nil
173                 }
174                 delete(v.Store, loc)
175                 return nil
176         }
177         return os.ErrNotExist
178 }
179
180 func (v *MockVolume) Status() *VolumeStatus {
181         var used uint64
182         for _, block := range v.Store {
183                 used = used + uint64(len(block))
184         }
185         return &VolumeStatus{"/bogo", 123, 1000000 - used, used}
186 }
187
188 func (v *MockVolume) String() string {
189         return "[MockVolume]"
190 }
191
192 func (v *MockVolume) Writable() bool {
193         return !v.Readonly
194 }