Merge branch '18947-githttpd'
[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.arvados.org/arvados.git/sdk/go/arvados"
15         "git.arvados.org/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{Cluster: &arvados.Cluster{
34                 ManagementToken: arvadostest.ManagementToken,
35         }}
36         s.req = httptest.NewRequest("GET", "/_health/all", nil)
37         s.req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
38         s.resp = httptest.NewRecorder()
39 }
40
41 func (s *AggregatorSuite) TestNoAuth(c *check.C) {
42         s.req.Header.Del("Authorization")
43         s.handler.ServeHTTP(s.resp, s.req)
44         s.checkError(c)
45         c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
46 }
47
48 func (s *AggregatorSuite) TestBadAuth(c *check.C) {
49         s.req.Header.Set("Authorization", "xyzzy")
50         s.handler.ServeHTTP(s.resp, s.req)
51         s.checkError(c)
52         c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
53 }
54
55 func (s *AggregatorSuite) TestNoServicesConfigured(c *check.C) {
56         s.handler.ServeHTTP(s.resp, s.req)
57         s.checkUnhealthy(c)
58 }
59
60 func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, string) {
61         srv := httptest.NewServer(handler)
62         var port string
63         if parts := strings.Split(srv.URL, ":"); len(parts) < 3 {
64                 panic(srv.URL)
65         } else {
66                 port = parts[len(parts)-1]
67         }
68         return srv, ":" + port
69 }
70
71 func (s *AggregatorSuite) TestUnhealthy(c *check.C) {
72         srv, listen := s.stubServer(&unhealthyHandler{})
73         defer srv.Close()
74         arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
75         s.handler.ServeHTTP(s.resp, s.req)
76         s.checkUnhealthy(c)
77 }
78
79 func (s *AggregatorSuite) TestHealthy(c *check.C) {
80         srv, listen := s.stubServer(&healthyHandler{})
81         defer srv.Close()
82         s.setAllServiceURLs(listen)
83         s.handler.ServeHTTP(s.resp, s.req)
84         resp := s.checkOK(c)
85         svc := "keepstore+http://localhost" + listen + "/_health/ping"
86         c.Logf("%#v", resp)
87         ep := resp.Checks[svc]
88         c.Check(ep.Health, check.Equals, "OK")
89         c.Check(ep.HTTPStatusCode, check.Equals, 200)
90 }
91
92 func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
93         srvH, listenH := s.stubServer(&healthyHandler{})
94         defer srvH.Close()
95         srvU, listenU := s.stubServer(&unhealthyHandler{})
96         defer srvU.Close()
97         s.setAllServiceURLs(listenH)
98         arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listenH+"/", "http://127.0.0.1"+listenU+"/")
99         s.handler.ServeHTTP(s.resp, s.req)
100         resp := s.checkUnhealthy(c)
101         ep := resp.Checks["keepstore+http://localhost"+listenH+"/_health/ping"]
102         c.Check(ep.Health, check.Equals, "OK")
103         c.Check(ep.HTTPStatusCode, check.Equals, 200)
104         ep = resp.Checks["keepstore+http://127.0.0.1"+listenU+"/_health/ping"]
105         c.Check(ep.Health, check.Equals, "ERROR")
106         c.Check(ep.HTTPStatusCode, check.Equals, 200)
107         c.Logf("%#v", ep)
108 }
109
110 func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
111         s.handler.timeout = arvados.Duration(100 * time.Millisecond)
112         srv, listen := s.stubServer(&slowHandler{})
113         defer srv.Close()
114         arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
115         s.handler.ServeHTTP(s.resp, s.req)
116         resp := s.checkUnhealthy(c)
117         ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
118         c.Check(ep.Health, check.Equals, "ERROR")
119         c.Check(ep.HTTPStatusCode, check.Equals, 0)
120         rt, err := ep.ResponseTime.Float64()
121         c.Check(err, check.IsNil)
122         c.Check(rt > 0.005, check.Equals, true)
123 }
124
125 func (s *AggregatorSuite) checkError(c *check.C) {
126         c.Check(s.resp.Code, check.Not(check.Equals), http.StatusOK)
127         var resp ClusterHealthResponse
128         err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
129         c.Check(err, check.IsNil)
130         c.Check(resp.Health, check.Not(check.Equals), "OK")
131 }
132
133 func (s *AggregatorSuite) checkUnhealthy(c *check.C) ClusterHealthResponse {
134         return s.checkResult(c, "ERROR")
135 }
136
137 func (s *AggregatorSuite) checkOK(c *check.C) ClusterHealthResponse {
138         return s.checkResult(c, "OK")
139 }
140
141 func (s *AggregatorSuite) checkResult(c *check.C, health string) ClusterHealthResponse {
142         c.Check(s.resp.Code, check.Equals, http.StatusOK)
143         var resp ClusterHealthResponse
144         c.Log(s.resp.Body.String())
145         err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
146         c.Check(err, check.IsNil)
147         c.Check(resp.Health, check.Equals, health)
148         return resp
149 }
150
151 func (s *AggregatorSuite) setAllServiceURLs(listen string) {
152         svcs := &s.handler.Cluster.Services
153         for _, svc := range []*arvados.Service{
154                 &svcs.Controller,
155                 &svcs.DispatchCloud,
156                 &svcs.DispatchLSF,
157                 &svcs.GitHTTP,
158                 &svcs.Keepbalance,
159                 &svcs.Keepproxy,
160                 &svcs.Keepstore,
161                 &svcs.Health,
162                 &svcs.RailsAPI,
163                 &svcs.WebDAV,
164                 &svcs.Websocket,
165                 &svcs.Workbench1,
166                 &svcs.Workbench2,
167         } {
168                 arvadostest.SetServiceURL(svc, "http://localhost"+listen+"/")
169         }
170 }
171
172 type unhealthyHandler struct{}
173
174 func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
175         if req.URL.Path == "/_health/ping" {
176                 resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
177         } else {
178                 http.Error(resp, "not found", http.StatusNotFound)
179         }
180 }
181
182 type healthyHandler struct{}
183
184 func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
185         if req.URL.Path == "/_health/ping" {
186                 resp.Write([]byte(`{"health":"OK"}`))
187         } else {
188                 http.Error(resp, "not found", http.StatusNotFound)
189         }
190 }
191
192 type slowHandler struct{}
193
194 func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
195         if req.URL.Path == "/_health/ping" {
196                 time.Sleep(3 * time.Second)
197                 resp.Write([]byte(`{"health":"OK"}`))
198         } else {
199                 http.Error(resp, "not found", http.StatusNotFound)
200         }
201 }