14360: Merge branch 'master' into 14360-dispatch-cloud
[arvados.git] / sdk / go / health / aggregator_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package health
6
7 import (
8         "encoding/json"
9         "net/http"
10         "net/http/httptest"
11         "strings"
12         "time"
13
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
16         "gopkg.in/check.v1"
17 )
18
19 type AggregatorSuite struct {
20         handler *Aggregator
21         req     *http.Request
22         resp    *httptest.ResponseRecorder
23 }
24
25 // Gocheck boilerplate
26 var _ = check.Suite(&AggregatorSuite{})
27
28 func (s *AggregatorSuite) TestInterface(c *check.C) {
29         var _ http.Handler = &Aggregator{}
30 }
31
32 func (s *AggregatorSuite) SetUpTest(c *check.C) {
33         s.handler = &Aggregator{Config: &arvados.Config{
34                 Clusters: map[string]arvados.Cluster{
35                         "zzzzz": {
36                                 ManagementToken: arvadostest.ManagementToken,
37                                 NodeProfiles:    map[string]arvados.NodeProfile{},
38                         },
39                 },
40         }}
41         s.req = httptest.NewRequest("GET", "/_health/all", nil)
42         s.req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
43         s.resp = httptest.NewRecorder()
44 }
45
46 func (s *AggregatorSuite) TestNoAuth(c *check.C) {
47         s.req.Header.Del("Authorization")
48         s.handler.ServeHTTP(s.resp, s.req)
49         s.checkError(c)
50         c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
51 }
52
53 func (s *AggregatorSuite) TestBadAuth(c *check.C) {
54         s.req.Header.Set("Authorization", "xyzzy")
55         s.handler.ServeHTTP(s.resp, s.req)
56         s.checkError(c)
57         c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
58 }
59
60 func (s *AggregatorSuite) TestEmptyConfig(c *check.C) {
61         s.handler.ServeHTTP(s.resp, s.req)
62         s.checkOK(c)
63 }
64
65 func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, string) {
66         srv := httptest.NewServer(handler)
67         var port string
68         if parts := strings.Split(srv.URL, ":"); len(parts) < 3 {
69                 panic(srv.URL)
70         } else {
71                 port = parts[len(parts)-1]
72         }
73         return srv, ":" + port
74 }
75
76 type unhealthyHandler struct{}
77
78 func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
79         if req.URL.Path == "/_health/ping" {
80                 resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
81         } else {
82                 http.Error(resp, "not found", http.StatusNotFound)
83         }
84 }
85
86 func (s *AggregatorSuite) TestUnhealthy(c *check.C) {
87         srv, listen := s.stubServer(&unhealthyHandler{})
88         defer srv.Close()
89         s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
90                 Keepstore: arvados.SystemServiceInstance{Listen: listen},
91         }
92         s.handler.ServeHTTP(s.resp, s.req)
93         s.checkUnhealthy(c)
94 }
95
96 type healthyHandler struct{}
97
98 func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
99         if req.URL.Path == "/_health/ping" {
100                 resp.Write([]byte(`{"health":"OK"}`))
101         } else {
102                 http.Error(resp, "not found", http.StatusNotFound)
103         }
104 }
105
106 func (s *AggregatorSuite) TestHealthy(c *check.C) {
107         srv, listen := s.stubServer(&healthyHandler{})
108         defer srv.Close()
109         s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
110                 Controller:    arvados.SystemServiceInstance{Listen: listen},
111                 DispatchCloud: arvados.SystemServiceInstance{Listen: listen},
112                 Keepbalance:   arvados.SystemServiceInstance{Listen: listen},
113                 Keepproxy:     arvados.SystemServiceInstance{Listen: listen},
114                 Keepstore:     arvados.SystemServiceInstance{Listen: listen},
115                 Keepweb:       arvados.SystemServiceInstance{Listen: listen},
116                 Nodemanager:   arvados.SystemServiceInstance{Listen: listen},
117                 RailsAPI:      arvados.SystemServiceInstance{Listen: listen},
118                 Websocket:     arvados.SystemServiceInstance{Listen: listen},
119                 Workbench:     arvados.SystemServiceInstance{Listen: listen},
120         }
121         s.handler.ServeHTTP(s.resp, s.req)
122         resp := s.checkOK(c)
123         svc := "keepstore+http://localhost" + listen + "/_health/ping"
124         c.Logf("%#v", resp)
125         ep := resp.Checks[svc]
126         c.Check(ep.Health, check.Equals, "OK")
127         c.Check(ep.HTTPStatusCode, check.Equals, 200)
128 }
129
130 func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
131         srvH, listenH := s.stubServer(&healthyHandler{})
132         defer srvH.Close()
133         srvU, listenU := s.stubServer(&unhealthyHandler{})
134         defer srvU.Close()
135         s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
136                 Controller:    arvados.SystemServiceInstance{Listen: listenH},
137                 DispatchCloud: arvados.SystemServiceInstance{Listen: listenH},
138                 Keepbalance:   arvados.SystemServiceInstance{Listen: listenH},
139                 Keepproxy:     arvados.SystemServiceInstance{Listen: listenH},
140                 Keepstore:     arvados.SystemServiceInstance{Listen: listenH},
141                 Keepweb:       arvados.SystemServiceInstance{Listen: listenH},
142                 Nodemanager:   arvados.SystemServiceInstance{Listen: listenH},
143                 RailsAPI:      arvados.SystemServiceInstance{Listen: listenH},
144                 Websocket:     arvados.SystemServiceInstance{Listen: listenH},
145                 Workbench:     arvados.SystemServiceInstance{Listen: listenH},
146         }
147         s.handler.Config.Clusters["zzzzz"].NodeProfiles["127.0.0.1"] = arvados.NodeProfile{
148                 Keepstore: arvados.SystemServiceInstance{Listen: listenU},
149         }
150         s.handler.ServeHTTP(s.resp, s.req)
151         resp := s.checkUnhealthy(c)
152         ep := resp.Checks["keepstore+http://localhost"+listenH+"/_health/ping"]
153         c.Check(ep.Health, check.Equals, "OK")
154         c.Check(ep.HTTPStatusCode, check.Equals, 200)
155         ep = resp.Checks["keepstore+http://127.0.0.1"+listenU+"/_health/ping"]
156         c.Check(ep.Health, check.Equals, "ERROR")
157         c.Check(ep.HTTPStatusCode, check.Equals, 200)
158         c.Logf("%#v", ep)
159 }
160
161 func (s *AggregatorSuite) checkError(c *check.C) {
162         c.Check(s.resp.Code, check.Not(check.Equals), http.StatusOK)
163         var resp ClusterHealthResponse
164         err := json.NewDecoder(s.resp.Body).Decode(&resp)
165         c.Check(err, check.IsNil)
166         c.Check(resp.Health, check.Not(check.Equals), "OK")
167 }
168
169 func (s *AggregatorSuite) checkUnhealthy(c *check.C) ClusterHealthResponse {
170         return s.checkResult(c, "ERROR")
171 }
172
173 func (s *AggregatorSuite) checkOK(c *check.C) ClusterHealthResponse {
174         return s.checkResult(c, "OK")
175 }
176
177 func (s *AggregatorSuite) checkResult(c *check.C, health string) ClusterHealthResponse {
178         c.Check(s.resp.Code, check.Equals, http.StatusOK)
179         var resp ClusterHealthResponse
180         err := json.NewDecoder(s.resp.Body).Decode(&resp)
181         c.Check(err, check.IsNil)
182         c.Check(resp.Health, check.Equals, health)
183         return resp
184 }
185
186 type slowHandler struct{}
187
188 func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
189         if req.URL.Path == "/_health/ping" {
190                 time.Sleep(3 * time.Second)
191                 resp.Write([]byte(`{"health":"OK"}`))
192         } else {
193                 http.Error(resp, "not found", http.StatusNotFound)
194         }
195 }
196
197 func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
198         s.handler.timeout = arvados.Duration(100 * time.Millisecond)
199         srv, listen := s.stubServer(&slowHandler{})
200         defer srv.Close()
201         s.handler.Config.Clusters["zzzzz"].NodeProfiles["localhost"] = arvados.NodeProfile{
202                 Keepstore: arvados.SystemServiceInstance{Listen: listen},
203         }
204         s.handler.ServeHTTP(s.resp, s.req)
205         resp := s.checkUnhealthy(c)
206         ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
207         c.Check(ep.Health, check.Equals, "ERROR")
208         c.Check(ep.HTTPStatusCode, check.Equals, 0)
209         rt, err := ep.ResponseTime.Float64()
210         c.Check(err, check.IsNil)
211         c.Check(rt > 0.005, check.Equals, true)
212 }