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