Skip to content

Commit d0a7ae2

Browse files
committed
add healthz
1 parent 04b1d6a commit d0a7ae2

2 files changed

Lines changed: 269 additions & 21 deletions

File tree

pkg/server/api.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ import (
99
"github.com/golang/glog"
1010
)
1111

12-
const (
13-
apiPathConfig = "/config/"
14-
)
15-
1612
type poolRequest struct {
1713
machinePool string
1814
}
1915

2016
// APIServer provides the HTTP(s) endpoint
2117
// for providing the machine configs.
2218
type APIServer struct {
23-
handler *APIHandler
19+
handler http.Handler
2420
port int
2521
insecure bool
2622
cert string
@@ -31,8 +27,13 @@ type APIServer struct {
3127
// that runs the Machine Config Server as a
3228
// handler.
3329
func NewAPIServer(a *APIHandler, p int, is bool, c, k string) *APIServer {
30+
mux := http.NewServeMux()
31+
mux.Handle("/config/", a)
32+
mux.Handle("/healthz", &healthHandler{})
33+
mux.Handle("/", &defaultHandler{})
34+
3435
return &APIServer{
35-
handler: a,
36+
handler: mux,
3637
port: p,
3738
insecure: is,
3839
cert: c,
@@ -42,12 +43,9 @@ func NewAPIServer(a *APIHandler, p int, is bool, c, k string) *APIServer {
4243

4344
// Serve launches the API Server.
4445
func (a *APIServer) Serve() {
45-
mux := http.NewServeMux()
46-
mux.Handle(apiPathConfig, a.handler)
47-
4846
mcs := &http.Server{
4947
Addr: fmt.Sprintf(":%v", a.port),
50-
Handler: mux,
48+
Handler: a.handler,
5149
}
5250

5351
glog.Info("launching server")
@@ -129,3 +127,32 @@ func (sh *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
129127
glog.Errorf("failed to write %v response: %v", cr, err)
130128
}
131129
}
130+
131+
type healthHandler struct{}
132+
133+
// ServeHTTP handles /healthz requests.
134+
func (h *healthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
135+
w.Header().Set("Content-Length", "0")
136+
if r.Method == http.MethodGet || r.Method == http.MethodHead {
137+
w.WriteHeader(http.StatusNoContent)
138+
return
139+
}
140+
141+
w.WriteHeader(http.StatusMethodNotAllowed)
142+
return
143+
}
144+
145+
// defaultHandler is the HTTP Handler for backstopping invalid requests.
146+
type defaultHandler struct{}
147+
148+
// ServeHTTP handles invalid requests.
149+
func (h *defaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
150+
w.Header().Set("Content-Length", "0")
151+
if r.Method == http.MethodGet || r.Method == http.MethodHead {
152+
w.WriteHeader(http.StatusNotFound)
153+
return
154+
}
155+
156+
w.WriteHeader(http.StatusMethodNotAllowed)
157+
return
158+
}

pkg/server/api_test.go

Lines changed: 232 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,179 @@ type scenario struct {
3030
func TestAPIHandler(t *testing.T) {
3131
scenarios := []scenario{
3232
{
33-
name: "get non-config path that does not exist",
34-
request: httptest.NewRequest(http.MethodGet, "http://testrequest/does-not-exist", nil),
33+
name: "get config path that does not exist",
34+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/does-not-exist", nil),
35+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
36+
return new(ignv2_2types.Config), fmt.Errorf("not acceptable")
37+
},
38+
checkResponse: func(t *testing.T, response *http.Response) {
39+
checkStatus(t, response, http.StatusInternalServerError)
40+
checkContentLength(t, response, 0)
41+
checkBodyLength(t, response, 0)
42+
},
43+
},
44+
{
45+
name: "get config path that exists",
46+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/master", nil),
47+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
48+
return new(ignv2_2types.Config), nil
49+
},
50+
checkResponse: func(t *testing.T, response *http.Response) {
51+
checkStatus(t, response, http.StatusOK)
52+
checkContentType(t, response, "application/json")
53+
checkContentLength(t, response, 114)
54+
checkBodyLength(t, response, 114)
55+
},
56+
},
57+
{
58+
name: "head config path that exists",
59+
request: httptest.NewRequest(http.MethodHead, "http://testrequest/config/master", nil),
60+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
61+
return new(ignv2_2types.Config), nil
62+
},
63+
checkResponse: func(t *testing.T, response *http.Response) {
64+
checkStatus(t, response, http.StatusOK)
65+
checkContentType(t, response, "application/json")
66+
checkContentLength(t, response, 114)
67+
checkBodyLength(t, response, 0)
68+
},
69+
},
70+
{
71+
name: "post config path that exists",
72+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
73+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
74+
return new(ignv2_2types.Config), nil
75+
},
76+
checkResponse: func(t *testing.T, response *http.Response) {
77+
checkStatus(t, response, http.StatusMethodNotAllowed)
78+
checkContentLength(t, response, 0)
79+
checkBodyLength(t, response, 0)
80+
},
81+
},
82+
}
83+
84+
for _, scenario := range scenarios {
85+
t.Run(scenario.name, func(t *testing.T) {
86+
w := httptest.NewRecorder()
87+
ms := &mockServer{
88+
GetConfigFn: scenario.serverFunc,
89+
}
90+
handler := NewServerAPIHandler(ms)
91+
handler.ServeHTTP(w, scenario.request)
92+
93+
resp := w.Result()
94+
defer resp.Body.Close()
95+
scenario.checkResponse(t, resp)
96+
})
97+
}
98+
}
99+
100+
func TestHealthzHandler(t *testing.T) {
101+
scenarios := []scenario{
102+
{
103+
name: "get healthz",
104+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz", nil),
105+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
106+
return new(ignv2_2types.Config), nil
107+
},
108+
checkResponse: func(t *testing.T, response *http.Response) {
109+
checkStatus(t, response, http.StatusNoContent)
110+
checkContentLength(t, response, 0)
111+
checkBodyLength(t, response, 0)
112+
},
113+
},
114+
{
115+
name: "head healthz",
116+
request: httptest.NewRequest(http.MethodHead, "http://testrequest/healthz", nil),
117+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
118+
return new(ignv2_2types.Config), nil
119+
},
120+
checkResponse: func(t *testing.T, response *http.Response) {
121+
checkStatus(t, response, http.StatusNoContent)
122+
checkContentLength(t, response, 0)
123+
checkBodyLength(t, response, 0)
124+
},
125+
},
126+
{
127+
name: "post healthz",
128+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/healthz", nil),
129+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
130+
return new(ignv2_2types.Config), nil
131+
},
132+
checkResponse: func(t *testing.T, response *http.Response) {
133+
checkStatus(t, response, http.StatusMethodNotAllowed)
134+
checkContentLength(t, response, 0)
135+
checkBodyLength(t, response, 0)
136+
},
137+
},
138+
}
139+
for _, scenario := range scenarios {
140+
t.Run(scenario.name, func(t *testing.T) {
141+
w := httptest.NewRecorder()
142+
handler := &healthHandler{}
143+
handler.ServeHTTP(w, scenario.request)
144+
145+
resp := w.Result()
146+
defer resp.Body.Close()
147+
scenario.checkResponse(t, resp)
148+
})
149+
}
150+
}
151+
152+
func TestDefaultHandler(t *testing.T) {
153+
scenarios := []scenario{
154+
{
155+
name: "get root",
156+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/", nil),
157+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
158+
return new(ignv2_2types.Config), nil
159+
},
160+
checkResponse: func(t *testing.T, response *http.Response) {
161+
checkStatus(t, response, http.StatusNotFound)
162+
checkContentLength(t, response, 0)
163+
checkBodyLength(t, response, 0)
164+
},
165+
},
166+
{
167+
name: "head root",
168+
request: httptest.NewRequest(http.MethodHead, "http://testrequest/", nil),
35169
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
36-
return nil, nil
170+
return new(ignv2_2types.Config), nil
37171
},
38172
checkResponse: func(t *testing.T, response *http.Response) {
39173
checkStatus(t, response, http.StatusNotFound)
40174
checkContentLength(t, response, 0)
41175
checkBodyLength(t, response, 0)
42176
},
43177
},
178+
{
179+
name: "post root",
180+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/", nil),
181+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
182+
return new(ignv2_2types.Config), nil
183+
},
184+
checkResponse: func(t *testing.T, response *http.Response) {
185+
checkStatus(t, response, http.StatusMethodNotAllowed)
186+
checkContentLength(t, response, 0)
187+
checkBodyLength(t, response, 0)
188+
},
189+
},
190+
}
191+
for _, scenario := range scenarios {
192+
t.Run(scenario.name, func(t *testing.T) {
193+
w := httptest.NewRecorder()
194+
handler := &defaultHandler{}
195+
handler.ServeHTTP(w, scenario.request)
196+
197+
resp := w.Result()
198+
defer resp.Body.Close()
199+
scenario.checkResponse(t, resp)
200+
})
201+
}
202+
}
203+
204+
func TestAPIServer(t *testing.T) {
205+
scenarios := []scenario{
44206
{
45207
name: "get config path that does not exist",
46208
request: httptest.NewRequest(http.MethodGet, "http://testrequest/config/does-not-exist", nil),
@@ -80,10 +242,10 @@ func TestAPIHandler(t *testing.T) {
80242
},
81243
},
82244
{
83-
name: "post non-config path that does not exist",
84-
request: httptest.NewRequest(http.MethodPost, "http://testrequest/post", nil),
245+
name: "post config path that exists",
246+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
85247
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
86-
return nil, nil
248+
return new(ignv2_2types.Config), nil
87249
},
88250
checkResponse: func(t *testing.T, response *http.Response) {
89251
checkStatus(t, response, http.StatusMethodNotAllowed)
@@ -92,8 +254,68 @@ func TestAPIHandler(t *testing.T) {
92254
},
93255
},
94256
{
95-
name: "post config path that exists",
96-
request: httptest.NewRequest(http.MethodPost, "http://testrequest/config/master", nil),
257+
name: "get healthz",
258+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/healthz", nil),
259+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
260+
return new(ignv2_2types.Config), nil
261+
},
262+
checkResponse: func(t *testing.T, response *http.Response) {
263+
checkStatus(t, response, http.StatusNoContent)
264+
checkContentLength(t, response, 0)
265+
checkBodyLength(t, response, 0)
266+
},
267+
},
268+
{
269+
name: "head healthz",
270+
request: httptest.NewRequest(http.MethodHead, "http://testrequest/healthz", nil),
271+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
272+
return new(ignv2_2types.Config), nil
273+
},
274+
checkResponse: func(t *testing.T, response *http.Response) {
275+
checkStatus(t, response, http.StatusNoContent)
276+
checkContentLength(t, response, 0)
277+
checkBodyLength(t, response, 0)
278+
},
279+
},
280+
{
281+
name: "post healthz",
282+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/healthz", nil),
283+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
284+
return new(ignv2_2types.Config), nil
285+
},
286+
checkResponse: func(t *testing.T, response *http.Response) {
287+
checkStatus(t, response, http.StatusMethodNotAllowed)
288+
checkContentLength(t, response, 0)
289+
checkBodyLength(t, response, 0)
290+
},
291+
},
292+
{
293+
name: "get root",
294+
request: httptest.NewRequest(http.MethodGet, "http://testrequest/", nil),
295+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
296+
return new(ignv2_2types.Config), nil
297+
},
298+
checkResponse: func(t *testing.T, response *http.Response) {
299+
checkStatus(t, response, http.StatusNotFound)
300+
checkContentLength(t, response, 0)
301+
checkBodyLength(t, response, 0)
302+
},
303+
},
304+
{
305+
name: "head root",
306+
request: httptest.NewRequest(http.MethodHead, "http://testrequest/", nil),
307+
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
308+
return new(ignv2_2types.Config), nil
309+
},
310+
checkResponse: func(t *testing.T, response *http.Response) {
311+
checkStatus(t, response, http.StatusNotFound)
312+
checkContentLength(t, response, 0)
313+
checkBodyLength(t, response, 0)
314+
},
315+
},
316+
{
317+
name: "post root",
318+
request: httptest.NewRequest(http.MethodPost, "http://testrequest/", nil),
97319
serverFunc: func(poolRequest) (*ignv2_2types.Config, error) {
98320
return new(ignv2_2types.Config), nil
99321
},
@@ -104,15 +326,14 @@ func TestAPIHandler(t *testing.T) {
104326
},
105327
},
106328
}
107-
108329
for _, scenario := range scenarios {
109330
t.Run(scenario.name, func(t *testing.T) {
110331
w := httptest.NewRecorder()
111332
ms := &mockServer{
112333
GetConfigFn: scenario.serverFunc,
113334
}
114-
handler := NewServerAPIHandler(ms)
115-
handler.ServeHTTP(w, scenario.request)
335+
server := NewAPIServer(NewServerAPIHandler(ms), 0, false, "", "")
336+
server.handler.ServeHTTP(w, scenario.request)
116337

117338
resp := w.Result()
118339
defer resp.Body.Close()

0 commit comments

Comments
 (0)