Merge branch '15928-fs-deadlock'
[arvados.git] / sdk / go / keepclient / keepclient_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package keepclient
6
7 import (
8         "bytes"
9         "crypto/md5"
10         "errors"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "log"
15         "net"
16         "net/http"
17         "os"
18         "strings"
19         "testing"
20         "time"
21
22         "git.arvados.org/arvados.git/sdk/go/arvadosclient"
23         "git.arvados.org/arvados.git/sdk/go/arvadostest"
24         . "gopkg.in/check.v1"
25 )
26
27 // Gocheck boilerplate
28 func Test(t *testing.T) {
29         TestingT(t)
30 }
31
32 // Gocheck boilerplate
33 var _ = Suite(&ServerRequiredSuite{})
34 var _ = Suite(&StandaloneSuite{})
35
36 // Tests that require the Keep server running
37 type ServerRequiredSuite struct{}
38
39 // Standalone tests
40 type StandaloneSuite struct{}
41
42 func (s *StandaloneSuite) SetUpTest(c *C) {
43         RefreshServiceDiscovery()
44 }
45
46 func pythonDir() string {
47         cwd, _ := os.Getwd()
48         return fmt.Sprintf("%s/../../python/tests", cwd)
49 }
50
51 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
52         arvadostest.StartAPI()
53         arvadostest.StartKeep(2, false)
54 }
55
56 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
57         arvadostest.StopKeep(2)
58         arvadostest.StopAPI()
59 }
60
61 func (s *ServerRequiredSuite) SetUpTest(c *C) {
62         RefreshServiceDiscovery()
63 }
64
65 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
66         arv, err := arvadosclient.MakeArvadosClient()
67         c.Assert(err, Equals, nil)
68
69         kc, err := MakeKeepClient(arv)
70
71         c.Assert(err, Equals, nil)
72         c.Check(len(kc.LocalRoots()), Equals, 2)
73         for _, root := range kc.LocalRoots() {
74                 c.Check(root, Matches, "http://localhost:\\d+")
75         }
76 }
77
78 func (s *ServerRequiredSuite) TestDefaultReplications(c *C) {
79         arv, err := arvadosclient.MakeArvadosClient()
80         c.Assert(err, Equals, nil)
81
82         kc, err := MakeKeepClient(arv)
83         c.Assert(kc.Want_replicas, Equals, 2)
84
85         arv.DiscoveryDoc["defaultCollectionReplication"] = 3.0
86         kc, err = MakeKeepClient(arv)
87         c.Assert(kc.Want_replicas, Equals, 3)
88
89         arv.DiscoveryDoc["defaultCollectionReplication"] = 1.0
90         kc, err = MakeKeepClient(arv)
91         c.Check(err, IsNil)
92         c.Assert(kc.Want_replicas, Equals, 1)
93 }
94
95 type StubPutHandler struct {
96         c                  *C
97         expectPath         string
98         expectApiToken     string
99         expectBody         string
100         expectStorageClass string
101         handled            chan string
102 }
103
104 func (sph StubPutHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
105         sph.c.Check(req.URL.Path, Equals, "/"+sph.expectPath)
106         sph.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sph.expectApiToken))
107         sph.c.Check(req.Header.Get("X-Keep-Storage-Classes"), Equals, sph.expectStorageClass)
108         body, err := ioutil.ReadAll(req.Body)
109         sph.c.Check(err, Equals, nil)
110         sph.c.Check(body, DeepEquals, []byte(sph.expectBody))
111         resp.WriteHeader(200)
112         sph.handled <- fmt.Sprintf("http://%s", req.Host)
113 }
114
115 func RunFakeKeepServer(st http.Handler) (ks KeepServer) {
116         var err error
117         // If we don't explicitly bind it to localhost, ks.listener.Addr() will
118         // bind to 0.0.0.0 or [::] which is not a valid address for Dial()
119         ks.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0})
120         if err != nil {
121                 panic(fmt.Sprintf("Could not listen on any port"))
122         }
123         ks.url = fmt.Sprintf("http://%s", ks.listener.Addr().String())
124         go http.Serve(ks.listener, st)
125         return
126 }
127
128 func UploadToStubHelper(c *C, st http.Handler, f func(*KeepClient, string,
129         io.ReadCloser, io.WriteCloser, chan uploadStatus)) {
130
131         ks := RunFakeKeepServer(st)
132         defer ks.listener.Close()
133
134         arv, _ := arvadosclient.MakeArvadosClient()
135         arv.ApiToken = "abc123"
136
137         kc, _ := MakeKeepClient(arv)
138
139         reader, writer := io.Pipe()
140         upload_status := make(chan uploadStatus)
141
142         f(kc, ks.url, reader, writer, upload_status)
143 }
144
145 func (s *StandaloneSuite) TestUploadToStubKeepServer(c *C) {
146         log.Printf("TestUploadToStubKeepServer")
147
148         st := StubPutHandler{
149                 c,
150                 "acbd18db4cc2f85cedef654fccc4a4d8",
151                 "abc123",
152                 "foo",
153                 "hot",
154                 make(chan string)}
155
156         UploadToStubHelper(c, st,
157                 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, upload_status chan uploadStatus) {
158                         kc.StorageClasses = []string{"hot"}
159                         go kc.uploadToKeepServer(url, st.expectPath, reader, upload_status, int64(len("foo")), kc.getRequestID())
160
161                         writer.Write([]byte("foo"))
162                         writer.Close()
163
164                         <-st.handled
165                         status := <-upload_status
166                         c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, ""})
167                 })
168 }
169
170 func (s *StandaloneSuite) TestUploadToStubKeepServerBufferReader(c *C) {
171         st := StubPutHandler{
172                 c,
173                 "acbd18db4cc2f85cedef654fccc4a4d8",
174                 "abc123",
175                 "foo",
176                 "",
177                 make(chan string)}
178
179         UploadToStubHelper(c, st,
180                 func(kc *KeepClient, url string, _ io.ReadCloser, _ io.WriteCloser, upload_status chan uploadStatus) {
181                         go kc.uploadToKeepServer(url, st.expectPath, bytes.NewBuffer([]byte("foo")), upload_status, 3, kc.getRequestID())
182
183                         <-st.handled
184
185                         status := <-upload_status
186                         c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, ""})
187                 })
188 }
189
190 type FailHandler struct {
191         handled chan string
192 }
193
194 func (fh FailHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
195         resp.WriteHeader(500)
196         fh.handled <- fmt.Sprintf("http://%s", req.Host)
197 }
198
199 type FailThenSucceedHandler struct {
200         handled        chan string
201         count          int
202         successhandler http.Handler
203         reqIDs         []string
204 }
205
206 func (fh *FailThenSucceedHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
207         fh.reqIDs = append(fh.reqIDs, req.Header.Get("X-Request-Id"))
208         if fh.count == 0 {
209                 resp.WriteHeader(500)
210                 fh.count += 1
211                 fh.handled <- fmt.Sprintf("http://%s", req.Host)
212         } else {
213                 fh.successhandler.ServeHTTP(resp, req)
214         }
215 }
216
217 type Error404Handler struct {
218         handled chan string
219 }
220
221 func (fh Error404Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
222         resp.WriteHeader(404)
223         fh.handled <- fmt.Sprintf("http://%s", req.Host)
224 }
225
226 func (s *StandaloneSuite) TestFailedUploadToStubKeepServer(c *C) {
227         st := FailHandler{
228                 make(chan string)}
229
230         hash := "acbd18db4cc2f85cedef654fccc4a4d8"
231
232         UploadToStubHelper(c, st,
233                 func(kc *KeepClient, url string, reader io.ReadCloser,
234                         writer io.WriteCloser, upload_status chan uploadStatus) {
235
236                         go kc.uploadToKeepServer(url, hash, reader, upload_status, 3, kc.getRequestID())
237
238                         writer.Write([]byte("foo"))
239                         writer.Close()
240
241                         <-st.handled
242
243                         status := <-upload_status
244                         c.Check(status.url, Equals, fmt.Sprintf("%s/%s", url, hash))
245                         c.Check(status.statusCode, Equals, 500)
246                 })
247 }
248
249 type KeepServer struct {
250         listener net.Listener
251         url      string
252 }
253
254 func RunSomeFakeKeepServers(st http.Handler, n int) (ks []KeepServer) {
255         ks = make([]KeepServer, n)
256
257         for i := 0; i < n; i += 1 {
258                 ks[i] = RunFakeKeepServer(st)
259         }
260
261         return ks
262 }
263
264 func (s *StandaloneSuite) TestPutB(c *C) {
265         hash := Md5String("foo")
266
267         st := StubPutHandler{
268                 c,
269                 hash,
270                 "abc123",
271                 "foo",
272                 "",
273                 make(chan string, 5)}
274
275         arv, _ := arvadosclient.MakeArvadosClient()
276         kc, _ := MakeKeepClient(arv)
277
278         kc.Want_replicas = 2
279         arv.ApiToken = "abc123"
280         localRoots := make(map[string]string)
281         writableLocalRoots := make(map[string]string)
282
283         ks := RunSomeFakeKeepServers(st, 5)
284
285         for i, k := range ks {
286                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
287                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
288                 defer k.listener.Close()
289         }
290
291         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
292
293         kc.PutB([]byte("foo"))
294
295         shuff := NewRootSorter(
296                 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
297
298         s1 := <-st.handled
299         s2 := <-st.handled
300         c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
301                 (s1 == shuff[1] && s2 == shuff[0]),
302                 Equals,
303                 true)
304 }
305
306 func (s *StandaloneSuite) TestPutHR(c *C) {
307         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
308
309         st := StubPutHandler{
310                 c,
311                 hash,
312                 "abc123",
313                 "foo",
314                 "",
315                 make(chan string, 5)}
316
317         arv, _ := arvadosclient.MakeArvadosClient()
318         kc, _ := MakeKeepClient(arv)
319
320         kc.Want_replicas = 2
321         arv.ApiToken = "abc123"
322         localRoots := make(map[string]string)
323         writableLocalRoots := make(map[string]string)
324
325         ks := RunSomeFakeKeepServers(st, 5)
326
327         for i, k := range ks {
328                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
329                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
330                 defer k.listener.Close()
331         }
332
333         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
334
335         reader, writer := io.Pipe()
336
337         go func() {
338                 writer.Write([]byte("foo"))
339                 writer.Close()
340         }()
341
342         kc.PutHR(hash, reader, 3)
343
344         shuff := NewRootSorter(kc.LocalRoots(), hash).GetSortedRoots()
345
346         s1 := <-st.handled
347         s2 := <-st.handled
348
349         c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
350                 (s1 == shuff[1] && s2 == shuff[0]),
351                 Equals,
352                 true)
353 }
354
355 func (s *StandaloneSuite) TestPutWithFail(c *C) {
356         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
357
358         st := StubPutHandler{
359                 c,
360                 hash,
361                 "abc123",
362                 "foo",
363                 "",
364                 make(chan string, 4)}
365
366         fh := FailHandler{
367                 make(chan string, 1)}
368
369         arv, err := arvadosclient.MakeArvadosClient()
370         kc, _ := MakeKeepClient(arv)
371
372         kc.Want_replicas = 2
373         arv.ApiToken = "abc123"
374         localRoots := make(map[string]string)
375         writableLocalRoots := make(map[string]string)
376
377         ks1 := RunSomeFakeKeepServers(st, 4)
378         ks2 := RunSomeFakeKeepServers(fh, 1)
379
380         for i, k := range ks1 {
381                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
382                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
383                 defer k.listener.Close()
384         }
385         for i, k := range ks2 {
386                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
387                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
388                 defer k.listener.Close()
389         }
390
391         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
392
393         shuff := NewRootSorter(
394                 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
395         c.Logf("%+v", shuff)
396
397         phash, replicas, err := kc.PutB([]byte("foo"))
398
399         <-fh.handled
400
401         c.Check(err, Equals, nil)
402         c.Check(phash, Equals, "")
403         c.Check(replicas, Equals, 2)
404
405         s1 := <-st.handled
406         s2 := <-st.handled
407
408         c.Check((s1 == shuff[1] && s2 == shuff[2]) ||
409                 (s1 == shuff[2] && s2 == shuff[1]),
410                 Equals,
411                 true)
412 }
413
414 func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
415         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
416
417         st := StubPutHandler{
418                 c,
419                 hash,
420                 "abc123",
421                 "foo",
422                 "",
423                 make(chan string, 1)}
424
425         fh := FailHandler{
426                 make(chan string, 4)}
427
428         arv, err := arvadosclient.MakeArvadosClient()
429         kc, _ := MakeKeepClient(arv)
430
431         kc.Want_replicas = 2
432         kc.Retries = 0
433         arv.ApiToken = "abc123"
434         localRoots := make(map[string]string)
435         writableLocalRoots := make(map[string]string)
436
437         ks1 := RunSomeFakeKeepServers(st, 1)
438         ks2 := RunSomeFakeKeepServers(fh, 4)
439
440         for i, k := range ks1 {
441                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
442                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
443                 defer k.listener.Close()
444         }
445         for i, k := range ks2 {
446                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
447                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
448                 defer k.listener.Close()
449         }
450
451         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
452
453         _, replicas, err := kc.PutB([]byte("foo"))
454
455         c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
456         c.Check(replicas, Equals, 1)
457         c.Check(<-st.handled, Equals, ks1[0].url)
458 }
459
460 type StubGetHandler struct {
461         c              *C
462         expectPath     string
463         expectApiToken string
464         httpStatus     int
465         body           []byte
466 }
467
468 func (sgh StubGetHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
469         sgh.c.Check(req.URL.Path, Equals, "/"+sgh.expectPath)
470         sgh.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sgh.expectApiToken))
471         resp.WriteHeader(sgh.httpStatus)
472         resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(sgh.body)))
473         resp.Write(sgh.body)
474 }
475
476 func (s *StandaloneSuite) TestGet(c *C) {
477         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
478
479         st := StubGetHandler{
480                 c,
481                 hash,
482                 "abc123",
483                 http.StatusOK,
484                 []byte("foo")}
485
486         ks := RunFakeKeepServer(st)
487         defer ks.listener.Close()
488
489         arv, err := arvadosclient.MakeArvadosClient()
490         kc, _ := MakeKeepClient(arv)
491         arv.ApiToken = "abc123"
492         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
493
494         r, n, url2, err := kc.Get(hash)
495         defer r.Close()
496         c.Check(err, Equals, nil)
497         c.Check(n, Equals, int64(3))
498         c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
499
500         content, err2 := ioutil.ReadAll(r)
501         c.Check(err2, Equals, nil)
502         c.Check(content, DeepEquals, []byte("foo"))
503 }
504
505 func (s *StandaloneSuite) TestGet404(c *C) {
506         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
507
508         st := Error404Handler{make(chan string, 1)}
509
510         ks := RunFakeKeepServer(st)
511         defer ks.listener.Close()
512
513         arv, err := arvadosclient.MakeArvadosClient()
514         kc, _ := MakeKeepClient(arv)
515         arv.ApiToken = "abc123"
516         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
517
518         r, n, url2, err := kc.Get(hash)
519         c.Check(err, Equals, BlockNotFound)
520         c.Check(n, Equals, int64(0))
521         c.Check(url2, Equals, "")
522         c.Check(r, Equals, nil)
523 }
524
525 func (s *StandaloneSuite) TestGetEmptyBlock(c *C) {
526         st := Error404Handler{make(chan string, 1)}
527
528         ks := RunFakeKeepServer(st)
529         defer ks.listener.Close()
530
531         arv, err := arvadosclient.MakeArvadosClient()
532         kc, _ := MakeKeepClient(arv)
533         arv.ApiToken = "abc123"
534         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
535
536         r, n, url2, err := kc.Get("d41d8cd98f00b204e9800998ecf8427e+0")
537         c.Check(err, IsNil)
538         c.Check(n, Equals, int64(0))
539         c.Check(url2, Equals, "")
540         c.Assert(r, NotNil)
541         buf, err := ioutil.ReadAll(r)
542         c.Check(err, IsNil)
543         c.Check(buf, DeepEquals, []byte{})
544 }
545
546 func (s *StandaloneSuite) TestGetFail(c *C) {
547         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
548
549         st := FailHandler{make(chan string, 1)}
550
551         ks := RunFakeKeepServer(st)
552         defer ks.listener.Close()
553
554         arv, err := arvadosclient.MakeArvadosClient()
555         kc, _ := MakeKeepClient(arv)
556         arv.ApiToken = "abc123"
557         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
558         kc.Retries = 0
559
560         r, n, url2, err := kc.Get(hash)
561         errNotFound, _ := err.(*ErrNotFound)
562         c.Check(errNotFound, NotNil)
563         c.Check(strings.Contains(errNotFound.Error(), "HTTP 500"), Equals, true)
564         c.Check(errNotFound.Temporary(), Equals, true)
565         c.Check(n, Equals, int64(0))
566         c.Check(url2, Equals, "")
567         c.Check(r, Equals, nil)
568 }
569
570 func (s *StandaloneSuite) TestGetFailRetry(c *C) {
571         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
572
573         st := &FailThenSucceedHandler{
574                 handled: make(chan string, 1),
575                 successhandler: StubGetHandler{
576                         c,
577                         hash,
578                         "abc123",
579                         http.StatusOK,
580                         []byte("foo")}}
581
582         ks := RunFakeKeepServer(st)
583         defer ks.listener.Close()
584
585         arv, err := arvadosclient.MakeArvadosClient()
586         kc, _ := MakeKeepClient(arv)
587         arv.ApiToken = "abc123"
588         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
589
590         r, n, url2, err := kc.Get(hash)
591         defer r.Close()
592         c.Check(err, Equals, nil)
593         c.Check(n, Equals, int64(3))
594         c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
595
596         content, err2 := ioutil.ReadAll(r)
597         c.Check(err2, Equals, nil)
598         c.Check(content, DeepEquals, []byte("foo"))
599
600         c.Logf("%q", st.reqIDs)
601         c.Assert(len(st.reqIDs) > 1, Equals, true)
602         for _, reqid := range st.reqIDs {
603                 c.Check(reqid, Not(Equals), "")
604                 c.Check(reqid, Equals, st.reqIDs[0])
605         }
606 }
607
608 func (s *StandaloneSuite) TestGetNetError(c *C) {
609         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
610
611         arv, err := arvadosclient.MakeArvadosClient()
612         kc, _ := MakeKeepClient(arv)
613         arv.ApiToken = "abc123"
614         kc.SetServiceRoots(map[string]string{"x": "http://localhost:62222"}, nil, nil)
615
616         r, n, url2, err := kc.Get(hash)
617         errNotFound, _ := err.(*ErrNotFound)
618         c.Check(errNotFound, NotNil)
619         c.Check(strings.Contains(errNotFound.Error(), "connection refused"), Equals, true)
620         c.Check(errNotFound.Temporary(), Equals, true)
621         c.Check(n, Equals, int64(0))
622         c.Check(url2, Equals, "")
623         c.Check(r, Equals, nil)
624 }
625
626 func (s *StandaloneSuite) TestGetWithServiceHint(c *C) {
627         uuid := "zzzzz-bi6l4-123451234512345"
628         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
629
630         // This one shouldn't be used:
631         ks0 := RunFakeKeepServer(StubGetHandler{
632                 c,
633                 "error if used",
634                 "abc123",
635                 http.StatusOK,
636                 []byte("foo")})
637         defer ks0.listener.Close()
638         // This one should be used:
639         ks := RunFakeKeepServer(StubGetHandler{
640                 c,
641                 hash + "+K@" + uuid,
642                 "abc123",
643                 http.StatusOK,
644                 []byte("foo")})
645         defer ks.listener.Close()
646
647         arv, err := arvadosclient.MakeArvadosClient()
648         kc, _ := MakeKeepClient(arv)
649         arv.ApiToken = "abc123"
650         kc.SetServiceRoots(
651                 map[string]string{"x": ks0.url},
652                 nil,
653                 map[string]string{uuid: ks.url})
654
655         r, n, uri, err := kc.Get(hash + "+K@" + uuid)
656         defer r.Close()
657         c.Check(err, Equals, nil)
658         c.Check(n, Equals, int64(3))
659         c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
660
661         content, err := ioutil.ReadAll(r)
662         c.Check(err, Equals, nil)
663         c.Check(content, DeepEquals, []byte("foo"))
664 }
665
666 // Use a service hint to fetch from a local disk service, overriding
667 // rendezvous probe order.
668 func (s *StandaloneSuite) TestGetWithLocalServiceHint(c *C) {
669         uuid := "zzzzz-bi6l4-zzzzzzzzzzzzzzz"
670         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
671
672         // This one shouldn't be used, although it appears first in
673         // rendezvous probe order:
674         ks0 := RunFakeKeepServer(StubGetHandler{
675                 c,
676                 "error if used",
677                 "abc123",
678                 http.StatusOK,
679                 []byte("foo")})
680         defer ks0.listener.Close()
681         // This one should be used:
682         ks := RunFakeKeepServer(StubGetHandler{
683                 c,
684                 hash + "+K@" + uuid,
685                 "abc123",
686                 http.StatusOK,
687                 []byte("foo")})
688         defer ks.listener.Close()
689
690         arv, err := arvadosclient.MakeArvadosClient()
691         kc, _ := MakeKeepClient(arv)
692         arv.ApiToken = "abc123"
693         kc.SetServiceRoots(
694                 map[string]string{
695                         "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
696                         "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
697                         "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
698                         uuid:                          ks.url},
699                 nil,
700                 map[string]string{
701                         "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
702                         "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
703                         "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
704                         uuid:                          ks.url},
705         )
706
707         r, n, uri, err := kc.Get(hash + "+K@" + uuid)
708         defer r.Close()
709         c.Check(err, Equals, nil)
710         c.Check(n, Equals, int64(3))
711         c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
712
713         content, err := ioutil.ReadAll(r)
714         c.Check(err, Equals, nil)
715         c.Check(content, DeepEquals, []byte("foo"))
716 }
717
718 func (s *StandaloneSuite) TestGetWithServiceHintFailoverToLocals(c *C) {
719         uuid := "zzzzz-bi6l4-123451234512345"
720         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
721
722         ksLocal := RunFakeKeepServer(StubGetHandler{
723                 c,
724                 hash + "+K@" + uuid,
725                 "abc123",
726                 http.StatusOK,
727                 []byte("foo")})
728         defer ksLocal.listener.Close()
729         ksGateway := RunFakeKeepServer(StubGetHandler{
730                 c,
731                 hash + "+K@" + uuid,
732                 "abc123",
733                 http.StatusInternalServerError,
734                 []byte("Error")})
735         defer ksGateway.listener.Close()
736
737         arv, err := arvadosclient.MakeArvadosClient()
738         kc, _ := MakeKeepClient(arv)
739         arv.ApiToken = "abc123"
740         kc.SetServiceRoots(
741                 map[string]string{"zzzzz-bi6l4-keepdisk0000000": ksLocal.url},
742                 nil,
743                 map[string]string{uuid: ksGateway.url})
744
745         r, n, uri, err := kc.Get(hash + "+K@" + uuid)
746         c.Assert(err, Equals, nil)
747         defer r.Close()
748         c.Check(n, Equals, int64(3))
749         c.Check(uri, Equals, fmt.Sprintf("%s/%s", ksLocal.url, hash+"+K@"+uuid))
750
751         content, err := ioutil.ReadAll(r)
752         c.Check(err, Equals, nil)
753         c.Check(content, DeepEquals, []byte("foo"))
754 }
755
756 type BarHandler struct {
757         handled chan string
758 }
759
760 func (this BarHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
761         resp.Write([]byte("bar"))
762         this.handled <- fmt.Sprintf("http://%s", req.Host)
763 }
764
765 func (s *StandaloneSuite) TestChecksum(c *C) {
766         foohash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
767         barhash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
768
769         st := BarHandler{make(chan string, 1)}
770
771         ks := RunFakeKeepServer(st)
772         defer ks.listener.Close()
773
774         arv, err := arvadosclient.MakeArvadosClient()
775         kc, _ := MakeKeepClient(arv)
776         arv.ApiToken = "abc123"
777         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
778
779         r, n, _, err := kc.Get(barhash)
780         _, err = ioutil.ReadAll(r)
781         c.Check(n, Equals, int64(3))
782         c.Check(err, Equals, nil)
783
784         <-st.handled
785
786         r, n, _, err = kc.Get(foohash)
787         _, err = ioutil.ReadAll(r)
788         c.Check(n, Equals, int64(3))
789         c.Check(err, Equals, BadChecksum)
790
791         <-st.handled
792 }
793
794 func (s *StandaloneSuite) TestGetWithFailures(c *C) {
795         content := []byte("waz")
796         hash := fmt.Sprintf("%x", md5.Sum(content))
797
798         fh := Error404Handler{
799                 make(chan string, 4)}
800
801         st := StubGetHandler{
802                 c,
803                 hash,
804                 "abc123",
805                 http.StatusOK,
806                 content}
807
808         arv, err := arvadosclient.MakeArvadosClient()
809         kc, _ := MakeKeepClient(arv)
810         arv.ApiToken = "abc123"
811         localRoots := make(map[string]string)
812         writableLocalRoots := make(map[string]string)
813
814         ks1 := RunSomeFakeKeepServers(st, 1)
815         ks2 := RunSomeFakeKeepServers(fh, 4)
816
817         for i, k := range ks1 {
818                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
819                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
820                 defer k.listener.Close()
821         }
822         for i, k := range ks2 {
823                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
824                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
825                 defer k.listener.Close()
826         }
827
828         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
829         kc.Retries = 0
830
831         // This test works only if one of the failing services is
832         // attempted before the succeeding service. Otherwise,
833         // <-fh.handled below will just hang! (Probe order depends on
834         // the choice of block content "waz" and the UUIDs of the fake
835         // servers, so we just tried different strings until we found
836         // an example that passes this Assert.)
837         c.Assert(NewRootSorter(localRoots, hash).GetSortedRoots()[0], Not(Equals), ks1[0].url)
838
839         r, n, url2, err := kc.Get(hash)
840
841         <-fh.handled
842         c.Check(err, Equals, nil)
843         c.Check(n, Equals, int64(3))
844         c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks1[0].url, hash))
845
846         read_content, err2 := ioutil.ReadAll(r)
847         c.Check(err2, Equals, nil)
848         c.Check(read_content, DeepEquals, content)
849 }
850
851 func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
852         content := []byte("TestPutGetHead")
853
854         arv, err := arvadosclient.MakeArvadosClient()
855         kc, err := MakeKeepClient(arv)
856         c.Assert(err, Equals, nil)
857
858         hash := fmt.Sprintf("%x", md5.Sum(content))
859
860         {
861                 n, _, err := kc.Ask(hash)
862                 c.Check(err, Equals, BlockNotFound)
863                 c.Check(n, Equals, int64(0))
864         }
865         {
866                 hash2, replicas, err := kc.PutB(content)
867                 c.Check(hash2, Matches, fmt.Sprintf(`%s\+%d\b.*`, hash, len(content)))
868                 c.Check(replicas, Equals, 2)
869                 c.Check(err, Equals, nil)
870         }
871         {
872                 r, n, url2, err := kc.Get(hash)
873                 c.Check(err, Equals, nil)
874                 c.Check(n, Equals, int64(len(content)))
875                 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
876
877                 read_content, err2 := ioutil.ReadAll(r)
878                 c.Check(err2, Equals, nil)
879                 c.Check(read_content, DeepEquals, content)
880         }
881         {
882                 n, url2, err := kc.Ask(hash)
883                 c.Check(err, Equals, nil)
884                 c.Check(n, Equals, int64(len(content)))
885                 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
886         }
887         {
888                 loc, err := kc.LocalLocator(hash)
889                 c.Check(err, Equals, nil)
890                 c.Assert(len(loc) >= 32, Equals, true)
891                 c.Check(loc[:32], Equals, hash[:32])
892         }
893         {
894                 content := []byte("the perth county conspiracy")
895                 loc, err := kc.LocalLocator(fmt.Sprintf("%x+%d+Rzaaaa-abcde@12345", md5.Sum(content), len(content)))
896                 c.Check(loc, Equals, "")
897                 c.Check(err, ErrorMatches, `.*HEAD .*\+R.*`)
898                 c.Check(err, ErrorMatches, `.*HTTP 400.*`)
899         }
900 }
901
902 type StubProxyHandler struct {
903         handled chan string
904 }
905
906 func (this StubProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
907         resp.Header().Set("X-Keep-Replicas-Stored", "2")
908         this.handled <- fmt.Sprintf("http://%s", req.Host)
909 }
910
911 func (s *StandaloneSuite) TestPutProxy(c *C) {
912         st := StubProxyHandler{make(chan string, 1)}
913
914         arv, err := arvadosclient.MakeArvadosClient()
915         kc, _ := MakeKeepClient(arv)
916
917         kc.Want_replicas = 2
918         arv.ApiToken = "abc123"
919         localRoots := make(map[string]string)
920         writableLocalRoots := make(map[string]string)
921
922         ks1 := RunSomeFakeKeepServers(st, 1)
923
924         for i, k := range ks1 {
925                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
926                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
927                 defer k.listener.Close()
928         }
929
930         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
931
932         _, replicas, err := kc.PutB([]byte("foo"))
933         <-st.handled
934
935         c.Check(err, Equals, nil)
936         c.Check(replicas, Equals, 2)
937 }
938
939 func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
940         st := StubProxyHandler{make(chan string, 1)}
941
942         arv, err := arvadosclient.MakeArvadosClient()
943         kc, _ := MakeKeepClient(arv)
944
945         kc.Want_replicas = 3
946         arv.ApiToken = "abc123"
947         localRoots := make(map[string]string)
948         writableLocalRoots := make(map[string]string)
949
950         ks1 := RunSomeFakeKeepServers(st, 1)
951
952         for i, k := range ks1 {
953                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
954                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
955                 defer k.listener.Close()
956         }
957         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
958
959         _, replicas, err := kc.PutB([]byte("foo"))
960         <-st.handled
961
962         c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
963         c.Check(replicas, Equals, 2)
964 }
965
966 func (s *StandaloneSuite) TestMakeLocator(c *C) {
967         l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+3+Aabcde@12345678")
968         c.Check(err, Equals, nil)
969         c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
970         c.Check(l.Size, Equals, 3)
971         c.Check(l.Hints, DeepEquals, []string{"3", "Aabcde@12345678"})
972 }
973
974 func (s *StandaloneSuite) TestMakeLocatorNoHints(c *C) {
975         l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce")
976         c.Check(err, Equals, nil)
977         c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
978         c.Check(l.Size, Equals, -1)
979         c.Check(l.Hints, DeepEquals, []string{})
980 }
981
982 func (s *StandaloneSuite) TestMakeLocatorNoSizeHint(c *C) {
983         l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+Aabcde@12345678")
984         c.Check(err, Equals, nil)
985         c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
986         c.Check(l.Size, Equals, -1)
987         c.Check(l.Hints, DeepEquals, []string{"Aabcde@12345678"})
988 }
989
990 func (s *StandaloneSuite) TestMakeLocatorPreservesUnrecognizedHints(c *C) {
991         str := "91f372a266fe2bf2823cb8ec7fda31ce+3+Unknown+Kzzzzz+Afoobar"
992         l, err := MakeLocator(str)
993         c.Check(err, Equals, nil)
994         c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
995         c.Check(l.Size, Equals, 3)
996         c.Check(l.Hints, DeepEquals, []string{"3", "Unknown", "Kzzzzz", "Afoobar"})
997         c.Check(l.String(), Equals, str)
998 }
999
1000 func (s *StandaloneSuite) TestMakeLocatorInvalidInput(c *C) {
1001         _, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31c")
1002         c.Check(err, Equals, InvalidLocatorError)
1003 }
1004
1005 func (s *StandaloneSuite) TestPutBWant2ReplicasWithOnlyOneWritableLocalRoot(c *C) {
1006         hash := Md5String("foo")
1007
1008         st := StubPutHandler{
1009                 c,
1010                 hash,
1011                 "abc123",
1012                 "foo",
1013                 "",
1014                 make(chan string, 5)}
1015
1016         arv, _ := arvadosclient.MakeArvadosClient()
1017         kc, _ := MakeKeepClient(arv)
1018
1019         kc.Want_replicas = 2
1020         arv.ApiToken = "abc123"
1021         localRoots := make(map[string]string)
1022         writableLocalRoots := make(map[string]string)
1023
1024         ks := RunSomeFakeKeepServers(st, 5)
1025
1026         for i, k := range ks {
1027                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1028                 if i == 0 {
1029                         writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1030                 }
1031                 defer k.listener.Close()
1032         }
1033
1034         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1035
1036         _, replicas, err := kc.PutB([]byte("foo"))
1037
1038         c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
1039         c.Check(replicas, Equals, 1)
1040
1041         c.Check(<-st.handled, Equals, localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", 0)])
1042 }
1043
1044 func (s *StandaloneSuite) TestPutBWithNoWritableLocalRoots(c *C) {
1045         hash := Md5String("foo")
1046
1047         st := StubPutHandler{
1048                 c,
1049                 hash,
1050                 "abc123",
1051                 "foo",
1052                 "",
1053                 make(chan string, 5)}
1054
1055         arv, _ := arvadosclient.MakeArvadosClient()
1056         kc, _ := MakeKeepClient(arv)
1057
1058         kc.Want_replicas = 2
1059         arv.ApiToken = "abc123"
1060         localRoots := make(map[string]string)
1061         writableLocalRoots := make(map[string]string)
1062
1063         ks := RunSomeFakeKeepServers(st, 5)
1064
1065         for i, k := range ks {
1066                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1067                 defer k.listener.Close()
1068         }
1069
1070         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1071
1072         _, replicas, err := kc.PutB([]byte("foo"))
1073
1074         c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
1075         c.Check(replicas, Equals, 0)
1076 }
1077
1078 type StubGetIndexHandler struct {
1079         c              *C
1080         expectPath     string
1081         expectAPIToken string
1082         httpStatus     int
1083         body           []byte
1084 }
1085
1086 func (h StubGetIndexHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1087         h.c.Check(req.URL.Path, Equals, h.expectPath)
1088         h.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", h.expectAPIToken))
1089         resp.WriteHeader(h.httpStatus)
1090         resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(h.body)))
1091         resp.Write(h.body)
1092 }
1093
1094 func (s *StandaloneSuite) TestGetIndexWithNoPrefix(c *C) {
1095         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1096
1097         st := StubGetIndexHandler{
1098                 c,
1099                 "/index",
1100                 "abc123",
1101                 http.StatusOK,
1102                 []byte(hash + "+3 1443559274\n\n")}
1103
1104         ks := RunFakeKeepServer(st)
1105         defer ks.listener.Close()
1106
1107         arv, err := arvadosclient.MakeArvadosClient()
1108         c.Assert(err, IsNil)
1109         kc, err := MakeKeepClient(arv)
1110         c.Assert(err, IsNil)
1111         arv.ApiToken = "abc123"
1112         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1113
1114         r, err := kc.GetIndex("x", "")
1115         c.Check(err, IsNil)
1116
1117         content, err2 := ioutil.ReadAll(r)
1118         c.Check(err2, Equals, nil)
1119         c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1120 }
1121
1122 func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
1123         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1124
1125         st := StubGetIndexHandler{
1126                 c,
1127                 "/index/" + hash[0:3],
1128                 "abc123",
1129                 http.StatusOK,
1130                 []byte(hash + "+3 1443559274\n\n")}
1131
1132         ks := RunFakeKeepServer(st)
1133         defer ks.listener.Close()
1134
1135         arv, err := arvadosclient.MakeArvadosClient()
1136         kc, _ := MakeKeepClient(arv)
1137         arv.ApiToken = "abc123"
1138         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1139
1140         r, err := kc.GetIndex("x", hash[0:3])
1141         c.Assert(err, Equals, nil)
1142
1143         content, err2 := ioutil.ReadAll(r)
1144         c.Check(err2, Equals, nil)
1145         c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1146 }
1147
1148 func (s *StandaloneSuite) TestGetIndexIncomplete(c *C) {
1149         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1150
1151         st := StubGetIndexHandler{
1152                 c,
1153                 "/index/" + hash[0:3],
1154                 "abc123",
1155                 http.StatusOK,
1156                 []byte(hash)}
1157
1158         ks := RunFakeKeepServer(st)
1159         defer ks.listener.Close()
1160
1161         arv, err := arvadosclient.MakeArvadosClient()
1162         kc, _ := MakeKeepClient(arv)
1163         arv.ApiToken = "abc123"
1164         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1165
1166         _, err = kc.GetIndex("x", hash[0:3])
1167         c.Check(err, Equals, ErrIncompleteIndex)
1168 }
1169
1170 func (s *StandaloneSuite) TestGetIndexWithNoSuchServer(c *C) {
1171         hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1172
1173         st := StubGetIndexHandler{
1174                 c,
1175                 "/index/" + hash[0:3],
1176                 "abc123",
1177                 http.StatusOK,
1178                 []byte(hash)}
1179
1180         ks := RunFakeKeepServer(st)
1181         defer ks.listener.Close()
1182
1183         arv, err := arvadosclient.MakeArvadosClient()
1184         kc, _ := MakeKeepClient(arv)
1185         arv.ApiToken = "abc123"
1186         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1187
1188         _, err = kc.GetIndex("y", hash[0:3])
1189         c.Check(err, Equals, ErrNoSuchKeepServer)
1190 }
1191
1192 func (s *StandaloneSuite) TestGetIndexWithNoSuchPrefix(c *C) {
1193         st := StubGetIndexHandler{
1194                 c,
1195                 "/index/abcd",
1196                 "abc123",
1197                 http.StatusOK,
1198                 []byte("\n")}
1199
1200         ks := RunFakeKeepServer(st)
1201         defer ks.listener.Close()
1202
1203         arv, err := arvadosclient.MakeArvadosClient()
1204         kc, _ := MakeKeepClient(arv)
1205         arv.ApiToken = "abc123"
1206         kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1207
1208         r, err := kc.GetIndex("x", "abcd")
1209         c.Check(err, Equals, nil)
1210
1211         content, err2 := ioutil.ReadAll(r)
1212         c.Check(err2, Equals, nil)
1213         c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1214 }
1215
1216 func (s *StandaloneSuite) TestPutBRetry(c *C) {
1217         st := &FailThenSucceedHandler{
1218                 handled: make(chan string, 1),
1219                 successhandler: StubPutHandler{
1220                         c,
1221                         Md5String("foo"),
1222                         "abc123",
1223                         "foo",
1224                         "",
1225                         make(chan string, 5)}}
1226
1227         arv, _ := arvadosclient.MakeArvadosClient()
1228         kc, _ := MakeKeepClient(arv)
1229
1230         kc.Want_replicas = 2
1231         arv.ApiToken = "abc123"
1232         localRoots := make(map[string]string)
1233         writableLocalRoots := make(map[string]string)
1234
1235         ks := RunSomeFakeKeepServers(st, 2)
1236
1237         for i, k := range ks {
1238                 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1239                 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1240                 defer k.listener.Close()
1241         }
1242
1243         kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1244
1245         hash, replicas, err := kc.PutB([]byte("foo"))
1246
1247         c.Check(err, Equals, nil)
1248         c.Check(hash, Equals, "")
1249         c.Check(replicas, Equals, 2)
1250 }
1251
1252 func (s *ServerRequiredSuite) TestMakeKeepClientWithNonDiskTypeService(c *C) {
1253         arv, err := arvadosclient.MakeArvadosClient()
1254         c.Assert(err, Equals, nil)
1255
1256         // Add an additional "testblobstore" keepservice
1257         blobKeepService := make(arvadosclient.Dict)
1258         err = arv.Create("keep_services",
1259                 arvadosclient.Dict{"keep_service": arvadosclient.Dict{
1260                         "service_host": "localhost",
1261                         "service_port": "21321",
1262                         "service_type": "testblobstore"}},
1263                 &blobKeepService)
1264         c.Assert(err, Equals, nil)
1265         defer func() { arv.Delete("keep_services", blobKeepService["uuid"].(string), nil, nil) }()
1266         RefreshServiceDiscovery()
1267
1268         // Make a keepclient and ensure that the testblobstore is included
1269         kc, err := MakeKeepClient(arv)
1270         c.Assert(err, Equals, nil)
1271
1272         // verify kc.LocalRoots
1273         c.Check(len(kc.LocalRoots()), Equals, 3)
1274         for _, root := range kc.LocalRoots() {
1275                 c.Check(root, Matches, "http://localhost:\\d+")
1276         }
1277         c.Assert(kc.LocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1278
1279         // verify kc.GatewayRoots
1280         c.Check(len(kc.GatewayRoots()), Equals, 3)
1281         for _, root := range kc.GatewayRoots() {
1282                 c.Check(root, Matches, "http://localhost:\\d+")
1283         }
1284         c.Assert(kc.GatewayRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1285
1286         // verify kc.WritableLocalRoots
1287         c.Check(len(kc.WritableLocalRoots()), Equals, 3)
1288         for _, root := range kc.WritableLocalRoots() {
1289                 c.Check(root, Matches, "http://localhost:\\d+")
1290         }
1291         c.Assert(kc.WritableLocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1292
1293         c.Assert(kc.replicasPerService, Equals, 0)
1294         c.Assert(kc.foundNonDiskSvc, Equals, true)
1295         c.Assert(kc.httpClient().(*http.Client).Timeout, Equals, 300*time.Second)
1296 }