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