18947: Refactor arv-git-httpd as an arvados-server subcommand.
[arvados.git] / sdk / go / health / aggregator_test.go
index 2cb7122543c62eb1be5f7d1f28aced9406e9759c..f8507ef4f5b53aa568652f3253d670548248b54d 100644 (file)
@@ -1,12 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
 package health
 
 import (
        "encoding/json"
        "net/http"
        "net/http/httptest"
+       "strings"
+       "time"
 
-       "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/arvadostest"
        "gopkg.in/check.v1"
 )
 
@@ -24,13 +30,8 @@ func (s *AggregatorSuite) TestInterface(c *check.C) {
 }
 
 func (s *AggregatorSuite) SetUpTest(c *check.C) {
-       s.handler = &Aggregator{Config: &arvados.Config{
-               Clusters: map[string]arvados.Cluster{
-                       "zzzzz": {
-                               ManagementToken: arvadostest.ManagementToken,
-                               SystemNodes:     map[string]arvados.SystemNode{},
-                       },
-               },
+       s.handler = &Aggregator{Cluster: &arvados.Cluster{
+               ManagementToken: arvadostest.ManagementToken,
        }}
        s.req = httptest.NewRequest("GET", "/_health/all", nil)
        s.req.Header.Set("Authorization", "Bearer "+arvadostest.ManagementToken)
@@ -51,23 +52,150 @@ func (s *AggregatorSuite) TestBadAuth(c *check.C) {
        c.Check(s.resp.Code, check.Equals, http.StatusUnauthorized)
 }
 
-func (s *AggregatorSuite) TestEmptyConfig(c *check.C) {
+func (s *AggregatorSuite) TestNoServicesConfigured(c *check.C) {
+       s.handler.ServeHTTP(s.resp, s.req)
+       s.checkUnhealthy(c)
+}
+
+func (s *AggregatorSuite) stubServer(handler http.Handler) (*httptest.Server, string) {
+       srv := httptest.NewServer(handler)
+       var port string
+       if parts := strings.Split(srv.URL, ":"); len(parts) < 3 {
+               panic(srv.URL)
+       } else {
+               port = parts[len(parts)-1]
+       }
+       return srv, ":" + port
+}
+
+func (s *AggregatorSuite) TestUnhealthy(c *check.C) {
+       srv, listen := s.stubServer(&unhealthyHandler{})
+       defer srv.Close()
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
+       s.handler.ServeHTTP(s.resp, s.req)
+       s.checkUnhealthy(c)
+}
+
+func (s *AggregatorSuite) TestHealthy(c *check.C) {
+       srv, listen := s.stubServer(&healthyHandler{})
+       defer srv.Close()
+       s.setAllServiceURLs(listen)
+       s.handler.ServeHTTP(s.resp, s.req)
+       resp := s.checkOK(c)
+       svc := "keepstore+http://localhost" + listen + "/_health/ping"
+       c.Logf("%#v", resp)
+       ep := resp.Checks[svc]
+       c.Check(ep.Health, check.Equals, "OK")
+       c.Check(ep.HTTPStatusCode, check.Equals, 200)
+}
+
+func (s *AggregatorSuite) TestHealthyAndUnhealthy(c *check.C) {
+       srvH, listenH := s.stubServer(&healthyHandler{})
+       defer srvH.Close()
+       srvU, listenU := s.stubServer(&unhealthyHandler{})
+       defer srvU.Close()
+       s.setAllServiceURLs(listenH)
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listenH+"/", "http://127.0.0.1"+listenU+"/")
        s.handler.ServeHTTP(s.resp, s.req)
-       s.checkOK(c)
+       resp := s.checkUnhealthy(c)
+       ep := resp.Checks["keepstore+http://localhost"+listenH+"/_health/ping"]
+       c.Check(ep.Health, check.Equals, "OK")
+       c.Check(ep.HTTPStatusCode, check.Equals, 200)
+       ep = resp.Checks["keepstore+http://127.0.0.1"+listenU+"/_health/ping"]
+       c.Check(ep.Health, check.Equals, "ERROR")
+       c.Check(ep.HTTPStatusCode, check.Equals, 200)
+       c.Logf("%#v", ep)
+}
+
+func (s *AggregatorSuite) TestPingTimeout(c *check.C) {
+       s.handler.timeout = arvados.Duration(100 * time.Millisecond)
+       srv, listen := s.stubServer(&slowHandler{})
+       defer srv.Close()
+       arvadostest.SetServiceURL(&s.handler.Cluster.Services.Keepstore, "http://localhost"+listen+"/")
+       s.handler.ServeHTTP(s.resp, s.req)
+       resp := s.checkUnhealthy(c)
+       ep := resp.Checks["keepstore+http://localhost"+listen+"/_health/ping"]
+       c.Check(ep.Health, check.Equals, "ERROR")
+       c.Check(ep.HTTPStatusCode, check.Equals, 0)
+       rt, err := ep.ResponseTime.Float64()
+       c.Check(err, check.IsNil)
+       c.Check(rt > 0.005, check.Equals, true)
 }
 
 func (s *AggregatorSuite) checkError(c *check.C) {
        c.Check(s.resp.Code, check.Not(check.Equals), http.StatusOK)
-       var body map[string]interface{}
-       err := json.NewDecoder(s.resp.Body).Decode(&body)
+       var resp ClusterHealthResponse
+       err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
        c.Check(err, check.IsNil)
-       c.Check(body["health"], check.Not(check.Equals), "OK")
+       c.Check(resp.Health, check.Not(check.Equals), "OK")
 }
 
-func (s *AggregatorSuite) checkOK(c *check.C) {
+func (s *AggregatorSuite) checkUnhealthy(c *check.C) ClusterHealthResponse {
+       return s.checkResult(c, "ERROR")
+}
+
+func (s *AggregatorSuite) checkOK(c *check.C) ClusterHealthResponse {
+       return s.checkResult(c, "OK")
+}
+
+func (s *AggregatorSuite) checkResult(c *check.C, health string) ClusterHealthResponse {
        c.Check(s.resp.Code, check.Equals, http.StatusOK)
-       var body map[string]interface{}
-       err := json.NewDecoder(s.resp.Body).Decode(&body)
+       var resp ClusterHealthResponse
+       c.Log(s.resp.Body.String())
+       err := json.Unmarshal(s.resp.Body.Bytes(), &resp)
        c.Check(err, check.IsNil)
-       c.Check(body["health"], check.Equals, "OK")
+       c.Check(resp.Health, check.Equals, health)
+       return resp
+}
+
+func (s *AggregatorSuite) setAllServiceURLs(listen string) {
+       svcs := &s.handler.Cluster.Services
+       for _, svc := range []*arvados.Service{
+               &svcs.Controller,
+               &svcs.DispatchCloud,
+               &svcs.DispatchLSF,
+               &svcs.GitHTTP,
+               &svcs.Keepbalance,
+               &svcs.Keepproxy,
+               &svcs.Keepstore,
+               &svcs.Health,
+               &svcs.RailsAPI,
+               &svcs.WebDAV,
+               &svcs.Websocket,
+               &svcs.Workbench1,
+               &svcs.Workbench2,
+       } {
+               arvadostest.SetServiceURL(svc, "http://localhost"+listen+"/")
+       }
+}
+
+type unhealthyHandler struct{}
+
+func (*unhealthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+       if req.URL.Path == "/_health/ping" {
+               resp.Write([]byte(`{"health":"ERROR","error":"the bends"}`))
+       } else {
+               http.Error(resp, "not found", http.StatusNotFound)
+       }
+}
+
+type healthyHandler struct{}
+
+func (*healthyHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+       if req.URL.Path == "/_health/ping" {
+               resp.Write([]byte(`{"health":"OK"}`))
+       } else {
+               http.Error(resp, "not found", http.StatusNotFound)
+       }
+}
+
+type slowHandler struct{}
+
+func (*slowHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+       if req.URL.Path == "/_health/ping" {
+               time.Sleep(3 * time.Second)
+               resp.Write([]byte(`{"health":"OK"}`))
+       } else {
+               http.Error(resp, "not found", http.StatusNotFound)
+       }
 }