Added TestPutBlockOneVol and TestPutBlockCorrupt. (refs #2292, refs #2449)
[arvados.git] / services / keep / keep_test.go
1 package main
2
3 import (
4         "fmt"
5         "io/ioutil"
6         "os"
7         "path"
8         "testing"
9 )
10
11 var TEST_BLOCK = []byte("The quick brown fox jumps over the lazy dog.")
12 var TEST_HASH = "e4d909c290d0fb1ca068ffaddf22cbd0"
13 var BAD_BLOCK = []byte("The magic words are squeamish ossifrage.")
14
15 // ========================================
16 // GetBlock tests.
17 // ========================================
18
19 // Test simple block reads.
20 func TestGetBlockOK(t *testing.T) {
21         defer teardown()
22
23         // Create two test Keep volumes and store a block in each of them.
24         KeepVolumes = setup(t, 2)
25
26         for _, vol := range KeepVolumes {
27                 store(t, vol, TEST_HASH, TEST_BLOCK)
28         }
29
30         // Check that GetBlock returns success.
31         result, err := GetBlock(TEST_HASH)
32         if err != nil {
33                 t.Errorf("GetBlock error: %s", err)
34         }
35         if fmt.Sprint(result) != fmt.Sprint(TEST_BLOCK) {
36                 t.Errorf("expected %s, got %s", TEST_BLOCK, result)
37         }
38 }
39
40 // Test block reads when one Keep volume is missing.
41 func TestGetBlockOneKeepOK(t *testing.T) {
42         defer teardown()
43
44         // Two test Keep volumes, only the second has a block.
45         KeepVolumes = setup(t, 2)
46         store(t, KeepVolumes[1], TEST_HASH, TEST_BLOCK)
47
48         // Check that GetBlock returns success.
49         result, err := GetBlock(TEST_HASH)
50         if err != nil {
51                 t.Errorf("GetBlock error: %s", err)
52         }
53         if fmt.Sprint(result) != fmt.Sprint(TEST_BLOCK) {
54                 t.Errorf("expected %s, got %s", TEST_BLOCK, result)
55         }
56 }
57
58 // Test block read failure.
59 func TestGetBlockFail(t *testing.T) {
60         defer teardown()
61
62         // Create two empty test Keep volumes.
63         KeepVolumes = setup(t, 2)
64
65         // Check that GetBlock returns failure.
66         result, err := GetBlock(TEST_HASH)
67         if err == nil {
68                 t.Errorf("GetBlock incorrectly returned success: ", result)
69         }
70 }
71
72 // Test reading a corrupt block.
73 func TestGetBlockCorrupt(t *testing.T) {
74         defer teardown()
75
76         // Create two test Keep volumes and store a block in each of them,
77         // but the hash of the block does not match the filename.
78         KeepVolumes = setup(t, 2)
79         for _, vol := range KeepVolumes {
80                 store(t, vol, TEST_HASH, BAD_BLOCK)
81         }
82
83         // Check that GetBlock returns failure.
84         result, err := GetBlock(TEST_HASH)
85         if err == nil {
86                 t.Errorf("GetBlock incorrectly returned success: %s", result)
87         }
88 }
89
90 // ========================================
91 // PutBlock tests
92 // ========================================
93
94 // Test simple block stores.
95
96 func TestPutBlockOK(t *testing.T) {
97         defer teardown()
98
99         // Create two test Keep volumes.
100         KeepVolumes = setup(t, 2)
101
102         // Check that PutBlock stores the data as expected.
103         if err := PutBlock(TEST_BLOCK, TEST_HASH); err != nil {
104                 t.Fatalf("PutBlock: %v", err)
105         }
106
107         result, err := GetBlock(TEST_HASH)
108         if err != nil {
109                 t.Fatalf("GetBlock: %s", err.Error())
110         }
111         if string(result) != string(TEST_BLOCK) {
112                 t.Error("PutBlock/GetBlock mismatch")
113                 t.Fatalf("PutBlock stored '%s', GetBlock retrieved '%s'",
114                         string(TEST_BLOCK), string(result))
115         }
116 }
117
118 // Test storing a block when only one volume (of many) is not available.
119 func TestPutBlockOneVol(t *testing.T) {
120         defer teardown()
121
122         // Create two test Keep volumes, but cripple one of them.
123         KeepVolumes = setup(t, 2)
124         os.Chmod(KeepVolumes[0], 000)
125
126         // Check that PutBlock stores the data as expected.
127         if err := PutBlock(TEST_BLOCK, TEST_HASH); err != nil {
128                 t.Fatalf("PutBlock: %v", err)
129         }
130
131         result, err := GetBlock(TEST_HASH)
132         if err != nil {
133                 t.Fatalf("GetBlock: %s", err.Error())
134         }
135         if string(result) != string(TEST_BLOCK) {
136                 t.Error("PutBlock/GetBlock mismatch")
137                 t.Fatalf("PutBlock stored '%s', GetBlock retrieved '%s'",
138                         string(TEST_BLOCK), string(result))
139         }
140 }
141
142 // TestPutBlockCorrupt
143 //     Check that PutBlock returns an error if passed a block and hash that
144 //     do not match.
145 //
146 func TestPutBlockCorrupt(t *testing.T) {
147         defer teardown()
148
149         // Create two test Keep volumes.
150         KeepVolumes = setup(t, 2)
151
152         // Check that PutBlock returns the expected error when the hash does
153         // not match the block.
154         if err := PutBlock(BAD_BLOCK, TEST_HASH); err == nil {
155                 t.Error("PutBlock succeeded despite a block mismatch")
156         } else {
157                 ke := err.(*KeepError)
158                 if ke.HTTPCode != 401 || ke.Err.Error() != "MD5Fail" {
159                         t.Errorf("PutBlock returned the wrong error (%v)", ke)
160                 }
161         }
162
163         // Confirm that GetBlock fails to return anything.
164         if result, err := GetBlock(TEST_HASH); err == nil {
165                 t.Errorf("GetBlock succeded after a corrupt block store, returned '%s'",
166                         string(result))
167         }
168 }
169
170 // Test finding Keep volumes.
171 func TestFindKeepVolumes(t *testing.T) {
172         defer teardown()
173
174         // Initialize two keep volumes.
175         var tempVols []string = setup(t, 2)
176
177         // Set up a bogus PROC_MOUNTS file.
178         if f, err := ioutil.TempFile("", "keeptest"); err == nil {
179                 for _, vol := range tempVols {
180                         fmt.Fprintf(f, "tmpfs %s tmpfs opts\n", path.Dir(vol))
181                 }
182                 f.Close()
183                 PROC_MOUNTS = f.Name()
184
185                 // Check that FindKeepVolumes finds the temp volumes.
186                 resultVols := FindKeepVolumes()
187                 if len(tempVols) != len(resultVols) {
188                         t.Fatalf("set up %d volumes, FindKeepVolumes found %d\n",
189                                 len(tempVols), len(resultVols))
190                 }
191                 for i := range tempVols {
192                         if tempVols[i] != resultVols[i] {
193                                 t.Errorf("FindKeepVolumes returned %s, expected %s\n",
194                                         resultVols[i], tempVols[i])
195                         }
196                 }
197
198                 os.Remove(f.Name())
199         }
200 }
201
202 // Test that FindKeepVolumes returns an empty slice when no Keep volumes
203 // are present.
204 func TestFindKeepVolumesFail(t *testing.T) {
205         defer teardown()
206
207         // Set up a bogus PROC_MOUNTS file with no Keep vols.
208         if f, err := ioutil.TempFile("", "keeptest"); err == nil {
209                 fmt.Fprintln(f, "rootfs / rootfs opts 0 0")
210                 fmt.Fprintln(f, "sysfs /sys sysfs opts 0 0")
211                 fmt.Fprintln(f, "proc /proc proc opts 0 0")
212                 fmt.Fprintln(f, "udev /dev devtmpfs opts 0 0")
213                 fmt.Fprintln(f, "devpts /dev/pts devpts opts 0 0")
214                 f.Close()
215                 PROC_MOUNTS = f.Name()
216
217                 // Check that FindKeepVolumes returns an empty array.
218                 resultVols := FindKeepVolumes()
219                 if len(resultVols) != 0 {
220                         t.Fatalf("FindKeepVolumes returned %v", resultVols)
221                 }
222
223                 os.Remove(PROC_MOUNTS)
224         }
225 }
226
227 // setup
228 //     Create KeepVolumes for testing.
229 //     Returns a slice of pathnames to temporary Keep volumes.
230 //
231 func setup(t *testing.T, num_volumes int) []string {
232         vols := make([]string, num_volumes)
233         for i := range vols {
234                 if dir, err := ioutil.TempDir(os.TempDir(), "keeptest"); err == nil {
235                         vols[i] = dir + "/keep"
236                         os.Mkdir(vols[i], 0755)
237                 } else {
238                         t.Fatal(err)
239                 }
240         }
241         return vols
242 }
243
244 // teardown
245 //     Cleanup to perform after each test.
246 //
247 func teardown() {
248         for _, vol := range KeepVolumes {
249                 os.RemoveAll(path.Dir(vol))
250         }
251 }
252
253 // store
254 //     Low-level code to write Keep blocks directly to disk for testing.
255 //
256 func store(t *testing.T, keepdir string, filename string, block []byte) error {
257         blockdir := fmt.Sprintf("%s/%s", keepdir, filename[:3])
258         if err := os.MkdirAll(blockdir, 0755); err != nil {
259                 t.Fatal(err)
260         }
261
262         blockpath := fmt.Sprintf("%s/%s", blockdir, filename)
263         if f, err := os.Create(blockpath); err == nil {
264                 f.Write(block)
265                 f.Close()
266         } else {
267                 t.Fatal(err)
268         }
269
270         return nil
271 }