Skip to content

Commit

Permalink
add healthz
Browse files Browse the repository at this point in the history
  • Loading branch information
abhinavdahiya committed Jan 5, 2019
1 parent 04b1d6a commit d0a7ae2
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 21 deletions.
47 changes: 37 additions & 10 deletions pkg/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ import (
"github.com/golang/glog"
)

const (
apiPathConfig = "/config/"
)

type poolRequest struct {
machinePool string
}

// APIServer provides the HTTP(s) endpoint
// for providing the machine configs.
type APIServer struct {
handler *APIHandler
handler http.Handler
port int
insecure bool
cert string
Expand All @@ -31,8 +27,13 @@ type APIServer struct {
// that runs the Machine Config Server as a
// handler.
func NewAPIServer(a *APIHandler, p int, is bool, c, k string) *APIServer {
mux := http.NewServeMux()
mux.Handle("/config/", a)
mux.Handle("/healthz", &healthHandler{})
mux.Handle("/", &defaultHandler{})

return &APIServer{
handler: a,
handler: mux,
port: p,
insecure: is,
cert: c,
Expand All @@ -42,12 +43,9 @@ func NewAPIServer(a *APIHandler, p int, is bool, c, k string) *APIServer {

// Serve launches the API Server.
func (a *APIServer) Serve() {
mux := http.NewServeMux()
mux.Handle(apiPathConfig, a.handler)

mcs := &http.Server{
Addr: fmt.Sprintf(":%v", a.port),
Handler: mux,
Handler: a.handler,
}

glog.Info("launching server")
Expand Down Expand Up @@ -129,3 +127,32 @@ func (sh *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
glog.Errorf("failed to write %v response: %v", cr, err)
}
}

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.Method == http.MethodGet || r.Method == http.MethodHead {
w.WriteHeader(http.StatusNoContent)
return
}

w.WriteHeader(http.StatusMethodNotAllowed)
return
}

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

// ServeHTTP handles invalid requests.
func (h *defaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "0")
if r.Method == http.MethodGet || r.Method == http.MethodHead {
w.WriteHeader(http.StatusNotFound)
return
}

w.WriteHeader(http.StatusMethodNotAllowed)
return
}
243 changes: 232 additions & 11 deletions pkg/server/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,179 @@ type scenario struct {
func TestAPIHandler(t *testing.T) {
scenarios := []scenario{
{
name: "get non-config path that does not exist",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/does-not-exist", nil),
name: "get config path that does not exist",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/does-not-exist", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), fmt.Errorf("not acceptable")
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusInternalServerError)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "get config path that exists",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/master", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusOK)
checkContentType(t, response, "application/json")
checkContentLength(t, response, 114)
checkBodyLength(t, response, 114)
},
},
{
name: "head config path that exists",
request: httptest.NewRequest(http.MethodHead, "http://testrequest/config/master", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusOK)
checkContentType(t, response, "application/json")
checkContentLength(t, response, 114)
checkBodyLength(t, response, 0)
},
},
{
name: "post config path that exists",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), 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 {
t.Run(scenario.name, func(t *testing.T) {
w := httptest.NewRecorder()
ms := &mockServer{
GetConfigFn: scenario.serverFunc,
}
handler := NewServerAPIHandler(ms)
handler.ServeHTTP(w, scenario.request)

resp := w.Result()
defer resp.Body.Close()
scenario.checkResponse(t, resp)
})
}
}

func TestHealthzHandler(t *testing.T) {
scenarios := []scenario{
{
name: "get healthz",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz", nil),
serverFunc: 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),
serverFunc: 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: "post healthz",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/healthz", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), 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 {
t.Run(scenario.name, func(t *testing.T) {
w := httptest.NewRecorder()
handler := &healthHandler{}
handler.ServeHTTP(w, scenario.request)

resp := w.Result()
defer resp.Body.Close()
scenario.checkResponse(t, resp)
})
}
}

func TestDefaultHandler(t *testing.T) {
scenarios := []scenario{
{
name: "get root",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "head root",
request: httptest.NewRequest(http.MethodHead, "http://testrequest/", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
return new(ignv2_2types.Config), 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),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), 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 {
t.Run(scenario.name, func(t *testing.T) {
w := httptest.NewRecorder()
handler := &defaultHandler{}
handler.ServeHTTP(w, scenario.request)

resp := w.Result()
defer resp.Body.Close()
scenario.checkResponse(t, resp)
})
}
}

func TestAPIServer(t *testing.T) {
scenarios := []scenario{
{
name: "get config path that does not exist",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/does-not-exist", nil),
Expand Down Expand Up @@ -80,10 +242,10 @@ func TestAPIHandler(t *testing.T) {
},
},
{
name: "post non-config path that does not exist",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/post", nil),
name: "post config path that exists",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return nil, nil
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusMethodNotAllowed)
Expand All @@ -92,8 +254,68 @@ func TestAPIHandler(t *testing.T) {
},
},
{
name: "post config path that exists",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
name: "get healthz",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz", nil),
serverFunc: 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),
serverFunc: 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: "post healthz",
request: httptest.NewRequest(http.MethodPost, "http://testrequest/healthz", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusMethodNotAllowed)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "get root",
request: httptest.NewRequest(http.MethodGet, "http://testrequest/", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
checkResponse: func(t *testing.T, response *http.Response) {
checkStatus(t, response, http.StatusNotFound)
checkContentLength(t, response, 0)
checkBodyLength(t, response, 0)
},
},
{
name: "head root",
request: httptest.NewRequest(http.MethodHead, "http://testrequest/", nil),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), 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),
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
return new(ignv2_2types.Config), nil
},
Expand All @@ -104,15 +326,14 @@ func TestAPIHandler(t *testing.T) {
},
},
}

for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
w := httptest.NewRecorder()
ms := &mockServer{
GetConfigFn: scenario.serverFunc,
}
handler := NewServerAPIHandler(ms)
handler.ServeHTTP(w, scenario.request)
server := NewAPIServer(NewServerAPIHandler(ms), 0, false, "", "")
server.handler.ServeHTTP(w, scenario.request)

resp := w.Result()
defer resp.Body.Close()
Expand Down

0 comments on commit d0a7ae2

Please sign in to comment.