1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
15 "git.curoverse.com/arvados.git/sdk/go/arvados"
16 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
17 "git.curoverse.com/arvados.git/sdk/go/httpserver"
18 "github.com/Sirupsen/logrus"
19 check "gopkg.in/check.v1"
22 // Gocheck boilerplate
23 var _ = check.Suite(&FederationSuite{})
25 type FederationSuite struct {
27 localServer *httpserver.Server
28 remoteServer *httpserver.Server
29 remoteMock *httpserver.Server
30 remoteMockRequests []http.Request
34 func (s *FederationSuite) SetUpTest(c *check.C) {
36 s.log.Formatter = &logrus.JSONFormatter{}
37 s.log.Out = &logWriter{c.Log}
39 s.remoteServer = newServerFromIntegrationTestEnv(c)
40 c.Assert(s.remoteServer.Start(), check.IsNil)
42 s.remoteMock = newServerFromIntegrationTestEnv(c)
43 s.remoteMock.Server.Handler = http.HandlerFunc(s.remoteMockHandler)
44 c.Assert(s.remoteMock.Start(), check.IsNil)
46 nodeProfile := arvados.NodeProfile{
47 Controller: arvados.SystemServiceInstance{Listen: ":"},
48 RailsAPI: arvados.SystemServiceInstance{Listen: ":1"}, // local reqs will error "connection refused"
50 s.handler = &Handler{Cluster: &arvados.Cluster{
52 NodeProfiles: map[string]arvados.NodeProfile{
55 }, NodeProfile: &nodeProfile}
56 s.localServer = newServerFromIntegrationTestEnv(c)
57 s.localServer.Server.Handler = httpserver.AddRequestIDs(httpserver.LogRequests(s.log, s.handler))
59 s.handler.Cluster.RemoteClusters = map[string]arvados.RemoteCluster{
61 Host: s.remoteServer.Addr,
66 Host: s.remoteMock.Addr,
72 c.Assert(s.localServer.Start(), check.IsNil)
75 func (s *FederationSuite) remoteMockHandler(w http.ResponseWriter, req *http.Request) {
76 s.remoteMockRequests = append(s.remoteMockRequests, *req)
79 func (s *FederationSuite) TearDownTest(c *check.C) {
80 if s.remoteServer != nil {
81 s.remoteServer.Close()
83 if s.localServer != nil {
88 func (s *FederationSuite) TestLocalRequest(c *check.C) {
89 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zhome-", 1), nil)
90 resp := httptest.NewRecorder()
91 s.handler.ServeHTTP(resp, req)
92 s.checkHandledLocally(c, resp)
95 func (s *FederationSuite) checkHandledLocally(c *check.C, resp *httptest.ResponseRecorder) {
96 // Our "home" controller can't handle local requests because
97 // it doesn't have its own stub/test Rails API, so we rely on
98 // "connection refused" to indicate the controller tried to
99 // proxy the request to its local Rails API.
100 c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
101 s.checkJSONErrorMatches(c, resp, `.*connection refused`)
104 func (s *FederationSuite) TestNoAuth(c *check.C) {
105 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
106 resp := httptest.NewRecorder()
107 s.handler.ServeHTTP(resp, req)
108 c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
109 s.checkJSONErrorMatches(c, resp, `Not logged in`)
112 func (s *FederationSuite) TestBadAuth(c *check.C) {
113 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
114 req.Header.Set("Authorization", "Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
115 resp := httptest.NewRecorder()
116 s.handler.ServeHTTP(resp, req)
117 c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
118 s.checkJSONErrorMatches(c, resp, `Not logged in`)
121 func (s *FederationSuite) TestNoAccess(c *check.C) {
122 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
123 req.Header.Set("Authorization", "Bearer "+arvadostest.SpectatorToken)
124 resp := httptest.NewRecorder()
125 s.handler.ServeHTTP(resp, req)
126 c.Check(resp.Code, check.Equals, http.StatusNotFound)
127 s.checkJSONErrorMatches(c, resp, `.*not found`)
130 func (s *FederationSuite) TestGetUnknownRemote(c *check.C) {
131 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zz404-", 1), nil)
132 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
133 resp := httptest.NewRecorder()
134 s.handler.ServeHTTP(resp, req)
135 c.Check(resp.Code, check.Equals, http.StatusNotFound)
136 s.checkJSONErrorMatches(c, resp, `.*no proxy available for cluster zz404`)
139 func (s *FederationSuite) TestRemoteError(c *check.C) {
140 rc := s.handler.Cluster.RemoteClusters["zzzzz"]
142 s.handler.Cluster.RemoteClusters["zzzzz"] = rc
144 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
145 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
146 resp := httptest.NewRecorder()
147 s.handler.ServeHTTP(resp, req)
148 c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
149 s.checkJSONErrorMatches(c, resp, `.*HTTP response to HTTPS client`)
152 func (s *FederationSuite) TestGetRemoteWorkflow(c *check.C) {
153 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
154 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
155 resp := httptest.NewRecorder()
156 s.handler.ServeHTTP(resp, req)
157 c.Check(resp.Code, check.Equals, http.StatusOK)
158 var wf arvados.Workflow
159 c.Check(json.Unmarshal(resp.Body.Bytes(), &wf), check.IsNil)
160 c.Check(wf.UUID, check.Equals, arvadostest.WorkflowWithDefinitionYAMLUUID)
161 c.Check(wf.OwnerUUID, check.Equals, arvadostest.ActiveUserUUID)
164 func (s *FederationSuite) TestRemoteWithTokenInQuery(c *check.C) {
165 req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zmock-", 1)+"?api_token="+arvadostest.ActiveToken, nil)
166 s.handler.ServeHTTP(httptest.NewRecorder(), req)
167 c.Assert(len(s.remoteMockRequests), check.Equals, 1)
168 c.Check(s.remoteMockRequests[0].URL.String(), check.Not(check.Matches), `.*api_token=.*`)
171 func (s *FederationSuite) TestUpdateRemoteWorkflow(c *check.C) {
172 updateDescription := func(descr string) *httptest.ResponseRecorder {
173 req := httptest.NewRequest("PATCH", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, strings.NewReader(url.Values{
174 "workflow": {`{"description":"` + descr + `"}`},
176 req.Header.Set("Content-type", "application/x-www-form-urlencoded")
177 req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
178 resp := httptest.NewRecorder()
179 s.handler.ServeHTTP(resp, req)
180 s.checkResponseOK(c, resp)
184 // Update description twice so running this test twice in a
185 // row still causes ModifiedAt to change
186 updateDescription("updated once by TestUpdateRemoteWorkflow")
187 resp := updateDescription("updated twice by TestUpdateRemoteWorkflow")
189 var wf arvados.Workflow
190 c.Check(json.Unmarshal(resp.Body.Bytes(), &wf), check.IsNil)
191 c.Check(wf.UUID, check.Equals, arvadostest.WorkflowWithDefinitionYAMLUUID)
192 c.Assert(wf.ModifiedAt, check.NotNil)
193 c.Logf("%s", *wf.ModifiedAt)
194 c.Check(time.Since(*wf.ModifiedAt) < time.Minute, check.Equals, true)
197 func (s *FederationSuite) checkResponseOK(c *check.C, resp *httptest.ResponseRecorder) {
198 c.Check(resp.Code, check.Equals, http.StatusOK)
199 if resp.Code != http.StatusOK {
200 c.Logf("... response body = %s\n", resp.Body.String())
204 func (s *FederationSuite) checkJSONErrorMatches(c *check.C, resp *httptest.ResponseRecorder, re string) {
205 var jresp httpserver.ErrorResponse
206 err := json.Unmarshal(resp.Body.Bytes(), &jresp)
207 c.Check(err, check.IsNil)
208 c.Assert(len(jresp.Errors), check.Equals, 1)
209 c.Check(jresp.Errors[0], check.Matches, re)