7fea560d5a726def8e2dd8b71d58ea24359adb7d
[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 TestUnixReadOnlyVolumeWithGenericTests(t *testing.T) {
74         DoGenericReadOnlyVolumeTests(t, func(t *testing.T) TestableVolume {
75                 return NewTestableUnixVolume(t, false, true)
76         })
77 }
78
79 func TestGetNotFound(t *testing.T) {
80         v := NewTestableUnixVolume(t, false, false)
81         defer v.Teardown()
82         v.Put(TEST_HASH, TEST_BLOCK)
83
84         buf, err := v.Get(TEST_HASH_2)
85         switch {
86         case os.IsNotExist(err):
87                 break
88         case err == nil:
89                 t.Errorf("Read should have failed, returned %s", string(buf))
90         default:
91                 t.Errorf("Read expected ErrNotExist, got: %s", err)
92         }
93 }
94
95 func TestPut(t *testing.T) {
96         v := NewTestableUnixVolume(t, false, false)
97         defer v.Teardown()
98
99         err := v.Put(TEST_HASH, TEST_BLOCK)
100         if err != nil {
101                 t.Error(err)
102         }
103         p := fmt.Sprintf("%s/%s/%s", v.root, TEST_HASH[:3], TEST_HASH)
104         if buf, err := ioutil.ReadFile(p); err != nil {
105                 t.Error(err)
106         } else if bytes.Compare(buf, TEST_BLOCK) != 0 {
107                 t.Errorf("Write should have stored %s, did store %s",
108                         string(TEST_BLOCK), string(buf))
109         }
110 }
111
112 func TestPutBadVolume(t *testing.T) {
113         v := NewTestableUnixVolume(t, false, false)
114         defer v.Teardown()
115
116         os.Chmod(v.root, 000)
117         err := v.Put(TEST_HASH, TEST_BLOCK)
118         if err == nil {
119                 t.Error("Write should have failed")
120         }
121 }
122
123 func TestUnixVolumeReadonly(t *testing.T) {
124         v := NewTestableUnixVolume(t, false, true)
125         defer v.Teardown()
126
127         v.PutRaw(TEST_HASH, TEST_BLOCK)
128
129         _, err := v.Get(TEST_HASH)
130         if err != nil {
131                 t.Errorf("got err %v, expected nil", err)
132         }
133
134         err = v.Put(TEST_HASH, TEST_BLOCK)
135         if err != MethodDisabledError {
136                 t.Errorf("got err %v, expected MethodDisabledError", err)
137         }
138
139         err = v.Touch(TEST_HASH)
140         if err != MethodDisabledError {
141                 t.Errorf("got err %v, expected MethodDisabledError", err)
142         }
143
144         err = v.Delete(TEST_HASH)
145         if err != MethodDisabledError {
146                 t.Errorf("got err %v, expected MethodDisabledError", err)
147         }
148 }
149
150 func TestIsFull(t *testing.T) {
151         v := NewTestableUnixVolume(t, false, false)
152         defer v.Teardown()
153
154         full_path := v.root + "/full"
155         now := fmt.Sprintf("%d", time.Now().Unix())
156         os.Symlink(now, full_path)
157         if !v.IsFull() {
158                 t.Errorf("%s: claims not to be full", v)
159         }
160         os.Remove(full_path)
161
162         // Test with an expired /full link.
163         expired := fmt.Sprintf("%d", time.Now().Unix()-3605)
164         os.Symlink(expired, full_path)
165         if v.IsFull() {
166                 t.Errorf("%s: should no longer be full", v)
167         }
168 }
169
170 func TestNodeStatus(t *testing.T) {
171         v := NewTestableUnixVolume(t, false, false)
172         defer v.Teardown()
173
174         // Get node status and make a basic sanity check.
175         volinfo := v.Status()
176         if volinfo.MountPoint != v.root {
177                 t.Errorf("GetNodeStatus mount_point %s, expected %s", volinfo.MountPoint, v.root)
178         }
179         if volinfo.DeviceNum == 0 {
180                 t.Errorf("uninitialized device_num in %v", volinfo)
181         }
182         if volinfo.BytesFree == 0 {
183                 t.Errorf("uninitialized bytes_free in %v", volinfo)
184         }
185         if volinfo.BytesUsed == 0 {
186                 t.Errorf("uninitialized bytes_used in %v", volinfo)
187         }
188 }
189
190 func TestUnixVolumeGetFuncWorkerError(t *testing.T) {
191         v := NewTestableUnixVolume(t, false, false)
192         defer v.Teardown()
193
194         v.Put(TEST_HASH, TEST_BLOCK)
195         mockErr := errors.New("Mock error")
196         err := v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
197                 return mockErr
198         })
199         if err != mockErr {
200                 t.Errorf("Got %v, expected %v", err, mockErr)
201         }
202 }
203
204 func TestUnixVolumeGetFuncFileError(t *testing.T) {
205         v := NewTestableUnixVolume(t, false, false)
206         defer v.Teardown()
207
208         funcCalled := false
209         err := v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
210                 funcCalled = true
211                 return nil
212         })
213         if err == nil {
214                 t.Errorf("Expected error opening non-existent file")
215         }
216         if funcCalled {
217                 t.Errorf("Worker func should not have been called")
218         }
219 }
220
221 func TestUnixVolumeGetFuncWorkerWaitsOnMutex(t *testing.T) {
222         v := NewTestableUnixVolume(t, false, false)
223         defer v.Teardown()
224
225         v.Put(TEST_HASH, TEST_BLOCK)
226
227         mtx := NewMockMutex()
228         v.locker = mtx
229
230         funcCalled := make(chan struct{})
231         go v.getFunc(v.blockPath(TEST_HASH), func(rdr io.Reader) error {
232                 funcCalled <- struct{}{}
233                 return nil
234         })
235         select {
236         case mtx.AllowLock <- struct{}{}:
237         case <-funcCalled:
238                 t.Fatal("Function was called before mutex was acquired")
239         case <-time.After(5 * time.Second):
240                 t.Fatal("Timed out before mutex was acquired")
241         }
242         select {
243         case <-funcCalled:
244         case mtx.AllowUnlock <- struct{}{}:
245                 t.Fatal("Mutex was released before function was called")
246         case <-time.After(5 * time.Second):
247                 t.Fatal("Timed out waiting for funcCalled")
248         }
249         select {
250         case mtx.AllowUnlock <- struct{}{}:
251         case <-time.After(5 * time.Second):
252                 t.Fatal("Timed out waiting for getFunc() to release mutex")
253         }
254 }
255
256 func TestUnixVolumeCompare(t *testing.T) {
257         v := NewTestableUnixVolume(t, false, false)
258         defer v.Teardown()
259
260         v.Put(TEST_HASH, TEST_BLOCK)
261         err := v.Compare(TEST_HASH, TEST_BLOCK)
262         if err != nil {
263                 t.Errorf("Got err %q, expected nil", err)
264         }
265
266         err = v.Compare(TEST_HASH, []byte("baddata"))
267         if err != CollisionError {
268                 t.Errorf("Got err %q, expected %q", err, CollisionError)
269         }
270
271         v.Put(TEST_HASH, []byte("baddata"))
272         err = v.Compare(TEST_HASH, TEST_BLOCK)
273         if err != DiskHashError {
274                 t.Errorf("Got err %q, expected %q", err, DiskHashError)
275         }
276
277         p := fmt.Sprintf("%s/%s/%s", v.root, TEST_HASH[:3], TEST_HASH)
278         os.Chmod(p, 000)
279         err = v.Compare(TEST_HASH, TEST_BLOCK)
280         if err == nil || strings.Index(err.Error(), "permission denied") < 0 {
281                 t.Errorf("Got err %q, expected %q", err, "permission denied")
282         }
283 }