Skip to content

Commit

Permalink
pkg/server/api: Add /healthz for load-balancer health checks
Browse files Browse the repository at this point in the history
The server currently 404s the root path.  From a master:

  [core@ip-10-0-26-134 ~]$ curl -ik https://wking-api.devcluster.openshift.com:49500/
  HTTP/1.1 404 Not Found
  Content-Type: text/plain; charset=utf-8
  X-Content-Type-Options: nosniff
  Date: Sun, 16 Dec 2018 06:30:14 GMT
  Content-Length: 19

  404 page not found

but we need a reliable response on the range 200-399 to satisfy our
network load balancer health checks, which do not support configurable
response status codes [1,2] (these are Terraform links, but they
discuss an AWS restriction that is not Terraform-specific).  This
commit adds a /healthz endpoint which always 204s (when the server is
alive to handle it).

[1]: hashicorp/terraform-provider-aws#2708 (comment)
[2]: https://github.com/terraform-providers/terraform-provider-aws/pull/2906/files#diff-375aea487c27a6ada86edfd817ba2401R612
  • Loading branch information
wking committed Dec 19, 2018
1 parent 6cfa51c commit 50f1439
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
21 changes: 21 additions & 0 deletions pkg/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type getConfig func(request poolRequest) (*ignv2_2types.Config, error)

func newHandler(getConfig getConfig) http.Handler {
mux := http.NewServeMux()
mux.Handle("/healthz", &healthHandler{})
mux.Handle(apiPathConfig, &configHandler{getConfig: getConfig})
mux.Handle("/", &defaultHandler{})
return mux
Expand Down Expand Up @@ -85,6 +86,26 @@ func (h *configHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

type healthHandler struct{}

// ServeHTTP handles /healthz requests.
func (h *healthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "0")

if r.URL.Path == "/healthz" {
if r.Method == http.MethodGet || r.Method == http.MethodHead {
w.WriteHeader(http.StatusNoContent)
return
}

w.WriteHeader(http.StatusMethodNotAllowed)
return
}

w.WriteHeader(http.StatusNotFound)
return
}

// defaultHandler is the HTTP Handler for backstopping invalid requests.
type defaultHandler struct{}

Expand Down
84 changes: 84 additions & 0 deletions pkg/server/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,78 @@ func TestHandler(t *testing.T) {
checkBodyLength(t, response, 0)
},
},
{
name: "get /healthz",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNoContent)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "head /healthz",
request: httptest.NewRequest(http.MethodHead, "http://testrequest/healthz", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNoContent)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "get health path that does not exist",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz-does-not-exist", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "get health child that does not exist",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz/does-not-exist", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "get root",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "post root",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "post non-config path that does not exist",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/post", nil),
Expand All @@ -103,6 +175,18 @@ func TestHandler(t *testing.T) {
checkBodyLength(t, response, 0)
},
},
{
name: "post /healthz",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/healthz", nil),
getConfig: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusMethodNotAllowed)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
}

for _, scenario := range scenarios {
Expand Down

0 comments on commit 50f1439

Please sign in to comment.