Kill LivenessProbe.Type

pull/6/head
Tim Hockin 2014-09-27 21:16:30 -07:00
parent 04cdf286a4
commit 1c02af3d16
14 changed files with 56 additions and 52 deletions

View File

@ -163,9 +163,11 @@ func main() {
float32(*registryPullQPS), float32(*registryPullQPS),
*registryBurst) *registryBurst)
health.AddHealthChecker("exec", health.NewExecHealthChecker(k)) // TODO: These should probably become more plugin-ish: register a factory func
health.AddHealthChecker("http", health.NewHTTPHealthChecker(&http.Client{})) // in each checker's init(), iterate those here.
health.AddHealthChecker("tcp", &health.TCPHealthChecker{}) health.AddHealthChecker(health.NewExecHealthChecker(k))
health.AddHealthChecker(health.NewHTTPHealthChecker(&http.Client{}))
health.AddHealthChecker(&health.TCPHealthChecker{})
// start the kubelet // start the kubelet
go util.Forever(func() { k.Run(cfg.Updates()) }, 0) go util.Forever(func() { k.Run(cfg.Updates()) }, 0)

View File

@ -171,8 +171,6 @@ type ExecAction struct {
// LivenessProbe describes a liveness probe to be examined to the container. // LivenessProbe describes a liveness probe to be examined to the container.
// TODO: pass structured data to the actions, and document that data here. // TODO: pass structured data to the actions, and document that data here.
type LivenessProbe struct { type LivenessProbe struct {
// Type of liveness probe. Current legal values "http", "tcp"
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// HTTPGetProbe parameters, required if Type == 'http' // HTTPGetProbe parameters, required if Type == 'http'
HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
// TCPSocketProbe parameter, required if Type == 'tcp' // TCPSocketProbe parameter, required if Type == 'tcp'

View File

@ -181,8 +181,6 @@ type ExecAction struct {
// LivenessProbe describes a liveness probe to be examined to the container. // LivenessProbe describes a liveness probe to be examined to the container.
// TODO: pass structured data to the actions, and document that data here. // TODO: pass structured data to the actions, and document that data here.
type LivenessProbe struct { type LivenessProbe struct {
// Type of liveness probe. Current legal values "http", "tcp"
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// HTTPGetProbe parameters, required if Type == 'http' // HTTPGetProbe parameters, required if Type == 'http'
HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
// TCPSocketProbe parameter, required if Type == 'tcp' // TCPSocketProbe parameter, required if Type == 'tcp'

View File

@ -180,8 +180,6 @@ type ExecAction struct {
// LivenessProbe describes a liveness probe to be examined to the container. // LivenessProbe describes a liveness probe to be examined to the container.
// TODO: pass structured data to the actions, and document that data here. // TODO: pass structured data to the actions, and document that data here.
type LivenessProbe struct { type LivenessProbe struct {
// Type of liveness probe. Current legal values "http", "tcp"
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// HTTPGetProbe parameters, required if Type == 'http' // HTTPGetProbe parameters, required if Type == 'http'
HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
// TCPSocketProbe parameter, required if Type == 'tcp' // TCPSocketProbe parameter, required if Type == 'tcp'

View File

@ -171,8 +171,6 @@ type ExecAction struct {
// LivenessProbe describes a liveness probe to be examined to the container. // LivenessProbe describes a liveness probe to be examined to the container.
// TODO: pass structured data to the actions, and document that data here. // TODO: pass structured data to the actions, and document that data here.
type LivenessProbe struct { type LivenessProbe struct {
// Type of liveness probe. Current legal values "http", "tcp"
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// HTTPGetProbe parameters, required if Type == 'http' // HTTPGetProbe parameters, required if Type == 'http'
HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"`
// TCPSocketProbe parameter, required if Type == 'tcp' // TCPSocketProbe parameter, required if Type == 'tcp'

View File

@ -57,3 +57,7 @@ func (e *ExecHealthChecker) HealthCheck(podFullName string, currentState api.Pod
} }
return Healthy, nil return Healthy, nil
} }
func (e *ExecHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
return probe.Exec != nil
}

View File

@ -49,22 +49,19 @@ func TestExec(t *testing.T) {
checker := ExecHealthChecker{&fake} checker := ExecHealthChecker{&fake}
tests := []healthCheckTest{ tests := []healthCheckTest{
// Missing parameters // Missing parameters
{Unknown, &api.LivenessProbe{Type: "exec"}, true, nil, nil}, {Unknown, &api.LivenessProbe{}, true, nil, nil},
// Ok // Ok
{Healthy, &api.LivenessProbe{ {Healthy, &api.LivenessProbe{
Type: "exec",
Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, Exec: &api.ExecAction{Command: []string{"ls", "-l"}},
}, false, []byte("OK"), nil}, }, false, []byte("OK"), nil},
// Run returns error // Run returns error
{Unknown, &api.LivenessProbe{ {Unknown, &api.LivenessProbe{
Type: "exec",
Exec: &api.ExecAction{ Exec: &api.ExecAction{
Command: []string{"ls", "-l"}, Command: []string{"ls", "-l"},
}, },
}, true, []byte("OK, NOT"), fmt.Errorf("test error")}, }, true, []byte("OK, NOT"), fmt.Errorf("test error")},
// Command error // Command error
{Unhealthy, &api.LivenessProbe{ {Unhealthy, &api.LivenessProbe{
Type: "exec",
Exec: &api.ExecAction{ Exec: &api.ExecAction{
Command: []string{"ls", "-l"}, Command: []string{"ls", "-l"},
}, },

View File

@ -36,54 +36,61 @@ const (
// HealthChecker defines an abstract interface for checking container health. // HealthChecker defines an abstract interface for checking container health.
type HealthChecker interface { type HealthChecker interface {
HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error) HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error)
CanCheck(probe *api.LivenessProbe) bool
} }
// protects checkers // protects allCheckers
var checkerLock = sync.Mutex{} var checkerLock = sync.Mutex{}
var checkers = map[string]HealthChecker{} var allCheckers = []HealthChecker{}
// AddHealthChecker adds a health checker to the list of known HealthChecker objects. // AddHealthChecker adds a health checker to the list of known HealthChecker objects.
// Any subsequent call to NewHealthChecker will know about this HealthChecker. // Any subsequent call to NewHealthChecker will know about this HealthChecker.
// Panics if 'key' is already present. func AddHealthChecker(checker HealthChecker) {
func AddHealthChecker(key string, checker HealthChecker) {
checkerLock.Lock() checkerLock.Lock()
defer checkerLock.Unlock() defer checkerLock.Unlock()
if _, found := checkers[key]; found { allCheckers = append(allCheckers, checker)
glog.Fatalf("HealthChecker already defined for key %s.", key)
}
checkers[key] = checker
} }
// NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes. // NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes.
func NewHealthChecker() HealthChecker { func NewHealthChecker() HealthChecker {
checkerLock.Lock() checkerLock.Lock()
defer checkerLock.Unlock() defer checkerLock.Unlock()
input := map[string]HealthChecker{}
for key, value := range checkers {
input[key] = value
}
return &muxHealthChecker{ return &muxHealthChecker{
checkers: input, checkers: append([]HealthChecker{}, allCheckers...),
} }
} }
// muxHealthChecker bundles multiple implementations of HealthChecker of different types. // muxHealthChecker bundles multiple implementations of HealthChecker of different types.
type muxHealthChecker struct { type muxHealthChecker struct {
checkers map[string]HealthChecker // Given a LivenessProbe, cycle through each known checker and see if it supports
// the specific kind of probe (by returning non-nil).
checkers []HealthChecker
}
func (m *muxHealthChecker) findCheckerFor(probe *api.LivenessProbe) HealthChecker {
for i := range m.checkers {
if m.checkers[i].CanCheck(probe) {
return m.checkers[i]
}
}
return nil
} }
// HealthCheck delegates the health-checking of the container to one of the bundled implementations. // HealthCheck delegates the health-checking of the container to one of the bundled implementations.
// It chooses an implementation according to container.LivenessProbe.Type. // If there is no health checker that can check container it returns Unknown, nil.
// If there is no matching health checker it returns Unknown, nil.
func (m *muxHealthChecker) HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error) { func (m *muxHealthChecker) HealthCheck(podFullName string, currentState api.PodState, container api.Container) (Status, error) {
checker, ok := m.checkers[container.LivenessProbe.Type] checker := m.findCheckerFor(container.LivenessProbe)
if !ok || checker == nil { if checker == nil {
glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type) glog.Warningf("Failed to find health checker for %s %+v", container.Name, container.LivenessProbe)
return Unknown, nil return Unknown, nil
} }
return checker.HealthCheck(podFullName, currentState, container) return checker.HealthCheck(podFullName, currentState, container)
} }
func (m *muxHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
return m.findCheckerFor(probe) != nil
}
// findPortByName is a helper function to look up a port in a container by name. // findPortByName is a helper function to look up a port in a container by name.
// Returns the HostPort if found, -1 if not found. // Returns the HostPort if found, -1 if not found.
func findPortByName(container api.Container, portName string) int { func findPortByName(container api.Container, portName string) int {

View File

@ -30,7 +30,7 @@ import (
const statusServerEarlyShutdown = -1 const statusServerEarlyShutdown = -1
func TestHealthChecker(t *testing.T) { func TestHealthChecker(t *testing.T) {
AddHealthChecker("http", &HTTPHealthChecker{client: &http.Client{}}) AddHealthChecker(&HTTPHealthChecker{client: &http.Client{}})
var healthCheckerTests = []struct { var healthCheckerTests = []struct {
status int status int
health Status health Status
@ -64,7 +64,6 @@ func TestHealthChecker(t *testing.T) {
Path: "/foo/bar", Path: "/foo/bar",
Host: host, Host: host,
}, },
Type: "http",
}, },
} }
hc := NewHealthChecker() hc := NewHealthChecker()
@ -100,19 +99,16 @@ func TestFindPortByName(t *testing.T) {
func TestMuxHealthChecker(t *testing.T) { func TestMuxHealthChecker(t *testing.T) {
muxHealthCheckerTests := []struct { muxHealthCheckerTests := []struct {
health Status health Status
probeType string
}{ }{
{Healthy, "http"}, // TODO: This test should run through a few different checker types.
{Unknown, "ftp"}, {Healthy},
} }
mc := &muxHealthChecker{ mc := &muxHealthChecker{
checkers: make(map[string]HealthChecker), checkers: []HealthChecker{
&HTTPHealthChecker{client: &http.Client{}},
},
} }
hc := &HTTPHealthChecker{
client: &http.Client{},
}
mc.checkers["http"] = hc
for _, muxHealthCheckerTest := range muxHealthCheckerTests { for _, muxHealthCheckerTest := range muxHealthCheckerTests {
tt := muxHealthCheckerTest tt := muxHealthCheckerTest
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -131,7 +127,6 @@ func TestMuxHealthChecker(t *testing.T) {
HTTPGet: &api.HTTPGetAction{}, HTTPGet: &api.HTTPGetAction{},
}, },
} }
container.LivenessProbe.Type = tt.probeType
container.LivenessProbe.HTTPGet.Port = util.NewIntOrStringFromString(port) container.LivenessProbe.HTTPGet.Port = util.NewIntOrStringFromString(port)
container.LivenessProbe.HTTPGet.Host = host container.LivenessProbe.HTTPGet.Host = host
health, err := mc.HealthCheck("test", api.PodState{}, container) health, err := mc.HealthCheck("test", api.PodState{}, container)

View File

@ -105,3 +105,7 @@ func (h *HTTPHealthChecker) HealthCheck(podFullName string, currentState api.Pod
} }
return DoHTTPCheck(formatURL(host, port, path), h.client) return DoHTTPCheck(formatURL(host, port, path), h.client)
} }
func (h *HTTPHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
return probe.HTTPGet != nil
}

View File

@ -51,7 +51,6 @@ func TestGetURLParts(t *testing.T) {
Ports: []api.Port{{Name: "found", HostPort: 93}}, Ports: []api.Port{{Name: "found", HostPort: 93}},
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
HTTPGet: test.probe, HTTPGet: test.probe,
Type: "http",
}, },
} }
host, port, path, err := getURLParts(state, container) host, port, path, err := getURLParts(state, container)
@ -117,7 +116,6 @@ func TestHTTPHealthChecker(t *testing.T) {
container := api.Container{ container := api.Container{
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
HTTPGet: test.probe, HTTPGet: test.probe,
Type: "http",
}, },
} }
params := container.LivenessProbe.HTTPGet params := container.LivenessProbe.HTTPGet

View File

@ -81,3 +81,7 @@ func (t *TCPHealthChecker) HealthCheck(podFullName string, currentState api.PodS
} }
return DoTCPCheck(net.JoinHostPort(host, strconv.Itoa(port))) return DoTCPCheck(net.JoinHostPort(host, strconv.Itoa(port)))
} }
func (t *TCPHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
return probe.TCPSocket != nil
}

View File

@ -49,7 +49,6 @@ func TestGetTCPAddrParts(t *testing.T) {
Ports: []api.Port{{Name: "found", HostPort: 93}}, Ports: []api.Port{{Name: "found", HostPort: 93}},
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
TCPSocket: test.probe, TCPSocket: test.probe,
Type: "tcp",
}, },
} }
host, port, err := getTCPAddrParts(state, container) host, port, err := getTCPAddrParts(state, container)
@ -95,7 +94,6 @@ func TestTcpHealthChecker(t *testing.T) {
container := api.Container{ container := api.Container{
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
TCPSocket: test.probe, TCPSocket: test.probe,
Type: "tcp",
}, },
} }
params := container.LivenessProbe.TCPSocket params := container.LivenessProbe.TCPSocket

View File

@ -457,6 +457,10 @@ func (f *FalseHealthChecker) HealthCheck(podFullName string, state api.PodState,
return health.Unhealthy, nil return health.Unhealthy, nil
} }
func (f *FalseHealthChecker) CanCheck(probe *api.LivenessProbe) bool {
return true
}
func TestSyncPodBadHash(t *testing.T) { func TestSyncPodBadHash(t *testing.T) {
kubelet, _, fakeDocker := newTestKubelet(t) kubelet, _, fakeDocker := newTestKubelet(t)
kubelet.healthChecker = &FalseHealthChecker{} kubelet.healthChecker = &FalseHealthChecker{}
@ -522,8 +526,7 @@ func TestSyncPodUnhealthy(t *testing.T) {
Containers: []api.Container{ Containers: []api.Container{
{Name: "bar", {Name: "bar",
LivenessProbe: &api.LivenessProbe{ LivenessProbe: &api.LivenessProbe{
// Always returns healthy == false // Always returns healthy == false
Type: "false",
}, },
}, },
}, },