7179: A few golint suggested updates. There are still a lot of golint complaints.
[arvados.git] / services / keepstore / volume_unix_test.go
1 package main
2
3 import (
4         "bytes"
5         "errors"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "os"
10         "strings"
11         "sync"
12         "syscall"
13         "testing"
14         "time"
15 )
16
17 type TestableUnixVolume struct {
18         UnixVolume
19         t *testing.T
20 }
21
22 func NewTestableUnixVolume(t *testing.T, serialize bool, readonly bool) *TestableUnixVolume {
23         d, err := ioutil.TempDir("", "volume_test")
24         if err != nil {
25                 t.Fatal(err)
26         }
27         var locker sync.Locker
28         if serialize {
29                 locker = &sync.Mutex{}
30         }
31         return &TestableUnixVolume{
32                 UnixVolume: UnixVolume{
33                         root:     d,
34                         locker:   locker,
35                         readonly: readonly,
36                 },
37                 t: t,
38         }
39 }
40
41 // PutRaw writes a Keep block directly into a UnixVolume, even if
42 // the volume is readonly.
43 func (v *TestableUnixVolume) PutRaw(locator string, data []byte) {
44         defer func(orig bool) {
45                 v.readonly = orig
46         }(v.readonly)
47         v.readonly = false
48         err := v.Put(locator, data)
49         if err != nil {
50                 v.t.Fatal(err)
51         }
52 }
53
54 func (v *TestableUnixVolume) TouchWithDate(locator string, lastPut time.Time) {
55         err := syscall.Utime(v.blockPath(locator), &syscall.Utimbuf{lastPut.Unix(), lastPut.Unix()})
56         if err != nil {
57                 v.t.Fatal(err)
58         }
59 }
60
61 func (v *TestableUnixVolume) Teardown() {
62         if err := os.RemoveAll(v.root); err != nil {
63                 v.t.Fatal(err)
64         }
65 }
66
67 func TestUnixVolumeWithGenericTests(t *testing.T) {
68         DoGenericVolumeTests(t, func(t *testing.T) TestableVolume {
69                 return NewTestableUnixVolume(t, false, false)
70         })
71 }
72
73 func TestUnixVolumeWithGenericTestsSerialized(t *testing.T) {
74         DoGenericVolumeTests(t, func(t *testing.T) TestableVolume {
75                 return NewTestableUnixVolume(t, true, false)
76         })
77 }
78
79 func TestUnixReadOnlyVolumeWithGenericTests(t *testing.T) {
80         DoGenericReadOnlyVolumeTests(t, func(t *testing.T) TestableVolume {
81                 return NewTestableUnixVolume(t, false, true)
82         })
83 }
84
85 func TestUnixReadOnlyVolumeWithGenericTestsSerialized(t *testing.T) {
86         DoGenericReadOnlyVolumeTests(t, func(t *testing.T) TestableVolume {
87                 return NewTestableUnixVolume(t, true, true)
88         })
89 }
90
91 func TestGetNotFound(t *testing.T) {
92         v := NewTestableUnixVolume(t, false, false)
93         defer v.Teardown()
94         v.Put(TEST_HASH, TEST_BLOCK)
95
96         buf, err := v.Get(TEST_HASH_2)
97         switch {
98         case os.IsNotExist(err):
99                 break
100         case err == nil:
101                 t.Errorf("Read should have failed, returned %s", string(buf))
102         default:
103                 t.Errorf("Read expected ErrNotExist, got: %s", err)
104         }
105 }
106
107 func TestPut(t *testing.T) {
108         v := NewTestableUnixVolume(t, false, false)
109         defer v.Teardown()
110
111         err := v.Put(TEST_HASH, TEST_BLOCK)
112         if err != nil {
113                 t.Error(err)
114         }
115         p := fmt.Sprintf("%s/%s/%s", v.root, TEST_HASH[:3], TEST_HASH)
116         if buf, err := ioutil.ReadFile(p); err != nil {
117                 t.Error(err)
118         } else if bytes.Compare(buf, TEST_BLOCK) != 0 {
119                 t.Errorf("Write should have stored %s, did store %s",
120                         string(TEST_BLOCK), string(buf))
121         }
122 }
123
124 func TestPutBadVolume(t *testing.T) {
125         v := NewTestableUnixVolume(t, false, false)
126         defer v.Teardown()
127
128         os.Chmod(v.root, 000)
129         err := v.Put(TEST_HASH, TEST_BLOCK)
130         if err == nil {
131                 t.Error("Write should have failed")
132         }
133 }
134
135 func TestUnixVolumeReadonly(t *testing.T) {
136         v := NewTestableUnixVolume(t, false, true)
137         defer v.Teardown()
138
139         v.PutRaw(TEST_HASH, TEST_BLOCK)
140
141         _, err := v.Get(TEST_HASH)
142         if err != nil {
143                 t.Errorf("got err %v, expected nil", err)
144         }
145
146         err = v.Put(TEST_HASH, TEST_BLOCK)
147         if err != MethodDisabledError {
148                 t.Errorf("got err %v, expected MethodDisabledError", err)
149         }
150
151         err = v.Touch(TEST_HASH)
152         if err != MethodDisabledError {
153                 t.Errorf("got err %v, expected MethodDisabledError", err)
154         }
155
156         err = v.Delete(TEST_HASH)
157         if err != MethodDisabledError {
158                 t.Errorf("got err %v, expected MethodDisabledError", err)
159         }
160 }
161
162 func TestIsFull(t *testing.T) {
163         v := NewTestableUnixVolume(t, false, false)
164         defer v.Teardown()
165
166         fullPath := v.root + "/full"
167         now := fmt.Sprintf("%d", time.Now().Unix())
168         os.Symlink(now, fullPath)
169         if !v.IsFull() {
170                 t.Errorf("%s: claims not to be full", v)
171         }
172         os.Remove(fullPath)
173
174         // Test with an expired /full link.
175         expired := fmt.Sprintf("%d", time.Now().Unix()-3605)
176         os.Symlink(expired, fullPath)
177         if v.IsFull() {
178                 t.Errorf("%s: should no longer be full", v)
179         }
180 }
181
182 func TestNodeStatus(t *testing.T) {
183         v := NewTestableUnixVolume(t, false, false)
184         defer v.Teardown()
185
186         // Get node status and make a basic sanity check.
187         volinfo := v.Status()
188         if volinfo.MountPoint != v.root {
189                 t.Errorf("GetNodeStatus mount_point %s, expected %s", volinfo.MountPoint, v.root)
190         }
191         if volinfo.DeviceNum == 0 {
192                 t.Errorf("uninitialized device_num in %v", volinfo)
193         }
194         if volinfo.BytesFree == 0 {
195                 t.Errorf("uninitialized bytes_free in %v", volinfo)
196         }
197         if volinfo.BytesUsed == 0 {
198                 t.Errorf("uninitialized bytes_used in %v", volinfo)
199         }
200 }
201
202 func TestUnixVolumeGetFuncWorkerError(t *testing.T) {
203         v := NewTestableUnixVolume(t, false, false)
204         defer v.Teardown()
205
206         v.Put(TEST_HASH, TEST_BLOCK)
207         mockErr := errors.New("Mock error")
208         err := v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
209                 return mockErr
210         })
211         if err != mockErr {
212                 t.Errorf("Got %v, expected %v", err, mockErr)
213         }
214 }
215
216 func TestUnixVolumeGetFuncFileError(t *testing.T) {
217         v := NewTestableUnixVolume(t, false, false)
218         defer v.Teardown()
219
220         funcCalled := false
221         err := v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
222                 funcCalled = true
223                 return nil
224         })
225         if err == nil {
226                 t.Errorf("Expected error opening non-existent file")
227         }
228         if funcCalled {
229                 t.Errorf("Worker func should not have been called")
230         }
231 }
232
233 func TestUnixVolumeGetFuncWorkerWaitsOnMutex(t *testing.T) {
234         v := NewTestableUnixVolume(t, false, false)
235         defer v.Teardown()
236
237         v.Put(TEST_HASH, TEST_BLOCK)
238
239         mtx := NewMockMutex()
240         v.locker = mtx
241
242         funcCalled := make(chan struct{})
243         go v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
244                 funcCalled <- struct{}{}
245                 return nil
246         })
247         select {
248         case mtx.AllowLock <- struct{}{}:
249         case <-funcCalled:
250                 t.Fatal("Function was called before mutex was acquired")
251         case <-time.After(5 * time.Second):
252                 t.Fatal("Timed out before mutex was acquired")
253         }
254         select {
255         case <-funcCalled:
256         case mtx.AllowUnlock <- struct{}{}:
257                 t.Fatal("Mutex was released before function was called")
258         case <-time.After(5 * time.Second):
259                 t.Fatal("Timed out waiting for funcCalled")
260         }
261         select {
262         case mtx.AllowUnlock <- struct{}{}:
263         case <-time.After(5 * time.Second):
264                 t.Fatal("Timed out waiting for getFunc() to release mutex")
265         }
266 }
267
268 func TestUnixVolumeCompare(t *testing.T) {
269         v := NewTestableUnixVolume(t, false, false)
270         defer v.Teardown()
271
272         v.Put(TEST_HASH, TEST_BLOCK)
273         err := v.Compare(TEST_HASH, TEST_BLOCK)
274         if err != nil {
275                 t.Errorf("Got err %q, expected nil", err)
276         }
277
278         err = v.Compare(TEST_HASH, []byte("baddata"))
279         if err != CollisionError {
280                 t.Errorf("Got err %q, expected %q", err, CollisionError)
281         }
282
283         v.Put(TEST_HASH, []byte("baddata"))
284         err = v.Compare(TEST_HASH, TEST_BLOCK)
285         if err != DiskHashError {
286                 t.Errorf("Got err %q, expected %q", err, DiskHashError)
287         }
288
289         p := fmt.Sprintf("%s/%s/%s", v.root, TEST_HASH[:3], TEST_HASH)
290         os.Chmod(p, 000)
291         err = v.Compare(TEST_HASH, TEST_BLOCK)
292         if err == nil || strings.Index(err.Error(), "permission denied") < 0 {
293                 t.Errorf("Got err %q, expected %q", err, "permission denied")
294         }
295 }
296
297 // TODO(twp): show that the underlying Read/Write operations executed
298 // serially and not concurrently. The easiest way to do this is
299 // probably to activate verbose or debug logging, capture log output
300 // and examine it to confirm that Reads and Writes did not overlap.
301 //
302 // TODO(twp): a proper test of I/O serialization requires that a
303 // second request start while the first one is still underway.
304 // Guaranteeing that the test behaves this way requires some tricky
305 // synchronization and mocking.  For now we'll just launch a bunch of
306 // requests simultaenously in goroutines and demonstrate that they
307 // return accurate results.