6260: gofmt
[arvados.git] / services / keepstore / volume_unix_test.go
1 package main
2
3 import (
4         "bytes"
5         "fmt"
6         "io/ioutil"
7         "os"
8         "syscall"
9         "testing"
10         "time"
11 )
12
13 func TempUnixVolume(t *testing.T, serialize bool, readonly bool) *UnixVolume {
14         d, err := ioutil.TempDir("", "volume_test")
15         if err != nil {
16                 t.Fatal(err)
17         }
18         return &UnixVolume{
19                 root:      d,
20                 serialize: serialize,
21                 readonly:  readonly,
22         }
23 }
24
25 func _teardown(v *UnixVolume) {
26         os.RemoveAll(v.root)
27 }
28
29 // _store writes a Keep block directly into a UnixVolume, bypassing
30 // the overhead and safeguards of Put(). Useful for storing bogus data
31 // and isolating unit tests from Put() behavior.
32 func _store(t *testing.T, vol *UnixVolume, filename string, block []byte) {
33         blockdir := fmt.Sprintf("%s/%s", vol.root, filename[:3])
34         if err := os.MkdirAll(blockdir, 0755); err != nil {
35                 t.Fatal(err)
36         }
37
38         blockpath := fmt.Sprintf("%s/%s", blockdir, filename)
39         if f, err := os.Create(blockpath); err == nil {
40                 f.Write(block)
41                 f.Close()
42         } else {
43                 t.Fatal(err)
44         }
45 }
46
47 func TestGet(t *testing.T) {
48         v := TempUnixVolume(t, false, false)
49         defer _teardown(v)
50         _store(t, v, TEST_HASH, TEST_BLOCK)
51
52         buf, err := v.Get(TEST_HASH)
53         if err != nil {
54                 t.Error(err)
55         }
56         if bytes.Compare(buf, TEST_BLOCK) != 0 {
57                 t.Errorf("expected %s, got %s", string(TEST_BLOCK), string(buf))
58         }
59 }
60
61 func TestGetNotFound(t *testing.T) {
62         v := TempUnixVolume(t, false, false)
63         defer _teardown(v)
64         _store(t, v, TEST_HASH, TEST_BLOCK)
65
66         buf, err := v.Get(TEST_HASH_2)
67         switch {
68         case os.IsNotExist(err):
69                 break
70         case err == nil:
71                 t.Errorf("Read should have failed, returned %s", string(buf))
72         default:
73                 t.Errorf("Read expected ErrNotExist, got: %s", err)
74         }
75 }
76
77 func TestPut(t *testing.T) {
78         v := TempUnixVolume(t, false, false)
79         defer _teardown(v)
80
81         err := v.Put(TEST_HASH, TEST_BLOCK)
82         if err != nil {
83                 t.Error(err)
84         }
85         p := fmt.Sprintf("%s/%s/%s", v.root, TEST_HASH[:3], TEST_HASH)
86         if buf, err := ioutil.ReadFile(p); err != nil {
87                 t.Error(err)
88         } else if bytes.Compare(buf, TEST_BLOCK) != 0 {
89                 t.Errorf("Write should have stored %s, did store %s",
90                         string(TEST_BLOCK), string(buf))
91         }
92 }
93
94 func TestPutBadVolume(t *testing.T) {
95         v := TempUnixVolume(t, false, false)
96         defer _teardown(v)
97
98         os.Chmod(v.root, 000)
99         err := v.Put(TEST_HASH, TEST_BLOCK)
100         if err == nil {
101                 t.Error("Write should have failed")
102         }
103 }
104
105 func TestUnixVolumeReadonly(t *testing.T) {
106         v := TempUnixVolume(t, false, false)
107         defer _teardown(v)
108
109         // First write something before marking readonly
110         err := v.Put(TEST_HASH, TEST_BLOCK)
111         if err != nil {
112                 t.Error("got err %v, expected nil", err)
113         }
114
115         v.readonly = true
116
117         _, err = v.Get(TEST_HASH)
118         if err != nil {
119                 t.Error("got err %v, expected nil", err)
120         }
121
122         err = v.Put(TEST_HASH, TEST_BLOCK)
123         if err != MethodDisabledError {
124                 t.Error("got err %v, expected MethodDisabledError", err)
125         }
126
127         err = v.Touch(TEST_HASH)
128         if err != MethodDisabledError {
129                 t.Error("got err %v, expected MethodDisabledError", err)
130         }
131
132         err = v.Delete(TEST_HASH)
133         if err != MethodDisabledError {
134                 t.Error("got err %v, expected MethodDisabledError", err)
135         }
136 }
137
138 // TestPutTouch
139 //     Test that when applying PUT to a block that already exists,
140 //     the block's modification time is updated.
141 func TestPutTouch(t *testing.T) {
142         v := TempUnixVolume(t, false, false)
143         defer _teardown(v)
144
145         if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
146                 t.Error(err)
147         }
148
149         // We'll verify { t0 < threshold < t1 }, where t0 is the
150         // existing block's timestamp on disk before Put() and t1 is
151         // its timestamp after Put().
152         threshold := time.Now().Add(-time.Second)
153
154         // Set the stored block's mtime far enough in the past that we
155         // can see the difference between "timestamp didn't change"
156         // and "timestamp granularity is too low".
157         {
158                 oldtime := time.Now().Add(-20 * time.Second).Unix()
159                 if err := syscall.Utime(v.blockPath(TEST_HASH),
160                         &syscall.Utimbuf{oldtime, oldtime}); err != nil {
161                         t.Error(err)
162                 }
163
164                 // Make sure v.Mtime() agrees the above Utime really worked.
165                 if t0, err := v.Mtime(TEST_HASH); err != nil || t0.IsZero() || !t0.Before(threshold) {
166                         t.Errorf("Setting mtime failed: %v, %v", t0, err)
167                 }
168         }
169
170         // Write the same block again.
171         if err := v.Put(TEST_HASH, TEST_BLOCK); err != nil {
172                 t.Error(err)
173         }
174
175         // Verify threshold < t1
176         t1, err := v.Mtime(TEST_HASH)
177         if err != nil {
178                 t.Error(err)
179         }
180         if t1.Before(threshold) {
181                 t.Errorf("t1 %v must be >= threshold %v after v.Put ",
182                         t1, threshold)
183         }
184 }
185
186 // Serialization tests: launch a bunch of concurrent
187 //
188 // TODO(twp): show that the underlying Read/Write operations executed
189 // serially and not concurrently. The easiest way to do this is
190 // probably to activate verbose or debug logging, capture log output
191 // and examine it to confirm that Reads and Writes did not overlap.
192 //
193 // TODO(twp): a proper test of I/O serialization requires that a
194 // second request start while the first one is still underway.
195 // Guaranteeing that the test behaves this way requires some tricky
196 // synchronization and mocking.  For now we'll just launch a bunch of
197 // requests simultaenously in goroutines and demonstrate that they
198 // return accurate results.
199 //
200 func TestGetSerialized(t *testing.T) {
201         // Create a volume with I/O serialization enabled.
202         v := TempUnixVolume(t, true, false)
203         defer _teardown(v)
204
205         _store(t, v, TEST_HASH, TEST_BLOCK)
206         _store(t, v, TEST_HASH_2, TEST_BLOCK_2)
207         _store(t, v, TEST_HASH_3, TEST_BLOCK_3)
208
209         sem := make(chan int)
210         go func(sem chan int) {
211                 buf, err := v.Get(TEST_HASH)
212                 if err != nil {
213                         t.Errorf("err1: %v", err)
214                 }
215                 if bytes.Compare(buf, TEST_BLOCK) != 0 {
216                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK), string(buf))
217                 }
218                 sem <- 1
219         }(sem)
220
221         go func(sem chan int) {
222                 buf, err := v.Get(TEST_HASH_2)
223                 if err != nil {
224                         t.Errorf("err2: %v", err)
225                 }
226                 if bytes.Compare(buf, TEST_BLOCK_2) != 0 {
227                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK_2), string(buf))
228                 }
229                 sem <- 1
230         }(sem)
231
232         go func(sem chan int) {
233                 buf, err := v.Get(TEST_HASH_3)
234                 if err != nil {
235                         t.Errorf("err3: %v", err)
236                 }
237                 if bytes.Compare(buf, TEST_BLOCK_3) != 0 {
238                         t.Errorf("buf should be %s, is %s", string(TEST_BLOCK_3), string(buf))
239                 }
240                 sem <- 1
241         }(sem)
242
243         // Wait for all goroutines to finish
244         for done := 0; done < 3; {
245                 done += <-sem
246         }
247 }
248
249 func TestPutSerialized(t *testing.T) {
250         // Create a volume with I/O serialization enabled.
251         v := TempUnixVolume(t, true, false)
252         defer _teardown(v)
253
254         sem := make(chan int)
255         go func(sem chan int) {
256                 err := v.Put(TEST_HASH, TEST_BLOCK)
257                 if err != nil {
258                         t.Errorf("err1: %v", err)
259                 }
260                 sem <- 1
261         }(sem)
262
263         go func(sem chan int) {
264                 err := v.Put(TEST_HASH_2, TEST_BLOCK_2)
265                 if err != nil {
266                         t.Errorf("err2: %v", err)
267                 }
268                 sem <- 1
269         }(sem)
270
271         go func(sem chan int) {
272                 err := v.Put(TEST_HASH_3, TEST_BLOCK_3)
273                 if err != nil {
274                         t.Errorf("err3: %v", err)
275                 }
276                 sem <- 1
277         }(sem)
278
279         // Wait for all goroutines to finish
280         for done := 0; done < 2; {
281                 done += <-sem
282         }
283
284         // Double check that we actually wrote the blocks we expected to write.
285         buf, err := v.Get(TEST_HASH)
286         if err != nil {
287                 t.Errorf("Get #1: %v", err)
288         }
289         if bytes.Compare(buf, TEST_BLOCK) != 0 {
290                 t.Errorf("Get #1: expected %s, got %s", string(TEST_BLOCK), string(buf))
291         }
292
293         buf, err = v.Get(TEST_HASH_2)
294         if err != nil {
295                 t.Errorf("Get #2: %v", err)
296         }
297         if bytes.Compare(buf, TEST_BLOCK_2) != 0 {
298                 t.Errorf("Get #2: expected %s, got %s", string(TEST_BLOCK_2), string(buf))
299         }
300
301         buf, err = v.Get(TEST_HASH_3)
302         if err != nil {
303                 t.Errorf("Get #3: %v", err)
304         }
305         if bytes.Compare(buf, TEST_BLOCK_3) != 0 {
306                 t.Errorf("Get #3: expected %s, got %s", string(TEST_BLOCK_3), string(buf))
307         }
308 }
309
310 func TestIsFull(t *testing.T) {
311         v := TempUnixVolume(t, false, false)
312         defer _teardown(v)
313
314         full_path := v.root + "/full"
315         now := fmt.Sprintf("%d", time.Now().Unix())
316         os.Symlink(now, full_path)
317         if !v.IsFull() {
318                 t.Errorf("%s: claims not to be full", v)
319         }
320         os.Remove(full_path)
321
322         // Test with an expired /full link.
323         expired := fmt.Sprintf("%d", time.Now().Unix()-3605)
324         os.Symlink(expired, full_path)
325         if v.IsFull() {
326                 t.Errorf("%s: should no longer be full", v)
327         }
328 }
329
330 func TestNodeStatus(t *testing.T) {
331         v := TempUnixVolume(t, false, false)
332         defer _teardown(v)
333
334         // Get node status and make a basic sanity check.
335         volinfo := v.Status()
336         if volinfo.MountPoint != v.root {
337                 t.Errorf("GetNodeStatus mount_point %s, expected %s", volinfo.MountPoint, v.root)
338         }
339         if volinfo.DeviceNum == 0 {
340                 t.Errorf("uninitialized device_num in %v", volinfo)
341         }
342         if volinfo.BytesFree == 0 {
343                 t.Errorf("uninitialized bytes_free in %v", volinfo)
344         }
345         if volinfo.BytesUsed == 0 {
346                 t.Errorf("uninitialized bytes_used in %v", volinfo)
347         }
348 }