Skip to content

Commit 2d634c6

Browse files
author
Keerthan Mala
committed
ref(probes): add controller check to the readiness probe
1 parent 02d1536 commit 2d634c6

5 files changed

Lines changed: 144 additions & 16 deletions

File tree

pkg/controller/utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type UserInfo struct {
2727
Apps []string
2828
}
2929

30-
func controllerURLStr(additionalPath ...string) (string, error) {
30+
func ControllerURLStr(additionalPath ...string) (string, error) {
3131
host := os.Getenv(hostEnvName)
3232
port := os.Getenv(portEnvName)
3333

@@ -65,7 +65,7 @@ func fingerprint(key ssh.PublicKey) string {
6565
// UserInfoFromKey makes a request to the controller to get the user info from they given key.
6666
func UserInfoFromKey(key ssh.PublicKey) (*UserInfo, error) {
6767
fp := fingerprint(key)
68-
url, err := controllerURLStr("v2", "hooks", "key", fp)
68+
url, err := ControllerURLStr("v2", "hooks", "key", fp)
6969
if err != nil {
7070
return nil, err
7171
}

pkg/healthsrv/controller_state.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package healthsrv
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/deis/builder/pkg/controller"
10+
)
11+
12+
// GetClient is an (*net/http).Client compatible interface that provides just the Get cross-section of functionality.
13+
// It can also be implemented for unit tests.
14+
type GetClient interface {
15+
Get(string) (*http.Response, error)
16+
}
17+
18+
type successGetClient struct{}
19+
20+
func (e successGetClient) Get(url string) (*http.Response, error) {
21+
resp := &http.Response{
22+
Body: ioutil.NopCloser(strings.NewReader("")),
23+
StatusCode: http.StatusOK,
24+
}
25+
return resp, nil
26+
}
27+
28+
type failureGetClient struct{}
29+
30+
func (e failureGetClient) Get(url string) (*http.Response, error) {
31+
resp := &http.Response{
32+
Body: ioutil.NopCloser(strings.NewReader("")),
33+
StatusCode: http.StatusServiceUnavailable,
34+
}
35+
return resp, nil
36+
}
37+
38+
type errGetClient struct {
39+
err error
40+
}
41+
42+
func (e errGetClient) Get(url string) (*http.Response, error) {
43+
return nil, e.err
44+
}
45+
46+
func controllerState(client GetClient, succCh chan<- string, errCh chan<- error, stopCh <-chan struct{}) {
47+
url, err := controller.ControllerURLStr("healthz")
48+
if err != nil {
49+
select {
50+
case errCh <- err:
51+
case <-stopCh:
52+
}
53+
return
54+
}
55+
resp, err := client.Get(url)
56+
if err != nil {
57+
select {
58+
case errCh <- err:
59+
case <-stopCh:
60+
}
61+
return
62+
}
63+
defer resp.Body.Close()
64+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
65+
select {
66+
case errCh <- fmt.Errorf("Failed to get controller health status"):
67+
case <-stopCh:
68+
}
69+
return
70+
}
71+
select {
72+
case succCh <- "controller is healthy":
73+
case <-stopCh:
74+
return
75+
}
76+
}

pkg/healthsrv/healthz_handler_test.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"net/http"
77
"net/http/httptest"
8+
"os"
89
"testing"
910

1011
"github.com/arschles/assert"
@@ -45,8 +46,37 @@ func TestHealthZBucketListErr(t *testing.T) {
4546

4647
func TestReadinessNamespaceListErr(t *testing.T) {
4748
nsLister := errNamespaceLister{err: errTest}
49+
client := successGetClient{}
50+
os.Setenv("DEIS_CONTROLLER_SERVICE_HOST", "127.0.0.1")
51+
os.Setenv("DEIS_CONTROLLER_SERVICE_PORT", "8000")
4852

49-
h := readinessHandler(nsLister)
53+
h := readinessHandler(client, nsLister)
54+
w := httptest.NewRecorder()
55+
r, err := http.NewRequest("GET", "/readiness", bytes.NewBuffer(nil))
56+
assert.NoErr(t, err)
57+
h.ServeHTTP(w, r)
58+
assert.Equal(t, w.Code, http.StatusServiceUnavailable, "response code")
59+
assert.Equal(t, w.Body.Len(), 0, "response body length")
60+
}
61+
62+
func TestReadinessControllerErr(t *testing.T) {
63+
nsLister := emptyNamespaceLister{}
64+
client := failureGetClient{}
65+
66+
h := readinessHandler(client, nsLister)
67+
w := httptest.NewRecorder()
68+
r, err := http.NewRequest("GET", "/readiness", bytes.NewBuffer(nil))
69+
assert.NoErr(t, err)
70+
h.ServeHTTP(w, r)
71+
assert.Equal(t, w.Code, http.StatusServiceUnavailable, "response code")
72+
assert.Equal(t, w.Body.Len(), 0, "response body length")
73+
}
74+
75+
func TestReadinessControllerGetErr(t *testing.T) {
76+
nsLister := emptyNamespaceLister{}
77+
client := errGetClient{err: errTest}
78+
79+
h := readinessHandler(client, nsLister)
5080
w := httptest.NewRecorder()
5181
r, err := http.NewRequest("GET", "/readiness", bytes.NewBuffer(nil))
5282
assert.NoErr(t, err)
@@ -71,8 +101,9 @@ func TestHealthZSuccess(t *testing.T) {
71101

72102
func TestReadinessSuccess(t *testing.T) {
73103
nsLister := emptyNamespaceLister{}
104+
client := successGetClient{}
74105

75-
h := readinessHandler(nsLister)
106+
h := readinessHandler(client, nsLister)
76107
w := httptest.NewRecorder()
77108
r, err := http.NewRequest("GET", "/readiness", bytes.NewBuffer(nil))
78109
assert.NoErr(t, err)

pkg/healthsrv/readiness_handler.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,46 @@ import (
88
"k8s.io/kubernetes/pkg/api"
99
)
1010

11-
func readinessHandler(nsLister NamespaceLister) http.Handler {
11+
func readinessHandler(client GetClient, nsLister NamespaceLister) http.Handler {
1212
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1313
stopCh := make(chan struct{})
1414

15+
numChecks := 0
1516
namespaceListerCh := make(chan *api.NamespaceList)
1617
namespaceListerErrCh := make(chan error)
1718
go listNamespaces(nsLister, namespaceListerCh, namespaceListerErrCh, stopCh)
19+
numChecks++
20+
21+
controllerStateCh := make(chan string)
22+
controllerStateErrCh := make(chan error)
23+
go controllerState(client, controllerStateCh, controllerStateErrCh, stopCh)
24+
numChecks++
1825

1926
timeoutCh := time.After(waitTimeout)
2027
defer close(stopCh)
21-
select {
22-
// listing k8s namespaces
23-
case <-namespaceListerCh:
24-
case err := <-namespaceListerErrCh:
25-
log.Printf("Healthcheck error listing namespaces (%s)", err)
26-
w.WriteHeader(http.StatusServiceUnavailable)
27-
// timeout for everything all of the above
28-
case <-timeoutCh:
29-
log.Printf("Healthcheck endpoint timed out after %s", waitTimeout)
30-
w.WriteHeader(http.StatusServiceUnavailable)
28+
for i := 0; i < numChecks; i++ {
29+
select {
30+
// listing k8s namespaces
31+
case <-namespaceListerCh:
32+
case err := <-namespaceListerErrCh:
33+
log.Printf("Readinesscheck error listing namespaces (%s)", err)
34+
w.WriteHeader(http.StatusServiceUnavailable)
35+
return
36+
37+
// listing k8s namespaces
38+
case <-controllerStateCh:
39+
case err := <-controllerStateErrCh:
40+
log.Printf("Readinesscheck error listning controller (%s)", err)
41+
w.WriteHeader(http.StatusServiceUnavailable)
42+
return
43+
44+
// timeout for everything all of the above
45+
case <-timeoutCh:
46+
log.Printf("Readinesscheck endpoint timed out after %s", waitTimeout)
47+
w.WriteHeader(http.StatusServiceUnavailable)
48+
return
49+
50+
}
3151
}
3252
w.WriteHeader(http.StatusOK)
3353
})

pkg/healthsrv/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import (
1111
// with the indicative error.
1212
func Start(port int, nsLister NamespaceLister, bLister BucketLister, sshServerCircuit *sshd.Circuit) error {
1313
mux := http.NewServeMux()
14+
client := &http.Client{}
1415
mux.Handle("/healthz", healthZHandler(bLister, sshServerCircuit))
15-
mux.Handle("/readiness", readinessHandler(nsLister))
16+
mux.Handle("/readiness", readinessHandler(client, nsLister))
1617

1718
hostStr := fmt.Sprintf(":%d", port)
1819
return http.ListenAndServe(hostStr, mux)

0 commit comments

Comments
 (0)