1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
17 "git.arvados.org/arvados.git/lib/config"
18 "git.arvados.org/arvados.git/sdk/go/arvados"
19 "git.arvados.org/arvados.git/sdk/go/arvadostest"
20 "git.arvados.org/arvados.git/sdk/go/ctxlog"
21 "github.com/ghodss/yaml"
25 type AggregatorSuite struct {
28 resp *httptest.ResponseRecorder
31 // Gocheck boilerplate
32 var _ = check.Suite(&AggregatorSuite{})
34 func (s *AggregatorSuite) TestInterface(c *check.C) {
35 var _ http.Handler = &Aggregator{}
38 func (s *AggregatorSuite) SetUpTest(c *check.C) {
39 ldr := config.NewLoader(bytes.NewBufferString(`Clusters: {zzzzz: {}}`), ctxlog.TestLogger(c))
41 cfg, err := ldr.Load()
42 c.Assert(err, check.IsNil)
43 cluster, err := cfg.GetCluster("")
44 c.Assert(err, check.IsNil)
45 cluster.ManagementToken = arvadostest.ManagementToken
46 cluster.SystemRootToken = arvadostest.SystemRootToken
47 cluster.Collections.BlobSigningKey = arvadostest.BlobSigningKey
48 cluster.Volumes["z"] = arvados.Volume{StorageClasses: map[string]bool{"default": true}}
49 s.handler = &Aggregator{Cluster: cluster}
50 s.req = httptest.NewRequest("GET", "/_health/all", nil)
51 s.req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
52 s.resp = httptest.NewRecorder()
55 func (s *AggregatorSuite) TestNoAuth(c *check.C) {
56 s.req.Header.Del("Authorization")
57 s.handler.ServeHTTP(s.resp, s.req)
59 c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
62 func (s *AggregatorSuite) TestBadAuth(c *check.C) {
63 s.req.Header.Set("Authorization", "xyzzy")
64 s.handler.ServeHTTP(s.resp, s.req)
66 c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
69 func (s *AggregatorSuite) TestNoServicesConfigured(c *check.C) {
70 s.handler.ServeHTTP(s.resp, s.req)
74 func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, string) {
75 srv := httptest.NewServer(handler)
77 if parts := strings.Split(srv.URL, ":"); len(parts) < 3 {
80 port = parts[len(parts)-1]
82 return srv, ":" + port
85 func (s *AggregatorSuite) TestUnhealthy(c *check.C) {
86 srv, listen := s.stubServer(&unhealthyHandler{})
88 arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
89 s.handler.ServeHTTP(s.resp, s.req)
93 func (s *AggregatorSuite) TestHealthy(c *check.C) {
94 srv, listen := s.stubServer(&healthyHandler{})
96 s.setAllServiceURLs(listen)
97 s.handler.ServeHTTP(s.resp, s.req)
99 svc := "keepstore+http://localhost" + listen + "/_health/ping"
101 ep := resp.Checks[svc]
102 c.Check(ep.Health, check.Equals, "OK")
103 c.Check(ep.HTTPStatusCode, check.Equals, 200)
106 func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
107 srvH, listenH := s.stubServer(&healthyHandler{})
109 srvU, listenU := s.stubServer(&unhealthyHandler{})
111 s.setAllServiceURLs(listenH)
112 arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listenH+"/", "http://127.0.0.1"+listenU+"/")
113 s.handler.ServeHTTP(s.resp, s.req)
114 resp := s.checkUnhealthy(c)
115 ep := resp.Checks["keepstore+http://localhost"+listenH+"/_health/ping"]
116 c.Check(ep.Health, check.Equals, "OK")
117 c.Check(ep.HTTPStatusCode, check.Equals, 200)
118 ep = resp.Checks["keepstore+http://127.0.0.1"+listenU+"/_health/ping"]
119 c.Check(ep.Health, check.Equals, "ERROR")
120 c.Check(ep.HTTPStatusCode, check.Equals, 200)
124 func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
125 s.handler.timeout = arvados.Duration(100 * time.Millisecond)
126 srv, listen := s.stubServer(&slowHandler{})
128 arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
129 s.handler.ServeHTTP(s.resp, s.req)
130 resp := s.checkUnhealthy(c)
131 ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
132 c.Check(ep.Health, check.Equals, "ERROR")
133 c.Check(ep.HTTPStatusCode, check.Equals, 0)
134 rt, err := ep.ResponseTime.Float64()
135 c.Check(err, check.IsNil)
136 c.Check(rt > 0.005, check.Equals, true)
139 func (s *AggregatorSuite) TestCheckCommand(c *check.C) {
140 srv, listen := s.stubServer(&healthyHandler{})
142 s.setAllServiceURLs(listen)
144 confdata, err := yaml.Marshal(arvados.Config{Clusters: map[string]arvados.Cluster{s.handler.Cluster.ClusterID: *s.handler.Cluster}})
145 c.Assert(err, check.IsNil)
146 confdata = regexp.MustCompile(`SourceTimestamp: [^\n]+\n`).ReplaceAll(confdata, []byte{})
147 err = ioutil.WriteFile(tmpdir+"/config.yml", confdata, 0777)
148 c.Assert(err, check.IsNil)
149 var stdout, stderr bytes.Buffer
150 exitcode := CheckCommand.RunCommand("check", []string{"-config=" + tmpdir + "/config.yml"}, &bytes.Buffer{}, &stdout, &stderr)
151 c.Check(exitcode, check.Equals, 0)
152 c.Check(stderr.String(), check.Equals, "")
153 c.Check(stdout.String(), check.Matches, `(?ms).*(\n|^)health: OK\n.*`)
156 func (s *AggregatorSuite) checkError(c *check.C) {
157 c.Check(s.resp.Code, check.Not(check.Equals), http.StatusOK)
158 var resp ClusterHealthResponse
159 err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
160 c.Check(err, check.IsNil)
161 c.Check(resp.Health, check.Not(check.Equals), "OK")
164 func (s *AggregatorSuite) checkUnhealthy(c *check.C) ClusterHealthResponse {
165 return s.checkResult(c, "ERROR")
168 func (s *AggregatorSuite) checkOK(c *check.C) ClusterHealthResponse {
169 return s.checkResult(c, "OK")
172 func (s *AggregatorSuite) checkResult(c *check.C, health string) ClusterHealthResponse {
173 c.Check(s.resp.Code, check.Equals, http.StatusOK)
174 var resp ClusterHealthResponse
175 c.Log(s.resp.Body.String())
176 err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
177 c.Check(err, check.IsNil)
178 c.Check(resp.Health, check.Equals, health)
182 func (s *AggregatorSuite) setAllServiceURLs(listen string) {
183 svcs := &s.handler.Cluster.Services
184 for _, svc := range []*arvados.Service{
198 arvadostest.SetServiceURL(svc, "http://localhost"+listen+"/")
202 type unhealthyHandler struct{}
204 func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
205 if req.URL.Path == "/_health/ping" {
206 resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
208 http.Error(resp, "not found", http.StatusNotFound)
212 type healthyHandler struct{}
214 func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
215 if req.URL.Path == "/_health/ping" {
216 resp.Write([]byte(`{"health":"OK"}`))
218 http.Error(resp, "not found", http.StatusNotFound)
222 type slowHandler struct{}
224 func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
225 if req.URL.Path == "/_health/ping" {
226 time.Sleep(3 * time.Second)
227 resp.Write([]byte(`{"health":"OK"}`))
229 http.Error(resp, "not found", http.StatusNotFound)