13619: Federated multi-object list wip
[arvados.git] / lib / controller / federation_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package controller
6
7 import (
8         "encoding/json"
9         "fmt"
10         "io/ioutil"
11         "log"
12         "net/http"
13         "net/http/httptest"
14         "net/url"
15         "os"
16         "strings"
17         "time"
18
19         "git.curoverse.com/arvados.git/sdk/go/arvados"
20         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21         "git.curoverse.com/arvados.git/sdk/go/httpserver"
22         "git.curoverse.com/arvados.git/sdk/go/keepclient"
23         "github.com/Sirupsen/logrus"
24         check "gopkg.in/check.v1"
25 )
26
27 // Gocheck boilerplate
28 var _ = check.Suite(&FederationSuite{})
29
30 type FederationSuite struct {
31         log *logrus.Logger
32         // testServer and testHandler are the controller being tested,
33         // "zhome".
34         testServer  *httpserver.Server
35         testHandler *Handler
36         // remoteServer ("zzzzz") forwards requests to the Rails API
37         // provided by the integration test environment.
38         remoteServer *httpserver.Server
39         // remoteMock ("zmock") appends each incoming request to
40         // remoteMockRequests, and returns an empty 200 response.
41         remoteMock         *httpserver.Server
42         remoteMockRequests []http.Request
43 }
44
45 func (s *FederationSuite) SetUpTest(c *check.C) {
46         s.log = logrus.New()
47         s.log.Formatter = &logrus.JSONFormatter{}
48         s.log.Out = &logWriter{c.Log}
49
50         s.remoteServer = newServerFromIntegrationTestEnv(c)
51         c.Assert(s.remoteServer.Start(), check.IsNil)
52
53         s.remoteMock = newServerFromIntegrationTestEnv(c)
54         s.remoteMock.Server.Handler = http.HandlerFunc(s.remoteMockHandler)
55         c.Assert(s.remoteMock.Start(), check.IsNil)
56
57         nodeProfile := arvados.NodeProfile{
58                 Controller: arvados.SystemServiceInstance{Listen: ":"},
59                 RailsAPI:   arvados.SystemServiceInstance{Listen: ":1"}, // local reqs will error "connection refused"
60         }
61         s.testHandler = &Handler{Cluster: &arvados.Cluster{
62                 ClusterID:  "zhome",
63                 PostgreSQL: integrationTestCluster().PostgreSQL,
64                 NodeProfiles: map[string]arvados.NodeProfile{
65                         "*": nodeProfile,
66                 },
67         }, NodeProfile: &nodeProfile}
68         s.testServer = newServerFromIntegrationTestEnv(c)
69         s.testServer.Server.Handler = httpserver.AddRequestIDs(httpserver.LogRequests(s.log, s.testHandler))
70
71         s.testHandler.Cluster.RemoteClusters = map[string]arvados.RemoteCluster{
72                 "zzzzz": {
73                         Host:   s.remoteServer.Addr,
74                         Proxy:  true,
75                         Scheme: "http",
76                 },
77                 "zmock": {
78                         Host:   s.remoteMock.Addr,
79                         Proxy:  true,
80                         Scheme: "http",
81                 },
82         }
83
84         c.Assert(s.testServer.Start(), check.IsNil)
85
86         s.remoteMockRequests = nil
87 }
88
89 func (s *FederationSuite) remoteMockHandler(w http.ResponseWriter, req *http.Request) {
90         s.remoteMockRequests = append(s.remoteMockRequests, *req)
91 }
92
93 func (s *FederationSuite) TearDownTest(c *check.C) {
94         if s.remoteServer != nil {
95                 s.remoteServer.Close()
96         }
97         if s.testServer != nil {
98                 s.testServer.Close()
99         }
100 }
101
102 func (s *FederationSuite) testRequest(req *http.Request) *http.Response {
103         resp := httptest.NewRecorder()
104         s.testServer.Server.Handler.ServeHTTP(resp, req)
105         return resp.Result()
106 }
107
108 func (s *FederationSuite) TestLocalRequest(c *check.C) {
109         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zhome-", 1), nil)
110         resp := s.testRequest(req)
111         s.checkHandledLocally(c, resp)
112 }
113
114 func (s *FederationSuite) checkHandledLocally(c *check.C, resp *http.Response) {
115         // Our "home" controller can't handle local requests because
116         // it doesn't have its own stub/test Rails API, so we rely on
117         // "connection refused" to indicate the controller tried to
118         // proxy the request to its local Rails API.
119         c.Check(resp.StatusCode, check.Equals, http.StatusBadGateway)
120         s.checkJSONErrorMatches(c, resp, `.*connection refused`)
121 }
122
123 func (s *FederationSuite) TestNoAuth(c *check.C) {
124         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
125         resp := s.testRequest(req)
126         c.Check(resp.StatusCode, check.Equals, http.StatusUnauthorized)
127         s.checkJSONErrorMatches(c, resp, `Not logged in`)
128 }
129
130 func (s *FederationSuite) TestBadAuth(c *check.C) {
131         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
132         req.Header.Set("Authorization", "Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
133         resp := s.testRequest(req)
134         c.Check(resp.StatusCode, check.Equals, http.StatusUnauthorized)
135         s.checkJSONErrorMatches(c, resp, `Not logged in`)
136 }
137
138 func (s *FederationSuite) TestNoAccess(c *check.C) {
139         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
140         req.Header.Set("Authorization", "Bearer "+arvadostest.SpectatorToken)
141         resp := s.testRequest(req)
142         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
143         s.checkJSONErrorMatches(c, resp, `.*not found`)
144 }
145
146 func (s *FederationSuite) TestGetUnknownRemote(c *check.C) {
147         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zz404-", 1), nil)
148         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
149         resp := s.testRequest(req)
150         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
151         s.checkJSONErrorMatches(c, resp, `.*no proxy available for cluster zz404`)
152 }
153
154 func (s *FederationSuite) TestRemoteError(c *check.C) {
155         rc := s.testHandler.Cluster.RemoteClusters["zzzzz"]
156         rc.Scheme = "https"
157         s.testHandler.Cluster.RemoteClusters["zzzzz"] = rc
158
159         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
160         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
161         resp := s.testRequest(req)
162         c.Check(resp.StatusCode, check.Equals, http.StatusBadGateway)
163         s.checkJSONErrorMatches(c, resp, `.*HTTP response to HTTPS client`)
164 }
165
166 func (s *FederationSuite) TestGetRemoteWorkflow(c *check.C) {
167         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
168         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
169         resp := s.testRequest(req)
170         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
171         var wf arvados.Workflow
172         c.Check(json.NewDecoder(resp.Body).Decode(&wf), check.IsNil)
173         c.Check(wf.UUID, check.Equals, arvadostest.WorkflowWithDefinitionYAMLUUID)
174         c.Check(wf.OwnerUUID, check.Equals, arvadostest.ActiveUserUUID)
175 }
176
177 func (s *FederationSuite) TestOptionsMethod(c *check.C) {
178         req := httptest.NewRequest("OPTIONS", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
179         req.Header.Set("Origin", "https://example.com")
180         resp := s.testRequest(req)
181         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
182         body, err := ioutil.ReadAll(resp.Body)
183         c.Check(err, check.IsNil)
184         c.Check(string(body), check.Equals, "")
185         c.Check(resp.Header.Get("Access-Control-Allow-Origin"), check.Equals, "*")
186         for _, hdr := range []string{"Authorization", "Content-Type"} {
187                 c.Check(resp.Header.Get("Access-Control-Allow-Headers"), check.Matches, ".*"+hdr+".*")
188         }
189         for _, method := range []string{"GET", "HEAD", "PUT", "POST", "DELETE"} {
190                 c.Check(resp.Header.Get("Access-Control-Allow-Methods"), check.Matches, ".*"+method+".*")
191         }
192 }
193
194 func (s *FederationSuite) TestRemoteWithTokenInQuery(c *check.C) {
195         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zmock-", 1)+"?api_token="+arvadostest.ActiveToken, nil)
196         s.testRequest(req)
197         c.Assert(len(s.remoteMockRequests), check.Equals, 1)
198         pr := s.remoteMockRequests[0]
199         // Token is salted and moved from query to Authorization header.
200         c.Check(pr.URL.String(), check.Not(check.Matches), `.*api_token=.*`)
201         c.Check(pr.Header.Get("Authorization"), check.Equals, "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/7fd31b61f39c0e82a4155592163218272cedacdc")
202 }
203
204 func (s *FederationSuite) TestLocalTokenSalted(c *check.C) {
205         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zmock-", 1), nil)
206         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
207         s.testRequest(req)
208         c.Assert(len(s.remoteMockRequests), check.Equals, 1)
209         pr := s.remoteMockRequests[0]
210         // The salted token here has a "zzzzz-" UUID instead of a
211         // "ztest-" UUID because ztest's local database has the
212         // "zzzzz-" test fixtures. The "secret" part is HMAC(sha1,
213         // arvadostest.ActiveToken, "zmock") = "7fd3...".
214         c.Check(pr.Header.Get("Authorization"), check.Equals, "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/7fd31b61f39c0e82a4155592163218272cedacdc")
215 }
216
217 func (s *FederationSuite) TestRemoteTokenNotSalted(c *check.C) {
218         // remoteToken can be any v1 token that doesn't appear in
219         // ztest's local db.
220         remoteToken := "abcdef00000000000000000000000000000000000000000000"
221         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zmock-", 1), nil)
222         req.Header.Set("Authorization", "Bearer "+remoteToken)
223         s.testRequest(req)
224         c.Assert(len(s.remoteMockRequests), check.Equals, 1)
225         pr := s.remoteMockRequests[0]
226         c.Check(pr.Header.Get("Authorization"), check.Equals, "Bearer "+remoteToken)
227 }
228
229 func (s *FederationSuite) TestWorkflowCRUD(c *check.C) {
230         wf := arvados.Workflow{
231                 Description: "TestCRUD",
232         }
233         {
234                 body := &strings.Builder{}
235                 json.NewEncoder(body).Encode(&wf)
236                 req := httptest.NewRequest("POST", "/arvados/v1/workflows", strings.NewReader(url.Values{
237                         "workflow": {body.String()},
238                 }.Encode()))
239                 req.Header.Set("Content-type", "application/x-www-form-urlencoded")
240                 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
241                 rec := httptest.NewRecorder()
242                 s.remoteServer.Server.Handler.ServeHTTP(rec, req) // direct to remote -- can't proxy a create req because no uuid
243                 resp := rec.Result()
244                 s.checkResponseOK(c, resp)
245                 json.NewDecoder(resp.Body).Decode(&wf)
246
247                 defer func() {
248                         req := httptest.NewRequest("DELETE", "/arvados/v1/workflows/"+wf.UUID, nil)
249                         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
250                         s.remoteServer.Server.Handler.ServeHTTP(httptest.NewRecorder(), req)
251                 }()
252                 c.Check(wf.UUID, check.Not(check.Equals), "")
253
254                 c.Assert(wf.ModifiedAt, check.NotNil)
255                 c.Logf("wf.ModifiedAt: %v", wf.ModifiedAt)
256                 c.Check(time.Since(*wf.ModifiedAt) < time.Minute, check.Equals, true)
257         }
258         for _, method := range []string{"PATCH", "PUT", "POST"} {
259                 form := url.Values{
260                         "workflow": {`{"description": "Updated with ` + method + `"}`},
261                 }
262                 if method == "POST" {
263                         form["_method"] = []string{"PATCH"}
264                 }
265                 req := httptest.NewRequest(method, "/arvados/v1/workflows/"+wf.UUID, strings.NewReader(form.Encode()))
266                 req.Header.Set("Content-type", "application/x-www-form-urlencoded")
267                 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
268                 resp := s.testRequest(req)
269                 s.checkResponseOK(c, resp)
270                 err := json.NewDecoder(resp.Body).Decode(&wf)
271                 c.Check(err, check.IsNil)
272
273                 c.Check(wf.Description, check.Equals, "Updated with "+method)
274         }
275         {
276                 req := httptest.NewRequest("DELETE", "/arvados/v1/workflows/"+wf.UUID, nil)
277                 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
278                 resp := s.testRequest(req)
279                 s.checkResponseOK(c, resp)
280                 err := json.NewDecoder(resp.Body).Decode(&wf)
281                 c.Check(err, check.IsNil)
282         }
283         {
284                 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+wf.UUID, nil)
285                 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
286                 resp := s.testRequest(req)
287                 c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
288         }
289 }
290
291 func (s *FederationSuite) checkResponseOK(c *check.C, resp *http.Response) {
292         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
293         if resp.StatusCode != http.StatusOK {
294                 body, err := ioutil.ReadAll(resp.Body)
295                 c.Logf("... response body = %q, %v\n", body, err)
296         }
297 }
298
299 func (s *FederationSuite) checkJSONErrorMatches(c *check.C, resp *http.Response, re string) {
300         var jresp httpserver.ErrorResponse
301         err := json.NewDecoder(resp.Body).Decode(&jresp)
302         c.Check(err, check.IsNil)
303         c.Assert(len(jresp.Errors), check.Equals, 1)
304         c.Check(jresp.Errors[0], check.Matches, re)
305 }
306
307 func (s *FederationSuite) localServiceReturns404(c *check.C) *httpserver.Server {
308         srv := &httpserver.Server{
309                 Server: http.Server{
310                         Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
311                                 w.WriteHeader(404)
312                         }),
313                 },
314         }
315
316         c.Assert(srv.Start(), check.IsNil)
317
318         np := arvados.NodeProfile{
319                 Controller: arvados.SystemServiceInstance{Listen: ":"},
320                 RailsAPI: arvados.SystemServiceInstance{Listen: srv.Addr,
321                         TLS: false, Insecure: true}}
322         s.testHandler.Cluster.NodeProfiles["*"] = np
323         s.testHandler.NodeProfile = &np
324
325         return srv
326 }
327
328 func (s *FederationSuite) TestGetLocalCollection(c *check.C) {
329         np := arvados.NodeProfile{
330                 Controller: arvados.SystemServiceInstance{Listen: ":"},
331                 RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
332                         TLS: true, Insecure: true}}
333         s.testHandler.Cluster.ClusterID = "zzzzz"
334         s.testHandler.Cluster.NodeProfiles["*"] = np
335         s.testHandler.NodeProfile = &np
336
337         req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementCollection, nil)
338         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
339         resp := s.testRequest(req)
340
341         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
342         var col arvados.Collection
343         c.Check(json.NewDecoder(resp.Body).Decode(&col), check.IsNil)
344         c.Check(col.UUID, check.Equals, arvadostest.UserAgreementCollection)
345         c.Check(col.ManifestText, check.Matches,
346                 `\. 6a4ff0499484c6c79c95cd8c566bd25f\+249025\+A[0-9a-f]{40}@[0-9a-f]{8} 0:249025:GNU_General_Public_License,_version_3.pdf
347 `)
348 }
349
350 func (s *FederationSuite) TestGetRemoteCollection(c *check.C) {
351         defer s.localServiceReturns404(c).Close()
352
353         req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementCollection, nil)
354         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
355         resp := s.testRequest(req)
356         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
357         var col arvados.Collection
358         c.Check(json.NewDecoder(resp.Body).Decode(&col), check.IsNil)
359         c.Check(col.UUID, check.Equals, arvadostest.UserAgreementCollection)
360         c.Check(col.ManifestText, check.Matches,
361                 `\. 6a4ff0499484c6c79c95cd8c566bd25f\+249025\+Rzzzzz-[0-9a-f]{40}@[0-9a-f]{8} 0:249025:GNU_General_Public_License,_version_3.pdf
362 `)
363 }
364
365 func (s *FederationSuite) TestGetRemoteCollectionError(c *check.C) {
366         defer s.localServiceReturns404(c).Close()
367
368         req := httptest.NewRequest("GET", "/arvados/v1/collections/zzzzz-4zz18-fakefakefakefak", nil)
369         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
370         resp := s.testRequest(req)
371         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
372 }
373
374 func (s *FederationSuite) TestSignedLocatorPattern(c *check.C) {
375         // Confirm the regular expression identifies other groups of hints correctly
376         c.Check(keepclient.SignedLocatorRe.FindStringSubmatch(`6a4ff0499484c6c79c95cd8c566bd25f+249025+B1+C2+A05227438989d04712ea9ca1c91b556cef01d5cc7@5ba5405b+D3+E4`),
377                 check.DeepEquals,
378                 []string{"6a4ff0499484c6c79c95cd8c566bd25f+249025+B1+C2+A05227438989d04712ea9ca1c91b556cef01d5cc7@5ba5405b+D3+E4",
379                         "6a4ff0499484c6c79c95cd8c566bd25f",
380                         "+249025",
381                         "+B1+C2", "+C2",
382                         "+A05227438989d04712ea9ca1c91b556cef01d5cc7@5ba5405b",
383                         "05227438989d04712ea9ca1c91b556cef01d5cc7", "5ba5405b",
384                         "+D3+E4", "+E4"})
385 }
386
387 func (s *FederationSuite) TestGetLocalCollectionByPDH(c *check.C) {
388         np := arvados.NodeProfile{
389                 Controller: arvados.SystemServiceInstance{Listen: ":"},
390                 RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
391                         TLS: true, Insecure: true}}
392         s.testHandler.Cluster.NodeProfiles["*"] = np
393         s.testHandler.NodeProfile = &np
394
395         req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementPDH, nil)
396         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
397         resp := s.testRequest(req)
398
399         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
400         var col arvados.Collection
401         c.Check(json.NewDecoder(resp.Body).Decode(&col), check.IsNil)
402         c.Check(col.PortableDataHash, check.Equals, arvadostest.UserAgreementPDH)
403         c.Check(col.ManifestText, check.Matches,
404                 `\. 6a4ff0499484c6c79c95cd8c566bd25f\+249025\+A[0-9a-f]{40}@[0-9a-f]{8} 0:249025:GNU_General_Public_License,_version_3.pdf
405 `)
406 }
407
408 func (s *FederationSuite) TestGetRemoteCollectionByPDH(c *check.C) {
409         defer s.localServiceReturns404(c).Close()
410
411         req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementPDH, nil)
412         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
413         resp := s.testRequest(req)
414
415         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
416
417         var col arvados.Collection
418         c.Check(json.NewDecoder(resp.Body).Decode(&col), check.IsNil)
419         c.Check(col.PortableDataHash, check.Equals, arvadostest.UserAgreementPDH)
420         c.Check(col.ManifestText, check.Matches,
421                 `\. 6a4ff0499484c6c79c95cd8c566bd25f\+249025\+Rzzzzz-[0-9a-f]{40}@[0-9a-f]{8} 0:249025:GNU_General_Public_License,_version_3.pdf
422 `)
423 }
424
425 func (s *FederationSuite) TestGetCollectionByPDHError(c *check.C) {
426         defer s.localServiceReturns404(c).Close()
427
428         req := httptest.NewRequest("GET", "/arvados/v1/collections/99999999999999999999999999999999+99", nil)
429         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
430
431         resp := s.testRequest(req)
432         defer resp.Body.Close()
433
434         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
435 }
436
437 func (s *FederationSuite) TestGetCollectionByPDHErrorBadHash(c *check.C) {
438         defer s.localServiceReturns404(c).Close()
439
440         srv2 := &httpserver.Server{
441                 Server: http.Server{
442                         Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
443                                 w.WriteHeader(200)
444                                 // Return a collection where the hash
445                                 // of the manifest text doesn't match
446                                 // PDH that was requested.
447                                 var col arvados.Collection
448                                 col.PortableDataHash = "99999999999999999999999999999999+99"
449                                 col.ManifestText = `. 6a4ff0499484c6c79c95cd8c566bd25f\+249025 0:249025:GNU_General_Public_License,_version_3.pdf
450 `
451                                 enc := json.NewEncoder(w)
452                                 enc.Encode(col)
453                         }),
454                 },
455         }
456
457         c.Assert(srv2.Start(), check.IsNil)
458         defer srv2.Close()
459
460         // Direct zzzzz to service that returns a 200 result with a bogus manifest_text
461         s.testHandler.Cluster.RemoteClusters["zzzzz"] = arvados.RemoteCluster{
462                 Host:   srv2.Addr,
463                 Proxy:  true,
464                 Scheme: "http",
465         }
466
467         req := httptest.NewRequest("GET", "/arvados/v1/collections/99999999999999999999999999999999+99", nil)
468         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
469
470         resp := s.testRequest(req)
471         defer resp.Body.Close()
472
473         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
474 }
475
476 func (s *FederationSuite) TestSaltedTokenGetCollectionByPDH(c *check.C) {
477         np := arvados.NodeProfile{
478                 Controller: arvados.SystemServiceInstance{Listen: ":"},
479                 RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
480                         TLS: true, Insecure: true}}
481         s.testHandler.Cluster.NodeProfiles["*"] = np
482         s.testHandler.NodeProfile = &np
483
484         req := httptest.NewRequest("GET", "/arvados/v1/collections/"+arvadostest.UserAgreementPDH, nil)
485         req.Header.Set("Authorization", "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/282d7d172b6cfdce364c5ed12ddf7417b2d00065")
486         resp := s.testRequest(req)
487
488         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
489         var col arvados.Collection
490         c.Check(json.NewDecoder(resp.Body).Decode(&col), check.IsNil)
491         c.Check(col.PortableDataHash, check.Equals, arvadostest.UserAgreementPDH)
492         c.Check(col.ManifestText, check.Matches,
493                 `\. 6a4ff0499484c6c79c95cd8c566bd25f\+249025\+A[0-9a-f]{40}@[0-9a-f]{8} 0:249025:GNU_General_Public_License,_version_3.pdf
494 `)
495 }
496
497 func (s *FederationSuite) TestSaltedTokenGetCollectionByPDHError(c *check.C) {
498         np := arvados.NodeProfile{
499                 Controller: arvados.SystemServiceInstance{Listen: ":"},
500                 RailsAPI: arvados.SystemServiceInstance{Listen: os.Getenv("ARVADOS_TEST_API_HOST"),
501                         TLS: true, Insecure: true}}
502         s.testHandler.Cluster.NodeProfiles["*"] = np
503         s.testHandler.NodeProfile = &np
504
505         req := httptest.NewRequest("GET", "/arvados/v1/collections/99999999999999999999999999999999+99", nil)
506         req.Header.Set("Authorization", "Bearer v2/zzzzz-gj3su-077z32aux8dg2s1/282d7d172b6cfdce364c5ed12ddf7417b2d00065")
507         resp := s.testRequest(req)
508
509         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
510 }
511
512 func (s *FederationSuite) TestGetRemoteContainerRequest(c *check.C) {
513         defer s.localServiceReturns404(c).Close()
514         req := httptest.NewRequest("GET", "/arvados/v1/container_requests/"+arvadostest.QueuedContainerRequestUUID, nil)
515         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
516         resp := s.testRequest(req)
517         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
518         var cr arvados.ContainerRequest
519         c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
520         c.Check(cr.UUID, check.Equals, arvadostest.QueuedContainerRequestUUID)
521         c.Check(cr.Priority, check.Equals, 1)
522 }
523
524 func (s *FederationSuite) TestUpdateRemoteContainerRequest(c *check.C) {
525         defer s.localServiceReturns404(c).Close()
526         req := httptest.NewRequest("PATCH", "/arvados/v1/container_requests/"+arvadostest.QueuedContainerRequestUUID,
527                 strings.NewReader(`{"container_request": {"priority": 696}}`))
528         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
529         req.Header.Set("Content-type", "application/json")
530         resp := s.testRequest(req)
531         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
532         var cr arvados.ContainerRequest
533         c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
534         c.Check(cr.UUID, check.Equals, arvadostest.QueuedContainerRequestUUID)
535         c.Check(cr.Priority, check.Equals, 696)
536 }
537
538 func (s *FederationSuite) TestCreateRemoteContainerRequest1(c *check.C) {
539         defer s.localServiceReturns404(c).Close()
540         req := httptest.NewRequest("POST", "/arvados/v1/container_requests",
541                 strings.NewReader(`{
542   "cluster_id": "zzzzz",
543   "container_request": {
544     "name": "hello world",
545     "state": "Uncommitted",
546     "output_path": "/",
547     "container_image": "123",
548     "command": ["abc"]
549   }
550 }
551 `))
552         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
553         req.Header.Set("Content-type", "application/json")
554         resp := s.testRequest(req)
555         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
556         var cr arvados.ContainerRequest
557         c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
558         c.Check(cr.Name, check.Equals, "hello world")
559         c.Check(strings.HasPrefix(cr.UUID, "zzzzz-"), check.Equals, true)
560 }
561
562 func (s *FederationSuite) TestCreateRemoteContainerRequest2(c *check.C) {
563         defer s.localServiceReturns404(c).Close()
564         // pass cluster_id via query parameter, this allows arvados-controller
565         // to avoid parsing the body
566         req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zzzzz",
567                 strings.NewReader(`{
568   "container_request": {
569     "name": "hello world",
570     "state": "Uncommitted",
571     "output_path": "/",
572     "container_image": "123",
573     "command": ["abc"]
574   }
575 }
576 `))
577         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
578         req.Header.Set("Content-type", "application/json")
579         resp := s.testRequest(req)
580         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
581         var cr arvados.ContainerRequest
582         c.Check(json.NewDecoder(resp.Body).Decode(&cr), check.IsNil)
583         c.Check(cr.Name, check.Equals, "hello world")
584         c.Check(strings.HasPrefix(cr.UUID, "zzzzz-"), check.Equals, true)
585 }
586
587 func (s *FederationSuite) TestCreateRemoteContainerRequestError(c *check.C) {
588         defer s.localServiceReturns404(c).Close()
589         // pass cluster_id via query parameter, this allows arvados-controller
590         // to avoid parsing the body
591         req := httptest.NewRequest("POST", "/arvados/v1/container_requests?cluster_id=zz404",
592                 strings.NewReader(`{
593   "container_request": {
594     "name": "hello world",
595     "state": "Uncommitted",
596     "output_path": "/",
597     "container_image": "123",
598     "command": ["abc"]
599   }
600 }
601 `))
602         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
603         req.Header.Set("Content-type", "application/json")
604         resp := s.testRequest(req)
605         c.Check(resp.StatusCode, check.Equals, http.StatusNotFound)
606 }
607
608 func (s *FederationSuite) TestGetRemoteContainer(c *check.C) {
609         defer s.localServiceReturns404(c).Close()
610         req := httptest.NewRequest("GET", "/arvados/v1/containers/"+arvadostest.QueuedContainerUUID, nil)
611         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
612         resp := s.testRequest(req)
613         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
614         var cn arvados.Container
615         c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
616         c.Check(cn.UUID, check.Equals, arvadostest.QueuedContainerUUID)
617 }
618
619 func (s *FederationSuite) TestListRemoteContainer(c *check.C) {
620         defer s.localServiceReturns404(c).Close()
621         req := httptest.NewRequest("GET", "/arvados/v1/containers?filters="+
622                 url.QueryEscape(fmt.Sprintf(`[["uuid", "in", ["%v"]]]`, arvadostest.QueuedContainerUUID)), nil)
623         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
624         resp := s.testRequest(req)
625         c.Check(resp.StatusCode, check.Equals, http.StatusOK)
626         var cn arvados.ContainerList
627         c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
628         c.Check(cn.Items[0].UUID, check.Equals, arvadostest.QueuedContainerUUID)
629 }
630
631 func (s *FederationSuite) TestListMultiRemoteContainers(c *check.C) {
632         defer s.localServiceReturns404(c).Close()
633         req := httptest.NewRequest("GET", "/arvados/v1/containers?filters="+
634                 url.QueryEscape(fmt.Sprintf(`[["uuid", "in", ["%v", "zhome-xvhdp-cr5queuedcontnr"]]]`, arvadostest.QueuedContainerUUID)), nil)
635         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
636         resp := s.testRequest(req)
637         log.Printf("got %+v", resp)
638         c.Assert(resp.StatusCode, check.Equals, http.StatusOK)
639         var cn arvados.ContainerList
640         c.Check(json.NewDecoder(resp.Body).Decode(&cn), check.IsNil)
641         c.Check(cn.Items[0].UUID, check.Equals, arvadostest.QueuedContainerUUID)
642 }