8724: performKeepBlockCheck() returns error when any of the listed blocks are not...
[arvados.git] / tools / keep-block-check / keep-block-check_test.go
1 package main
2
3 import (
4         "fmt"
5         "io/ioutil"
6         "log"
7         "os"
8         "regexp"
9         "strings"
10         "testing"
11
12         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
13         "git.curoverse.com/arvados.git/sdk/go/keepclient"
14
15         . "gopkg.in/check.v1"
16 )
17
18 // Gocheck boilerplate
19 func Test(t *testing.T) {
20         TestingT(t)
21 }
22
23 // Gocheck boilerplate
24 var _ = Suite(&ServerRequiredSuite{})
25 var _ = Suite(&DoMainTestSuite{})
26
27 type ServerRequiredSuite struct{}
28 type DoMainTestSuite struct{}
29
30 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
31         arvadostest.StartAPI()
32 }
33
34 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
35         arvadostest.StopAPI()
36         arvadostest.ResetEnv()
37 }
38
39 func (s *ServerRequiredSuite) SetUpTest(c *C) {
40         blobSigningKey = ""
41         keepServicesJSON = ""
42
43         tempfile, err := ioutil.TempFile(os.TempDir(), "temp-log-file")
44         c.Check(err, IsNil)
45         log.SetOutput(tempfile)
46         tempLogFileName = tempfile.Name()
47 }
48
49 func (s *ServerRequiredSuite) TearDownTest(c *C) {
50         arvadostest.StopKeep(2)
51         os.Remove(tempLogFileName)
52 }
53
54 var tempLogFileName = ""
55 var initialArgs []string
56 var kc *keepclient.KeepClient
57 var keepServicesJSON, blobSigningKey string
58
59 func (s *DoMainTestSuite) SetUpSuite(c *C) {
60         initialArgs = os.Args
61 }
62
63 func (s *DoMainTestSuite) SetUpTest(c *C) {
64         blobSigningKey = ""
65         keepServicesJSON = ""
66
67         args := []string{"keep-block-check"}
68         os.Args = args
69
70         tempfile, err := ioutil.TempFile(os.TempDir(), "temp-log-file")
71         c.Check(err, IsNil)
72         log.SetOutput(tempfile)
73         tempLogFileName = tempfile.Name()
74 }
75
76 func (s *DoMainTestSuite) TearDownTest(c *C) {
77         os.Remove(tempLogFileName)
78         os.Args = initialArgs
79 }
80
81 var testKeepServicesJSON = "{ \"kind\":\"arvados#keepServiceList\", \"etag\":\"\", \"self_link\":\"\", \"offset\":null, \"limit\":null, \"items\":[ { \"href\":\"/keep_services/zzzzz-bi6l4-123456789012340\", \"kind\":\"arvados#keepService\", \"etag\":\"641234567890enhj7hzx432e5\", \"uuid\":\"zzzzz-bi6l4-123456789012340\", \"owner_uuid\":\"zzzzz-tpzed-123456789012345\", \"service_host\":\"keep0.zzzzz.arvadosapi.com\", \"service_port\":25107, \"service_ssl_flag\":false, \"service_type\":\"disk\", \"read_only\":false }, { \"href\":\"/keep_services/zzzzz-bi6l4-123456789012341\", \"kind\":\"arvados#keepService\", \"etag\":\"641234567890enhj7hzx432e5\", \"uuid\":\"zzzzz-bi6l4-123456789012341\", \"owner_uuid\":\"zzzzz-tpzed-123456789012345\", \"service_host\":\"keep0.zzzzz.arvadosapi.com\", \"service_port\":25108, \"service_ssl_flag\":false, \"service_type\":\"disk\", \"read_only\":false } ], \"items_available\":2 }"
82
83 var TestHash = "aaaa09c290d0fb1ca068ffaddf22cbd0"
84 var TestHash2 = "aaaac516f788aec4f30932ffb6395c39"
85
86 func setupKeepBlockCheck(c *C, enforcePermissions bool) {
87         var config apiConfig
88         config.APIHost = os.Getenv("ARVADOS_API_HOST")
89         config.APIToken = arvadostest.DataManagerToken
90         config.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE"))
91         if enforcePermissions {
92                 blobSigningKey = "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc"
93         }
94
95         // Start Keep servers
96         arvadostest.StartKeep(2, enforcePermissions)
97
98         // setup keepclients
99         var err error
100         kc, err = setupKeepClient(config, keepServicesJSON)
101         c.Check(err, IsNil)
102 }
103
104 // Setup test data
105 var allLocators []string
106
107 func setupTestData(c *C) {
108         allLocators = []string{}
109
110         // Put a few blocks
111         for i := 0; i < 5; i++ {
112                 hash, _, err := kc.PutB([]byte(fmt.Sprintf("keep-block-check-test-data-%d", i)))
113                 c.Check(err, IsNil)
114                 allLocators = append(allLocators, strings.Split(hash, "+A")[0])
115         }
116 }
117
118 func setupConfigFile(c *C, fileName string) string {
119         // Setup a config file
120         file, err := ioutil.TempFile(os.TempDir(), fileName)
121         c.Check(err, IsNil)
122
123         // Add config to file. While at it, throw some extra white space
124         fileContent := "ARVADOS_API_HOST=" + os.Getenv("ARVADOS_API_HOST") + "\n"
125         fileContent += "ARVADOS_API_TOKEN=" + arvadostest.DataManagerToken + "\n"
126         fileContent += "\n"
127         fileContent += "ARVADOS_API_HOST_INSECURE=" + os.Getenv("ARVADOS_API_HOST_INSECURE") + "\n"
128         fileContent += " ARVADOS_EXTERNAL_CLIENT = false \n"
129         fileContent += "ARVADOS_BLOB_SIGNING_KEY=abcdefg\n"
130
131         _, err = file.Write([]byte(fileContent))
132         c.Check(err, IsNil)
133
134         return file.Name()
135 }
136
137 func setupBlockHashFile(c *C, name string, blocks []string) string {
138         // Setup a block hash file
139         file, err := ioutil.TempFile(os.TempDir(), name)
140         c.Check(err, IsNil)
141
142         // Add the hashes to the file. While at it, throw some extra white space
143         fileContent := ""
144         for _, hash := range blocks {
145                 fileContent += fmt.Sprintf(" %s \n", hash)
146         }
147         fileContent += "\n"
148         _, err = file.Write([]byte(fileContent))
149         c.Check(err, IsNil)
150
151         return file.Name()
152 }
153
154 func checkErrorLog(c *C, blocks []string, prefix, suffix string) {
155         buf, _ := ioutil.ReadFile(tempLogFileName)
156         if len(blocks) == 0 {
157                 expected := prefix + `.*` + suffix
158                 match, _ := regexp.MatchString(expected, string(buf))
159                 c.Assert(match, Equals, false)
160                 return
161         }
162         for _, hash := range blocks {
163                 expected := prefix + `.*` + hash + `.*` + suffix
164                 match, _ := regexp.MatchString(expected, string(buf))
165                 c.Assert(match, Equals, true)
166         }
167 }
168
169 func (s *ServerRequiredSuite) TestBlockCheck(c *C) {
170         setupKeepBlockCheck(c, false)
171         setupTestData(c)
172         err := performKeepBlockCheck(kc, blobSigningKey, "", allLocators)
173         c.Check(err, IsNil)
174         checkErrorLog(c, []string{}, "head", "Block not found") // no errors
175 }
176
177 func (s *ServerRequiredSuite) TestBlockCheckWithBlobSigning(c *C) {
178         setupKeepBlockCheck(c, true)
179         setupTestData(c)
180         err := performKeepBlockCheck(kc, blobSigningKey, "", allLocators)
181         c.Check(err, IsNil)
182         checkErrorLog(c, []string{}, "head", "Block not found") // no errors
183 }
184
185 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock(c *C) {
186         setupKeepBlockCheck(c, false)
187         setupTestData(c)
188         allLocators = append(allLocators, TestHash)
189         allLocators = append(allLocators, TestHash2)
190         err := performKeepBlockCheck(kc, blobSigningKey, "", allLocators)
191         c.Check(err, NotNil)
192         c.Assert(err.Error(), Equals, "Head information not found for 2 out of 7 blocks with matching prefix.")
193         checkErrorLog(c, []string{TestHash, TestHash2}, "head", "Block not found")
194 }
195
196 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock_WithMatchingPrefix(c *C) {
197         setupKeepBlockCheck(c, false)
198         setupTestData(c)
199         allLocators = append(allLocators, TestHash)
200         allLocators = append(allLocators, TestHash2)
201         err := performKeepBlockCheck(kc, blobSigningKey, "aaa", allLocators)
202         c.Check(err, NotNil)
203         // Of the 7 blocks given, only two match the prefix and hence only those are checked
204         c.Assert(err.Error(), Equals, "Head information not found for 2 out of 2 blocks with matching prefix.")
205         checkErrorLog(c, []string{TestHash, TestHash2}, "head", "Block not found")
206 }
207
208 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock_WithPrefixMismatch(c *C) {
209         setupKeepBlockCheck(c, false)
210         setupTestData(c)
211         allLocators = append(allLocators, TestHash)
212         allLocators = append(allLocators, TestHash2)
213         err := performKeepBlockCheck(kc, blobSigningKey, "999", allLocators)
214         c.Check(err, IsNil)
215         checkErrorLog(c, []string{}, "head", "Block not found") // no errors
216 }
217
218 // Setup block-check using keepServicesJSON with fake keepservers.
219 // Expect error during performKeepBlockCheck due to unreachable keepservers.
220 func (s *ServerRequiredSuite) TestErrorDuringKeepBlockCheck_FakeKeepservers(c *C) {
221         keepServicesJSON = testKeepServicesJSON
222         setupKeepBlockCheck(c, false)
223         err := performKeepBlockCheck(kc, blobSigningKey, "", []string{TestHash, TestHash2})
224         c.Assert(err.Error(), Equals, "Head information not found for 2 out of 2 blocks with matching prefix.")
225         checkErrorLog(c, []string{TestHash, TestHash2}, "head", "no such host")
226 }
227
228 func (s *ServerRequiredSuite) TestBlockCheck_BadSignature(c *C) {
229         setupKeepBlockCheck(c, true)
230         setupTestData(c)
231         err := performKeepBlockCheck(kc, "badblobsigningkey", "", []string{TestHash, TestHash2})
232         c.Assert(err.Error(), Equals, "Head information not found for 2 out of 2 blocks with matching prefix.")
233         checkErrorLog(c, []string{TestHash, TestHash2}, "head", "HTTP 403")
234 }
235
236 // Test keep-block-check initialization with keepServicesJSON
237 func (s *ServerRequiredSuite) TestKeepBlockCheck_InitializeWithKeepServicesJSON(c *C) {
238         keepServicesJSON = testKeepServicesJSON
239         setupKeepBlockCheck(c, false)
240         found := 0
241         for k := range kc.LocalRoots() {
242                 if k == "zzzzz-bi6l4-123456789012340" || k == "zzzzz-bi6l4-123456789012341" {
243                         found++
244                 }
245         }
246         c.Check(found, Equals, 2)
247 }
248
249 // Test loadConfig func
250 func (s *ServerRequiredSuite) TestLoadConfig(c *C) {
251         // Setup config file
252         configFile := setupConfigFile(c, "config")
253         defer os.Remove(configFile)
254
255         // load configuration from the file
256         config, blobSigningKey, err := loadConfig(configFile)
257         c.Check(err, IsNil)
258
259         c.Assert(config.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
260         c.Assert(config.APIToken, Equals, arvadostest.DataManagerToken)
261         c.Assert(config.APIHostInsecure, Equals, matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")))
262         c.Assert(config.ExternalClient, Equals, false)
263         c.Assert(blobSigningKey, Equals, "abcdefg")
264 }
265
266 func (s *DoMainTestSuite) Test_doMain_WithNoConfig(c *C) {
267         args := []string{"-prefix", "a"}
268         os.Args = append(os.Args, args...)
269         err := doMain()
270         c.Check(err, NotNil)
271         c.Assert(strings.Contains(err.Error(), "config file not specified"), Equals, true)
272 }
273
274 func (s *DoMainTestSuite) Test_doMain_WithNoSuchConfigFile(c *C) {
275         args := []string{"-config", "no-such-file"}
276         os.Args = append(os.Args, args...)
277         err := doMain()
278         c.Check(err, NotNil)
279         c.Assert(strings.Contains(err.Error(), "no such file or directory"), Equals, true)
280 }
281
282 func (s *DoMainTestSuite) Test_doMain_WithNoBlockHashFile(c *C) {
283         config := setupConfigFile(c, "config")
284         defer os.Remove(config)
285
286         args := []string{"-config", config}
287         os.Args = append(os.Args, args...)
288
289         // Start keepservers.
290         arvadostest.StartKeep(2, false)
291         defer arvadostest.StopKeep(2)
292
293         err := doMain()
294         c.Assert(strings.Contains(err.Error(), "block-hash-file not specified"), Equals, true)
295 }
296
297 func (s *DoMainTestSuite) Test_doMain_WithNoSuchBlockHashFile(c *C) {
298         config := setupConfigFile(c, "config")
299         defer os.Remove(config)
300
301         args := []string{"-config", config, "-block-hash-file", "no-such-file"}
302         os.Args = append(os.Args, args...)
303
304         // Start keepservers.
305         arvadostest.StartKeep(2, false)
306         defer arvadostest.StopKeep(2)
307
308         err := doMain()
309         c.Assert(strings.Contains(err.Error(), "no such file or directory"), Equals, true)
310 }
311
312 func (s *DoMainTestSuite) Test_doMain(c *C) {
313         // Start keepservers.
314         arvadostest.StartKeep(2, false)
315         defer arvadostest.StopKeep(2)
316
317         config := setupConfigFile(c, "config")
318         defer os.Remove(config)
319
320         locatorFile := setupBlockHashFile(c, "block-hash", []string{TestHash, TestHash2})
321         defer os.Remove(locatorFile)
322
323         args := []string{"-config", config, "-block-hash-file", locatorFile}
324         os.Args = append(os.Args, args...)
325
326         err := doMain()
327         c.Check(err, NotNil)
328         c.Assert(err.Error(), Equals, "Head information not found for 2 out of 2 blocks with matching prefix.")
329         checkErrorLog(c, []string{TestHash, TestHash2}, "head", "Block not found")
330 }