60c5fb51d135dd9e58f87e106457978ea2aa44ef
[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         keepclient.ClearCache()
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 = matchTrue.MatchString(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.ClearCache()
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 += " ARVADOS_EXTERNAL_CLIENT = false \n"
122         fileContent += " NotANameValuePairAndShouldGetIgnored \n"
123         fileContent += "ARVADOS_BLOB_SIGNING_KEY=abcdefg\n"
124
125         _, err = file.Write([]byte(fileContent))
126         c.Check(err, IsNil)
127
128         return file.Name()
129 }
130
131 func setupBlockHashFile(c *C, name string, blocks []string) string {
132         // Setup a block hash file
133         file, err := ioutil.TempFile(os.TempDir(), name)
134         c.Check(err, IsNil)
135
136         // Add the hashes to the file. While at it, throw some extra white space
137         fileContent := ""
138         for _, hash := range blocks {
139                 fileContent += fmt.Sprintf(" %s \n", hash)
140         }
141         fileContent += "\n"
142         _, err = file.Write([]byte(fileContent))
143         c.Check(err, IsNil)
144
145         return file.Name()
146 }
147
148 func checkErrorLog(c *C, blocks []string, prefix, suffix string) {
149         for _, hash := range blocks {
150                 expected := `(?ms).*` + prefix + `.*` + hash + `.*` + suffix + `.*`
151                 c.Check(logBuffer.String(), Matches, expected)
152         }
153 }
154
155 func checkNoErrorsLogged(c *C, prefix, suffix string) {
156         expected := prefix + `.*` + suffix
157         match, _ := regexp.MatchString(expected, logBuffer.String())
158         c.Assert(match, Equals, false)
159 }
160
161 func (s *ServerRequiredSuite) TestBlockCheck(c *C) {
162         setupKeepBlockCheck(c, false, "")
163         allLocators := setupTestData(c)
164         err := performKeepBlockCheck(kc, blobSignatureTTL, "", allLocators, true)
165         c.Check(err, IsNil)
166         checkNoErrorsLogged(c, "Error verifying block", "Block not found")
167 }
168
169 func (s *ServerRequiredSuite) TestBlockCheckWithBlobSigning(c *C) {
170         setupKeepBlockCheck(c, true, "")
171         allLocators := setupTestData(c)
172         err := performKeepBlockCheck(kc, blobSignatureTTL, arvadostest.BlobSigningKey, allLocators, true)
173         c.Check(err, IsNil)
174         checkNoErrorsLogged(c, "Error verifying block", "Block not found")
175 }
176
177 func (s *ServerRequiredSuite) TestBlockCheckWithBlobSigningAndTTLFromDiscovery(c *C) {
178         setupKeepBlockCheckWithTTL(c, true, "", 0)
179         allLocators := setupTestData(c)
180         err := performKeepBlockCheck(kc, blobSignatureTTL, arvadostest.BlobSigningKey, allLocators, true)
181         c.Check(err, IsNil)
182         checkNoErrorsLogged(c, "Error verifying block", "Block not found")
183 }
184
185 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock(c *C) {
186         setupKeepBlockCheck(c, false, "")
187         allLocators := setupTestData(c)
188         allLocators = append(allLocators, TestHash)
189         allLocators = append(allLocators, TestHash2)
190         err := performKeepBlockCheck(kc, blobSignatureTTL, "", allLocators, true)
191         c.Check(err, NotNil)
192         c.Assert(err.Error(), Equals, "Block verification failed for 2 out of 7 blocks with matching prefix.")
193         checkErrorLog(c, []string{TestHash, TestHash2}, "Error verifying block", "Block not found")
194 }
195
196 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock_WithMatchingPrefix(c *C) {
197         setupKeepBlockCheck(c, false, "")
198         allLocators := setupTestData(c)
199         allLocators = append(allLocators, TestHash)
200         allLocators = append(allLocators, TestHash2)
201         locatorFile := setupBlockHashFile(c, "block-hash", allLocators)
202         defer os.Remove(locatorFile)
203         locators, err := getBlockLocators(locatorFile, "aaa")
204         c.Check(err, IsNil)
205         err = performKeepBlockCheck(kc, blobSignatureTTL, "", locators, true)
206         c.Check(err, NotNil)
207         // Of the 7 blocks in allLocators, only two match the prefix and hence only those are checked
208         c.Assert(err.Error(), Equals, "Block verification failed for 2 out of 2 blocks with matching prefix.")
209         checkErrorLog(c, []string{TestHash, TestHash2}, "Error verifying block", "Block not found")
210 }
211
212 func (s *ServerRequiredSuite) TestBlockCheck_NoSuchBlock_WithPrefixMismatch(c *C) {
213         setupKeepBlockCheck(c, false, "")
214         allLocators := setupTestData(c)
215         allLocators = append(allLocators, TestHash)
216         allLocators = append(allLocators, TestHash2)
217         locatorFile := setupBlockHashFile(c, "block-hash", allLocators)
218         defer os.Remove(locatorFile)
219         locators, err := getBlockLocators(locatorFile, "999")
220         c.Check(err, IsNil)
221         err = performKeepBlockCheck(kc, blobSignatureTTL, "", locators, true)
222         c.Check(err, IsNil) // there were no matching locators in file and hence nothing was checked
223 }
224
225 func (s *ServerRequiredSuite) TestBlockCheck_BadSignature(c *C) {
226         setupKeepBlockCheck(c, true, "")
227         setupTestData(c)
228         err := performKeepBlockCheck(kc, blobSignatureTTL, "badblobsigningkey", []string{TestHash, TestHash2}, false)
229         c.Assert(err.Error(), Equals, "Block verification failed for 2 out of 2 blocks with matching prefix.")
230         checkErrorLog(c, []string{TestHash, TestHash2}, "Error verifying block", "HTTP 403")
231         // verbose logging not requested
232         c.Assert(strings.Contains(logBuffer.String(), "Verifying block 1 of 2"), Equals, false)
233 }
234
235 var testKeepServicesJSON = `{
236   "kind":"arvados#keepServiceList",
237   "etag":"",
238   "self_link":"",
239   "offset":null, "limit":null,
240   "items":[
241     {"href":"/keep_services/zzzzz-bi6l4-123456789012340",
242      "kind":"arvados#keepService",
243      "uuid":"zzzzz-bi6l4-123456789012340",
244      "service_host":"keep0.zzzzz.arvadosapi.com",
245      "service_port":25107,
246      "service_ssl_flag":false,
247      "service_type":"disk",
248      "read_only":false },
249     {"href":"/keep_services/zzzzz-bi6l4-123456789012341",
250      "kind":"arvados#keepService",
251      "uuid":"zzzzz-bi6l4-123456789012341",
252      "service_host":"keep0.zzzzz.arvadosapi.com",
253      "service_port":25108,
254      "service_ssl_flag":false,
255      "service_type":"disk",
256      "read_only":false }
257     ],
258   "items_available":2 }`
259
260 // Setup block-check using keepServicesJSON with fake keepservers.
261 // Expect error during performKeepBlockCheck due to unreachable keepservers.
262 func (s *ServerRequiredSuite) TestErrorDuringKeepBlockCheck_FakeKeepservers(c *C) {
263         setupKeepBlockCheck(c, false, testKeepServicesJSON)
264         err := performKeepBlockCheck(kc, blobSignatureTTL, "", []string{TestHash, TestHash2}, true)
265         c.Assert(err.Error(), Equals, "Block verification failed for 2 out of 2 blocks with matching prefix.")
266         checkErrorLog(c, []string{TestHash, TestHash2}, "Error verifying block", "")
267 }
268
269 // Test keep-block-check initialization with keepServicesJSON
270 func (s *ServerRequiredSuite) TestKeepBlockCheck_InitializeWithKeepServicesJSON(c *C) {
271         setupKeepBlockCheck(c, false, testKeepServicesJSON)
272         found := 0
273         for k := range kc.LocalRoots() {
274                 if k == "zzzzz-bi6l4-123456789012340" || k == "zzzzz-bi6l4-123456789012341" {
275                         found++
276                 }
277         }
278         c.Check(found, Equals, 2)
279 }
280
281 // Test loadConfig func
282 func (s *ServerRequiredSuite) TestLoadConfig(c *C) {
283         // Setup config file
284         configFile := setupConfigFile(c, "config")
285         defer os.Remove(configFile)
286
287         // load configuration from the file
288         config, blobSigningKey, err := loadConfig(configFile)
289         c.Check(err, IsNil)
290
291         c.Assert(config.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
292         c.Assert(config.APIToken, Equals, arvadostest.DataManagerToken)
293         c.Assert(config.APIHostInsecure, Equals, matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")))
294         c.Assert(config.ExternalClient, Equals, false)
295         c.Assert(blobSigningKey, Equals, "abcdefg")
296 }
297
298 func (s *DoMainTestSuite) Test_doMain_WithNoConfig(c *C) {
299         args := []string{"-prefix", "a"}
300         err := doMain(args)
301         c.Check(err, NotNil)
302         c.Assert(strings.Contains(err.Error(), "config file not specified"), Equals, true)
303 }
304
305 func (s *DoMainTestSuite) Test_doMain_WithNoSuchConfigFile(c *C) {
306         args := []string{"-config", "no-such-file"}
307         err := doMain(args)
308         c.Check(err, NotNil)
309         c.Assert(strings.Contains(err.Error(), "no such file or directory"), Equals, true)
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         err := doMain(args)
322         c.Assert(strings.Contains(err.Error(), "block-hash-file not specified"), Equals, true)
323 }
324
325 func (s *DoMainTestSuite) Test_doMain_WithNoSuchBlockHashFile(c *C) {
326         config := setupConfigFile(c, "config")
327         defer os.Remove(config)
328
329         arvadostest.StartKeep(2, false)
330         defer arvadostest.StopKeep(2)
331
332         args := []string{"-config", config, "-block-hash-file", "no-such-file"}
333         err := doMain(args)
334         c.Assert(strings.Contains(err.Error(), "no such file or directory"), Equals, true)
335 }
336
337 func (s *DoMainTestSuite) Test_doMain(c *C) {
338         // Start keepservers.
339         arvadostest.StartKeep(2, false)
340         defer arvadostest.StopKeep(2)
341
342         config := setupConfigFile(c, "config")
343         defer os.Remove(config)
344
345         locatorFile := setupBlockHashFile(c, "block-hash", []string{TestHash, TestHash2})
346         defer os.Remove(locatorFile)
347
348         args := []string{"-config", config, "-block-hash-file", locatorFile, "-v"}
349         err := doMain(args)
350         c.Check(err, NotNil)
351         c.Assert(err.Error(), Equals, "Block verification failed for 2 out of 2 blocks with matching prefix.")
352         checkErrorLog(c, []string{TestHash, TestHash2}, "Error verifying block", "Block not found")
353         c.Assert(strings.Contains(logBuffer.String(), "Verifying block 1 of 2"), Equals, true)
354 }