Commit 015bc3b7 authored by Clayton Coleman's avatar Clayton Coleman

Remove global map from healthz

It currently is impossible to use two healthz handlers on different ports in the same process. This removes the global variables in favor of requiring the consumer to specify all health checks up front.
parent 1ce5dda6
...@@ -32,7 +32,6 @@ import ( ...@@ -32,7 +32,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
nodeControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller" nodeControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller"
replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota" "github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota"
"github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/service"
......
...@@ -26,12 +26,17 @@ import ( ...@@ -26,12 +26,17 @@ import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/cmd/kube-controller-manager/app" "github.com/GoogleCloudPlatform/kubernetes/cmd/kube-controller-manager/app"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewCMServer() s := app.NewCMServer()
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
......
...@@ -22,12 +22,17 @@ import ( ...@@ -22,12 +22,17 @@ import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/cmd/kube-proxy/app" "github.com/GoogleCloudPlatform/kubernetes/cmd/kube-proxy/app"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewProxyServer() s := app.NewProxyServer()
......
...@@ -20,97 +20,109 @@ import ( ...@@ -20,97 +20,109 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"sync"
) )
var ( // HealthzChecker is a named healthz check.
// guards names and checks type HealthzChecker interface {
lock = sync.RWMutex{} Name() string
// used to ensure checks are performed in the order added Check(req *http.Request) error
names = []string{} }
checks = map[string]*healthzCheck{}
)
func init() { // DefaultHealthz installs the default healthz check to the http.DefaultServeMux.
http.HandleFunc("/healthz", handleRootHealthz) func DefaultHealthz(checks ...HealthzChecker) {
// add ping health check by default InstallHandler(http.DefaultServeMux, checks...)
AddHealthzFunc("ping", func(_ *http.Request) error {
return nil
})
} }
// AddHealthzFunc adds a health check under the url /healhz/{name} // PingHealthz returns true automatically when checked
func AddHealthzFunc(name string, check func(r *http.Request) error) { var PingHealthz HealthzChecker = ping{}
lock.Lock()
defer lock.Unlock() // ping implements the simplest possible health checker.
if _, found := checks[name]; !found { type ping struct{}
names = append(names, name)
} func (ping) Name() string {
checks[name] = &healthzCheck{name, check} return "ping"
}
// PingHealthz is a health check that returns true.
func (ping) Check(_ *http.Request) error {
return nil
}
// NamedCheck returns a health checker for the given name and function.
func NamedCheck(name string, check func(r *http.Request) error) HealthzChecker {
return &healthzCheck{name, check}
} }
// InstallHandler registers a handler for health checking on the path "/healthz" to mux. // InstallHandler registers a handler for health checking on the path "/healthz" to mux.
func InstallHandler(mux mux) { func InstallHandler(mux mux, checks ...HealthzChecker) {
lock.RLock() if len(checks) == 0 {
defer lock.RUnlock() checks = []HealthzChecker{PingHealthz}
mux.HandleFunc("/healthz", handleRootHealthz) }
mux.Handle("/healthz", handleRootHealthz(checks...))
for _, check := range checks { for _, check := range checks {
mux.HandleFunc(fmt.Sprintf("/healthz/%v", check.name), adaptCheckToHandler(check.check)) mux.Handle(fmt.Sprintf("/healthz/%v", check.Name()), adaptCheckToHandler(check.Check))
} }
} }
// mux is an interface describing the methods InstallHandler requires. // mux is an interface describing the methods InstallHandler requires.
type mux interface { type mux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) Handle(pattern string, handler http.Handler)
} }
// healthzCheck implements HealthzChecker on an arbitrary name and check function.
type healthzCheck struct { type healthzCheck struct {
name string name string
check func(r *http.Request) error check func(r *http.Request) error
} }
func handleRootHealthz(w http.ResponseWriter, r *http.Request) { var _ HealthzChecker = &healthzCheck{}
lock.RLock()
defer lock.RUnlock() func (c *healthzCheck) Name() string {
failed := false return c.name
var verboseOut bytes.Buffer }
for _, name := range names {
check, found := checks[name] func (c *healthzCheck) Check(r *http.Request) error {
if !found { return c.check(r)
// this should not happen }
http.Error(w, fmt.Sprintf("Internal server error: check \"%q\" not registered", name), http.StatusInternalServerError)
// handleRootHealthz returns an http.HandlerFunc that serves the provided checks.
func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
failed := false
var verboseOut bytes.Buffer
for _, check := range checks {
err := check.Check(r)
if err != nil {
fmt.Fprintf(&verboseOut, "[-]%v failed: %v\n", check.Name(), err)
failed = true
} else {
fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name())
}
}
// always be verbose on failure
if failed {
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
return return
} }
err := check.check(r)
if err != nil { if _, found := r.URL.Query()["verbose"]; !found {
fmt.Fprintf(&verboseOut, "[-]%v failed: %v\n", check.name, err) fmt.Fprint(w, "ok")
failed = true return
} else {
fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.name)
} }
}
// always be verbose on failure
if failed {
http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError)
return
}
if _, found := r.URL.Query()["verbose"]; !found {
fmt.Fprint(w, "ok")
return
} else {
verboseOut.WriteTo(w) verboseOut.WriteTo(w)
fmt.Fprint(w, "healthz check passed\n") fmt.Fprint(w, "healthz check passed\n")
} })
} }
func adaptCheckToHandler(c func(r *http.Request) error) func(w http.ResponseWriter, r *http.Request) { // adaptCheckToHandler returns an http.HandlerFunc that serves the provided checks.
return func(w http.ResponseWriter, r *http.Request) { func adaptCheckToHandler(c func(r *http.Request) error) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := c(r) err := c(r)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError)
} else { } else {
fmt.Fprint(w, "ok") fmt.Fprint(w, "ok")
} }
} })
} }
...@@ -59,12 +59,13 @@ func TestMulitipleChecks(t *testing.T) { ...@@ -59,12 +59,13 @@ func TestMulitipleChecks(t *testing.T) {
for i, test := range tests { for i, test := range tests {
mux := http.NewServeMux() mux := http.NewServeMux()
checks := []HealthzChecker{PingHealthz}
if test.addBadCheck { if test.addBadCheck {
AddHealthzFunc("bad", func(_ *http.Request) error { checks = append(checks, NamedCheck("bad", func(_ *http.Request) error {
return errors.New("this will fail") return errors.New("this will fail")
}) }))
} }
InstallHandler(mux) InstallHandler(mux, checks...)
req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%v", test.path), nil) req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%v", test.path), nil)
if err != nil { if err != nil {
t.Fatalf("case[%d] Unexpected error: %v", i, err) t.Fatalf("case[%d] Unexpected error: %v", i, err)
......
...@@ -110,9 +110,11 @@ func NewServer(host HostInterface, enableDebuggingHandlers bool) Server { ...@@ -110,9 +110,11 @@ func NewServer(host HostInterface, enableDebuggingHandlers bool) Server {
// InstallDefaultHandlers registers the default set of supported HTTP request patterns with the mux. // InstallDefaultHandlers registers the default set of supported HTTP request patterns with the mux.
func (s *Server) InstallDefaultHandlers() { func (s *Server) InstallDefaultHandlers() {
healthz.AddHealthzFunc("docker", s.dockerHealthCheck) healthz.InstallHandler(s.mux,
healthz.AddHealthzFunc("hostname", s.hostnameHealthCheck) healthz.PingHealthz,
healthz.InstallHandler(s.mux) healthz.NamedCheck("docker", s.dockerHealthCheck),
healthz.NamedCheck("hostname", s.hostnameHealthCheck),
)
s.mux.HandleFunc("/podInfo", s.handlePodInfoOld) s.mux.HandleFunc("/podInfo", s.handlePodInfoOld)
s.mux.HandleFunc("/api/v1beta1/podInfo", s.handlePodInfoVersioned) s.mux.HandleFunc("/api/v1beta1/podInfo", s.handlePodInfoVersioned)
s.mux.HandleFunc("/api/v1beta1/nodeInfo", s.handleNodeInfoVersioned) s.mux.HandleFunc("/api/v1beta1/nodeInfo", s.handleNodeInfoVersioned)
......
...@@ -28,7 +28,6 @@ import ( ...@@ -28,7 +28,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler"
......
...@@ -19,6 +19,7 @@ package main ...@@ -19,6 +19,7 @@ package main
import ( import (
"runtime" "runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/GoogleCloudPlatform/kubernetes/plugin/cmd/kube-scheduler/app" "github.com/GoogleCloudPlatform/kubernetes/plugin/cmd/kube-scheduler/app"
...@@ -26,6 +27,10 @@ import ( ...@@ -26,6 +27,10 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func init() {
healthz.DefaultHealthz()
}
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
s := app.NewSchedulerServer() s := app.NewSchedulerServer()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment