1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
23 "git.arvados.org/arvados.git/sdk/go/arvados"
24 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
25 "git.arvados.org/arvados.git/sdk/go/arvadostest"
27 check "gopkg.in/check.v1"
30 // Gocheck boilerplate
31 func Test(t *testing.T) {
35 // Gocheck boilerplate
36 var _ = Suite(&ServerRequiredSuite{})
37 var _ = Suite(&StandaloneSuite{})
39 // Tests that require the Keep server running
40 type ServerRequiredSuite struct{}
43 type StandaloneSuite struct{}
45 func (s *StandaloneSuite) SetUpTest(c *C) {
46 RefreshServiceDiscovery()
49 func pythonDir() string {
51 return fmt.Sprintf("%s/../../python/tests", cwd)
54 func (s *ServerRequiredSuite) SetUpSuite(c *C) {
55 arvadostest.StartAPI()
56 arvadostest.StartKeep(2, false)
59 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
60 arvadostest.StopKeep(2)
64 func (s *ServerRequiredSuite) SetUpTest(c *C) {
65 RefreshServiceDiscovery()
68 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
69 arv, err := arvadosclient.MakeArvadosClient()
70 c.Assert(err, Equals, nil)
72 kc, err := MakeKeepClient(arv)
74 c.Assert(err, Equals, nil)
75 c.Check(len(kc.LocalRoots()), Equals, 2)
76 for _, root := range kc.LocalRoots() {
77 c.Check(root, Matches, "http://localhost:\\d+")
81 func (s *ServerRequiredSuite) TestDefaultReplications(c *C) {
82 arv, err := arvadosclient.MakeArvadosClient()
83 c.Assert(err, Equals, nil)
85 kc, err := MakeKeepClient(arv)
87 c.Assert(kc.Want_replicas, Equals, 2)
89 arv.DiscoveryDoc["defaultCollectionReplication"] = 3.0
90 kc, err = MakeKeepClient(arv)
92 c.Assert(kc.Want_replicas, Equals, 3)
94 arv.DiscoveryDoc["defaultCollectionReplication"] = 1.0
95 kc, err = MakeKeepClient(arv)
97 c.Assert(kc.Want_replicas, Equals, 1)
100 type StubPutHandler struct {
103 expectAPIToken string
105 expectStorageClass string
106 returnStorageClasses string
108 requests []*http.Request
112 func (sph *StubPutHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
114 sph.requests = append(sph.requests, req)
116 sph.c.Check(req.URL.Path, Equals, "/"+sph.expectPath)
117 sph.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sph.expectAPIToken))
118 if sph.expectStorageClass != "*" {
119 sph.c.Check(req.Header.Get("X-Keep-Storage-Classes"), Equals, sph.expectStorageClass)
121 body, err := ioutil.ReadAll(req.Body)
122 sph.c.Check(err, Equals, nil)
123 sph.c.Check(body, DeepEquals, []byte(sph.expectBody))
124 resp.Header().Set("X-Keep-Replicas-Stored", "1")
125 if sph.returnStorageClasses != "" {
126 resp.Header().Set("X-Keep-Storage-Classes-Confirmed", sph.returnStorageClasses)
128 resp.WriteHeader(200)
129 sph.handled <- fmt.Sprintf("http://%s", req.Host)
132 func RunFakeKeepServer(st http.Handler) (ks KeepServer) {
134 // If we don't explicitly bind it to localhost, ks.listener.Addr() will
135 // bind to 0.0.0.0 or [::] which is not a valid address for Dial()
136 ks.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0})
138 panic(fmt.Sprintf("Could not listen on any port"))
140 ks.url = fmt.Sprintf("http://%s", ks.listener.Addr().String())
141 go http.Serve(ks.listener, st)
145 func UploadToStubHelper(c *C, st http.Handler, f func(*KeepClient, string,
146 io.ReadCloser, io.WriteCloser, chan uploadStatus)) {
148 ks := RunFakeKeepServer(st)
149 defer ks.listener.Close()
151 arv, _ := arvadosclient.MakeArvadosClient()
152 arv.ApiToken = "abc123"
154 kc, _ := MakeKeepClient(arv)
156 reader, writer := io.Pipe()
157 uploadStatusChan := make(chan uploadStatus)
159 f(kc, ks.url, reader, writer, uploadStatusChan)
162 func (s *StandaloneSuite) TestUploadToStubKeepServer(c *C) {
163 log.Printf("TestUploadToStubKeepServer")
165 st := &StubPutHandler{
167 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
168 expectAPIToken: "abc123",
170 expectStorageClass: "",
171 returnStorageClasses: "default=1",
172 handled: make(chan string),
175 UploadToStubHelper(c, st,
176 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
177 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
179 writer.Write([]byte("foo"))
183 status := <-uploadStatusChan
184 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
188 func (s *StandaloneSuite) TestUploadToStubKeepServerBufferReader(c *C) {
189 st := &StubPutHandler{
191 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
192 expectAPIToken: "abc123",
194 expectStorageClass: "",
195 returnStorageClasses: "default=1",
196 handled: make(chan string),
199 UploadToStubHelper(c, st,
200 func(kc *KeepClient, url string, _ io.ReadCloser, _ io.WriteCloser, uploadStatusChan chan uploadStatus) {
201 go kc.uploadToKeepServer(url, st.expectPath, nil, bytes.NewBuffer([]byte("foo")), uploadStatusChan, 3, kc.getRequestID())
205 status := <-uploadStatusChan
206 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
210 func (s *StandaloneSuite) TestUploadWithStorageClasses(c *C) {
211 for _, trial := range []struct {
213 expectMap map[string]int
216 {"foo=1", map[string]int{"foo": 1}},
217 {" foo=1 , bar=2 ", map[string]int{"foo": 1, "bar": 2}},
221 st := &StubPutHandler{
223 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
224 expectAPIToken: "abc123",
226 expectStorageClass: "",
227 returnStorageClasses: trial.respHeader,
228 handled: make(chan string),
231 UploadToStubHelper(c, st,
232 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
233 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
235 writer.Write([]byte("foo"))
239 status := <-uploadStatusChan
240 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, trial.expectMap, ""})
245 func (s *StandaloneSuite) TestPutWithStorageClasses(c *C) {
247 for _, trial := range []struct {
249 clientClasses []string
250 putClasses []string // putClasses takes precedence over clientClasses
255 {1, []string{"class1"}, nil, 1, 1, true},
256 {2, []string{"class1"}, nil, 1, 2, true},
257 {3, []string{"class1"}, nil, 2, 3, true},
258 {1, []string{"class1", "class2"}, nil, 1, 1, true},
259 {3, nil, []string{"class1"}, 2, 3, true},
260 {1, nil, []string{"class1", "class2"}, 1, 1, true},
261 {1, []string{"class404"}, []string{"class1", "class2"}, 1, 1, true},
262 {1, []string{"class1"}, []string{"class404", "class2"}, nServers, nServers, false},
263 {nServers*2 + 1, []string{"class1"}, nil, nServers, nServers, false},
264 {1, []string{"class404"}, nil, nServers, nServers, false},
265 {1, []string{"class1", "class404"}, nil, nServers, nServers, false},
266 {1, nil, []string{"class1", "class404"}, nServers, nServers, false},
269 st := &StubPutHandler{
271 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
272 expectAPIToken: "abc123",
274 expectStorageClass: "*",
275 returnStorageClasses: "class1=2, class2=2",
276 handled: make(chan string, 100),
278 ks := RunSomeFakeKeepServers(st, nServers)
279 arv, _ := arvadosclient.MakeArvadosClient()
280 kc, _ := MakeKeepClient(arv)
281 kc.Want_replicas = trial.replicas
282 kc.StorageClasses = trial.clientClasses
283 arv.ApiToken = "abc123"
284 localRoots := make(map[string]string)
285 writableLocalRoots := make(map[string]string)
286 for i, k := range ks {
287 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
288 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
289 defer k.listener.Close()
291 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
293 _, err := kc.BlockWrite(context.Background(), arvados.BlockWriteOptions{
295 StorageClasses: trial.putClasses,
298 c.Check(err, check.IsNil)
300 c.Check(err, check.NotNil)
302 c.Check(len(st.handled) >= trial.minRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.minRequests==%d", len(st.handled), trial.minRequests))
303 c.Check(len(st.handled) <= trial.maxRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.maxRequests==%d", len(st.handled), trial.maxRequests))
304 if !trial.success && trial.replicas == 1 && c.Check(len(st.requests) >= 2, check.Equals, true) {
305 // Max concurrency should be 1. First request
306 // should have succeeded for class1. Second
307 // request should only ask for class404.
308 c.Check(st.requests[1].Header.Get("X-Keep-Storage-Classes"), check.Equals, "class404")
313 type FailHandler struct {
317 func (fh FailHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
318 resp.WriteHeader(500)
319 fh.handled <- fmt.Sprintf("http://%s", req.Host)
322 type FailThenSucceedHandler struct {
325 successhandler http.Handler
329 func (fh *FailThenSucceedHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
330 fh.reqIDs = append(fh.reqIDs, req.Header.Get("X-Request-Id"))
332 resp.WriteHeader(500)
334 fh.handled <- fmt.Sprintf("http://%s", req.Host)
336 fh.successhandler.ServeHTTP(resp, req)
340 type Error404Handler struct {
344 func (fh Error404Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
345 resp.WriteHeader(404)
346 fh.handled <- fmt.Sprintf("http://%s", req.Host)
349 func (s *StandaloneSuite) TestFailedUploadToStubKeepServer(c *C) {
353 hash := "acbd18db4cc2f85cedef654fccc4a4d8"
355 UploadToStubHelper(c, st,
356 func(kc *KeepClient, url string, reader io.ReadCloser,
357 writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
359 go kc.uploadToKeepServer(url, hash, nil, reader, uploadStatusChan, 3, kc.getRequestID())
361 writer.Write([]byte("foo"))
366 status := <-uploadStatusChan
367 c.Check(status.url, Equals, fmt.Sprintf("%s/%s", url, hash))
368 c.Check(status.statusCode, Equals, 500)
372 type KeepServer struct {
373 listener net.Listener
377 func RunSomeFakeKeepServers(st http.Handler, n int) (ks []KeepServer) {
378 ks = make([]KeepServer, n)
380 for i := 0; i < n; i++ {
381 ks[i] = RunFakeKeepServer(st)
387 func (s *StandaloneSuite) TestPutB(c *C) {
388 hash := Md5String("foo")
390 st := &StubPutHandler{
393 expectAPIToken: "abc123",
395 expectStorageClass: "",
396 returnStorageClasses: "",
397 handled: make(chan string, 5),
400 arv, _ := arvadosclient.MakeArvadosClient()
401 kc, _ := MakeKeepClient(arv)
404 arv.ApiToken = "abc123"
405 localRoots := make(map[string]string)
406 writableLocalRoots := make(map[string]string)
408 ks := RunSomeFakeKeepServers(st, 5)
410 for i, k := range ks {
411 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
412 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
413 defer k.listener.Close()
416 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
418 kc.PutB([]byte("foo"))
420 shuff := NewRootSorter(
421 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
425 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
426 (s1 == shuff[1] && s2 == shuff[0]),
431 func (s *StandaloneSuite) TestPutHR(c *C) {
432 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
434 st := &StubPutHandler{
437 expectAPIToken: "abc123",
439 expectStorageClass: "",
440 returnStorageClasses: "",
441 handled: make(chan string, 5),
444 arv, _ := arvadosclient.MakeArvadosClient()
445 kc, _ := MakeKeepClient(arv)
448 arv.ApiToken = "abc123"
449 localRoots := make(map[string]string)
450 writableLocalRoots := make(map[string]string)
452 ks := RunSomeFakeKeepServers(st, 5)
454 for i, k := range ks {
455 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
456 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
457 defer k.listener.Close()
460 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
462 reader, writer := io.Pipe()
465 writer.Write([]byte("foo"))
469 kc.PutHR(hash, reader, 3)
471 shuff := NewRootSorter(kc.LocalRoots(), hash).GetSortedRoots()
476 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
477 (s1 == shuff[1] && s2 == shuff[0]),
482 func (s *StandaloneSuite) TestPutWithFail(c *C) {
483 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
485 st := &StubPutHandler{
488 expectAPIToken: "abc123",
490 expectStorageClass: "",
491 returnStorageClasses: "",
492 handled: make(chan string, 4),
496 make(chan string, 1)}
498 arv, err := arvadosclient.MakeArvadosClient()
500 kc, _ := MakeKeepClient(arv)
503 arv.ApiToken = "abc123"
504 localRoots := make(map[string]string)
505 writableLocalRoots := make(map[string]string)
507 ks1 := RunSomeFakeKeepServers(st, 4)
508 ks2 := RunSomeFakeKeepServers(fh, 1)
510 for i, k := range ks1 {
511 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
512 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
513 defer k.listener.Close()
515 for i, k := range ks2 {
516 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
517 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
518 defer k.listener.Close()
521 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
523 shuff := NewRootSorter(
524 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
527 phash, replicas, err := kc.PutB([]byte("foo"))
531 c.Check(err, Equals, nil)
532 c.Check(phash, Equals, "")
533 c.Check(replicas, Equals, 2)
538 c.Check((s1 == shuff[1] && s2 == shuff[2]) ||
539 (s1 == shuff[2] && s2 == shuff[1]),
544 func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
545 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
547 st := &StubPutHandler{
550 expectAPIToken: "abc123",
552 expectStorageClass: "",
553 returnStorageClasses: "",
554 handled: make(chan string, 1),
558 make(chan string, 4)}
560 arv, err := arvadosclient.MakeArvadosClient()
562 kc, _ := MakeKeepClient(arv)
566 arv.ApiToken = "abc123"
567 localRoots := make(map[string]string)
568 writableLocalRoots := make(map[string]string)
570 ks1 := RunSomeFakeKeepServers(st, 1)
571 ks2 := RunSomeFakeKeepServers(fh, 4)
573 for i, k := range ks1 {
574 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
575 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
576 defer k.listener.Close()
578 for i, k := range ks2 {
579 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
580 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
581 defer k.listener.Close()
584 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
586 _, replicas, err := kc.PutB([]byte("foo"))
588 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
589 c.Check(replicas, Equals, 1)
590 c.Check(<-st.handled, Equals, ks1[0].url)
593 type StubGetHandler struct {
596 expectAPIToken string
601 func (sgh StubGetHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
602 sgh.c.Check(req.URL.Path, Equals, "/"+sgh.expectPath)
603 sgh.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sgh.expectAPIToken))
604 resp.WriteHeader(sgh.httpStatus)
605 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(sgh.body)))
609 func (s *StandaloneSuite) TestGet(c *C) {
610 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
612 st := StubGetHandler{
619 ks := RunFakeKeepServer(st)
620 defer ks.listener.Close()
622 arv, err := arvadosclient.MakeArvadosClient()
624 kc, _ := MakeKeepClient(arv)
625 arv.ApiToken = "abc123"
626 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
628 r, n, url2, err := kc.Get(hash)
630 c.Check(err, Equals, nil)
631 c.Check(n, Equals, int64(3))
632 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
634 content, err2 := ioutil.ReadAll(r)
635 c.Check(err2, Equals, nil)
636 c.Check(content, DeepEquals, []byte("foo"))
639 func (s *StandaloneSuite) TestGet404(c *C) {
640 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
642 st := Error404Handler{make(chan string, 1)}
644 ks := RunFakeKeepServer(st)
645 defer ks.listener.Close()
647 arv, err := arvadosclient.MakeArvadosClient()
649 kc, _ := MakeKeepClient(arv)
650 arv.ApiToken = "abc123"
651 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
653 r, n, url2, err := kc.Get(hash)
654 c.Check(err, Equals, BlockNotFound)
655 c.Check(n, Equals, int64(0))
656 c.Check(url2, Equals, "")
657 c.Check(r, Equals, nil)
660 func (s *StandaloneSuite) TestGetEmptyBlock(c *C) {
661 st := Error404Handler{make(chan string, 1)}
663 ks := RunFakeKeepServer(st)
664 defer ks.listener.Close()
666 arv, err := arvadosclient.MakeArvadosClient()
668 kc, _ := MakeKeepClient(arv)
669 arv.ApiToken = "abc123"
670 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
672 r, n, url2, err := kc.Get("d41d8cd98f00b204e9800998ecf8427e+0")
674 c.Check(n, Equals, int64(0))
675 c.Check(url2, Equals, "")
677 buf, err := ioutil.ReadAll(r)
679 c.Check(buf, DeepEquals, []byte{})
682 func (s *StandaloneSuite) TestGetFail(c *C) {
683 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
685 st := FailHandler{make(chan string, 1)}
687 ks := RunFakeKeepServer(st)
688 defer ks.listener.Close()
690 arv, err := arvadosclient.MakeArvadosClient()
692 kc, _ := MakeKeepClient(arv)
693 arv.ApiToken = "abc123"
694 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
697 r, n, url2, err := kc.Get(hash)
698 errNotFound, _ := err.(*ErrNotFound)
699 c.Check(errNotFound, NotNil)
700 c.Check(strings.Contains(errNotFound.Error(), "HTTP 500"), Equals, true)
701 c.Check(errNotFound.Temporary(), Equals, true)
702 c.Check(n, Equals, int64(0))
703 c.Check(url2, Equals, "")
704 c.Check(r, Equals, nil)
707 func (s *StandaloneSuite) TestGetFailRetry(c *C) {
708 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
710 st := &FailThenSucceedHandler{
711 handled: make(chan string, 1),
712 successhandler: StubGetHandler{
719 ks := RunFakeKeepServer(st)
720 defer ks.listener.Close()
722 arv, err := arvadosclient.MakeArvadosClient()
724 kc, _ := MakeKeepClient(arv)
725 arv.ApiToken = "abc123"
726 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
728 r, n, url2, err := kc.Get(hash)
730 c.Check(err, Equals, nil)
731 c.Check(n, Equals, int64(3))
732 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
734 content, err2 := ioutil.ReadAll(r)
735 c.Check(err2, Equals, nil)
736 c.Check(content, DeepEquals, []byte("foo"))
738 c.Logf("%q", st.reqIDs)
739 c.Assert(len(st.reqIDs) > 1, Equals, true)
740 for _, reqid := range st.reqIDs {
741 c.Check(reqid, Not(Equals), "")
742 c.Check(reqid, Equals, st.reqIDs[0])
746 func (s *StandaloneSuite) TestGetNetError(c *C) {
747 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
749 arv, err := arvadosclient.MakeArvadosClient()
751 kc, _ := MakeKeepClient(arv)
752 arv.ApiToken = "abc123"
753 kc.SetServiceRoots(map[string]string{"x": "http://localhost:62222"}, nil, nil)
755 r, n, url2, err := kc.Get(hash)
756 errNotFound, _ := err.(*ErrNotFound)
757 c.Check(errNotFound, NotNil)
758 c.Check(strings.Contains(errNotFound.Error(), "connection refused"), Equals, true)
759 c.Check(errNotFound.Temporary(), Equals, true)
760 c.Check(n, Equals, int64(0))
761 c.Check(url2, Equals, "")
762 c.Check(r, Equals, nil)
765 func (s *StandaloneSuite) TestGetWithServiceHint(c *C) {
766 uuid := "zzzzz-bi6l4-123451234512345"
767 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
769 // This one shouldn't be used:
770 ks0 := RunFakeKeepServer(StubGetHandler{
776 defer ks0.listener.Close()
777 // This one should be used:
778 ks := RunFakeKeepServer(StubGetHandler{
784 defer ks.listener.Close()
786 arv, err := arvadosclient.MakeArvadosClient()
788 kc, _ := MakeKeepClient(arv)
789 arv.ApiToken = "abc123"
791 map[string]string{"x": ks0.url},
793 map[string]string{uuid: ks.url})
795 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
797 c.Check(err, Equals, nil)
798 c.Check(n, Equals, int64(3))
799 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
801 content, err := ioutil.ReadAll(r)
802 c.Check(err, Equals, nil)
803 c.Check(content, DeepEquals, []byte("foo"))
806 // Use a service hint to fetch from a local disk service, overriding
807 // rendezvous probe order.
808 func (s *StandaloneSuite) TestGetWithLocalServiceHint(c *C) {
809 uuid := "zzzzz-bi6l4-zzzzzzzzzzzzzzz"
810 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
812 // This one shouldn't be used, although it appears first in
813 // rendezvous probe order:
814 ks0 := RunFakeKeepServer(StubGetHandler{
820 defer ks0.listener.Close()
821 // This one should be used:
822 ks := RunFakeKeepServer(StubGetHandler{
828 defer ks.listener.Close()
830 arv, err := arvadosclient.MakeArvadosClient()
832 kc, _ := MakeKeepClient(arv)
833 arv.ApiToken = "abc123"
836 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
837 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
838 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
842 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
843 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
844 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
848 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
850 c.Check(err, Equals, nil)
851 c.Check(n, Equals, int64(3))
852 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
854 content, err := ioutil.ReadAll(r)
855 c.Check(err, Equals, nil)
856 c.Check(content, DeepEquals, []byte("foo"))
859 func (s *StandaloneSuite) TestGetWithServiceHintFailoverToLocals(c *C) {
860 uuid := "zzzzz-bi6l4-123451234512345"
861 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
863 ksLocal := RunFakeKeepServer(StubGetHandler{
869 defer ksLocal.listener.Close()
870 ksGateway := RunFakeKeepServer(StubGetHandler{
874 http.StatusInternalServerError,
876 defer ksGateway.listener.Close()
878 arv, err := arvadosclient.MakeArvadosClient()
880 kc, _ := MakeKeepClient(arv)
881 arv.ApiToken = "abc123"
883 map[string]string{"zzzzz-bi6l4-keepdisk0000000": ksLocal.url},
885 map[string]string{uuid: ksGateway.url})
887 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
888 c.Assert(err, Equals, nil)
890 c.Check(n, Equals, int64(3))
891 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ksLocal.url, hash+"+K@"+uuid))
893 content, err := ioutil.ReadAll(r)
894 c.Check(err, Equals, nil)
895 c.Check(content, DeepEquals, []byte("foo"))
898 type BarHandler struct {
902 func (h BarHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
903 resp.Write([]byte("bar"))
904 h.handled <- fmt.Sprintf("http://%s", req.Host)
907 func (s *StandaloneSuite) TestChecksum(c *C) {
908 foohash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
909 barhash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
911 st := BarHandler{make(chan string, 1)}
913 ks := RunFakeKeepServer(st)
914 defer ks.listener.Close()
916 arv, err := arvadosclient.MakeArvadosClient()
918 kc, _ := MakeKeepClient(arv)
919 arv.ApiToken = "abc123"
920 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
922 r, n, _, err := kc.Get(barhash)
924 _, err = ioutil.ReadAll(r)
925 c.Check(n, Equals, int64(3))
926 c.Check(err, Equals, nil)
930 r, n, _, err = kc.Get(foohash)
932 _, err = ioutil.ReadAll(r)
933 c.Check(n, Equals, int64(3))
934 c.Check(err, Equals, BadChecksum)
939 func (s *StandaloneSuite) TestGetWithFailures(c *C) {
940 content := []byte("waz")
941 hash := fmt.Sprintf("%x", md5.Sum(content))
943 fh := Error404Handler{
944 make(chan string, 4)}
946 st := StubGetHandler{
953 arv, err := arvadosclient.MakeArvadosClient()
955 kc, _ := MakeKeepClient(arv)
956 arv.ApiToken = "abc123"
957 localRoots := make(map[string]string)
958 writableLocalRoots := make(map[string]string)
960 ks1 := RunSomeFakeKeepServers(st, 1)
961 ks2 := RunSomeFakeKeepServers(fh, 4)
963 for i, k := range ks1 {
964 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
965 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
966 defer k.listener.Close()
968 for i, k := range ks2 {
969 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
970 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
971 defer k.listener.Close()
974 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
977 // This test works only if one of the failing services is
978 // attempted before the succeeding service. Otherwise,
979 // <-fh.handled below will just hang! (Probe order depends on
980 // the choice of block content "waz" and the UUIDs of the fake
981 // servers, so we just tried different strings until we found
982 // an example that passes this Assert.)
983 c.Assert(NewRootSorter(localRoots, hash).GetSortedRoots()[0], Not(Equals), ks1[0].url)
985 r, n, url2, err := kc.Get(hash)
988 c.Check(err, Equals, nil)
989 c.Check(n, Equals, int64(3))
990 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks1[0].url, hash))
992 readContent, err2 := ioutil.ReadAll(r)
993 c.Check(err2, Equals, nil)
994 c.Check(readContent, DeepEquals, content)
997 func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
998 content := []byte("TestPutGetHead")
1000 arv, err := arvadosclient.MakeArvadosClient()
1002 kc, err := MakeKeepClient(arv)
1003 c.Assert(err, Equals, nil)
1005 hash := fmt.Sprintf("%x", md5.Sum(content))
1008 n, _, err := kc.Ask(hash)
1009 c.Check(err, Equals, BlockNotFound)
1010 c.Check(n, Equals, int64(0))
1013 hash2, replicas, err := kc.PutB(content)
1014 c.Check(hash2, Matches, fmt.Sprintf(`%s\+%d\b.*`, hash, len(content)))
1015 c.Check(replicas, Equals, 2)
1016 c.Check(err, Equals, nil)
1019 r, n, url2, err := kc.Get(hash)
1020 c.Check(err, Equals, nil)
1021 c.Check(n, Equals, int64(len(content)))
1022 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1024 readContent, err2 := ioutil.ReadAll(r)
1025 c.Check(err2, Equals, nil)
1026 c.Check(readContent, DeepEquals, content)
1029 n, url2, err := kc.Ask(hash)
1030 c.Check(err, Equals, nil)
1031 c.Check(n, Equals, int64(len(content)))
1032 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1035 loc, err := kc.LocalLocator(hash)
1036 c.Check(err, Equals, nil)
1037 c.Assert(len(loc) >= 32, Equals, true)
1038 c.Check(loc[:32], Equals, hash[:32])
1041 content := []byte("the perth county conspiracy")
1042 loc, err := kc.LocalLocator(fmt.Sprintf("%x+%d+Rzaaaa-abcde@12345", md5.Sum(content), len(content)))
1043 c.Check(loc, Equals, "")
1044 c.Check(err, ErrorMatches, `.*HEAD .*\+R.*`)
1045 c.Check(err, ErrorMatches, `.*HTTP 400.*`)
1049 type StubProxyHandler struct {
1053 func (h StubProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1054 resp.Header().Set("X-Keep-Replicas-Stored", "2")
1055 h.handled <- fmt.Sprintf("http://%s", req.Host)
1058 func (s *StandaloneSuite) TestPutProxy(c *C) {
1059 st := StubProxyHandler{make(chan string, 1)}
1061 arv, err := arvadosclient.MakeArvadosClient()
1063 kc, _ := MakeKeepClient(arv)
1065 kc.Want_replicas = 2
1066 arv.ApiToken = "abc123"
1067 localRoots := make(map[string]string)
1068 writableLocalRoots := make(map[string]string)
1070 ks1 := RunSomeFakeKeepServers(st, 1)
1072 for i, k := range ks1 {
1073 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1074 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1075 defer k.listener.Close()
1078 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1080 _, replicas, err := kc.PutB([]byte("foo"))
1083 c.Check(err, Equals, nil)
1084 c.Check(replicas, Equals, 2)
1087 func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
1088 st := StubProxyHandler{make(chan string, 1)}
1090 arv, err := arvadosclient.MakeArvadosClient()
1092 kc, _ := MakeKeepClient(arv)
1094 kc.Want_replicas = 3
1095 arv.ApiToken = "abc123"
1096 localRoots := make(map[string]string)
1097 writableLocalRoots := make(map[string]string)
1099 ks1 := RunSomeFakeKeepServers(st, 1)
1101 for i, k := range ks1 {
1102 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1103 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1104 defer k.listener.Close()
1106 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1108 _, replicas, err := kc.PutB([]byte("foo"))
1111 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1112 c.Check(replicas, Equals, 2)
1115 func (s *StandaloneSuite) TestMakeLocator(c *C) {
1116 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+3+Aabcde@12345678")
1117 c.Check(err, Equals, nil)
1118 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1119 c.Check(l.Size, Equals, 3)
1120 c.Check(l.Hints, DeepEquals, []string{"3", "Aabcde@12345678"})
1123 func (s *StandaloneSuite) TestMakeLocatorNoHints(c *C) {
1124 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce")
1125 c.Check(err, Equals, nil)
1126 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1127 c.Check(l.Size, Equals, -1)
1128 c.Check(l.Hints, DeepEquals, []string{})
1131 func (s *StandaloneSuite) TestMakeLocatorNoSizeHint(c *C) {
1132 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+Aabcde@12345678")
1133 c.Check(err, Equals, nil)
1134 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1135 c.Check(l.Size, Equals, -1)
1136 c.Check(l.Hints, DeepEquals, []string{"Aabcde@12345678"})
1139 func (s *StandaloneSuite) TestMakeLocatorPreservesUnrecognizedHints(c *C) {
1140 str := "91f372a266fe2bf2823cb8ec7fda31ce+3+Unknown+Kzzzzz+Afoobar"
1141 l, err := MakeLocator(str)
1142 c.Check(err, Equals, nil)
1143 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1144 c.Check(l.Size, Equals, 3)
1145 c.Check(l.Hints, DeepEquals, []string{"3", "Unknown", "Kzzzzz", "Afoobar"})
1146 c.Check(l.String(), Equals, str)
1149 func (s *StandaloneSuite) TestMakeLocatorInvalidInput(c *C) {
1150 _, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31c")
1151 c.Check(err, Equals, InvalidLocatorError)
1154 func (s *StandaloneSuite) TestPutBWant2ReplicasWithOnlyOneWritableLocalRoot(c *C) {
1155 hash := Md5String("foo")
1157 st := &StubPutHandler{
1160 expectAPIToken: "abc123",
1162 expectStorageClass: "",
1163 returnStorageClasses: "",
1164 handled: make(chan string, 5),
1167 arv, _ := arvadosclient.MakeArvadosClient()
1168 kc, _ := MakeKeepClient(arv)
1170 kc.Want_replicas = 2
1171 arv.ApiToken = "abc123"
1172 localRoots := make(map[string]string)
1173 writableLocalRoots := make(map[string]string)
1175 ks := RunSomeFakeKeepServers(st, 5)
1177 for i, k := range ks {
1178 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1180 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1182 defer k.listener.Close()
1185 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1187 _, replicas, err := kc.PutB([]byte("foo"))
1189 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1190 c.Check(replicas, Equals, 1)
1192 c.Check(<-st.handled, Equals, localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", 0)])
1195 func (s *StandaloneSuite) TestPutBWithNoWritableLocalRoots(c *C) {
1196 hash := Md5String("foo")
1198 st := &StubPutHandler{
1201 expectAPIToken: "abc123",
1203 expectStorageClass: "",
1204 returnStorageClasses: "",
1205 handled: make(chan string, 5),
1208 arv, _ := arvadosclient.MakeArvadosClient()
1209 kc, _ := MakeKeepClient(arv)
1211 kc.Want_replicas = 2
1212 arv.ApiToken = "abc123"
1213 localRoots := make(map[string]string)
1214 writableLocalRoots := make(map[string]string)
1216 ks := RunSomeFakeKeepServers(st, 5)
1218 for i, k := range ks {
1219 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1220 defer k.listener.Close()
1223 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1225 _, replicas, err := kc.PutB([]byte("foo"))
1227 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1228 c.Check(replicas, Equals, 0)
1231 type StubGetIndexHandler struct {
1234 expectAPIToken string
1239 func (h StubGetIndexHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1240 h.c.Check(req.URL.Path, Equals, h.expectPath)
1241 h.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", h.expectAPIToken))
1242 resp.WriteHeader(h.httpStatus)
1243 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(h.body)))
1247 func (s *StandaloneSuite) TestGetIndexWithNoPrefix(c *C) {
1248 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1250 st := StubGetIndexHandler{
1255 []byte(hash + "+3 1443559274\n\n")}
1257 ks := RunFakeKeepServer(st)
1258 defer ks.listener.Close()
1260 arv, err := arvadosclient.MakeArvadosClient()
1261 c.Assert(err, IsNil)
1262 kc, err := MakeKeepClient(arv)
1263 c.Assert(err, IsNil)
1264 arv.ApiToken = "abc123"
1265 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1267 r, err := kc.GetIndex("x", "")
1270 content, err2 := ioutil.ReadAll(r)
1271 c.Check(err2, Equals, nil)
1272 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1275 func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
1276 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1278 st := StubGetIndexHandler{
1280 "/index/" + hash[0:3],
1283 []byte(hash + "+3 1443559274\n\n")}
1285 ks := RunFakeKeepServer(st)
1286 defer ks.listener.Close()
1288 arv, err := arvadosclient.MakeArvadosClient()
1290 kc, _ := MakeKeepClient(arv)
1291 arv.ApiToken = "abc123"
1292 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1294 r, err := kc.GetIndex("x", hash[0:3])
1295 c.Assert(err, Equals, nil)
1297 content, err2 := ioutil.ReadAll(r)
1298 c.Check(err2, Equals, nil)
1299 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1302 func (s *StandaloneSuite) TestGetIndexIncomplete(c *C) {
1303 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1305 st := StubGetIndexHandler{
1307 "/index/" + hash[0:3],
1312 ks := RunFakeKeepServer(st)
1313 defer ks.listener.Close()
1315 arv, err := arvadosclient.MakeArvadosClient()
1317 kc, _ := MakeKeepClient(arv)
1318 arv.ApiToken = "abc123"
1319 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1321 _, err = kc.GetIndex("x", hash[0:3])
1322 c.Check(err, Equals, ErrIncompleteIndex)
1325 func (s *StandaloneSuite) TestGetIndexWithNoSuchServer(c *C) {
1326 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1328 st := StubGetIndexHandler{
1330 "/index/" + hash[0:3],
1335 ks := RunFakeKeepServer(st)
1336 defer ks.listener.Close()
1338 arv, err := arvadosclient.MakeArvadosClient()
1340 kc, _ := MakeKeepClient(arv)
1341 arv.ApiToken = "abc123"
1342 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1344 _, err = kc.GetIndex("y", hash[0:3])
1345 c.Check(err, Equals, ErrNoSuchKeepServer)
1348 func (s *StandaloneSuite) TestGetIndexWithNoSuchPrefix(c *C) {
1349 st := StubGetIndexHandler{
1356 ks := RunFakeKeepServer(st)
1357 defer ks.listener.Close()
1359 arv, err := arvadosclient.MakeArvadosClient()
1361 kc, _ := MakeKeepClient(arv)
1362 arv.ApiToken = "abc123"
1363 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1365 r, err := kc.GetIndex("x", "abcd")
1366 c.Check(err, Equals, nil)
1368 content, err2 := ioutil.ReadAll(r)
1369 c.Check(err2, Equals, nil)
1370 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1373 func (s *StandaloneSuite) TestPutBRetry(c *C) {
1374 st := &FailThenSucceedHandler{
1375 handled: make(chan string, 1),
1376 successhandler: &StubPutHandler{
1378 expectPath: Md5String("foo"),
1379 expectAPIToken: "abc123",
1381 expectStorageClass: "",
1382 returnStorageClasses: "",
1383 handled: make(chan string, 5),
1387 arv, _ := arvadosclient.MakeArvadosClient()
1388 kc, _ := MakeKeepClient(arv)
1390 kc.Want_replicas = 2
1391 arv.ApiToken = "abc123"
1392 localRoots := make(map[string]string)
1393 writableLocalRoots := make(map[string]string)
1395 ks := RunSomeFakeKeepServers(st, 2)
1397 for i, k := range ks {
1398 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1399 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1400 defer k.listener.Close()
1403 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1405 hash, replicas, err := kc.PutB([]byte("foo"))
1407 c.Check(err, Equals, nil)
1408 c.Check(hash, Equals, "")
1409 c.Check(replicas, Equals, 2)
1412 func (s *ServerRequiredSuite) TestMakeKeepClientWithNonDiskTypeService(c *C) {
1413 arv, err := arvadosclient.MakeArvadosClient()
1414 c.Assert(err, Equals, nil)
1416 // Add an additional "testblobstore" keepservice
1417 blobKeepService := make(arvadosclient.Dict)
1418 err = arv.Create("keep_services",
1419 arvadosclient.Dict{"keep_service": arvadosclient.Dict{
1420 "service_host": "localhost",
1421 "service_port": "21321",
1422 "service_type": "testblobstore"}},
1424 c.Assert(err, Equals, nil)
1425 defer func() { arv.Delete("keep_services", blobKeepService["uuid"].(string), nil, nil) }()
1426 RefreshServiceDiscovery()
1428 // Make a keepclient and ensure that the testblobstore is included
1429 kc, err := MakeKeepClient(arv)
1430 c.Assert(err, Equals, nil)
1432 // verify kc.LocalRoots
1433 c.Check(len(kc.LocalRoots()), Equals, 3)
1434 for _, root := range kc.LocalRoots() {
1435 c.Check(root, Matches, "http://localhost:\\d+")
1437 c.Assert(kc.LocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1439 // verify kc.GatewayRoots
1440 c.Check(len(kc.GatewayRoots()), Equals, 3)
1441 for _, root := range kc.GatewayRoots() {
1442 c.Check(root, Matches, "http://localhost:\\d+")
1444 c.Assert(kc.GatewayRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1446 // verify kc.WritableLocalRoots
1447 c.Check(len(kc.WritableLocalRoots()), Equals, 3)
1448 for _, root := range kc.WritableLocalRoots() {
1449 c.Check(root, Matches, "http://localhost:\\d+")
1451 c.Assert(kc.WritableLocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1453 c.Assert(kc.replicasPerService, Equals, 0)
1454 c.Assert(kc.foundNonDiskSvc, Equals, true)
1455 c.Assert(kc.httpClient().(*http.Client).Timeout, Equals, 300*time.Second)