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