From 7de96c8e1734030754ef5d5c7957aa7c108f8c43 Mon Sep 17 00:00:00 2001 From: Bill Robinson Date: Fri, 20 Jan 2017 16:06:05 -0800 Subject: [PATCH] Added /health endpoint which returns the version + status of the proxy and its netmaster Signed-off-by: Bill Robinson --- proxy/handlers.go | 83 +++++++++++++++++++++++++++++++++++++++ proxy/proxy.go | 8 ++++ systemtests/basic_test.go | 41 ++++++++++++++++++- 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/proxy/handlers.go b/proxy/handlers.go index 7e5ba837..320ccb70 100644 --- a/proxy/handlers.go +++ b/proxy/handlers.go @@ -69,6 +69,89 @@ func loginHandler(w http.ResponseWriter, req *http.Request) { writeJSONResponse(w, LoginResponse{Token: tokenStr}) } +const ( + // StatusHealthy is used to indicate a healthy response + StatusHealthy = "healthy" + + // StatusUnhealthy is used to indicate an unhealthy response + StatusUnhealthy = "unhealthy" +) + +// NetmasterHealthCheckResponse represents our netmaster's health and version info. +type NetmasterHealthCheckResponse struct { + Status string `json:"status"` + + // if netmaster is up and working, there's no "reason" for it to be unhealthy + Reason string `json:"reason,omitempty"` + + // if we can't reach netmaster, we won't have a version + Version string `json:"version,omitempty"` +} + +// MarkHealthy marks netmaster as being healthy and running the specified version +func (nhcr *NetmasterHealthCheckResponse) MarkHealthy(version string) { + nhcr.Status = StatusHealthy + nhcr.Version = version +} + +// MarkUnhealthy marks netmaster as being unhealthy +func (nhcr *NetmasterHealthCheckResponse) MarkUnhealthy(reason string) { + nhcr.Status = StatusUnhealthy + nhcr.Reason = reason +} + +// HealthCheckResponse represents a response from the /health endpoint. +// It contains our health status + the health status of our netmaster +type HealthCheckResponse struct { + NetmasterHealth *NetmasterHealthCheckResponse `json:"netmaster"` + Status string `json:"status"` + Version string `json:"version"` +} + +// MarkUnhealthy marks the proxy as being unhealthy +func (hcr *HealthCheckResponse) MarkUnhealthy() { + hcr.Status = StatusUnhealthy +} + +// healthCheckHandler handles /health requests +func healthCheckHandler(config *Config) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + common.SetJSONContentType(w) + + hcr := &HealthCheckResponse{ + Status: StatusHealthy, // default to being healthy + Version: config.Version, + } + + nhcr := &NetmasterHealthCheckResponse{} + + // + // check our netmaster's /version endpoint + // + if version, err := common.GetNetmasterVersion(config.NetmasterAddress); err != nil { + nhcr.MarkUnhealthy(err.Error()) + + // if netmaster is unhealthy, so are we + hcr.MarkUnhealthy() + } else { + nhcr.MarkHealthy(version) + } + + hcr.NetmasterHealth = nhcr + + // + // prepare the response + // + data, err := json.Marshal(hcr) + if err != nil { + serverError(w, errors.New("failed to marshal health check response: "+err.Error())) + return + } + + w.Write(data) + } +} + // VersionResponse represents a response from the /version endpoint type VersionResponse struct { Version string `json:"version"` diff --git a/proxy/proxy.go b/proxy/proxy.go index 05e6d0c5..651c9b58 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -17,6 +17,9 @@ const ( // LoginPath is the authentication endpoint on the proxy LoginPath = "/api/v1/ccn_proxy/login" + // HealthCheckPath is the health check endpoint on the proxy + HealthCheckPath = "/health" + // VersionPath is the version endpoint on the proxy VersionPath = "/version" @@ -184,6 +187,11 @@ func addRoutes(s *Server, router *mux.Router) { // router.Path(VersionPath).Methods("GET").HandlerFunc(versionHandler(s.config.Version)) + // + // Health check endpoint + // + router.Path(HealthCheckPath).Methods("GET").HandlerFunc(healthCheckHandler(s.config)) + // // Authentication endpoint // diff --git a/systemtests/basic_test.go b/systemtests/basic_test.go index e35f6798..8148e8f7 100644 --- a/systemtests/basic_test.go +++ b/systemtests/basic_test.go @@ -84,7 +84,7 @@ func (s *systemtestSuite) TestLogin(c *C) { // TestVersion tests that /version endpoint responds with something sane. func (s *systemtestSuite) TestVersion(c *C) { runTest(func(ms *MockServer) { - resp, data := proxyGet(c, noToken, "/version") + resp, data := proxyGet(c, noToken, proxy.VersionPath) c.Assert(resp.StatusCode, Equals, 200) vr := &proxy.VersionResponse{} @@ -93,6 +93,45 @@ func (s *systemtestSuite) TestVersion(c *C) { }) } +// TestHealthCheck tests that /health endpoint responds properly. +func (s *systemtestSuite) TestHealthCheck(c *C) { + runTest(func(ms *MockServer) { + + testFunc := func() *proxy.HealthCheckResponse { + resp, data := proxyGet(c, noToken, proxy.HealthCheckPath) + c.Assert(resp.StatusCode, Equals, 200) + + hcr := &proxy.HealthCheckResponse{} + err := json.Unmarshal(data, hcr) + c.Assert(err, IsNil) + + return hcr + } + + // + // first check: with no configured /version endpoint on the mockserver, + // the netmaster should be marked unhealthy. + // + hcr := testFunc() + + c.Assert(hcr.Status, Equals, proxy.StatusUnhealthy) + c.Assert(hcr.NetmasterHealth.Status, Equals, proxy.StatusUnhealthy) + + // + // second check: we add a /version to mockserver and should get back + // a healthy response. + // + versionResponse := `{"GitCommit":"x","Version":"y","BuildTime":"z"}` + ms.AddHardcodedResponse("/version", []byte(versionResponse)) + + hcr = testFunc() + + c.Assert(hcr.Status, Equals, proxy.StatusHealthy) + c.Assert(hcr.NetmasterHealth.Status, Equals, proxy.StatusHealthy) + c.Assert(hcr.NetmasterHealth.Version, Equals, "y") + }) +} + // TestRequestProxying tests that authenticated requests are proxied to the mock // server and the response is returned properly. func (s *systemtestSuite) TestRequestProxying(c *C) {