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