diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index a9299944a7d..6042368d900 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -39,11 +39,11 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" nodeControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/controller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/empty_dir" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/master" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/standalone" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -84,8 +84,8 @@ func (fakeKubeletClient) GetPodStatus(host, podNamespace, podID string) (api.Pod return c.GetPodStatus("localhost", podNamespace, podID) } -func (fakeKubeletClient) HealthCheck(host string) (health.Status, error) { - return health.Healthy, nil +func (fakeKubeletClient) HealthCheck(host string) (probe.Status, error) { + return probe.Healthy, nil } type delegateHandler struct { diff --git a/pkg/apiserver/validator.go b/pkg/apiserver/validator.go index bd8bb1e21e6..0d2aebae467 100644 --- a/pkg/apiserver/validator.go +++ b/pkg/apiserver/validator.go @@ -24,7 +24,7 @@ import ( "net/http" "strconv" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" ) // TODO: this basic interface is duplicated in N places. consolidate? @@ -45,29 +45,30 @@ type validator struct { client httpGet } -func (s *Server) check(client httpGet) (health.Status, string, error) { +// TODO: can this use pkg/probe/http +func (s *Server) check(client httpGet) (probe.Status, string, error) { resp, err := client.Get("http://" + net.JoinHostPort(s.Addr, strconv.Itoa(s.Port)) + s.Path) if err != nil { - return health.Unknown, "", err + return probe.Unknown, "", err } defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { - return health.Unknown, string(data), err + return probe.Unknown, string(data), err } if resp.StatusCode != http.StatusOK { - return health.Unhealthy, string(data), + return probe.Unhealthy, string(data), fmt.Errorf("unhealthy http status code: %d (%s)", resp.StatusCode, resp.Status) } - return health.Healthy, string(data), nil + return probe.Healthy, string(data), nil } type ServerStatus struct { - Component string `json:"component,omitempty"` - Health string `json:"health,omitempty"` - HealthCode health.Status `json:"healthCode,omitempty"` - Msg string `json:"msg,omitempty"` - Err string `json:"err,omitempty"` + Component string `json:"component,omitempty"` + Health string `json:"health,omitempty"` + HealthCode probe.Status `json:"healthCode,omitempty"` + Msg string `json:"msg,omitempty"` + Err string `json:"err,omitempty"` } func (v *validator) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/apiserver/validator_test.go b/pkg/apiserver/validator_test.go index ee4d5f03b86..79011211964 100644 --- a/pkg/apiserver/validator_test.go +++ b/pkg/apiserver/validator_test.go @@ -25,7 +25,7 @@ import ( "net/http/httptest" "testing" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -54,13 +54,13 @@ func TestValidate(t *testing.T) { tests := []struct { err error data string - expectedStatus health.Status + expectedStatus probe.Status code int expectErr bool }{ - {fmt.Errorf("test error"), "", health.Unknown, 500 /*ignored*/, true}, - {nil, "foo", health.Healthy, 200, false}, - {nil, "foo", health.Unhealthy, 500, true}, + {fmt.Errorf("test error"), "", probe.Unknown, 500 /*ignored*/, true}, + {nil, "foo", probe.Healthy, 200, false}, + {nil, "foo", probe.Unhealthy, 500, true}, } s := Server{Addr: "foo.com", Port: 8080, Path: "/healthz"} diff --git a/pkg/client/kubelet.go b/pkg/client/kubelet.go index 8c1c5b7186b..7bbc396d232 100644 --- a/pkg/client/kubelet.go +++ b/pkg/client/kubelet.go @@ -26,7 +26,8 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" + httprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/http" ) // ErrPodInfoNotAvailable may be returned when the requested pod info is not available. @@ -40,7 +41,7 @@ type KubeletClient interface { // KubeletHealthchecker is an interface for healthchecking kubelets type KubeletHealthChecker interface { - HealthCheck(host string) (health.Status, error) + HealthCheck(host string) (probe.Status, error) } // PodInfoGetter is an interface for things that can get information about a pod's containers. @@ -146,8 +147,8 @@ func (c *HTTPKubeletClient) GetPodStatus(host, podNamespace, podID string) (api. return status, nil } -func (c *HTTPKubeletClient) HealthCheck(host string) (health.Status, error) { - return health.DoHTTPCheck(fmt.Sprintf("%s/healthz", c.url(host)), c.Client) +func (c *HTTPKubeletClient) HealthCheck(host string) (probe.Status, error) { + return httprobe.DoHTTPProbe(fmt.Sprintf("%s/healthz", c.url(host)), c.Client) } // FakeKubeletClient is a fake implementation of KubeletClient which returns an error @@ -160,6 +161,6 @@ func (c FakeKubeletClient) GetPodStatus(host, podNamespace string, podID string) return api.PodStatusResult{}, errors.New("Not Implemented") } -func (c FakeKubeletClient) HealthCheck(host string) (health.Status, error) { - return health.Unknown, errors.New("Not Implemented") +func (c FakeKubeletClient) HealthCheck(host string) (probe.Status, error) { + return probe.Unknown, errors.New("Not Implemented") } diff --git a/pkg/client/kubelet_test.go b/pkg/client/kubelet_test.go index a36b2986ec2..a5205f8781c 100644 --- a/pkg/client/kubelet_test.go +++ b/pkg/client/kubelet_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -134,8 +134,8 @@ func TestNewKubeletClient(t *testing.T) { host := "127.0.0.1" healthStatus, err := client.HealthCheck(host) - if healthStatus != health.Unhealthy { - t.Errorf("Expected %v and got %v.", health.Unhealthy, healthStatus) + if healthStatus != probe.Unhealthy { + t.Errorf("Expected %v and got %v.", probe.Unhealthy, healthStatus) } if err != nil { t.Error("Expected a nil error") diff --git a/pkg/health/doc.go b/pkg/health/doc.go deleted file mode 100644 index 6c5580a1301..00000000000 --- a/pkg/health/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package health contains utilities for health checking, as well as health status information. -package health diff --git a/pkg/health/exec.go b/pkg/health/exec.go deleted file mode 100644 index 9f0ad4434c1..00000000000 --- a/pkg/health/exec.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "fmt" - "strings" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/types" - "github.com/golang/glog" -) - -const defaultHealthyOutput = "ok" - -type CommandRunner interface { - RunInContainer(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) -} - -type ExecHealthChecker struct { - runner CommandRunner -} - -func NewExecHealthChecker(runner CommandRunner) HealthChecker { - return &ExecHealthChecker{runner} -} - -func (e *ExecHealthChecker) HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (Status, error) { - if container.LivenessProbe.Exec == nil { - return Unknown, fmt.Errorf("missing exec parameters") - } - data, err := e.runner.RunInContainer(podFullName, podUID, container.Name, container.LivenessProbe.Exec.Command) - glog.V(1).Infof("container %s health check response: %s", podFullName, string(data)) - if err != nil { - return Unknown, err - } - if strings.ToLower(string(data)) != defaultHealthyOutput { - return Unhealthy, nil - } - return Healthy, nil -} - -func (e *ExecHealthChecker) CanCheck(probe *api.LivenessProbe) bool { - return probe.Exec != nil -} diff --git a/pkg/health/exec_test.go b/pkg/health/exec_test.go deleted file mode 100644 index a9aa2199c89..00000000000 --- a/pkg/health/exec_test.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "fmt" - "reflect" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/types" -) - -type FakeExec struct { - cmd []string - out []byte - err error -} - -func (f *FakeExec) RunInContainer(podFullName string, uid types.UID, container string, cmd []string) ([]byte, error) { - f.cmd = cmd - return f.out, f.err -} - -type healthCheckTest struct { - expectedStatus Status - probe *api.LivenessProbe - expectError bool - output []byte - err error -} - -func TestExec(t *testing.T) { - fake := FakeExec{} - checker := ExecHealthChecker{&fake} - tests := []healthCheckTest{ - // Missing parameters - {Unknown, &api.LivenessProbe{}, true, nil, nil}, - // Ok - {Healthy, &api.LivenessProbe{ - Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, - }, false, []byte("OK"), nil}, - // Run returns error - {Unknown, &api.LivenessProbe{ - Exec: &api.ExecAction{ - Command: []string{"ls", "-l"}, - }, - }, true, []byte("OK, NOT"), fmt.Errorf("test error")}, - // Unhealthy - {Unhealthy, &api.LivenessProbe{ - Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, - }, false, []byte("Fail"), nil}, - } - for _, test := range tests { - fake.out = test.output - fake.err = test.err - status, err := checker.HealthCheck("test", "", api.PodStatus{}, api.Container{LivenessProbe: test.probe}) - if status != test.expectedStatus { - t.Errorf("expected %v, got %v", test.expectedStatus, status) - } - if err != nil && test.expectError == false { - t.Errorf("unexpected error: %v", err) - } - if err == nil && test.expectError == true { - t.Errorf("unexpected non-error") - } - if test.probe.Exec != nil && !reflect.DeepEqual(fake.cmd, test.probe.Exec.Command) { - t.Errorf("expected: %v, got %v", test.probe.Exec.Command, fake.cmd) - } - } -} diff --git a/pkg/health/health.go b/pkg/health/health.go deleted file mode 100644 index 64233e166ee..00000000000 --- a/pkg/health/health.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "sync" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/types" - "github.com/golang/glog" -) - -// Status represents the result of a single health-check operation. -type Status int - -// Status values must be one of these constants. -const ( - Healthy Status = iota - Unhealthy - Unknown -) - -// HealthChecker defines an abstract interface for checking container health. -type HealthChecker interface { - HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (Status, error) - CanCheck(probe *api.LivenessProbe) bool -} - -// protects allCheckers -var checkerLock = sync.Mutex{} -var allCheckers = []HealthChecker{} - -// AddHealthChecker adds a health checker to the list of known HealthChecker objects. -// Any subsequent call to NewHealthChecker will know about this HealthChecker. -func AddHealthChecker(checker HealthChecker) { - checkerLock.Lock() - defer checkerLock.Unlock() - allCheckers = append(allCheckers, checker) -} - -// NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes. -func NewHealthChecker() HealthChecker { - checkerLock.Lock() - defer checkerLock.Unlock() - return &muxHealthChecker{ - checkers: append([]HealthChecker{}, allCheckers...), - } -} - -// muxHealthChecker bundles multiple implementations of HealthChecker of different types. -type muxHealthChecker struct { - // 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. -// If there is no health checker that can check container it returns Unknown, nil. -func (m *muxHealthChecker) HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (Status, error) { - checker := m.findCheckerFor(container.LivenessProbe) - if checker == nil { - glog.Warningf("Failed to find health checker for %s %+v", container.Name, container.LivenessProbe) - return Unknown, nil - } - return checker.HealthCheck(podFullName, podUID, status, 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. -// Returns the HostPort if found, -1 if not found. -func findPortByName(container api.Container, portName string) int { - for _, port := range container.Ports { - if port.Name == portName { - return port.HostPort - } - } - return -1 -} - -func (s Status) String() string { - switch s { - case Healthy: - return "healthy" - case Unhealthy: - return "unhealthy" - default: - return "unknown" - } -} diff --git a/pkg/health/health_test.go b/pkg/health/health_test.go deleted file mode 100644 index c3b29aad482..00000000000 --- a/pkg/health/health_test.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "net" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) - -const statusServerEarlyShutdown = -1 - -func TestHealthChecker(t *testing.T) { - AddHealthChecker(&HTTPHealthChecker{client: &http.Client{}}) - var healthCheckerTests = []struct { - status int - health Status - }{ - {http.StatusOK, Healthy}, - {statusServerEarlyShutdown, Unhealthy}, - {http.StatusBadRequest, Unhealthy}, - {http.StatusBadGateway, Unhealthy}, - {http.StatusInternalServerError, Unhealthy}, - } - for _, healthCheckerTest := range healthCheckerTests { - tt := healthCheckerTest - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(tt.status) - })) - defer ts.Close() - u, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if tt.status == statusServerEarlyShutdown { - ts.Close() - } - container := api.Container{ - LivenessProbe: &api.LivenessProbe{ - HTTPGet: &api.HTTPGetAction{ - Port: util.NewIntOrStringFromString(port), - Path: "/foo/bar", - Host: host, - }, - }, - } - hc := NewHealthChecker() - health, err := hc.HealthCheck("test", "", api.PodStatus{}, container) - if err != nil && tt.health != Unhealthy { - t.Errorf("Unexpected error: %v", err) - } - if health != tt.health { - t.Errorf("Expected %v, got %v", tt.health, health) - } - } -} - -func TestFindPortByName(t *testing.T) { - container := api.Container{ - Ports: []api.Port{ - { - Name: "foo", - HostPort: 8080, - }, - { - Name: "bar", - HostPort: 9000, - }, - }, - } - want := 8080 - got := findPortByName(container, "foo") - if got != want { - t.Errorf("Expected %v, got %v", want, got) - } -} - -func TestMuxHealthChecker(t *testing.T) { - muxHealthCheckerTests := []struct { - health Status - }{ - // TODO: This test should run through a few different checker types. - {Healthy}, - } - mc := &muxHealthChecker{ - checkers: []HealthChecker{ - &HTTPHealthChecker{client: &http.Client{}}, - }, - } - for _, muxHealthCheckerTest := range muxHealthCheckerTests { - tt := muxHealthCheckerTest - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer ts.Close() - u, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - container := api.Container{ - LivenessProbe: &api.LivenessProbe{ - HTTPGet: &api.HTTPGetAction{}, - }, - } - container.LivenessProbe.HTTPGet.Port = util.NewIntOrStringFromString(port) - container.LivenessProbe.HTTPGet.Host = host - health, err := mc.HealthCheck("test", "", api.PodStatus{}, container) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if health != tt.health { - t.Errorf("Expected %v, got %v", tt.health, health) - } - } -} diff --git a/pkg/health/http.go b/pkg/health/http.go deleted file mode 100644 index f5b44793887..00000000000 --- a/pkg/health/http.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "fmt" - "net" - "net/http" - "net/url" - "strconv" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/types" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" -) - -// HTTPGetInterface is an abstract interface for testability. It abstracts the interface of http.Client.Get. -// This is exported because some other packages may want to do direct HTTP checks. -type HTTPGetInterface interface { - Get(url string) (*http.Response, error) -} - -// HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests. -type HTTPHealthChecker struct { - client HTTPGetInterface -} - -func NewHTTPHealthChecker(client *http.Client) HealthChecker { - return &HTTPHealthChecker{client: &http.Client{}} -} - -// getURLParts parses the components of the target URL. For testability. -func getURLParts(status api.PodStatus, container api.Container) (string, int, string, error) { - params := container.LivenessProbe.HTTPGet - if params == nil { - return "", -1, "", fmt.Errorf("no HTTP parameters specified: %v", container) - } - port := -1 - switch params.Port.Kind { - case util.IntstrInt: - port = params.Port.IntVal - case util.IntstrString: - port = findPortByName(container, params.Port.StrVal) - if port == -1 { - // Last ditch effort - maybe it was an int stored as string? - var err error - if port, err = strconv.Atoi(params.Port.StrVal); err != nil { - return "", -1, "", err - } - } - } - if port == -1 { - return "", -1, "", fmt.Errorf("unknown port: %v", params.Port) - } - var host string - if len(params.Host) > 0 { - host = params.Host - } else { - host = status.PodIP - } - - return host, port, params.Path, nil -} - -// formatURL formats a URL from args. For testability. -func formatURL(host string, port int, path string) string { - u := url.URL{ - Scheme: "http", - Host: net.JoinHostPort(host, strconv.Itoa(port)), - Path: path, - } - return u.String() -} - -// DoHTTPCheck checks if a GET request to the url succeeds. -// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Healthy. -// If the HTTP response code is unsuccessful or HTTP communication fails, it returns Unhealthy. -// This is exported because some other packages may want to do direct HTTP checks. -func DoHTTPCheck(url string, client HTTPGetInterface) (Status, error) { - res, err := client.Get(url) - if err != nil { - glog.V(1).Infof("HTTP probe error: %v", err) - return Unhealthy, nil - } - defer res.Body.Close() - if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { - return Healthy, nil - } - glog.V(1).Infof("Health check failed for %s, Response: %v", url, *res) - return Unhealthy, nil -} - -// HealthCheck checks if the container is healthy by trying sending HTTP Get requests to the container. -func (h *HTTPHealthChecker) HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (Status, error) { - host, port, path, err := getURLParts(status, container) - if err != nil { - return Unknown, err - } - return DoHTTPCheck(formatURL(host, port, path), h.client) -} - -func (h *HTTPHealthChecker) CanCheck(probe *api.LivenessProbe) bool { - return probe.HTTPGet != nil -} diff --git a/pkg/health/http_test.go b/pkg/health/http_test.go deleted file mode 100644 index f83649cb9c5..00000000000 --- a/pkg/health/http_test.go +++ /dev/null @@ -1,137 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "net" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) - -func TestGetURLParts(t *testing.T) { - testCases := []struct { - probe *api.HTTPGetAction - ok bool - host string - port int - path string - }{ - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(-1), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString(""), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("-1"), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("not-found"), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(76), Path: ""}, true, "127.0.0.1", 76, ""}, - {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, - {&api.HTTPGetAction{Host: "hostname", Port: util.NewIntOrStringFromInt(76), Path: "path"}, true, "hostname", 76, "path"}, - } - - for _, test := range testCases { - state := api.PodStatus{PodIP: "127.0.0.1"} - container := api.Container{ - Ports: []api.Port{{Name: "found", HostPort: 93}}, - LivenessProbe: &api.LivenessProbe{ - HTTPGet: test.probe, - }, - } - host, port, path, err := getURLParts(state, container) - if !test.ok && err == nil { - t.Errorf("Expected error for %+v, got %s:%d/%s", test, host, port, path) - } - if test.ok && err != nil { - t.Errorf("Unexpected error: %v", err) - } - if test.ok { - if host != test.host || port != test.port || path != test.path { - t.Errorf("Expected %s:%d/%s, got %s:%d/%s", - test.host, test.port, test.path, host, port, path) - } - } - } -} - -func TestFormatURL(t *testing.T) { - testCases := []struct { - host string - port int - path string - result string - }{ - {"localhost", 93, "", "http://localhost:93"}, - {"localhost", 93, "/path", "http://localhost:93/path"}, - } - for _, test := range testCases { - url := formatURL(test.host, test.port, test.path) - if url != test.result { - t.Errorf("Expected %s, got %s", test.result, url) - } - } -} - -func TestHTTPHealthChecker(t *testing.T) { - testCases := []struct { - probe *api.HTTPGetAction - status int - health Status - }{ - // The probe will be filled in below. This is primarily testing that an HTTP GET happens. - {&api.HTTPGetAction{}, http.StatusOK, Healthy}, - {&api.HTTPGetAction{}, -1, Unhealthy}, - {nil, -1, Unknown}, - } - hc := &HTTPHealthChecker{ - client: &http.Client{}, - } - for _, test := range testCases { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(test.status) - })) - u, err := url.Parse(ts.URL) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - container := api.Container{ - LivenessProbe: &api.LivenessProbe{ - HTTPGet: test.probe, - }, - } - params := container.LivenessProbe.HTTPGet - if params != nil { - params.Port = util.NewIntOrStringFromString(port) - params.Host = host - } - health, err := hc.HealthCheck("test", "", api.PodStatus{PodIP: host}, container) - if test.health == Unknown && err == nil { - t.Errorf("Expected error") - } - if test.health != Unknown && err != nil { - t.Errorf("Unexpected error: %v", err) - } - if health != test.health { - t.Errorf("Expected %v, got %v", test.health, health) - } - } -} diff --git a/pkg/health/tcp.go b/pkg/health/tcp.go deleted file mode 100644 index 039e82d140c..00000000000 --- a/pkg/health/tcp.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "fmt" - "net" - "strconv" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/types" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - "github.com/golang/glog" -) - -type TCPHealthChecker struct{} - -// getTCPAddrParts parses the components of a TCP connection address. For testability. -func getTCPAddrParts(status api.PodStatus, container api.Container) (string, int, error) { - params := container.LivenessProbe.TCPSocket - if params == nil { - return "", -1, fmt.Errorf("error, no TCP parameters specified: %v", container) - } - port := -1 - switch params.Port.Kind { - case util.IntstrInt: - port = params.Port.IntVal - case util.IntstrString: - port = findPortByName(container, params.Port.StrVal) - if port == -1 { - // Last ditch effort - maybe it was an int stored as string? - var err error - if port, err = strconv.Atoi(params.Port.StrVal); err != nil { - return "", -1, err - } - } - } - if port == -1 { - return "", -1, fmt.Errorf("unknown port: %v", params.Port) - } - if len(status.PodIP) == 0 { - return "", -1, fmt.Errorf("no host specified.") - } - - return status.PodIP, port, nil -} - -// DoTCPCheck checks that a TCP socket to the address can be opened. -// If the socket can be opened, it returns Healthy. -// If the socket fails to open, it returns Unhealthy. -// This is exported because some other packages may want to do direct TCP checks. -func DoTCPCheck(addr string) (Status, error) { - conn, err := net.Dial("tcp", addr) - if err != nil { - return Unhealthy, nil - } - err = conn.Close() - if err != nil { - glog.Errorf("unexpected error closing health check socket: %v (%#v)", err, err) - } - return Healthy, nil -} - -func (t *TCPHealthChecker) HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (Status, error) { - host, port, err := getTCPAddrParts(status, container) - if err != nil { - return Unknown, err - } - return DoTCPCheck(net.JoinHostPort(host, strconv.Itoa(port))) -} - -func (t *TCPHealthChecker) CanCheck(probe *api.LivenessProbe) bool { - return probe.TCPSocket != nil -} diff --git a/pkg/health/tcp_test.go b/pkg/health/tcp_test.go deleted file mode 100644 index b693a9731f6..00000000000 --- a/pkg/health/tcp_test.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package health - -import ( - "net" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) - -func TestGetTCPAddrParts(t *testing.T) { - testCases := []struct { - probe *api.TCPSocketAction - ok bool - host string - port int - }{ - {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(-1)}, false, "", -1}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("")}, false, "", -1}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("-1")}, false, "", -1}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("not-found")}, false, "", -1}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("found")}, true, "1.2.3.4", 93}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(76)}, true, "1.2.3.4", 76}, - {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("118")}, true, "1.2.3.4", 118}, - } - - for _, test := range testCases { - state := api.PodStatus{PodIP: "1.2.3.4"} - container := api.Container{ - Ports: []api.Port{{Name: "found", HostPort: 93}}, - LivenessProbe: &api.LivenessProbe{ - TCPSocket: test.probe, - }, - } - host, port, err := getTCPAddrParts(state, container) - if !test.ok && err == nil { - t.Errorf("Expected error for %+v, got %s:%d", test, host, port) - } - if test.ok && err != nil { - t.Errorf("Unexpected error: %v", err) - } - if test.ok { - if host != test.host || port != test.port { - t.Errorf("Expected %s:%d, got %s:%d", test.host, test.port, host, port) - } - } - } -} - -func TestTcpHealthChecker(t *testing.T) { - tests := []struct { - probe *api.TCPSocketAction - expectedStatus Status - expectError bool - }{ - // The probe will be filled in below. This is primarily testing that a connection is made. - {&api.TCPSocketAction{}, Healthy, false}, - {&api.TCPSocketAction{}, Unhealthy, false}, - {nil, Unknown, true}, - } - - checker := &TCPHealthChecker{} - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer server.Close() - u, err := url.Parse(server.URL) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - for _, test := range tests { - container := api.Container{ - LivenessProbe: &api.LivenessProbe{ - TCPSocket: test.probe, - }, - } - params := container.LivenessProbe.TCPSocket - if params != nil && test.expectedStatus == Healthy { - params.Port = util.NewIntOrStringFromString(port) - } - status, err := checker.HealthCheck("test", "", api.PodStatus{PodIP: host}, container) - if status != test.expectedStatus { - t.Errorf("expected: %v, got: %v", test.expectedStatus, status) - } - if err != nil && !test.expectError { - t.Errorf("unexpected error: %#v", err) - } - if err == nil && test.expectError { - t.Errorf("unexpected non-error.") - } - } -} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c2be5fe94b8..242d808d153 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -36,11 +36,11 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/envvars" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" @@ -164,8 +164,6 @@ type Kubelet struct { // Optional, no events will be sent without it etcdClient tools.EtcdClient - // Optional, defaults to simple implementaiton - healthChecker health.HealthChecker // Optional, defaults to simple Docker implementation dockerPuller dockertools.DockerPuller // Optional, defaults to /logs/ from /var/log @@ -427,9 +425,6 @@ func (kl *Kubelet) Run(updates <-chan PodUpdate) { if kl.dockerPuller == nil { kl.dockerPuller = dockertools.NewDockerPuller(kl.dockerClient, kl.pullQPS, kl.pullBurst) } - if kl.healthChecker == nil { - kl.healthChecker = health.NewHealthChecker() - } kl.syncLoop(updates, kl) } @@ -1038,13 +1033,13 @@ func (kl *Kubelet) syncPod(pod *api.BoundPod, dockerContainers dockertools.Docke // look for changes in the container. if hash == 0 || hash == expectedHash { // TODO: This should probably be separated out into a separate goroutine. - healthy, err := kl.healthy(podFullName, uid, podStatus, container, dockerContainer) + healthy, err := kl.probeLiveness(podFullName, uid, podStatus, container, dockerContainer) if err != nil { glog.V(1).Infof("health check errored: %v", err) containersToKeep[containerID] = empty{} continue } - if healthy == health.Healthy { + if healthy == probe.Healthy { containersToKeep[containerID] = empty{} continue } @@ -1403,18 +1398,15 @@ func (kl *Kubelet) GetPodStatus(podFullName string, uid types.UID) (api.PodStatu return podStatus, err } -func (kl *Kubelet) healthy(podFullName string, podUID types.UID, status api.PodStatus, container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) { +func (kl *Kubelet) probeLiveness(podFullName string, podUID types.UID, status api.PodStatus, container api.Container, dockerContainer *docker.APIContainers) (probe.Status, error) { // Give the container 60 seconds to start up. if container.LivenessProbe == nil { - return health.Healthy, nil + return probe.Healthy, nil } if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds { - return health.Healthy, nil + return probe.Healthy, nil } - if kl.healthChecker == nil { - return health.Healthy, nil - } - return kl.healthChecker.HealthCheck(podFullName, podUID, status, container) + return kl.probeContainer(container.LivenessProbe, podFullName, podUID, status, container) } // Returns logs of current machine. diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 3c181135989..3a0bd5fc823 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -31,7 +31,6 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/host_path" @@ -845,19 +844,8 @@ func TestSyncPodDeletesDuplicate(t *testing.T) { } } -type FalseHealthChecker struct{} - -func (f *FalseHealthChecker) HealthCheck(podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (health.Status, error) { - return health.Unhealthy, nil -} - -func (f *FalseHealthChecker) CanCheck(probe *api.LivenessProbe) bool { - return true -} - func TestSyncPodBadHash(t *testing.T) { kubelet, fakeDocker := newTestKubelet(t) - kubelet.healthChecker = &FalseHealthChecker{} dockerContainers := dockertools.DockerContainers{ "1234": &docker.APIContainers{ // the k8s prefix is required for the kubelet to manage the container @@ -904,7 +892,6 @@ func TestSyncPodBadHash(t *testing.T) { func TestSyncPodUnhealthy(t *testing.T) { kubelet, fakeDocker := newTestKubelet(t) - kubelet.healthChecker = &FalseHealthChecker{} dockerContainers := dockertools.DockerContainers{ "1234": &docker.APIContainers{ // the k8s prefix is required for the kubelet to manage the container diff --git a/pkg/kubelet/probe.go b/pkg/kubelet/probe.go index 98937f3d0ec..185c2b3bab7 100644 --- a/pkg/kubelet/probe.go +++ b/pkg/kubelet/probe.go @@ -32,7 +32,7 @@ import ( "github.com/golang/glog" ) -func (kl *Kubelet) makeLivenessProbeRunner(p *api.LivenessProbe, podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (probe.Status, error) { +func (kl *Kubelet) probeContainer(p *api.LivenessProbe, podFullName string, podUID types.UID, status api.PodStatus, container api.Container) (probe.Status, error) { if p.Exec != nil { return execprobe.Probe(kl.newExecInContainer(podFullName, podUID, container)) } diff --git a/pkg/kubelet/util.go b/pkg/kubelet/util.go index 079acc04658..07bb42bbb8a 100644 --- a/pkg/kubelet/util.go +++ b/pkg/kubelet/util.go @@ -17,14 +17,12 @@ limitations under the License. package kubelet import ( - "net/http" "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" @@ -46,15 +44,6 @@ func MonitorCAdvisor(k *Kubelet, cp uint) { k.SetCadvisorClient(cadvisorClient) } -// TODO: move this into the kubelet itself -func InitHealthChecking(k *Kubelet) { - // TODO: These should probably become more plugin-ish: register a factory func - // in each checker's init(), iterate those here. - health.AddHealthChecker(health.NewExecHealthChecker(k)) - health.AddHealthChecker(health.NewHTTPHealthChecker(&http.Client{})) - health.AddHealthChecker(&health.TCPHealthChecker{}) -} - // TODO: move this into a pkg/tools/etcd_tools func EtcdClientOrDie(etcdServerList util.StringList, etcdConfigFile string) tools.EtcdClient { if len(etcdServerList) > 0 { diff --git a/pkg/registry/minion/healthy_registry.go b/pkg/registry/minion/healthy_registry.go index a6d32607e08..65271616bc4 100644 --- a/pkg/registry/minion/healthy_registry.go +++ b/pkg/registry/minion/healthy_registry.go @@ -22,8 +22,8 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" @@ -115,7 +115,7 @@ func (r *HealthyRegistry) doCheck(key string) util.T { case err != nil: glog.V(2).Infof("HealthyRegistry: node %q health check error: %v", key, err) nodeStatus = api.ConditionUnknown - case status == health.Unhealthy: + case status == probe.Unhealthy: nodeStatus = api.ConditionNone default: nodeStatus = api.ConditionFull diff --git a/pkg/registry/minion/healthy_registry_test.go b/pkg/registry/minion/healthy_registry_test.go index ba4ea3bb531..b3cd48d6818 100644 --- a/pkg/registry/minion/healthy_registry_test.go +++ b/pkg/registry/minion/healthy_registry_test.go @@ -22,15 +22,15 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - "github.com/GoogleCloudPlatform/kubernetes/pkg/health" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) type alwaysYes struct{} -func (alwaysYes) HealthCheck(host string) (health.Status, error) { - return health.Healthy, nil +func (alwaysYes) HealthCheck(host string) (probe.Status, error) { + return probe.Healthy, nil } func TestBasicDelegation(t *testing.T) { @@ -75,11 +75,11 @@ type notMinion struct { minion string } -func (n *notMinion) HealthCheck(host string) (health.Status, error) { +func (n *notMinion) HealthCheck(host string) (probe.Status, error) { if host != n.minion { - return health.Healthy, nil + return probe.Healthy, nil } else { - return health.Unhealthy, nil + return probe.Unhealthy, nil } } diff --git a/pkg/standalone/standalone.go b/pkg/standalone/standalone.go index 73949fd3caa..0fc0b3db6c8 100644 --- a/pkg/standalone/standalone.go +++ b/pkg/standalone/standalone.go @@ -302,7 +302,6 @@ func createAndInitKubelet(kc *KubeletConfig, pc *config.PodConfig) (*kubelet.Kub go k.GarbageCollectLoop() go kubelet.MonitorCAdvisor(k, kc.CAdvisorPort) - kubelet.InitHealthChecking(k) return k, nil }