13493: Test PATCH request.
[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         "net/http"
10         "net/http/httptest"
11         "net/url"
12         "strings"
13         "time"
14
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"
20 )
21
22 // Gocheck boilerplate
23 var _ = check.Suite(&FederationSuite{})
24
25 type FederationSuite struct {
26         log          *logrus.Logger
27         localServer  *httpserver.Server
28         remoteServer *httpserver.Server
29         handler      *Handler
30 }
31
32 func (s *FederationSuite) SetUpTest(c *check.C) {
33         s.log = logrus.New()
34         s.log.Formatter = &logrus.JSONFormatter{}
35         s.log.Out = &logWriter{c.Log}
36
37         s.remoteServer = newServerFromIntegrationTestEnv(c)
38         c.Assert(s.remoteServer.Start(), check.IsNil)
39
40         nodeProfile := arvados.NodeProfile{
41                 Controller: arvados.SystemServiceInstance{Listen: ":"},
42                 RailsAPI:   arvados.SystemServiceInstance{Listen: ":1"}, // local reqs will error "connection refused"
43         }
44         s.handler = &Handler{Cluster: &arvados.Cluster{
45                 ClusterID: "zhome",
46                 NodeProfiles: map[string]arvados.NodeProfile{
47                         "*": nodeProfile,
48                 },
49         }, NodeProfile: &nodeProfile}
50         s.localServer = newServerFromIntegrationTestEnv(c)
51         s.localServer.Server.Handler = httpserver.AddRequestIDs(httpserver.LogRequests(s.log, s.handler))
52         s.handler.Cluster.RemoteClusters = map[string]arvados.RemoteCluster{
53                 "zzzzz": {
54                         Host:   s.remoteServer.Addr,
55                         Proxy:  true,
56                         Scheme: "http",
57                 },
58         }
59         c.Assert(s.localServer.Start(), check.IsNil)
60 }
61
62 func (s *FederationSuite) TearDownTest(c *check.C) {
63         if s.remoteServer != nil {
64                 s.remoteServer.Close()
65         }
66         if s.localServer != nil {
67                 s.localServer.Close()
68         }
69 }
70
71 func (s *FederationSuite) TestLocalRequest(c *check.C) {
72         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zhome-", 1), nil)
73         resp := httptest.NewRecorder()
74         s.handler.ServeHTTP(resp, req)
75         s.checkHandledLocally(c, resp)
76 }
77
78 func (s *FederationSuite) checkHandledLocally(c *check.C, resp *httptest.ResponseRecorder) {
79         // Our "home" controller can't handle local requests because
80         // it doesn't have its own stub/test Rails API, so we rely on
81         // "connection refused" to indicate the controller tried to
82         // proxy the request to its local Rails API.
83         c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
84         s.checkJSONErrorMatches(c, resp, `.*connection refused`)
85 }
86
87 func (s *FederationSuite) TestNoAuth(c *check.C) {
88         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
89         resp := httptest.NewRecorder()
90         s.handler.ServeHTTP(resp, req)
91         c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
92         s.checkJSONErrorMatches(c, resp, `Not logged in`)
93 }
94
95 func (s *FederationSuite) TestBadAuth(c *check.C) {
96         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
97         req.Header.Set("Authorization", "Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
98         resp := httptest.NewRecorder()
99         s.handler.ServeHTTP(resp, req)
100         c.Check(resp.Code, check.Equals, http.StatusUnauthorized)
101         s.checkJSONErrorMatches(c, resp, `Not logged in`)
102 }
103
104 func (s *FederationSuite) TestNoAccess(c *check.C) {
105         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
106         req.Header.Set("Authorization", "Bearer "+arvadostest.SpectatorToken)
107         resp := httptest.NewRecorder()
108         s.handler.ServeHTTP(resp, req)
109         c.Check(resp.Code, check.Equals, http.StatusNotFound)
110         s.checkJSONErrorMatches(c, resp, `.*not found`)
111 }
112
113 func (s *FederationSuite) TestGetUnknownRemote(c *check.C) {
114         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+strings.Replace(arvadostest.WorkflowWithDefinitionYAMLUUID, "zzzzz-", "zz404-", 1), nil)
115         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
116         resp := httptest.NewRecorder()
117         s.handler.ServeHTTP(resp, req)
118         c.Check(resp.Code, check.Equals, http.StatusNotFound)
119         s.checkJSONErrorMatches(c, resp, `.*no proxy available for cluster zz404`)
120 }
121
122 func (s *FederationSuite) TestRemoteError(c *check.C) {
123         rc := s.handler.Cluster.RemoteClusters["zzzzz"]
124         rc.Scheme = "https"
125         s.handler.Cluster.RemoteClusters["zzzzz"] = rc
126
127         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
128         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
129         resp := httptest.NewRecorder()
130         s.handler.ServeHTTP(resp, req)
131         c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
132         s.checkJSONErrorMatches(c, resp, `.*HTTP response to HTTPS client`)
133 }
134
135 func (s *FederationSuite) TestGetRemoteWorkflow(c *check.C) {
136         req := httptest.NewRequest("GET", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, nil)
137         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
138         resp := httptest.NewRecorder()
139         s.handler.ServeHTTP(resp, req)
140         c.Check(resp.Code, check.Equals, http.StatusOK)
141         var wf arvados.Workflow
142         c.Check(json.Unmarshal(resp.Body.Bytes(), &wf), check.IsNil)
143         c.Check(wf.UUID, check.Equals, arvadostest.WorkflowWithDefinitionYAMLUUID)
144         c.Check(wf.OwnerUUID, check.Equals, arvadostest.ActiveUserUUID)
145 }
146
147 func (s *FederationSuite) TestUpdateRemoteWorkflow(c *check.C) {
148         req := httptest.NewRequest("PATCH", "/arvados/v1/workflows/"+arvadostest.WorkflowWithDefinitionYAMLUUID, strings.NewReader(url.Values{
149                 "workflow": {`{"description":"updated by TestUpdateRemoteWorkflow"}`},
150         }.Encode()))
151         req.Header.Set("Content-type", "application/x-www-form-urlencoded")
152         req.Header.Set("Authorization", "Bearer "+arvadostest.ActiveToken)
153         resp := httptest.NewRecorder()
154         s.handler.ServeHTTP(resp, req)
155         s.checkResponseOK(c, resp)
156         var wf arvados.Workflow
157         c.Check(json.Unmarshal(resp.Body.Bytes(), &wf), check.IsNil)
158         c.Check(wf.UUID, check.Equals, arvadostest.WorkflowWithDefinitionYAMLUUID)
159         c.Assert(wf.ModifiedAt, check.NotNil)
160         c.Logf("%s", *wf.ModifiedAt)
161         c.Check(time.Since(*wf.ModifiedAt) < time.Minute, check.Equals, true)
162 }
163
164 func (s *FederationSuite) checkResponseOK(c *check.C, resp *httptest.ResponseRecorder) {
165         c.Check(resp.Code, check.Equals, http.StatusOK)
166         if resp.Code != http.StatusOK {
167                 c.Logf("... response body = %s\n", resp.Body.String())
168         }
169 }
170
171 func (s *FederationSuite) checkJSONErrorMatches(c *check.C, resp *httptest.ResponseRecorder, re string) {
172         var jresp httpserver.ErrorResponse
173         err := json.Unmarshal(resp.Body.Bytes(), &jresp)
174         c.Check(err, check.IsNil)
175         c.Assert(len(jresp.Errors), check.Equals, 1)
176         c.Check(jresp.Errors[0], check.Matches, re)
177 }