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