closes #7490
[arvados.git] / services / datamanager / keep / keep_test.go
1 package keep
2
3 import (
4         "encoding/json"
5         "fmt"
6         "net"
7         "net/http"
8         "net/http/httptest"
9         "net/url"
10         "strconv"
11         "strings"
12         "testing"
13
14         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
15         "git.curoverse.com/arvados.git/sdk/go/blockdigest"
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 type KeepSuite struct{}
27
28 var _ = Suite(&KeepSuite{})
29
30 type TestHandler struct {
31         request TrashList
32 }
33
34 func (ts *TestHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
35         r := json.NewDecoder(req.Body)
36         r.Decode(&ts.request)
37 }
38
39 func (s *KeepSuite) TestSendTrashLists(c *C) {
40         th := TestHandler{}
41         server := httptest.NewServer(&th)
42         defer server.Close()
43
44         tl := map[string]TrashList{
45                 server.URL: TrashList{TrashRequest{"000000000000000000000000deadbeef", 99}}}
46
47         arv := arvadosclient.ArvadosClient{ApiToken: "abc123"}
48         kc := keepclient.KeepClient{Arvados: &arv, Client: &http.Client{}}
49         kc.SetServiceRoots(map[string]string{"xxxx": server.URL},
50                 map[string]string{"xxxx": server.URL},
51                 map[string]string{})
52
53         err := SendTrashLists(&kc, tl)
54
55         c.Check(err, IsNil)
56
57         c.Check(th.request,
58                 DeepEquals,
59                 tl[server.URL])
60
61 }
62
63 type TestHandlerError struct {
64 }
65
66 func (tse *TestHandlerError) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
67         http.Error(writer, "I'm a teapot", 418)
68 }
69
70 func sendTrashListError(c *C, server *httptest.Server) {
71         tl := map[string]TrashList{
72                 server.URL: TrashList{TrashRequest{"000000000000000000000000deadbeef", 99}}}
73
74         arv := arvadosclient.ArvadosClient{ApiToken: "abc123"}
75         kc := keepclient.KeepClient{Arvados: &arv, Client: &http.Client{}}
76         kc.SetServiceRoots(map[string]string{"xxxx": server.URL},
77                 map[string]string{"xxxx": server.URL},
78                 map[string]string{})
79
80         err := SendTrashLists(&kc, tl)
81
82         c.Check(err, NotNil)
83         c.Check(err[0], NotNil)
84 }
85
86 func (s *KeepSuite) TestSendTrashListErrorResponse(c *C) {
87         server := httptest.NewServer(&TestHandlerError{})
88         sendTrashListError(c, server)
89         defer server.Close()
90 }
91
92 func (s *KeepSuite) TestSendTrashListUnreachable(c *C) {
93         sendTrashListError(c, httptest.NewUnstartedServer(&TestHandler{}))
94 }
95
96 type StatusAndBody struct {
97         respStatus   int
98         responseBody string
99 }
100
101 type APIStub struct {
102         data map[string]StatusAndBody
103 }
104
105 func (stub *APIStub) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
106         if req.URL.Path == "/redirect-loop" {
107                 http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
108                 return
109         }
110
111         pathResponse := stub.data[req.URL.Path]
112         if pathResponse.responseBody != "" {
113                 if pathResponse.respStatus == -1 {
114                         http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
115                 } else {
116                         resp.WriteHeader(pathResponse.respStatus)
117                         resp.Write([]byte(pathResponse.responseBody))
118                 }
119         } else {
120                 resp.WriteHeader(500)
121                 resp.Write([]byte(``))
122         }
123 }
124
125 type KeepServerStub struct {
126         data map[string]StatusAndBody
127 }
128
129 func (stub *KeepServerStub) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
130         if req.URL.Path == "/redirect-loop" {
131                 http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
132                 return
133         }
134
135         pathResponse := stub.data[req.URL.Path]
136         if pathResponse.responseBody != "" {
137                 if pathResponse.respStatus == -1 {
138                         http.Redirect(resp, req, "/redirect-loop", http.StatusFound)
139                 } else {
140                         resp.WriteHeader(pathResponse.respStatus)
141                         resp.Write([]byte(pathResponse.responseBody))
142                 }
143         } else {
144                 resp.WriteHeader(500)
145                 resp.Write([]byte(``))
146         }
147 }
148
149 type APITestData struct {
150         numServers int
151         serverType string
152         statusCode int
153 }
154
155 func (s *KeepSuite) TestGetKeepServers_UnsupportedServiceType(c *C) {
156         testGetKeepServersFromAPI(c, APITestData{1, "notadisk", 200}, "Unsupported service type")
157 }
158
159 func (s *KeepSuite) TestGetKeepServers_ReceivedTooFewServers(c *C) {
160         testGetKeepServersFromAPI(c, APITestData{2, "disk", 200}, "Did not receive all available keep servers")
161 }
162
163 func (s *KeepSuite) TestGetKeepServers_ServerError(c *C) {
164         testGetKeepServersFromAPI(c, APITestData{-1, "disk", -1}, "arvados API server error")
165 }
166
167 func testGetKeepServersFromAPI(c *C, testData APITestData, expectedError string) {
168         keepServers := ServiceList{
169                 ItemsAvailable: testData.numServers,
170                 KeepServers: []ServerAddress{{
171                         SSL:         false,
172                         Host:        "example.com",
173                         Port:        12345,
174                         UUID:        "abcdefg",
175                         ServiceType: testData.serverType,
176                 }},
177         }
178
179         ksJSON, _ := json.Marshal(keepServers)
180         apiData := make(map[string]StatusAndBody)
181         apiData["/arvados/v1/keep_services"] = StatusAndBody{testData.statusCode, string(ksJSON)}
182         apiStub := APIStub{apiData}
183
184         api := httptest.NewServer(&apiStub)
185         defer api.Close()
186
187         arv := arvadosclient.ArvadosClient{
188                 Scheme:    "http",
189                 ApiServer: api.URL[7:],
190                 ApiToken:  "abc123",
191                 Client:    &http.Client{Transport: &http.Transport{}},
192         }
193
194         kc := keepclient.KeepClient{Arvados: &arv, Client: &http.Client{}}
195         kc.SetServiceRoots(map[string]string{"xxxx": "http://example.com:23456"},
196                 map[string]string{"xxxx": "http://example.com:23456"},
197                 map[string]string{})
198
199         params := GetKeepServersParams{
200                 Client: arv,
201                 Logger: nil,
202                 Limit:  10,
203         }
204
205         _, err := GetKeepServersAndSummarize(params)
206         c.Assert(err, NotNil)
207         c.Assert(err, ErrorMatches, fmt.Sprintf(".*%s.*", expectedError))
208 }
209
210 type KeepServerTestData struct {
211         // handle /status.json
212         statusStatusCode int
213
214         // handle /index
215         indexStatusCode   int
216         indexResponseBody string
217
218         // expected error, if any
219         expectedError string
220 }
221
222 func (s *KeepSuite) TestGetKeepServers_ErrorGettingKeepServerStatus(c *C) {
223         testGetKeepServersAndSummarize(c, KeepServerTestData{500, 200, "ok",
224                 ".*http://.* 500 Internal Server Error"})
225 }
226
227 func (s *KeepSuite) TestGetKeepServers_GettingIndex(c *C) {
228         testGetKeepServersAndSummarize(c, KeepServerTestData{200, -1, "notok",
229                 ".*redirect-loop.*"})
230 }
231
232 func (s *KeepSuite) TestGetKeepServers_ErrorReadServerResponse(c *C) {
233         testGetKeepServersAndSummarize(c, KeepServerTestData{200, 500, "notok",
234                 ".*http://.* 500 Internal Server Error"})
235 }
236
237 func (s *KeepSuite) TestGetKeepServers_ReadServerResponseTuncatedAtLineOne(c *C) {
238         testGetKeepServersAndSummarize(c, KeepServerTestData{200, 200,
239                 "notterminatedwithnewline", "Index from http://.* truncated at line 1"})
240 }
241
242 func (s *KeepSuite) TestGetKeepServers_InvalidBlockLocatorPattern(c *C) {
243         testGetKeepServersAndSummarize(c, KeepServerTestData{200, 200, "testing\n",
244                 "Error parsing BlockInfo from index line.*"})
245 }
246
247 func (s *KeepSuite) TestGetKeepServers_ReadServerResponseEmpty(c *C) {
248         testGetKeepServersAndSummarize(c, KeepServerTestData{200, 200, "\n", ""})
249 }
250
251 func (s *KeepSuite) TestGetKeepServers_ReadServerResponseWithTwoBlocks(c *C) {
252         testGetKeepServersAndSummarize(c, KeepServerTestData{200, 200,
253                 "51752ba076e461ec9ec1d27400a08548+20 1447526361\na048cc05c02ba1ee43ad071274b9e547+52 1447526362\n\n", ""})
254 }
255
256 func testGetKeepServersAndSummarize(c *C, testData KeepServerTestData) {
257         ksData := make(map[string]StatusAndBody)
258         ksData["/status.json"] = StatusAndBody{testData.statusStatusCode, string(`{}`)}
259         ksData["/index"] = StatusAndBody{testData.indexStatusCode, testData.indexResponseBody}
260         ksStub := KeepServerStub{ksData}
261         ks := httptest.NewServer(&ksStub)
262         defer ks.Close()
263
264         ksURL, err := url.Parse(ks.URL)
265         c.Check(err, IsNil)
266         ksHost, port, err := net.SplitHostPort(ksURL.Host)
267         ksPort, err := strconv.Atoi(port)
268         c.Check(err, IsNil)
269
270         servers_list := ServiceList{
271                 ItemsAvailable: 1,
272                 KeepServers: []ServerAddress{{
273                         SSL:         false,
274                         Host:        ksHost,
275                         Port:        ksPort,
276                         UUID:        "abcdefg",
277                         ServiceType: "disk",
278                 }},
279         }
280         ksJSON, _ := json.Marshal(servers_list)
281         apiData := make(map[string]StatusAndBody)
282         apiData["/arvados/v1/keep_services"] = StatusAndBody{200, string(ksJSON)}
283         apiStub := APIStub{apiData}
284
285         api := httptest.NewServer(&apiStub)
286         defer api.Close()
287
288         arv := arvadosclient.ArvadosClient{
289                 Scheme:    "http",
290                 ApiServer: api.URL[7:],
291                 ApiToken:  "abc123",
292                 Client:    &http.Client{Transport: &http.Transport{}},
293         }
294
295         kc := keepclient.KeepClient{Arvados: &arv, Client: &http.Client{}}
296         kc.SetServiceRoots(map[string]string{"xxxx": ks.URL},
297                 map[string]string{"xxxx": ks.URL},
298                 map[string]string{})
299
300         params := GetKeepServersParams{
301                 Client: arv,
302                 Logger: nil,
303                 Limit:  10,
304         }
305
306         // GetKeepServersAndSummarize
307         results, err := GetKeepServersAndSummarize(params)
308
309         if testData.expectedError == "" {
310                 c.Assert(err, IsNil)
311                 c.Assert(results, NotNil)
312
313                 blockToServers := results.BlockToServers
314
315                 blockLocators := strings.Split(testData.indexResponseBody, "\n")
316                 for _, loc := range blockLocators {
317                         locator := strings.Split(loc, " ")[0]
318                         if locator != "" {
319                                 blockLocator, err := blockdigest.ParseBlockLocator(locator)
320                                 c.Assert(err, IsNil)
321
322                                 blockDigestWithSize := blockdigest.DigestWithSize{blockLocator.Digest, uint32(blockLocator.Size)}
323                                 blockServerInfo := blockToServers[blockDigestWithSize]
324                                 c.Assert(blockServerInfo[0].Mtime, NotNil)
325                         }
326                 }
327         } else {
328                 c.Assert(err, ErrorMatches, testData.expectedError)
329         }
330 }