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.StartKeep(2, false)
58 func (s *ServerRequiredSuite) TearDownSuite(c *C) {
59 arvadostest.StopKeep(2)
62 func (s *ServerRequiredSuite) SetUpTest(c *C) {
63 RefreshServiceDiscovery()
66 func (s *ServerRequiredSuite) TestMakeKeepClient(c *C) {
67 arv, err := arvadosclient.MakeArvadosClient()
68 c.Assert(err, Equals, nil)
70 kc, err := MakeKeepClient(arv)
72 c.Assert(err, Equals, nil)
73 c.Check(len(kc.LocalRoots()), Equals, 2)
74 for _, root := range kc.LocalRoots() {
75 c.Check(root, Matches, "http://localhost:\\d+")
79 func (s *ServerRequiredSuite) TestDefaultReplications(c *C) {
80 arv, err := arvadosclient.MakeArvadosClient()
81 c.Assert(err, Equals, nil)
83 kc, err := MakeKeepClient(arv)
85 c.Assert(kc.Want_replicas, Equals, 2)
87 arv.DiscoveryDoc["defaultCollectionReplication"] = 3.0
88 kc, err = MakeKeepClient(arv)
90 c.Assert(kc.Want_replicas, Equals, 3)
92 arv.DiscoveryDoc["defaultCollectionReplication"] = 1.0
93 kc, err = MakeKeepClient(arv)
95 c.Assert(kc.Want_replicas, Equals, 1)
98 type StubPutHandler struct {
101 expectAPIToken string
103 expectStorageClass string
104 returnStorageClasses string
106 requests []*http.Request
110 func (sph *StubPutHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
112 sph.requests = append(sph.requests, req)
114 sph.c.Check(req.URL.Path, Equals, "/"+sph.expectPath)
115 sph.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sph.expectAPIToken))
116 if sph.expectStorageClass != "*" {
117 sph.c.Check(req.Header.Get("X-Keep-Storage-Classes"), Equals, sph.expectStorageClass)
119 body, err := ioutil.ReadAll(req.Body)
120 sph.c.Check(err, Equals, nil)
121 sph.c.Check(body, DeepEquals, []byte(sph.expectBody))
122 resp.Header().Set("X-Keep-Replicas-Stored", "1")
123 if sph.returnStorageClasses != "" {
124 resp.Header().Set("X-Keep-Storage-Classes-Confirmed", sph.returnStorageClasses)
126 resp.WriteHeader(200)
127 sph.handled <- fmt.Sprintf("http://%s", req.Host)
130 func RunFakeKeepServer(st http.Handler) (ks KeepServer) {
132 // If we don't explicitly bind it to localhost, ks.listener.Addr() will
133 // bind to 0.0.0.0 or [::] which is not a valid address for Dial()
134 ks.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 0})
136 panic(fmt.Sprintf("Could not listen on any port"))
138 ks.url = fmt.Sprintf("http://%s", ks.listener.Addr().String())
139 go http.Serve(ks.listener, st)
143 func UploadToStubHelper(c *C, st http.Handler, f func(*KeepClient, string,
144 io.ReadCloser, io.WriteCloser, chan uploadStatus)) {
146 ks := RunFakeKeepServer(st)
147 defer ks.listener.Close()
149 arv, _ := arvadosclient.MakeArvadosClient()
150 arv.ApiToken = "abc123"
152 kc, _ := MakeKeepClient(arv)
154 reader, writer := io.Pipe()
155 uploadStatusChan := make(chan uploadStatus)
157 f(kc, ks.url, reader, writer, uploadStatusChan)
160 func (s *StandaloneSuite) TestUploadToStubKeepServer(c *C) {
161 log.Printf("TestUploadToStubKeepServer")
163 st := &StubPutHandler{
165 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
166 expectAPIToken: "abc123",
168 expectStorageClass: "",
169 returnStorageClasses: "default=1",
170 handled: make(chan string),
173 UploadToStubHelper(c, st,
174 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
175 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
177 writer.Write([]byte("foo"))
181 status := <-uploadStatusChan
182 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
186 func (s *StandaloneSuite) TestUploadToStubKeepServerBufferReader(c *C) {
187 st := &StubPutHandler{
189 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
190 expectAPIToken: "abc123",
192 expectStorageClass: "",
193 returnStorageClasses: "default=1",
194 handled: make(chan string),
197 UploadToStubHelper(c, st,
198 func(kc *KeepClient, url string, _ io.ReadCloser, _ io.WriteCloser, uploadStatusChan chan uploadStatus) {
199 go kc.uploadToKeepServer(url, st.expectPath, nil, bytes.NewBuffer([]byte("foo")), uploadStatusChan, 3, kc.getRequestID())
203 status := <-uploadStatusChan
204 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, map[string]int{"default": 1}, ""})
208 func (s *StandaloneSuite) TestUploadWithStorageClasses(c *C) {
209 for _, trial := range []struct {
211 expectMap map[string]int
214 {"foo=1", map[string]int{"foo": 1}},
215 {" foo=1 , bar=2 ", map[string]int{"foo": 1, "bar": 2}},
219 st := &StubPutHandler{
221 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
222 expectAPIToken: "abc123",
224 expectStorageClass: "",
225 returnStorageClasses: trial.respHeader,
226 handled: make(chan string),
229 UploadToStubHelper(c, st,
230 func(kc *KeepClient, url string, reader io.ReadCloser, writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
231 go kc.uploadToKeepServer(url, st.expectPath, nil, reader, uploadStatusChan, len("foo"), kc.getRequestID())
233 writer.Write([]byte("foo"))
237 status := <-uploadStatusChan
238 c.Check(status, DeepEquals, uploadStatus{nil, fmt.Sprintf("%s/%s", url, st.expectPath), 200, 1, trial.expectMap, ""})
243 func (s *StandaloneSuite) TestPutWithStorageClasses(c *C) {
245 for _, trial := range []struct {
247 clientClasses []string
248 putClasses []string // putClasses takes precedence over clientClasses
253 {1, []string{"class1"}, nil, 1, 1, true},
254 {2, []string{"class1"}, nil, 1, 2, true},
255 {3, []string{"class1"}, nil, 2, 3, true},
256 {1, []string{"class1", "class2"}, nil, 1, 1, true},
257 {3, nil, []string{"class1"}, 2, 3, true},
258 {1, nil, []string{"class1", "class2"}, 1, 1, true},
259 {1, []string{"class404"}, []string{"class1", "class2"}, 1, 1, true},
260 {1, []string{"class1"}, []string{"class404", "class2"}, nServers, nServers, false},
261 {nServers*2 + 1, []string{"class1"}, nil, nServers, nServers, false},
262 {1, []string{"class404"}, nil, nServers, nServers, false},
263 {1, []string{"class1", "class404"}, nil, nServers, nServers, false},
264 {1, nil, []string{"class1", "class404"}, nServers, nServers, false},
267 st := &StubPutHandler{
269 expectPath: "acbd18db4cc2f85cedef654fccc4a4d8",
270 expectAPIToken: "abc123",
272 expectStorageClass: "*",
273 returnStorageClasses: "class1=2, class2=2",
274 handled: make(chan string, 100),
276 ks := RunSomeFakeKeepServers(st, nServers)
277 arv, _ := arvadosclient.MakeArvadosClient()
278 kc, _ := MakeKeepClient(arv)
279 kc.Want_replicas = trial.replicas
280 kc.StorageClasses = trial.clientClasses
281 arv.ApiToken = "abc123"
282 localRoots := make(map[string]string)
283 writableLocalRoots := make(map[string]string)
284 for i, k := range ks {
285 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
286 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
287 defer k.listener.Close()
289 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
291 _, err := kc.BlockWrite(context.Background(), arvados.BlockWriteOptions{
293 StorageClasses: trial.putClasses,
296 c.Check(err, check.IsNil)
298 c.Check(err, check.NotNil)
300 c.Check(len(st.handled) >= trial.minRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.minRequests==%d", len(st.handled), trial.minRequests))
301 c.Check(len(st.handled) <= trial.maxRequests, check.Equals, true, check.Commentf("len(st.handled)==%d, trial.maxRequests==%d", len(st.handled), trial.maxRequests))
302 if !trial.success && trial.replicas == 1 && c.Check(len(st.requests) >= 2, check.Equals, true) {
303 // Max concurrency should be 1. First request
304 // should have succeeded for class1. Second
305 // request should only ask for class404.
306 c.Check(st.requests[1].Header.Get("X-Keep-Storage-Classes"), check.Equals, "class404")
311 type FailHandler struct {
315 func (fh FailHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
316 resp.WriteHeader(500)
317 fh.handled <- fmt.Sprintf("http://%s", req.Host)
320 type FailThenSucceedHandler struct {
323 successhandler http.Handler
327 func (fh *FailThenSucceedHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
328 fh.reqIDs = append(fh.reqIDs, req.Header.Get("X-Request-Id"))
330 resp.WriteHeader(500)
332 fh.handled <- fmt.Sprintf("http://%s", req.Host)
334 fh.successhandler.ServeHTTP(resp, req)
338 type Error404Handler struct {
342 func (fh Error404Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
343 resp.WriteHeader(404)
344 fh.handled <- fmt.Sprintf("http://%s", req.Host)
347 func (s *StandaloneSuite) TestFailedUploadToStubKeepServer(c *C) {
351 hash := "acbd18db4cc2f85cedef654fccc4a4d8"
353 UploadToStubHelper(c, st,
354 func(kc *KeepClient, url string, reader io.ReadCloser,
355 writer io.WriteCloser, uploadStatusChan chan uploadStatus) {
357 go kc.uploadToKeepServer(url, hash, nil, reader, uploadStatusChan, 3, kc.getRequestID())
359 writer.Write([]byte("foo"))
364 status := <-uploadStatusChan
365 c.Check(status.url, Equals, fmt.Sprintf("%s/%s", url, hash))
366 c.Check(status.statusCode, Equals, 500)
370 type KeepServer struct {
371 listener net.Listener
375 func RunSomeFakeKeepServers(st http.Handler, n int) (ks []KeepServer) {
376 ks = make([]KeepServer, n)
378 for i := 0; i < n; i++ {
379 ks[i] = RunFakeKeepServer(st)
385 func (s *StandaloneSuite) TestPutB(c *C) {
386 hash := Md5String("foo")
388 st := &StubPutHandler{
391 expectAPIToken: "abc123",
393 expectStorageClass: "",
394 returnStorageClasses: "",
395 handled: make(chan string, 5),
398 arv, _ := arvadosclient.MakeArvadosClient()
399 kc, _ := MakeKeepClient(arv)
402 arv.ApiToken = "abc123"
403 localRoots := make(map[string]string)
404 writableLocalRoots := make(map[string]string)
406 ks := RunSomeFakeKeepServers(st, 5)
408 for i, k := range ks {
409 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
410 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
411 defer k.listener.Close()
414 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
416 kc.PutB([]byte("foo"))
418 shuff := NewRootSorter(
419 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
423 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
424 (s1 == shuff[1] && s2 == shuff[0]),
429 func (s *StandaloneSuite) TestPutHR(c *C) {
430 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
432 st := &StubPutHandler{
435 expectAPIToken: "abc123",
437 expectStorageClass: "",
438 returnStorageClasses: "",
439 handled: make(chan string, 5),
442 arv, _ := arvadosclient.MakeArvadosClient()
443 kc, _ := MakeKeepClient(arv)
446 arv.ApiToken = "abc123"
447 localRoots := make(map[string]string)
448 writableLocalRoots := make(map[string]string)
450 ks := RunSomeFakeKeepServers(st, 5)
452 for i, k := range ks {
453 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
454 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
455 defer k.listener.Close()
458 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
460 reader, writer := io.Pipe()
463 writer.Write([]byte("foo"))
467 kc.PutHR(hash, reader, 3)
469 shuff := NewRootSorter(kc.LocalRoots(), hash).GetSortedRoots()
474 c.Check((s1 == shuff[0] && s2 == shuff[1]) ||
475 (s1 == shuff[1] && s2 == shuff[0]),
480 func (s *StandaloneSuite) TestPutWithFail(c *C) {
481 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
483 st := &StubPutHandler{
486 expectAPIToken: "abc123",
488 expectStorageClass: "",
489 returnStorageClasses: "",
490 handled: make(chan string, 4),
494 make(chan string, 1)}
496 arv, err := arvadosclient.MakeArvadosClient()
498 kc, _ := MakeKeepClient(arv)
501 arv.ApiToken = "abc123"
502 localRoots := make(map[string]string)
503 writableLocalRoots := make(map[string]string)
505 ks1 := RunSomeFakeKeepServers(st, 4)
506 ks2 := RunSomeFakeKeepServers(fh, 1)
508 for i, k := range ks1 {
509 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
510 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
511 defer k.listener.Close()
513 for i, k := range ks2 {
514 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
515 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
516 defer k.listener.Close()
519 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
521 shuff := NewRootSorter(
522 kc.LocalRoots(), Md5String("foo")).GetSortedRoots()
525 phash, replicas, err := kc.PutB([]byte("foo"))
529 c.Check(err, Equals, nil)
530 c.Check(phash, Equals, "")
531 c.Check(replicas, Equals, 2)
536 c.Check((s1 == shuff[1] && s2 == shuff[2]) ||
537 (s1 == shuff[2] && s2 == shuff[1]),
542 func (s *StandaloneSuite) TestPutWithTooManyFail(c *C) {
543 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
545 st := &StubPutHandler{
548 expectAPIToken: "abc123",
550 expectStorageClass: "",
551 returnStorageClasses: "",
552 handled: make(chan string, 1),
556 make(chan string, 4)}
558 arv, err := arvadosclient.MakeArvadosClient()
560 kc, _ := MakeKeepClient(arv)
564 arv.ApiToken = "abc123"
565 localRoots := make(map[string]string)
566 writableLocalRoots := make(map[string]string)
568 ks1 := RunSomeFakeKeepServers(st, 1)
569 ks2 := RunSomeFakeKeepServers(fh, 4)
571 for i, k := range ks1 {
572 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
573 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
574 defer k.listener.Close()
576 for i, k := range ks2 {
577 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
578 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
579 defer k.listener.Close()
582 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
584 _, replicas, err := kc.PutB([]byte("foo"))
586 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
587 c.Check(replicas, Equals, 1)
588 c.Check(<-st.handled, Equals, ks1[0].url)
591 type StubGetHandler struct {
594 expectAPIToken string
599 func (sgh StubGetHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
600 sgh.c.Check(req.URL.Path, Equals, "/"+sgh.expectPath)
601 sgh.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", sgh.expectAPIToken))
602 resp.WriteHeader(sgh.httpStatus)
603 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(sgh.body)))
607 func (s *StandaloneSuite) TestGet(c *C) {
608 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
610 st := StubGetHandler{
617 ks := RunFakeKeepServer(st)
618 defer ks.listener.Close()
620 arv, err := arvadosclient.MakeArvadosClient()
622 kc, _ := MakeKeepClient(arv)
623 arv.ApiToken = "abc123"
624 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
626 r, n, url2, err := kc.Get(hash)
628 c.Check(err, Equals, nil)
629 c.Check(n, Equals, int64(3))
630 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
632 content, err2 := ioutil.ReadAll(r)
633 c.Check(err2, Equals, nil)
634 c.Check(content, DeepEquals, []byte("foo"))
637 func (s *StandaloneSuite) TestGet404(c *C) {
638 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
640 st := Error404Handler{make(chan string, 1)}
642 ks := RunFakeKeepServer(st)
643 defer ks.listener.Close()
645 arv, err := arvadosclient.MakeArvadosClient()
647 kc, _ := MakeKeepClient(arv)
648 arv.ApiToken = "abc123"
649 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
651 r, n, url2, err := kc.Get(hash)
652 c.Check(err, Equals, BlockNotFound)
653 c.Check(n, Equals, int64(0))
654 c.Check(url2, Equals, "")
655 c.Check(r, Equals, nil)
658 func (s *StandaloneSuite) TestGetEmptyBlock(c *C) {
659 st := Error404Handler{make(chan string, 1)}
661 ks := RunFakeKeepServer(st)
662 defer ks.listener.Close()
664 arv, err := arvadosclient.MakeArvadosClient()
666 kc, _ := MakeKeepClient(arv)
667 arv.ApiToken = "abc123"
668 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
670 r, n, url2, err := kc.Get("d41d8cd98f00b204e9800998ecf8427e+0")
672 c.Check(n, Equals, int64(0))
673 c.Check(url2, Equals, "")
675 buf, err := ioutil.ReadAll(r)
677 c.Check(buf, DeepEquals, []byte{})
680 func (s *StandaloneSuite) TestGetFail(c *C) {
681 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
683 st := FailHandler{make(chan string, 1)}
685 ks := RunFakeKeepServer(st)
686 defer ks.listener.Close()
688 arv, err := arvadosclient.MakeArvadosClient()
690 kc, _ := MakeKeepClient(arv)
691 arv.ApiToken = "abc123"
692 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
695 r, n, url2, err := kc.Get(hash)
696 errNotFound, _ := err.(*ErrNotFound)
697 c.Check(errNotFound, NotNil)
698 c.Check(strings.Contains(errNotFound.Error(), "HTTP 500"), Equals, true)
699 c.Check(errNotFound.Temporary(), Equals, true)
700 c.Check(n, Equals, int64(0))
701 c.Check(url2, Equals, "")
702 c.Check(r, Equals, nil)
705 func (s *StandaloneSuite) TestGetFailRetry(c *C) {
706 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
708 st := &FailThenSucceedHandler{
709 handled: make(chan string, 1),
710 successhandler: StubGetHandler{
717 ks := RunFakeKeepServer(st)
718 defer ks.listener.Close()
720 arv, err := arvadosclient.MakeArvadosClient()
722 kc, _ := MakeKeepClient(arv)
723 arv.ApiToken = "abc123"
724 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
726 r, n, url2, err := kc.Get(hash)
728 c.Check(err, Equals, nil)
729 c.Check(n, Equals, int64(3))
730 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks.url, hash))
732 content, err2 := ioutil.ReadAll(r)
733 c.Check(err2, Equals, nil)
734 c.Check(content, DeepEquals, []byte("foo"))
736 c.Logf("%q", st.reqIDs)
737 c.Assert(len(st.reqIDs) > 1, Equals, true)
738 for _, reqid := range st.reqIDs {
739 c.Check(reqid, Not(Equals), "")
740 c.Check(reqid, Equals, st.reqIDs[0])
744 func (s *StandaloneSuite) TestGetNetError(c *C) {
745 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
747 arv, err := arvadosclient.MakeArvadosClient()
749 kc, _ := MakeKeepClient(arv)
750 arv.ApiToken = "abc123"
751 kc.SetServiceRoots(map[string]string{"x": "http://localhost:62222"}, nil, nil)
753 r, n, url2, err := kc.Get(hash)
754 errNotFound, _ := err.(*ErrNotFound)
755 c.Check(errNotFound, NotNil)
756 c.Check(strings.Contains(errNotFound.Error(), "connection refused"), Equals, true)
757 c.Check(errNotFound.Temporary(), Equals, true)
758 c.Check(n, Equals, int64(0))
759 c.Check(url2, Equals, "")
760 c.Check(r, Equals, nil)
763 func (s *StandaloneSuite) TestGetWithServiceHint(c *C) {
764 uuid := "zzzzz-bi6l4-123451234512345"
765 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
767 // This one shouldn't be used:
768 ks0 := RunFakeKeepServer(StubGetHandler{
774 defer ks0.listener.Close()
775 // This one should be used:
776 ks := RunFakeKeepServer(StubGetHandler{
782 defer ks.listener.Close()
784 arv, err := arvadosclient.MakeArvadosClient()
786 kc, _ := MakeKeepClient(arv)
787 arv.ApiToken = "abc123"
789 map[string]string{"x": ks0.url},
791 map[string]string{uuid: ks.url})
793 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
795 c.Check(err, Equals, nil)
796 c.Check(n, Equals, int64(3))
797 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
799 content, err := ioutil.ReadAll(r)
800 c.Check(err, Equals, nil)
801 c.Check(content, DeepEquals, []byte("foo"))
804 // Use a service hint to fetch from a local disk service, overriding
805 // rendezvous probe order.
806 func (s *StandaloneSuite) TestGetWithLocalServiceHint(c *C) {
807 uuid := "zzzzz-bi6l4-zzzzzzzzzzzzzzz"
808 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
810 // This one shouldn't be used, although it appears first in
811 // rendezvous probe order:
812 ks0 := RunFakeKeepServer(StubGetHandler{
818 defer ks0.listener.Close()
819 // This one should be used:
820 ks := RunFakeKeepServer(StubGetHandler{
826 defer ks.listener.Close()
828 arv, err := arvadosclient.MakeArvadosClient()
830 kc, _ := MakeKeepClient(arv)
831 arv.ApiToken = "abc123"
834 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
835 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
836 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
840 "zzzzz-bi6l4-yyyyyyyyyyyyyyy": ks0.url,
841 "zzzzz-bi6l4-xxxxxxxxxxxxxxx": ks0.url,
842 "zzzzz-bi6l4-wwwwwwwwwwwwwww": ks0.url,
846 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
848 c.Check(err, Equals, nil)
849 c.Check(n, Equals, int64(3))
850 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ks.url, hash+"+K@"+uuid))
852 content, err := ioutil.ReadAll(r)
853 c.Check(err, Equals, nil)
854 c.Check(content, DeepEquals, []byte("foo"))
857 func (s *StandaloneSuite) TestGetWithServiceHintFailoverToLocals(c *C) {
858 uuid := "zzzzz-bi6l4-123451234512345"
859 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
861 ksLocal := RunFakeKeepServer(StubGetHandler{
867 defer ksLocal.listener.Close()
868 ksGateway := RunFakeKeepServer(StubGetHandler{
872 http.StatusInternalServerError,
874 defer ksGateway.listener.Close()
876 arv, err := arvadosclient.MakeArvadosClient()
878 kc, _ := MakeKeepClient(arv)
879 arv.ApiToken = "abc123"
881 map[string]string{"zzzzz-bi6l4-keepdisk0000000": ksLocal.url},
883 map[string]string{uuid: ksGateway.url})
885 r, n, uri, err := kc.Get(hash + "+K@" + uuid)
886 c.Assert(err, Equals, nil)
888 c.Check(n, Equals, int64(3))
889 c.Check(uri, Equals, fmt.Sprintf("%s/%s", ksLocal.url, hash+"+K@"+uuid))
891 content, err := ioutil.ReadAll(r)
892 c.Check(err, Equals, nil)
893 c.Check(content, DeepEquals, []byte("foo"))
896 type BarHandler struct {
900 func (h BarHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
901 resp.Write([]byte("bar"))
902 h.handled <- fmt.Sprintf("http://%s", req.Host)
905 func (s *StandaloneSuite) TestChecksum(c *C) {
906 foohash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
907 barhash := fmt.Sprintf("%x", md5.Sum([]byte("bar")))
909 st := BarHandler{make(chan string, 1)}
911 ks := RunFakeKeepServer(st)
912 defer ks.listener.Close()
914 arv, err := arvadosclient.MakeArvadosClient()
916 kc, _ := MakeKeepClient(arv)
917 arv.ApiToken = "abc123"
918 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
920 r, n, _, err := kc.Get(barhash)
922 _, err = ioutil.ReadAll(r)
923 c.Check(n, Equals, int64(3))
924 c.Check(err, Equals, nil)
928 r, n, _, err = kc.Get(foohash)
930 _, err = ioutil.ReadAll(r)
931 c.Check(n, Equals, int64(3))
932 c.Check(err, Equals, BadChecksum)
937 func (s *StandaloneSuite) TestGetWithFailures(c *C) {
938 content := []byte("waz")
939 hash := fmt.Sprintf("%x", md5.Sum(content))
941 fh := Error404Handler{
942 make(chan string, 4)}
944 st := StubGetHandler{
951 arv, err := arvadosclient.MakeArvadosClient()
953 kc, _ := MakeKeepClient(arv)
954 arv.ApiToken = "abc123"
955 localRoots := make(map[string]string)
956 writableLocalRoots := make(map[string]string)
958 ks1 := RunSomeFakeKeepServers(st, 1)
959 ks2 := RunSomeFakeKeepServers(fh, 4)
961 for i, k := range ks1 {
962 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
963 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
964 defer k.listener.Close()
966 for i, k := range ks2 {
967 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
968 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i+len(ks1))] = k.url
969 defer k.listener.Close()
972 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
975 // This test works only if one of the failing services is
976 // attempted before the succeeding service. Otherwise,
977 // <-fh.handled below will just hang! (Probe order depends on
978 // the choice of block content "waz" and the UUIDs of the fake
979 // servers, so we just tried different strings until we found
980 // an example that passes this Assert.)
981 c.Assert(NewRootSorter(localRoots, hash).GetSortedRoots()[0], Not(Equals), ks1[0].url)
983 r, n, url2, err := kc.Get(hash)
986 c.Check(err, Equals, nil)
987 c.Check(n, Equals, int64(3))
988 c.Check(url2, Equals, fmt.Sprintf("%s/%s", ks1[0].url, hash))
990 readContent, err2 := ioutil.ReadAll(r)
991 c.Check(err2, Equals, nil)
992 c.Check(readContent, DeepEquals, content)
995 func (s *ServerRequiredSuite) TestPutGetHead(c *C) {
996 content := []byte("TestPutGetHead")
998 arv, err := arvadosclient.MakeArvadosClient()
1000 kc, err := MakeKeepClient(arv)
1001 c.Assert(err, Equals, nil)
1003 hash := fmt.Sprintf("%x", md5.Sum(content))
1006 n, _, err := kc.Ask(hash)
1007 c.Check(err, Equals, BlockNotFound)
1008 c.Check(n, Equals, int64(0))
1011 hash2, replicas, err := kc.PutB(content)
1012 c.Check(hash2, Matches, fmt.Sprintf(`%s\+%d\b.*`, hash, len(content)))
1013 c.Check(replicas, Equals, 2)
1014 c.Check(err, Equals, nil)
1017 r, n, url2, err := kc.Get(hash)
1018 c.Check(err, Equals, nil)
1019 c.Check(n, Equals, int64(len(content)))
1020 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1022 readContent, err2 := ioutil.ReadAll(r)
1023 c.Check(err2, Equals, nil)
1024 c.Check(readContent, DeepEquals, content)
1027 n, url2, err := kc.Ask(hash)
1028 c.Check(err, Equals, nil)
1029 c.Check(n, Equals, int64(len(content)))
1030 c.Check(url2, Matches, fmt.Sprintf("http://localhost:\\d+/%s", hash))
1033 loc, err := kc.LocalLocator(hash)
1034 c.Check(err, Equals, nil)
1035 c.Assert(len(loc) >= 32, Equals, true)
1036 c.Check(loc[:32], Equals, hash[:32])
1039 content := []byte("the perth county conspiracy")
1040 loc, err := kc.LocalLocator(fmt.Sprintf("%x+%d+Rzaaaa-abcde@12345", md5.Sum(content), len(content)))
1041 c.Check(loc, Equals, "")
1042 c.Check(err, ErrorMatches, `.*HEAD .*\+R.*`)
1043 c.Check(err, ErrorMatches, `.*HTTP 400.*`)
1047 type StubProxyHandler struct {
1051 func (h StubProxyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1052 resp.Header().Set("X-Keep-Replicas-Stored", "2")
1053 h.handled <- fmt.Sprintf("http://%s", req.Host)
1056 func (s *StandaloneSuite) TestPutProxy(c *C) {
1057 st := StubProxyHandler{make(chan string, 1)}
1059 arv, err := arvadosclient.MakeArvadosClient()
1061 kc, _ := MakeKeepClient(arv)
1063 kc.Want_replicas = 2
1064 arv.ApiToken = "abc123"
1065 localRoots := make(map[string]string)
1066 writableLocalRoots := make(map[string]string)
1068 ks1 := RunSomeFakeKeepServers(st, 1)
1070 for i, k := range ks1 {
1071 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1072 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1073 defer k.listener.Close()
1076 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1078 _, replicas, err := kc.PutB([]byte("foo"))
1081 c.Check(err, Equals, nil)
1082 c.Check(replicas, Equals, 2)
1085 func (s *StandaloneSuite) TestPutProxyInsufficientReplicas(c *C) {
1086 st := StubProxyHandler{make(chan string, 1)}
1088 arv, err := arvadosclient.MakeArvadosClient()
1090 kc, _ := MakeKeepClient(arv)
1092 kc.Want_replicas = 3
1093 arv.ApiToken = "abc123"
1094 localRoots := make(map[string]string)
1095 writableLocalRoots := make(map[string]string)
1097 ks1 := RunSomeFakeKeepServers(st, 1)
1099 for i, k := range ks1 {
1100 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1101 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1102 defer k.listener.Close()
1104 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1106 _, replicas, err := kc.PutB([]byte("foo"))
1109 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1110 c.Check(replicas, Equals, 2)
1113 func (s *StandaloneSuite) TestMakeLocator(c *C) {
1114 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+3+Aabcde@12345678")
1115 c.Check(err, Equals, nil)
1116 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1117 c.Check(l.Size, Equals, 3)
1118 c.Check(l.Hints, DeepEquals, []string{"3", "Aabcde@12345678"})
1121 func (s *StandaloneSuite) TestMakeLocatorNoHints(c *C) {
1122 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce")
1123 c.Check(err, Equals, nil)
1124 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1125 c.Check(l.Size, Equals, -1)
1126 c.Check(l.Hints, DeepEquals, []string{})
1129 func (s *StandaloneSuite) TestMakeLocatorNoSizeHint(c *C) {
1130 l, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31ce+Aabcde@12345678")
1131 c.Check(err, Equals, nil)
1132 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1133 c.Check(l.Size, Equals, -1)
1134 c.Check(l.Hints, DeepEquals, []string{"Aabcde@12345678"})
1137 func (s *StandaloneSuite) TestMakeLocatorPreservesUnrecognizedHints(c *C) {
1138 str := "91f372a266fe2bf2823cb8ec7fda31ce+3+Unknown+Kzzzzz+Afoobar"
1139 l, err := MakeLocator(str)
1140 c.Check(err, Equals, nil)
1141 c.Check(l.Hash, Equals, "91f372a266fe2bf2823cb8ec7fda31ce")
1142 c.Check(l.Size, Equals, 3)
1143 c.Check(l.Hints, DeepEquals, []string{"3", "Unknown", "Kzzzzz", "Afoobar"})
1144 c.Check(l.String(), Equals, str)
1147 func (s *StandaloneSuite) TestMakeLocatorInvalidInput(c *C) {
1148 _, err := MakeLocator("91f372a266fe2bf2823cb8ec7fda31c")
1149 c.Check(err, Equals, InvalidLocatorError)
1152 func (s *StandaloneSuite) TestPutBWant2ReplicasWithOnlyOneWritableLocalRoot(c *C) {
1153 hash := Md5String("foo")
1155 st := &StubPutHandler{
1158 expectAPIToken: "abc123",
1160 expectStorageClass: "",
1161 returnStorageClasses: "",
1162 handled: make(chan string, 5),
1165 arv, _ := arvadosclient.MakeArvadosClient()
1166 kc, _ := MakeKeepClient(arv)
1168 kc.Want_replicas = 2
1169 arv.ApiToken = "abc123"
1170 localRoots := make(map[string]string)
1171 writableLocalRoots := make(map[string]string)
1173 ks := RunSomeFakeKeepServers(st, 5)
1175 for i, k := range ks {
1176 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1178 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1180 defer k.listener.Close()
1183 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1185 _, replicas, err := kc.PutB([]byte("foo"))
1187 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1188 c.Check(replicas, Equals, 1)
1190 c.Check(<-st.handled, Equals, localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", 0)])
1193 func (s *StandaloneSuite) TestPutBWithNoWritableLocalRoots(c *C) {
1194 hash := Md5String("foo")
1196 st := &StubPutHandler{
1199 expectAPIToken: "abc123",
1201 expectStorageClass: "",
1202 returnStorageClasses: "",
1203 handled: make(chan string, 5),
1206 arv, _ := arvadosclient.MakeArvadosClient()
1207 kc, _ := MakeKeepClient(arv)
1209 kc.Want_replicas = 2
1210 arv.ApiToken = "abc123"
1211 localRoots := make(map[string]string)
1212 writableLocalRoots := make(map[string]string)
1214 ks := RunSomeFakeKeepServers(st, 5)
1216 for i, k := range ks {
1217 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1218 defer k.listener.Close()
1221 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1223 _, replicas, err := kc.PutB([]byte("foo"))
1225 c.Check(err, FitsTypeOf, InsufficientReplicasError{})
1226 c.Check(replicas, Equals, 0)
1229 type StubGetIndexHandler struct {
1232 expectAPIToken string
1237 func (h StubGetIndexHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
1238 h.c.Check(req.URL.Path, Equals, h.expectPath)
1239 h.c.Check(req.Header.Get("Authorization"), Equals, fmt.Sprintf("OAuth2 %s", h.expectAPIToken))
1240 resp.WriteHeader(h.httpStatus)
1241 resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(h.body)))
1245 func (s *StandaloneSuite) TestGetIndexWithNoPrefix(c *C) {
1246 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1248 st := StubGetIndexHandler{
1253 []byte(hash + "+3 1443559274\n\n")}
1255 ks := RunFakeKeepServer(st)
1256 defer ks.listener.Close()
1258 arv, err := arvadosclient.MakeArvadosClient()
1259 c.Assert(err, IsNil)
1260 kc, err := MakeKeepClient(arv)
1261 c.Assert(err, IsNil)
1262 arv.ApiToken = "abc123"
1263 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1265 r, err := kc.GetIndex("x", "")
1268 content, err2 := ioutil.ReadAll(r)
1269 c.Check(err2, Equals, nil)
1270 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1273 func (s *StandaloneSuite) TestGetIndexWithPrefix(c *C) {
1274 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1276 st := StubGetIndexHandler{
1278 "/index/" + hash[0:3],
1281 []byte(hash + "+3 1443559274\n\n")}
1283 ks := RunFakeKeepServer(st)
1284 defer ks.listener.Close()
1286 arv, err := arvadosclient.MakeArvadosClient()
1288 kc, _ := MakeKeepClient(arv)
1289 arv.ApiToken = "abc123"
1290 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1292 r, err := kc.GetIndex("x", hash[0:3])
1293 c.Assert(err, Equals, nil)
1295 content, err2 := ioutil.ReadAll(r)
1296 c.Check(err2, Equals, nil)
1297 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1300 func (s *StandaloneSuite) TestGetIndexIncomplete(c *C) {
1301 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1303 st := StubGetIndexHandler{
1305 "/index/" + hash[0:3],
1310 ks := RunFakeKeepServer(st)
1311 defer ks.listener.Close()
1313 arv, err := arvadosclient.MakeArvadosClient()
1315 kc, _ := MakeKeepClient(arv)
1316 arv.ApiToken = "abc123"
1317 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1319 _, err = kc.GetIndex("x", hash[0:3])
1320 c.Check(err, Equals, ErrIncompleteIndex)
1323 func (s *StandaloneSuite) TestGetIndexWithNoSuchServer(c *C) {
1324 hash := fmt.Sprintf("%x", md5.Sum([]byte("foo")))
1326 st := StubGetIndexHandler{
1328 "/index/" + hash[0:3],
1333 ks := RunFakeKeepServer(st)
1334 defer ks.listener.Close()
1336 arv, err := arvadosclient.MakeArvadosClient()
1338 kc, _ := MakeKeepClient(arv)
1339 arv.ApiToken = "abc123"
1340 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1342 _, err = kc.GetIndex("y", hash[0:3])
1343 c.Check(err, Equals, ErrNoSuchKeepServer)
1346 func (s *StandaloneSuite) TestGetIndexWithNoSuchPrefix(c *C) {
1347 st := StubGetIndexHandler{
1354 ks := RunFakeKeepServer(st)
1355 defer ks.listener.Close()
1357 arv, err := arvadosclient.MakeArvadosClient()
1359 kc, _ := MakeKeepClient(arv)
1360 arv.ApiToken = "abc123"
1361 kc.SetServiceRoots(map[string]string{"x": ks.url}, nil, nil)
1363 r, err := kc.GetIndex("x", "abcd")
1364 c.Check(err, Equals, nil)
1366 content, err2 := ioutil.ReadAll(r)
1367 c.Check(err2, Equals, nil)
1368 c.Check(content, DeepEquals, st.body[0:len(st.body)-1])
1371 func (s *StandaloneSuite) TestPutBRetry(c *C) {
1372 st := &FailThenSucceedHandler{
1373 handled: make(chan string, 1),
1374 successhandler: &StubPutHandler{
1376 expectPath: Md5String("foo"),
1377 expectAPIToken: "abc123",
1379 expectStorageClass: "",
1380 returnStorageClasses: "",
1381 handled: make(chan string, 5),
1385 arv, _ := arvadosclient.MakeArvadosClient()
1386 kc, _ := MakeKeepClient(arv)
1388 kc.Want_replicas = 2
1389 arv.ApiToken = "abc123"
1390 localRoots := make(map[string]string)
1391 writableLocalRoots := make(map[string]string)
1393 ks := RunSomeFakeKeepServers(st, 2)
1395 for i, k := range ks {
1396 localRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1397 writableLocalRoots[fmt.Sprintf("zzzzz-bi6l4-fakefakefake%03d", i)] = k.url
1398 defer k.listener.Close()
1401 kc.SetServiceRoots(localRoots, writableLocalRoots, nil)
1403 hash, replicas, err := kc.PutB([]byte("foo"))
1405 c.Check(err, Equals, nil)
1406 c.Check(hash, Equals, "")
1407 c.Check(replicas, Equals, 2)
1410 func (s *ServerRequiredSuite) TestMakeKeepClientWithNonDiskTypeService(c *C) {
1411 arv, err := arvadosclient.MakeArvadosClient()
1412 c.Assert(err, Equals, nil)
1414 // Add an additional "testblobstore" keepservice
1415 blobKeepService := make(arvadosclient.Dict)
1416 err = arv.Create("keep_services",
1417 arvadosclient.Dict{"keep_service": arvadosclient.Dict{
1418 "service_host": "localhost",
1419 "service_port": "21321",
1420 "service_type": "testblobstore"}},
1422 c.Assert(err, Equals, nil)
1423 defer func() { arv.Delete("keep_services", blobKeepService["uuid"].(string), nil, nil) }()
1424 RefreshServiceDiscovery()
1426 // Make a keepclient and ensure that the testblobstore is included
1427 kc, err := MakeKeepClient(arv)
1428 c.Assert(err, Equals, nil)
1430 // verify kc.LocalRoots
1431 c.Check(len(kc.LocalRoots()), Equals, 3)
1432 for _, root := range kc.LocalRoots() {
1433 c.Check(root, Matches, "http://localhost:\\d+")
1435 c.Assert(kc.LocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1437 // verify kc.GatewayRoots
1438 c.Check(len(kc.GatewayRoots()), Equals, 3)
1439 for _, root := range kc.GatewayRoots() {
1440 c.Check(root, Matches, "http://localhost:\\d+")
1442 c.Assert(kc.GatewayRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1444 // verify kc.WritableLocalRoots
1445 c.Check(len(kc.WritableLocalRoots()), Equals, 3)
1446 for _, root := range kc.WritableLocalRoots() {
1447 c.Check(root, Matches, "http://localhost:\\d+")
1449 c.Assert(kc.WritableLocalRoots()[blobKeepService["uuid"].(string)], Not(Equals), "")
1451 c.Assert(kc.replicasPerService, Equals, 0)
1452 c.Assert(kc.foundNonDiskSvc, Equals, true)
1453 c.Assert(kc.httpClient().(*http.Client).Timeout, Equals, 300*time.Second)