1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
24 "git.arvados.org/arvados.git/sdk/go/arvados"
25 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
26 "git.arvados.org/arvados.git/sdk/go/arvadostest"
28 check "gopkg.in/check.v1"
31 // Gocheck boilerplate
32 func Test(t *testing.T) {
36 // Gocheck boilerplate
37 var _ = Suite(&ServerRequiredSuite{})
38 var _ = Suite(&StandaloneSuite{})
40 // Tests that require the Keep server running
41 type ServerRequiredSuite struct{}
44 type StandaloneSuite struct{}
46 func (s *StandaloneSuite) SetUpTest(c *C) {
47 RefreshServiceDiscovery()
50 func pythonDir() string {
52 return fmt.Sprintf("%s/../../python/tests", cwd)
55 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
56 arvadostest.StartAPI()
57 arvadostest.StartKeep(2, false)
60 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
61 arvadostest.StopKeep(2)
65 func (s *ServerRequiredSuite) SetUpTest(c *C) {
66 RefreshServiceDiscovery()
69 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
70 arv, err := arvadosclient.MakeArvadosClient()
71 c.Assert(err, Equals, nil)
73 kc, err := MakeKeepClient(arv)
75 c.Assert(err, Equals, nil)
76 c.Check(len(kc.LocalRoots()), Equals, 2)
77 for _, root := range kc.LocalRoots() {
78 c.Check(root, Matches, "http://localhost:\\d+")
82 func (s *ServerRequiredSuite) TestDefaultReplications(c *C) {
83 arv, err := arvadosclient.MakeArvadosClient()
84 c.Assert(err, Equals, nil)
86 kc, err := MakeKeepClient(arv)
88 c.Assert(kc.Want_replicas, Equals, 2)
90 arv.DiscoveryDoc["defaultCollectionReplication"] = 3.0
91 kc, err = MakeKeepClient(arv)
93 c.Assert(kc.Want_replicas, Equals, 3)
95 arv.DiscoveryDoc["defaultCollectionReplication"] = 1.0
96 kc, err = MakeKeepClient(arv)
98 c.Assert(kc.Want_replicas, Equals, 1)
101 type StubPutHandler struct {
104 expectAPIToken string
106 expectStorageClass string
107 returnStorageClasses string
109 requests []*http.Request
113 func (sph *StubPutHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
115 sph.requests = append(sph.requests, req)
117 sph.c.Check(req.URL.Path, Equals, "/"+sph.expectPath)
118 sph.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sph.expectAPIToken))
119 if sph.expectStorageClass != "*" {
120 sph.c.Check(req.Header.Get("X-Keep-Storage-Classes"), Equals, sph.expectStorageClass)
122 body, err := ioutil.ReadAll(req.Body)
123 sph.c.Check(err, Equals, nil)
124 sph.c.Check(body, DeepEquals, []byte(sph.expectBody))
125 resp.Header().Set("X-Keep-Replicas-Stored", "1")
126 if sph.returnStorageClasses != "" {
127 resp.Header().Set("X-Keep-Storage-Classes-Confirmed", sph.returnStorageClasses)
129 resp.WriteHeader(200)
130 sph.handled <- fmt.Sprintf("http://%s", req.Host)
133 func RunFakeKeepServer(st http.Handler) (ks KeepServer) {
135 // If we don't explicitly bind it to localhost, ks.listener.Addr() will
136 // bind to 0.0.0.0 or [::] which is not a valid address for Dial()
137 ks.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0})
139 panic(fmt.Sprintf("Could not listen on any port"))
141 ks.url = fmt.Sprintf("http://%s", ks.listener.Addr().String())
142 go http.Serve(ks.listener, st)
146 func UploadToStubHelper(c *C, st http.Handler, f func(*KeepClient, string,
147 io.ReadCloser, io.WriteCloser, chan uploadStatus)) {
149 ks := RunFakeKeepServer(st)
150 defer ks.listener.Close()
152 arv, _ := arvadosclient.MakeArvadosClient()
153 arv.ApiToken = "abc123"
155 kc, _ := MakeKeepClient(arv)
157 reader, writer := io.Pipe()
158 uploadStatusChan := make(chan uploadStatus)
160 f(kc, ks.url, reader, writer, uploadStatusChan)
163 func (s *StandaloneSuite) TestUploadToStubKeepServer(c *C) {
164 log.Printf("TestUploadToStubKeepServer")
166 st := &StubPutHandler{
168 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
169 expectAPIToken: "abc123",
171 expectStorageClass: "",
172 returnStorageClasses: "default=1",
173 handled: make(chan string),
176 UploadToStubHelper(c, st,
177 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
178 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
180 writer.Write([]byte("foo"))
184 status := <-uploadStatusChan
185 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
189 func (s *StandaloneSuite) TestUploadToStubKeepServerBufferReader(c *C) {
190 st := &StubPutHandler{
192 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
193 expectAPIToken: "abc123",
195 expectStorageClass: "",
196 returnStorageClasses: "default=1",
197 handled: make(chan string),
200 UploadToStubHelper(c, st,
201 func(kc *KeepClient, url string, _ io.ReadCloser, _ io.WriteCloser, uploadStatusChan chan uploadStatus) {
202 go kc.uploadToKeepServer(url, st.expectPath, nil, bytes.NewBuffer([]byte("foo")), uploadStatusChan, 3, kc.getRequestID())
206 status := <-uploadStatusChan
207 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
211 func (s *StandaloneSuite) TestUploadWithStorageClasses(c *C) {
212 for _, trial := range []struct {
214 expectMap map[string]int
217 {"foo=1", map[string]int{"foo": 1}},
218 {" foo=1 , bar=2 ", map[string]int{"foo": 1, "bar": 2}},
222 st := &StubPutHandler{
224 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
225 expectAPIToken: "abc123",
227 expectStorageClass: "",
228 returnStorageClasses: trial.respHeader,
229 handled: make(chan string),
232 UploadToStubHelper(c, st,
233 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
234 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
236 writer.Write([]byte("foo"))
240 status := <-uploadStatusChan
241 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, trial.expectMap, ""})
246 func (s *StandaloneSuite) TestPutWithStorageClasses(c *C) {
248 for _, trial := range []struct {
250 clientClasses []string
251 putClasses []string // putClasses takes precedence over clientClasses
256 {1, []string{"class1"}, nil, 1, 1, true},
257 {2, []string{"class1"}, nil, 1, 2, true},
258 {3, []string{"class1"}, nil, 2, 3, true},
259 {1, []string{"class1", "class2"}, nil, 1, 1, true},
260 {3, nil, []string{"class1"}, 2, 3, true},
261 {1, nil, []string{"class1", "class2"}, 1, 1, true},
262 {1, []string{"class404"}, []string{"class1", "class2"}, 1, 1, true},
263 {1, []string{"class1"}, []string{"class404", "class2"}, nServers, nServers, false},
264 {nServers*2 + 1, []string{"class1"}, nil, nServers, nServers, false},
265 {1, []string{"class404"}, nil, nServers, nServers, false},
266 {1, []string{"class1", "class404"}, nil, nServers, nServers, false},
267 {1, nil, []string{"class1", "class404"}, nServers, nServers, false},
270 st := &StubPutHandler{
272 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
273 expectAPIToken: "abc123",
275 expectStorageClass: "*",
276 returnStorageClasses: "class1=2, class2=2",
277 handled: make(chan string, 100),
279 ks := RunSomeFakeKeepServers(st, nServers)
280 arv, _ := arvadosclient.MakeArvadosClient()
281 kc, _ := MakeKeepClient(arv)
282 kc.Want_replicas = trial.replicas
283 kc.StorageClasses = trial.clientClasses
284 arv.ApiToken = "abc123"
285 localRoots := make(map[string]string)
286 writableLocalRoots := make(map[string]string)
287 for i, k := range ks {
288 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
289 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
290 defer k.listener.Close()
292 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
294 _, err := kc.BlockWrite(context.Background(), arvados.BlockWriteOptions{
296 StorageClasses: trial.putClasses,
299 c.Check(err, check.IsNil)
301 c.Check(err, check.NotNil)
303 c.Check(len(st.handled) >= trial.minRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.minRequests==%d", len(st.handled), trial.minRequests))
304 c.Check(len(st.handled) <= trial.maxRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.maxRequests==%d", len(st.handled), trial.maxRequests))
305 if !trial.success && trial.replicas == 1 && c.Check(len(st.requests) >= 2, check.Equals, true) {
306 // Max concurrency should be 1. First request
307 // should have succeeded for class1. Second
308 // request should only ask for class404.
309 c.Check(st.requests[1].Header.Get("X-Keep-Storage-Classes"), check.Equals, "class404")
314 type FailHandler struct {
318 func (fh FailHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
319 resp.WriteHeader(500)
320 fh.handled <- fmt.Sprintf("http://%s", req.Host)
323 type FailThenSucceedHandler struct {
326 successhandler http.Handler
330 func (fh *FailThenSucceedHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
331 fh.reqIDs = append(fh.reqIDs, req.Header.Get("X-Request-Id"))
333 resp.WriteHeader(500)
335 fh.handled <- fmt.Sprintf("http://%s", req.Host)
337 fh.successhandler.ServeHTTP(resp, req)
341 type Error404Handler struct {
345 func (fh Error404Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
346 resp.WriteHeader(404)
347 fh.handled <- fmt.Sprintf("http://%s", req.Host)
350 func (s *StandaloneSuite) TestFailedUploadToStubKeepServer(c *C) {
354 hash := "acbd18db4cc2f85cedef654fccc4a4d8"
356 UploadToStubHelper(c, st,
357 func(kc *KeepClient, url string, reader io.ReadCloser,
358 writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
360 go kc.uploadToKeepServer(url, hash, nil, reader, uploadStatusChan, 3, kc.getRequestID())
362 writer.Write([]byte("foo"))
367 status := <-uploadStatusChan
368 c.Check(status.url, Equals, fmt.Sprintf("%s/%s", url, hash))
369 c.Check(status.statusCode, Equals, 500)
373 type KeepServer struct {
374 listener net.Listener
378 func RunSomeFakeKeepServers(st http.Handler, n int) (ks []KeepServer) {
379 ks = make([]KeepServer, n)
381 for i := 0; i < n; i++ {
382 ks[i] = RunFakeKeepServer(st)
388 func (s *StandaloneSuite) TestPutB(c *C) {
389 hash := Md5String("foo")
391 st := &StubPutHandler{
394 expectAPIToken: "abc123",
396 expectStorageClass: "",
397 returnStorageClasses: "",
398 handled: make(chan string, 5),
401 arv, _ := arvadosclient.MakeArvadosClient()
402 kc, _ := MakeKeepClient(arv)
405 arv.ApiToken = "abc123"
406 localRoots := make(map[string]string)
407 writableLocalRoots := make(map[string]string)
409 ks := RunSomeFakeKeepServers(st, 5)
411 for i, k := range ks {
412 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
413 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
414 defer k.listener.Close()
417 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
419 kc.PutB([]byte("foo"))
421 shuff := NewRootSorter(
422 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
426 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
427 (s1 == shuff[1] && s2 == shuff[0]),
432 func (s *StandaloneSuite) TestPutHR(c *C) {
433 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
435 st := &StubPutHandler{
438 expectAPIToken: "abc123",
440 expectStorageClass: "",
441 returnStorageClasses: "",
442 handled: make(chan string, 5),
445 arv, _ := arvadosclient.MakeArvadosClient()
446 kc, _ := MakeKeepClient(arv)
449 arv.ApiToken = "abc123"
450 localRoots := make(map[string]string)
451 writableLocalRoots := make(map[string]string)
453 ks := RunSomeFakeKeepServers(st, 5)
455 for i, k := range ks {
456 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
457 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
458 defer k.listener.Close()
461 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
463 reader, writer := io.Pipe()
466 writer.Write([]byte("foo"))
470 kc.PutHR(hash, reader, 3)
472 shuff := NewRootSorter(kc.LocalRoots(), hash).GetSortedRoots()
477 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
478 (s1 == shuff[1] && s2 == shuff[0]),
483 func (s *StandaloneSuite) TestPutWithFail(c *C) {
484 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
486 st := &StubPutHandler{
489 expectAPIToken: "abc123",
491 expectStorageClass: "",
492 returnStorageClasses: "",
493 handled: make(chan string, 4),
497 make(chan string, 1)}
499 arv, err := arvadosclient.MakeArvadosClient()
501 kc, _ := MakeKeepClient(arv)
504 arv.ApiToken = "abc123"
505 localRoots := make(map[string]string)
506 writableLocalRoots := make(map[string]string)
508 ks1 := RunSomeFakeKeepServers(st, 4)
509 ks2 := RunSomeFakeKeepServers(fh, 1)
511 for i, k := range ks1 {
512 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
513 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
514 defer k.listener.Close()
516 for i, k := range ks2 {
517 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
518 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
519 defer k.listener.Close()
522 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
524 shuff := NewRootSorter(
525 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
528 phash, replicas, err := kc.PutB([]byte("foo"))
532 c.Check(err, Equals, nil)
533 c.Check(phash, Equals, "")
534 c.Check(replicas, Equals, 2)
539 c.Check((s1 == shuff[1] && s2 == shuff[2]) ||
540 (s1 == shuff[2] && s2 == shuff[1]),
545 func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
546 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
548 st := &StubPutHandler{
551 expectAPIToken: "abc123",
553 expectStorageClass: "",
554 returnStorageClasses: "",
555 handled: make(chan string, 1),
559 make(chan string, 4)}
561 arv, err := arvadosclient.MakeArvadosClient()
563 kc, _ := MakeKeepClient(arv)
567 arv.ApiToken = "abc123"
568 localRoots := make(map[string]string)
569 writableLocalRoots := make(map[string]string)
571 ks1 := RunSomeFakeKeepServers(st, 1)
572 ks2 := RunSomeFakeKeepServers(fh, 4)
574 for i, k := range ks1 {
575 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
576 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
577 defer k.listener.Close()
579 for i, k := range ks2 {
580 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
581 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
582 defer k.listener.Close()
585 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
587 _, replicas, err := kc.PutB([]byte("foo"))
589 c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
590 c.Check(replicas, Equals, 1)
591 c.Check(<-st.handled, Equals, ks1[0].url)
594 type StubGetHandler struct {
597 expectAPIToken string
602 func (sgh StubGetHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
603 sgh.c.Check(req.URL.Path, Equals, "/"+sgh.expectPath)
604 sgh.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sgh.expectAPIToken))
605 resp.WriteHeader(sgh.httpStatus)
606 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(sgh.body)))
610 func (s *StandaloneSuite) TestGet(c *C) {
611 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
613 st := StubGetHandler{
620 ks := RunFakeKeepServer(st)
621 defer ks.listener.Close()
623 arv, err := arvadosclient.MakeArvadosClient()
625 kc, _ := MakeKeepClient(arv)
626 arv.ApiToken = "abc123"
627 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
629 r, n, url2, err := kc.Get(hash)
631 c.Check(err, Equals, nil)
632 c.Check(n, Equals, int64(3))
633 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
635 content, err2 := ioutil.ReadAll(r)
636 c.Check(err2, Equals, nil)
637 c.Check(content, DeepEquals, []byte("foo"))
640 func (s *StandaloneSuite) TestGet404(c *C) {
641 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
643 st := Error404Handler{make(chan string, 1)}
645 ks := RunFakeKeepServer(st)
646 defer ks.listener.Close()
648 arv, err := arvadosclient.MakeArvadosClient()
650 kc, _ := MakeKeepClient(arv)
651 arv.ApiToken = "abc123"
652 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
654 r, n, url2, err := kc.Get(hash)
655 c.Check(err, Equals, BlockNotFound)
656 c.Check(n, Equals, int64(0))
657 c.Check(url2, Equals, "")
658 c.Check(r, Equals, nil)
661 func (s *StandaloneSuite) TestGetEmptyBlock(c *C) {
662 st := Error404Handler{make(chan string, 1)}
664 ks := RunFakeKeepServer(st)
665 defer ks.listener.Close()
667 arv, err := arvadosclient.MakeArvadosClient()
669 kc, _ := MakeKeepClient(arv)
670 arv.ApiToken = "abc123"
671 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
673 r, n, url2, err := kc.Get("d41d8cd98f00b204e9800998ecf8427e+0")
675 c.Check(n, Equals, int64(0))
676 c.Check(url2, Equals, "")
678 buf, err := ioutil.ReadAll(r)
680 c.Check(buf, DeepEquals, []byte{})
683 func (s *StandaloneSuite) TestGetFail(c *C) {
684 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
686 st := FailHandler{make(chan string, 1)}
688 ks := RunFakeKeepServer(st)
689 defer ks.listener.Close()
691 arv, err := arvadosclient.MakeArvadosClient()
693 kc, _ := MakeKeepClient(arv)
694 arv.ApiToken = "abc123"
695 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
698 r, n, url2, err := kc.Get(hash)
699 errNotFound, _ := err.(*ErrNotFound)
700 c.Check(errNotFound, NotNil)
701 c.Check(strings.Contains(errNotFound.Error(), "HTTP 500"), Equals, true)
702 c.Check(errNotFound.Temporary(), Equals, true)
703 c.Check(n, Equals, int64(0))
704 c.Check(url2, Equals, "")
705 c.Check(r, Equals, nil)
708 func (s *StandaloneSuite) TestGetFailRetry(c *C) {
709 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
711 st := &FailThenSucceedHandler{
712 handled: make(chan string, 1),
713 successhandler: StubGetHandler{
720 ks := RunFakeKeepServer(st)
721 defer ks.listener.Close()
723 arv, err := arvadosclient.MakeArvadosClient()
725 kc, _ := MakeKeepClient(arv)
726 arv.ApiToken = "abc123"
727 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
729 r, n, url2, err := kc.Get(hash)
731 c.Check(err, Equals, nil)
732 c.Check(n, Equals, int64(3))
733 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
735 content, err2 := ioutil.ReadAll(r)
736 c.Check(err2, Equals, nil)
737 c.Check(content, DeepEquals, []byte("foo"))
739 c.Logf("%q", st.reqIDs)
740 c.Assert(len(st.reqIDs) > 1, Equals, true)
741 for _, reqid := range st.reqIDs {
742 c.Check(reqid, Not(Equals), "")
743 c.Check(reqid, Equals, st.reqIDs[0])
747 func (s *StandaloneSuite) TestGetNetError(c *C) {
748 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
750 arv, err := arvadosclient.MakeArvadosClient()
752 kc, _ := MakeKeepClient(arv)
753 arv.ApiToken = "abc123"
754 kc.SetServiceRoots(map[string]string{"x": "http://localhost:62222"}, nil, nil)
756 r, n, url2, err := kc.Get(hash)
757 errNotFound, _ := err.(*ErrNotFound)
758 c.Check(errNotFound, NotNil)
759 c.Check(strings.Contains(errNotFound.Error(), "connection refused"), Equals, true)
760 c.Check(errNotFound.Temporary(), Equals, true)
761 c.Check(n, Equals, int64(0))
762 c.Check(url2, Equals, "")
763 c.Check(r, Equals, nil)
766 func (s *StandaloneSuite) TestGetWithServiceHint(c *C) {
767 uuid := "zzzzz-bi6l4-123451234512345"
768 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
770 // This one shouldn't be used:
771 ks0 := RunFakeKeepServer(StubGetHandler{
777 defer ks0.listener.Close()
778 // This one should be used:
779 ks := RunFakeKeepServer(StubGetHandler{
785 defer ks.listener.Close()
787 arv, err := arvadosclient.MakeArvadosClient()
789 kc, _ := MakeKeepClient(arv)
790 arv.ApiToken = "abc123"
792 map[string]string{"x": ks0.url},
794 map[string]string{uuid: ks.url})
796 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
798 c.Check(err, Equals, nil)
799 c.Check(n, Equals, int64(3))
800 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
802 content, err := ioutil.ReadAll(r)
803 c.Check(err, Equals, nil)
804 c.Check(content, DeepEquals, []byte("foo"))
807 // Use a service hint to fetch from a local disk service, overriding
808 // rendezvous probe order.
809 func (s *StandaloneSuite) TestGetWithLocalServiceHint(c *C) {
810 uuid := "zzzzz-bi6l4-zzzzzzzzzzzzzzz"
811 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
813 // This one shouldn't be used, although it appears first in
814 // rendezvous probe order:
815 ks0 := RunFakeKeepServer(StubGetHandler{
821 defer ks0.listener.Close()
822 // This one should be used:
823 ks := RunFakeKeepServer(StubGetHandler{
829 defer ks.listener.Close()
831 arv, err := arvadosclient.MakeArvadosClient()
833 kc, _ := MakeKeepClient(arv)
834 arv.ApiToken = "abc123"
837 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
838 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
839 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
843 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
844 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
845 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
849 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
851 c.Check(err, Equals, nil)
852 c.Check(n, Equals, int64(3))
853 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
855 content, err := ioutil.ReadAll(r)
856 c.Check(err, Equals, nil)
857 c.Check(content, DeepEquals, []byte("foo"))
860 func (s *StandaloneSuite) TestGetWithServiceHintFailoverToLocals(c *C) {
861 uuid := "zzzzz-bi6l4-123451234512345"
862 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
864 ksLocal := RunFakeKeepServer(StubGetHandler{
870 defer ksLocal.listener.Close()
871 ksGateway := RunFakeKeepServer(StubGetHandler{
875 http.StatusInternalServerError,
877 defer ksGateway.listener.Close()
879 arv, err := arvadosclient.MakeArvadosClient()
881 kc, _ := MakeKeepClient(arv)
882 arv.ApiToken = "abc123"
884 map[string]string{"zzzzz-bi6l4-keepdisk0000000": ksLocal.url},
886 map[string]string{uuid: ksGateway.url})
888 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
889 c.Assert(err, Equals, nil)
891 c.Check(n, Equals, int64(3))
892 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ksLocal.url, hash+"+K@"+uuid))
894 content, err := ioutil.ReadAll(r)
895 c.Check(err, Equals, nil)
896 c.Check(content, DeepEquals, []byte("foo"))
899 type BarHandler struct {
903 func (h BarHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
904 resp.Write([]byte("bar"))
905 h.handled <- fmt.Sprintf("http://%s", req.Host)
908 func (s *StandaloneSuite) TestChecksum(c *C) {
909 foohash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
910 barhash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
912 st := BarHandler{make(chan string, 1)}
914 ks := RunFakeKeepServer(st)
915 defer ks.listener.Close()
917 arv, err := arvadosclient.MakeArvadosClient()
919 kc, _ := MakeKeepClient(arv)
920 arv.ApiToken = "abc123"
921 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
923 r, n, _, err := kc.Get(barhash)
925 _, err = ioutil.ReadAll(r)
926 c.Check(n, Equals, int64(3))
927 c.Check(err, Equals, nil)
931 r, n, _, err = kc.Get(foohash)
933 _, err = ioutil.ReadAll(r)
934 c.Check(n, Equals, int64(3))
935 c.Check(err, Equals, BadChecksum)
940 func (s *StandaloneSuite) TestGetWithFailures(c *C) {
941 content := []byte("waz")
942 hash := fmt.Sprintf("%x", md5.Sum(content))
944 fh := Error404Handler{
945 make(chan string, 4)}
947 st := StubGetHandler{
954 arv, err := arvadosclient.MakeArvadosClient()
956 kc, _ := MakeKeepClient(arv)
957 arv.ApiToken = "abc123"
958 localRoots := make(map[string]string)
959 writableLocalRoots := make(map[string]string)
961 ks1 := RunSomeFakeKeepServers(st, 1)
962 ks2 := RunSomeFakeKeepServers(fh, 4)
964 for i, k := range ks1 {
965 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
966 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
967 defer k.listener.Close()
969 for i, k := range ks2 {
970 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
971 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
972 defer k.listener.Close()
975 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
978 // This test works only if one of the failing services is
979 // attempted before the succeeding service. Otherwise,
980 // <-fh.handled below will just hang! (Probe order depends on
981 // the choice of block content "waz" and the UUIDs of the fake
982 // servers, so we just tried different strings until we found
983 // an example that passes this Assert.)
984 c.Assert(NewRootSorter(localRoots, hash).GetSortedRoots()[0], Not(Equals), ks1[0].url)
986 r, n, url2, err := kc.Get(hash)
989 c.Check(err, Equals, nil)
990 c.Check(n, Equals, int64(3))
991 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks1[0].url, hash))
993 readContent, err2 := ioutil.ReadAll(r)
994 c.Check(err2, Equals, nil)
995 c.Check(readContent, DeepEquals, content)
998 func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
999 content := []byte("TestPutGetHead")
1001 arv, err := arvadosclient.MakeArvadosClient()
1003 kc, err := MakeKeepClient(arv)
1004 c.Assert(err, Equals, nil)
1006 hash := fmt.Sprintf("%x", md5.Sum(content))
1009 n, _, err := kc.Ask(hash)
1010 c.Check(err, Equals, BlockNotFound)
1011 c.Check(n, Equals, int64(0))
1014 hash2, replicas, err := kc.PutB(content)
1015 c.Check(hash2, Matches, fmt.Sprintf(`%s\+%d\b.*`, hash, len(content)))
1016 c.Check(replicas, Equals, 2)
1017 c.Check(err, Equals, nil)
1020 r, n, url2, err := kc.Get(hash)
1021 c.Check(err, Equals, nil)
1022 c.Check(n, Equals, int64(len(content)))
1023 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1025 readContent, err2 := ioutil.ReadAll(r)
1026 c.Check(err2, Equals, nil)
1027 c.Check(readContent, DeepEquals, content)
1030 n, url2, err := kc.Ask(hash)
1031 c.Check(err, Equals, nil)
1032 c.Check(n, Equals, int64(len(content)))
1033 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1036 loc, err := kc.LocalLocator(hash)
1037 c.Check(err, Equals, nil)
1038 c.Assert(len(loc) >= 32, Equals, true)
1039 c.Check(loc[:32], Equals, hash[:32])
1042 content := []byte("the perth county conspiracy")
1043 loc, err := kc.LocalLocator(fmt.Sprintf("%x+%d+Rzaaaa-abcde@12345", md5.Sum(content), len(content)))
1044 c.Check(loc, Equals, "")
1045 c.Check(err, ErrorMatches, `.*HEAD .*\+R.*`)
1046 c.Check(err, ErrorMatches, `.*HTTP 400.*`)
1050 type StubProxyHandler struct {
1054 func (h StubProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1055 resp.Header().Set("X-Keep-Replicas-Stored", "2")
1056 h.handled <- fmt.Sprintf("http://%s", req.Host)
1059 func (s *StandaloneSuite) TestPutProxy(c *C) {
1060 st := StubProxyHandler{make(chan string, 1)}
1062 arv, err := arvadosclient.MakeArvadosClient()
1064 kc, _ := MakeKeepClient(arv)
1066 kc.Want_replicas = 2
1067 arv.ApiToken = "abc123"
1068 localRoots := make(map[string]string)
1069 writableLocalRoots := make(map[string]string)
1071 ks1 := RunSomeFakeKeepServers(st, 1)
1073 for i, k := range ks1 {
1074 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1075 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1076 defer k.listener.Close()
1079 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1081 _, replicas, err := kc.PutB([]byte("foo"))
1084 c.Check(err, Equals, nil)
1085 c.Check(replicas, Equals, 2)
1088 func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
1089 st := StubProxyHandler{make(chan string, 1)}
1091 arv, err := arvadosclient.MakeArvadosClient()
1093 kc, _ := MakeKeepClient(arv)
1095 kc.Want_replicas = 3
1096 arv.ApiToken = "abc123"
1097 localRoots := make(map[string]string)
1098 writableLocalRoots := make(map[string]string)
1100 ks1 := RunSomeFakeKeepServers(st, 1)
1102 for i, k := range ks1 {
1103 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1104 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1105 defer k.listener.Close()
1107 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1109 _, replicas, err := kc.PutB([]byte("foo"))
1112 c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
1113 c.Check(replicas, Equals, 2)
1116 func (s *StandaloneSuite) TestMakeLocator(c *C) {
1117 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+3+Aabcde@12345678")
1118 c.Check(err, Equals, nil)
1119 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1120 c.Check(l.Size, Equals, 3)
1121 c.Check(l.Hints, DeepEquals, []string{"3", "Aabcde@12345678"})
1124 func (s *StandaloneSuite) TestMakeLocatorNoHints(c *C) {
1125 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce")
1126 c.Check(err, Equals, nil)
1127 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1128 c.Check(l.Size, Equals, -1)
1129 c.Check(l.Hints, DeepEquals, []string{})
1132 func (s *StandaloneSuite) TestMakeLocatorNoSizeHint(c *C) {
1133 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+Aabcde@12345678")
1134 c.Check(err, Equals, nil)
1135 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1136 c.Check(l.Size, Equals, -1)
1137 c.Check(l.Hints, DeepEquals, []string{"Aabcde@12345678"})
1140 func (s *StandaloneSuite) TestMakeLocatorPreservesUnrecognizedHints(c *C) {
1141 str := "91f372a266fe2bf2823cb8ec7fda31ce+3+Unknown+Kzzzzz+Afoobar"
1142 l, err := MakeLocator(str)
1143 c.Check(err, Equals, nil)
1144 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1145 c.Check(l.Size, Equals, 3)
1146 c.Check(l.Hints, DeepEquals, []string{"3", "Unknown", "Kzzzzz", "Afoobar"})
1147 c.Check(l.String(), Equals, str)
1150 func (s *StandaloneSuite) TestMakeLocatorInvalidInput(c *C) {
1151 _, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31c")
1152 c.Check(err, Equals, InvalidLocatorError)
1155 func (s *StandaloneSuite) TestPutBWant2ReplicasWithOnlyOneWritableLocalRoot(c *C) {
1156 hash := Md5String("foo")
1158 st := &StubPutHandler{
1161 expectAPIToken: "abc123",
1163 expectStorageClass: "",
1164 returnStorageClasses: "",
1165 handled: make(chan string, 5),
1168 arv, _ := arvadosclient.MakeArvadosClient()
1169 kc, _ := MakeKeepClient(arv)
1171 kc.Want_replicas = 2
1172 arv.ApiToken = "abc123"
1173 localRoots := make(map[string]string)
1174 writableLocalRoots := make(map[string]string)
1176 ks := RunSomeFakeKeepServers(st, 5)
1178 for i, k := range ks {
1179 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1181 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1183 defer k.listener.Close()
1186 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1188 _, replicas, err := kc.PutB([]byte("foo"))
1190 c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
1191 c.Check(replicas, Equals, 1)
1193 c.Check(<-st.handled, Equals, localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", 0)])
1196 func (s *StandaloneSuite) TestPutBWithNoWritableLocalRoots(c *C) {
1197 hash := Md5String("foo")
1199 st := &StubPutHandler{
1202 expectAPIToken: "abc123",
1204 expectStorageClass: "",
1205 returnStorageClasses: "",
1206 handled: make(chan string, 5),
1209 arv, _ := arvadosclient.MakeArvadosClient()
1210 kc, _ := MakeKeepClient(arv)
1212 kc.Want_replicas = 2
1213 arv.ApiToken = "abc123"
1214 localRoots := make(map[string]string)
1215 writableLocalRoots := make(map[string]string)
1217 ks := RunSomeFakeKeepServers(st, 5)
1219 for i, k := range ks {
1220 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1221 defer k.listener.Close()
1224 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1226 _, replicas, err := kc.PutB([]byte("foo"))
1228 c.Check(err, FitsTypeOf, InsufficientReplicasError(errors.New("")))
1229 c.Check(replicas, Equals, 0)
1232 type StubGetIndexHandler struct {
1235 expectAPIToken string
1240 func (h StubGetIndexHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1241 h.c.Check(req.URL.Path, Equals, h.expectPath)
1242 h.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", h.expectAPIToken))
1243 resp.WriteHeader(h.httpStatus)
1244 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(h.body)))
1248 func (s *StandaloneSuite) TestGetIndexWithNoPrefix(c *C) {
1249 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1251 st := StubGetIndexHandler{
1256 []byte(hash + "+3 1443559274\n\n")}
1258 ks := RunFakeKeepServer(st)
1259 defer ks.listener.Close()
1261 arv, err := arvadosclient.MakeArvadosClient()
1262 c.Assert(err, IsNil)
1263 kc, err := MakeKeepClient(arv)
1264 c.Assert(err, IsNil)
1265 arv.ApiToken = "abc123"
1266 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1268 r, err := kc.GetIndex("x", "")
1271 content, err2 := ioutil.ReadAll(r)
1272 c.Check(err2, Equals, nil)
1273 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1276 func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
1277 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1279 st := StubGetIndexHandler{
1281 "/index/" + hash[0:3],
1284 []byte(hash + "+3 1443559274\n\n")}
1286 ks := RunFakeKeepServer(st)
1287 defer ks.listener.Close()
1289 arv, err := arvadosclient.MakeArvadosClient()
1291 kc, _ := MakeKeepClient(arv)
1292 arv.ApiToken = "abc123"
1293 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1295 r, err := kc.GetIndex("x", hash[0:3])
1296 c.Assert(err, Equals, nil)
1298 content, err2 := ioutil.ReadAll(r)
1299 c.Check(err2, Equals, nil)
1300 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1303 func (s *StandaloneSuite) TestGetIndexIncomplete(c *C) {
1304 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1306 st := StubGetIndexHandler{
1308 "/index/" + hash[0:3],
1313 ks := RunFakeKeepServer(st)
1314 defer ks.listener.Close()
1316 arv, err := arvadosclient.MakeArvadosClient()
1318 kc, _ := MakeKeepClient(arv)
1319 arv.ApiToken = "abc123"
1320 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1322 _, err = kc.GetIndex("x", hash[0:3])
1323 c.Check(err, Equals, ErrIncompleteIndex)
1326 func (s *StandaloneSuite) TestGetIndexWithNoSuchServer(c *C) {
1327 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1329 st := StubGetIndexHandler{
1331 "/index/" + hash[0:3],
1336 ks := RunFakeKeepServer(st)
1337 defer ks.listener.Close()
1339 arv, err := arvadosclient.MakeArvadosClient()
1341 kc, _ := MakeKeepClient(arv)
1342 arv.ApiToken = "abc123"
1343 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1345 _, err = kc.GetIndex("y", hash[0:3])
1346 c.Check(err, Equals, ErrNoSuchKeepServer)
1349 func (s *StandaloneSuite) TestGetIndexWithNoSuchPrefix(c *C) {
1350 st := StubGetIndexHandler{
1357 ks := RunFakeKeepServer(st)
1358 defer ks.listener.Close()
1360 arv, err := arvadosclient.MakeArvadosClient()
1362 kc, _ := MakeKeepClient(arv)
1363 arv.ApiToken = "abc123"
1364 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1366 r, err := kc.GetIndex("x", "abcd")
1367 c.Check(err, Equals, nil)
1369 content, err2 := ioutil.ReadAll(r)
1370 c.Check(err2, Equals, nil)
1371 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1374 func (s *StandaloneSuite) TestPutBRetry(c *C) {
1375 st := &FailThenSucceedHandler{
1376 handled: make(chan string, 1),
1377 successhandler: &StubPutHandler{
1379 expectPath: Md5String("foo"),
1380 expectAPIToken: "abc123",
1382 expectStorageClass: "",
1383 returnStorageClasses: "",
1384 handled: make(chan string, 5),
1388 arv, _ := arvadosclient.MakeArvadosClient()
1389 kc, _ := MakeKeepClient(arv)
1391 kc.Want_replicas = 2
1392 arv.ApiToken = "abc123"
1393 localRoots := make(map[string]string)
1394 writableLocalRoots := make(map[string]string)
1396 ks := RunSomeFakeKeepServers(st, 2)
1398 for i, k := range ks {
1399 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1400 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1401 defer k.listener.Close()
1404 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1406 hash, replicas, err := kc.PutB([]byte("foo"))
1408 c.Check(err, Equals, nil)
1409 c.Check(hash, Equals, "")
1410 c.Check(replicas, Equals, 2)
1413 func (s *ServerRequiredSuite) TestMakeKeepClientWithNonDiskTypeService(c *C) {
1414 arv, err := arvadosclient.MakeArvadosClient()
1415 c.Assert(err, Equals, nil)
1417 // Add an additional "testblobstore" keepservice
1418 blobKeepService := make(arvadosclient.Dict)
1419 err = arv.Create("keep_services",
1420 arvadosclient.Dict{"keep_service": arvadosclient.Dict{
1421 "service_host": "localhost",
1422 "service_port": "21321",
1423 "service_type": "testblobstore"}},
1425 c.Assert(err, Equals, nil)
1426 defer func() { arv.Delete("keep_services", blobKeepService["uuid"].(string), nil, nil) }()
1427 RefreshServiceDiscovery()
1429 // Make a keepclient and ensure that the testblobstore is included
1430 kc, err := MakeKeepClient(arv)
1431 c.Assert(err, Equals, nil)
1433 // verify kc.LocalRoots
1434 c.Check(len(kc.LocalRoots()), Equals, 3)
1435 for _, root := range kc.LocalRoots() {
1436 c.Check(root, Matches, "http://localhost:\\d+")
1438 c.Assert(kc.LocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1440 // verify kc.GatewayRoots
1441 c.Check(len(kc.GatewayRoots()), Equals, 3)
1442 for _, root := range kc.GatewayRoots() {
1443 c.Check(root, Matches, "http://localhost:\\d+")
1445 c.Assert(kc.GatewayRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1447 // verify kc.WritableLocalRoots
1448 c.Check(len(kc.WritableLocalRoots()), Equals, 3)
1449 for _, root := range kc.WritableLocalRoots() {
1450 c.Check(root, Matches, "http://localhost:\\d+")
1452 c.Assert(kc.WritableLocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1454 c.Assert(kc.replicasPerService, Equals, 0)
1455 c.Assert(kc.foundNonDiskSvc, Equals, true)
1456 c.Assert(kc.httpClient().(*http.Client).Timeout, Equals, 300*time.Second)