Merge pull request #847 from thockin/health

Use IntOrString for health checks
This commit is contained in:
Daniel Smith 2014-08-11 16:28:24 -07:00
commit c372b74bc8
12 changed files with 529 additions and 257 deletions

View File

@ -136,18 +136,18 @@ type EnvVar struct {
// HTTPGetProbe describes a liveness probe based on HTTP Get requests.
type HTTPGetProbe struct {
// Path to access on the http server
// Optional: Path to access on the HTTP server.
Path string `yaml:"path,omitempty" json:"path,omitempty"`
// Name or number of the port to access on the container
Port string `yaml:"port,omitempty" json:"port,omitempty"`
// Host name to connect to. Optional, default: "localhost"
// Required: Name or number of the port to access on the container.
Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
// Optional: Host name to connect to, defaults to the pod IP.
Host string `yaml:"host,omitempty" json:"host,omitempty"`
}
// TCPSocketProbe describes a liveness probe based on opening a socket
type TCPSocketProbe struct {
// Port is the port to connect to. Required.
Port int `yaml:"port,omitempty" json:"port,omitempty"`
// Required: Port to connect to.
Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
}
// LivenessProbe describes a liveness probe to be examined to the container.

View File

@ -139,18 +139,18 @@ type EnvVar struct {
// HTTPGetProbe describes a liveness probe based on HTTP Get requests.
type HTTPGetProbe struct {
// Path to access on the http server
// Optional: Path to access on the HTTP server.
Path string `yaml:"path,omitempty" json:"path,omitempty"`
// Name or number of the port to access on the container
Port string `yaml:"port,omitempty" json:"port,omitempty"`
// Host name to connect to. Optional, default: "localhost"
// Required: Name or number of the port to access on the container.
Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
// Optional: Host name to connect to, defaults to the pod IP.
Host string `yaml:"host,omitempty" json:"host,omitempty"`
}
// TCPSocketProbe describes a liveness probe based on opening a socket
type TCPSocketProbe struct {
// Port is the port to connect to. Required.
Port int `yaml:"port,omitempty" json:"port,omitempty"`
// Required: Port to connect to.
Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"`
}
// LivenessProbe describes a liveness probe to be examined to the container.

View File

@ -19,36 +19,61 @@ package health
import (
"net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/golang/glog"
)
// Status represents the result of a single health-check operation.
type Status int
// Status takes only one of values of these constants.
// Status values must be one of these constants.
const (
Healthy Status = iota
Unhealthy
Unknown
)
// HTTPGetInterface is an abstract interface for testability. It abstracts the interface of http.Client.Get.
type HTTPGetInterface interface {
Get(url string) (*http.Response, error)
// HealthChecker defines an abstract interface for checking container health.
type HealthChecker interface {
HealthCheck(currentState api.PodState, container api.Container) (Status, error)
}
// Check 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, it returns Unhealthy.
// It returns Unknown and err if the HTTP communication itself fails.
func Check(url string, client HTTPGetInterface) (Status, error) {
res, err := client.Get(url)
if err != nil {
return Unknown, err
// NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes.
func NewHealthChecker() HealthChecker {
return &muxHealthChecker{
checkers: map[string]HealthChecker{
"http": &HTTPHealthChecker{
client: &http.Client{},
},
"tcp": &TCPHealthChecker{},
},
}
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
}
// muxHealthChecker bundles multiple implementations of HealthChecker of different types.
type muxHealthChecker struct {
checkers map[string]HealthChecker
}
// 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 matching health checker it returns Unknown, nil.
func (m *muxHealthChecker) HealthCheck(currentState api.PodState, container api.Container) (Status, error) {
checker, ok := m.checkers[container.LivenessProbe.Type]
if !ok || checker == nil {
glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type)
return Unknown, nil
}
return checker.HealthCheck(currentState, container)
}
// 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
}

View File

@ -1,121 +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"
"strconv"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/golang/glog"
)
// HealthChecker defines an abstract interface for checking container health.
type HealthChecker interface {
HealthCheck(currentState api.PodState, container api.Container) (Status, error)
}
// NewHealthChecker creates a new HealthChecker which supports multiple types of liveness probes.
func NewHealthChecker() HealthChecker {
return &MuxHealthChecker{
checkers: map[string]HealthChecker{
"http": &HTTPHealthChecker{
client: &http.Client{},
},
"tcp": &TCPHealthChecker{},
},
}
}
// MuxHealthChecker bundles multiple implementations of HealthChecker of different types.
type MuxHealthChecker struct {
checkers map[string]HealthChecker
}
// 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 matching health checker it returns Unknown, nil.
func (m *MuxHealthChecker) HealthCheck(currentState api.PodState, container api.Container) (Status, error) {
checker, ok := m.checkers[container.LivenessProbe.Type]
if !ok || checker == nil {
glog.Warningf("Failed to find health checker for %s %s", container.Name, container.LivenessProbe.Type)
return Unknown, nil
}
return checker.HealthCheck(currentState, container)
}
// HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests.
type HTTPHealthChecker struct {
client HTTPGetInterface
}
func (h *HTTPHealthChecker) findPort(container api.Container, portName string) int64 {
for _, port := range container.Ports {
if port.Name == portName {
// TODO This means you can only health check exposed ports
return int64(port.HostPort)
}
}
return -1
}
// HealthCheck checks if the container is healthy by trying sending HTTP Get requests to the container.
func (h *HTTPHealthChecker) HealthCheck(currentState api.PodState, container api.Container) (Status, error) {
params := container.LivenessProbe.HTTPGet
if params == nil {
return Unknown, fmt.Errorf("Error, no HTTP parameters specified: %v", container)
}
port := h.findPort(container, params.Port)
if port == -1 {
var err error
port, err = strconv.ParseInt(params.Port, 10, 0)
if err != nil {
return Unknown, err
}
}
var host string
if len(params.Host) > 0 {
host = params.Host
} else {
host = currentState.PodIP
}
url := fmt.Sprintf("http://%s:%d%s", host, port, params.Path)
return Check(url, h.client)
}
type TCPHealthChecker struct{}
func (t *TCPHealthChecker) HealthCheck(currentState api.PodState, container api.Container) (Status, error) {
params := container.LivenessProbe.TCPSocket
if params == nil {
return Unknown, fmt.Errorf("error, no TCP parameters specified: %v", container)
}
if len(currentState.PodIP) == 0 {
return Unknown, fmt.Errorf("no host specified.")
}
conn, err := net.Dial("tcp", net.JoinHostPort(currentState.PodIP, strconv.Itoa(params.Port)))
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
}

View File

@ -21,10 +21,10 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
const statusServerEarlyShutdown = -1
@ -59,7 +59,7 @@ func TestHealthChecker(t *testing.T) {
container := api.Container{
LivenessProbe: &api.LivenessProbe{
HTTPGet: &api.HTTPGetProbe{
Port: port,
Port: util.MakeIntOrStringFromString(port),
Path: "/foo/bar",
Host: host,
},
@ -77,7 +77,7 @@ func TestHealthChecker(t *testing.T) {
}
}
func TestFindPort(t *testing.T) {
func TestFindPortByName(t *testing.T) {
container := api.Container{
Ports: []api.Port{
{
@ -90,110 +90,13 @@ func TestFindPort(t *testing.T) {
},
},
}
checker := HTTPHealthChecker{}
want := int64(8080)
got := checker.findPort(container, "foo")
want := 8080
got := findPortByName(container, "foo")
if got != want {
t.Errorf("Expected %v, got %v", want, got)
}
}
func TestHTTPHealthChecker(t *testing.T) {
httpHealthCheckerTests := []struct {
probe *api.HTTPGetProbe
status int
health Status
}{
{&api.HTTPGetProbe{Host: "httptest"}, http.StatusOK, Healthy},
{&api.HTTPGetProbe{}, http.StatusOK, Healthy},
{nil, -1, Unknown},
{&api.HTTPGetProbe{Port: "-1"}, -1, Unknown},
}
hc := &HTTPHealthChecker{
client: &http.Client{},
}
for _, httpHealthCheckerTest := range httpHealthCheckerTests {
tt := httpHealthCheckerTest
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(tt.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: tt.probe,
Type: "http",
},
}
params := container.LivenessProbe.HTTPGet
if params != nil {
if params.Port == "" {
params.Port = port
}
if params.Host == "httptest" {
params.Host = host
}
}
health, err := hc.HealthCheck(api.PodState{PodIP: host}, container)
if tt.health == Unknown && err == nil {
t.Errorf("Expected error")
}
if tt.health != Unknown && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if health != tt.health {
t.Errorf("Expected %v, got %v", tt.health, health)
}
}
}
func TestTcpHealthChecker(t *testing.T) {
type tcpHealthTest struct {
probe *api.LivenessProbe
expectedStatus Status
expectError bool
}
checker := &TCPHealthChecker{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
u, err := url.Parse(server.URL)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
host, port, err := net.SplitHostPort(u.Host)
portNum, _ := strconv.Atoi(port)
tests := []tcpHealthTest{
{&api.LivenessProbe{TCPSocket: &api.TCPSocketProbe{Port: portNum}}, Healthy, false},
{&api.LivenessProbe{TCPSocket: &api.TCPSocketProbe{Port: 100000}}, Unhealthy, false},
{&api.LivenessProbe{}, Unknown, true},
}
for _, test := range tests {
probe := test.probe
container := api.Container{
LivenessProbe: probe,
}
status, err := checker.HealthCheck(api.PodState{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.")
}
}
}
func TestMuxHealthChecker(t *testing.T) {
muxHealthCheckerTests := []struct {
health Status
@ -202,7 +105,7 @@ func TestMuxHealthChecker(t *testing.T) {
{Healthy, "http"},
{Unknown, "ftp"},
}
mc := &MuxHealthChecker{
mc := &muxHealthChecker{
checkers: make(map[string]HealthChecker),
}
hc := &HTTPHealthChecker{
@ -228,7 +131,7 @@ func TestMuxHealthChecker(t *testing.T) {
},
}
container.LivenessProbe.Type = tt.probeType
container.LivenessProbe.HTTPGet.Port = port
container.LivenessProbe.HTTPGet.Port = util.MakeIntOrStringFromString(port)
container.LivenessProbe.HTTPGet.Host = host
health, err := mc.HealthCheck(api.PodState{}, container)
if err != nil {

103
pkg/health/http.go Normal file
View File

@ -0,0 +1,103 @@
/*
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/http"
"strconv"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"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)
}
// DoHTTPCheck checks if a GET request to the url succeeds.
// HTTPHealthChecker is an implementation of HealthChecker which checks container health by sending HTTP Get requests.
type HTTPHealthChecker struct {
client HTTPGetInterface
}
// Get the components of the target URL. For testability.
func getURLParts(currentState api.PodState, 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 = currentState.PodIP
}
return host, port, params.Path, nil
}
// Formats a URL from args. For testability.
func formatURL(host string, port int, path string) string {
return fmt.Sprintf("http://%s:%d%s", host, port, path)
}
// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Healthy.
// If the HTTP response code is unsuccessful, it returns Unhealthy.
// It returns Unknown and err if the HTTP communication itself fails.
// 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 {
return Unknown, err
}
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(currentState api.PodState, container api.Container) (Status, error) {
host, port, path, err := getURLParts(currentState, container)
if err != nil {
return Unknown, err
}
return DoHTTPCheck(formatURL(host, port, path), h.client)
}

139
pkg/health/http_test.go Normal file
View File

@ -0,0 +1,139 @@
/*
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.HTTPGetProbe
ok bool
host string
port int
path string
}{
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromInt(-1), Path: ""}, false, "", -1, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromString(""), Path: ""}, false, "", -1, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromString("-1"), Path: ""}, false, "", -1, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromString("not-found"), Path: ""}, false, "", -1, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromString("found"), Path: ""}, true, "127.0.0.1", 93, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromInt(76), Path: ""}, true, "127.0.0.1", 76, ""},
{&api.HTTPGetProbe{Host: "", Port: util.MakeIntOrStringFromString("118"), Path: ""}, true, "127.0.0.1", 118, ""},
{&api.HTTPGetProbe{Host: "hostname", Port: util.MakeIntOrStringFromInt(76), Path: "path"}, true, "hostname", 76, "path"},
}
for _, test := range testCases {
state := api.PodState{PodIP: "127.0.0.1"}
container := api.Container{
Ports: []api.Port{{Name: "found", HostPort: 93}},
LivenessProbe: &api.LivenessProbe{
HTTPGet: test.probe,
Type: "http",
},
}
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.HTTPGetProbe
status int
health Status
}{
// The probe will be filled in below. This is primarily testing that an HTTP GET happens.
{&api.HTTPGetProbe{}, http.StatusOK, Healthy},
{&api.HTTPGetProbe{}, -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,
Type: "http",
},
}
params := container.LivenessProbe.HTTPGet
if params != nil {
params.Port = util.MakeIntOrStringFromString(port)
params.Host = host
}
health, err := hc.HealthCheck(api.PodState{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)
}
}
}

83
pkg/health/tcp.go Normal file
View File

@ -0,0 +1,83 @@
/*
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/util"
"github.com/golang/glog"
)
type TCPHealthChecker struct{}
// Get the components of a TCP connection address. For testability.
func getTCPAddrParts(currentState api.PodState, 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(currentState.PodIP) == 0 {
return "", -1, fmt.Errorf("no host specified.")
}
return currentState.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(currentState api.PodState, container api.Container) (Status, error) {
host, port, err := getTCPAddrParts(currentState, container)
if err != nil {
return Unknown, err
}
return DoTCPCheck(net.JoinHostPort(host, strconv.Itoa(port)))
}

116
pkg/health/tcp_test.go Normal file
View File

@ -0,0 +1,116 @@
/*
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.TCPSocketProbe
ok bool
host string
port int
}{
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromInt(-1)}, false, "", -1},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromString("")}, false, "", -1},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromString("-1")}, false, "", -1},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromString("not-found")}, false, "", -1},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromString("found")}, true, "1.2.3.4", 93},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromInt(76)}, true, "1.2.3.4", 76},
{&api.TCPSocketProbe{Port: util.MakeIntOrStringFromString("118")}, true, "1.2.3.4", 118},
}
for _, test := range testCases {
state := api.PodState{PodIP: "1.2.3.4"}
container := api.Container{
Ports: []api.Port{{Name: "found", HostPort: 93}},
LivenessProbe: &api.LivenessProbe{
TCPSocket: test.probe,
Type: "tcp",
},
}
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.TCPSocketProbe
expectedStatus Status
expectError bool
}{
// The probe will be filled in below. This is primarily testing that a connection is made.
{&api.TCPSocketProbe{}, Healthy, false},
{&api.TCPSocketProbe{}, Unhealthy, false},
{nil, Unknown, true},
}
checker := &TCPHealthChecker{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
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,
Type: "tcp",
},
}
params := container.LivenessProbe.TCPSocket
if params != nil && test.expectedStatus == Healthy {
params.Port = util.MakeIntOrStringFromString(port)
}
status, err := checker.HealthCheck(api.PodState{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.")
}
}
}

View File

@ -49,7 +49,7 @@ func (h *HealthyMinionRegistry) List() (currentMinions []string, err error) {
return result, err
}
for _, minion := range list {
status, err := health.Check(h.makeMinionURL(minion), h.client)
status, err := health.DoHTTPCheck(h.makeMinionURL(minion), h.client)
if err != nil {
glog.Errorf("%s failed health check with error: %s", minion, err)
continue
@ -77,7 +77,7 @@ func (h *HealthyMinionRegistry) Contains(minion string) (bool, error) {
if !contains {
return false, nil
}
status, err := health.Check(h.makeMinionURL(minion), h.client)
status, err := health.DoHTTPCheck(h.makeMinionURL(minion), h.client)
if err != nil {
return false, err
}

View File

@ -83,6 +83,16 @@ const (
IntstrString // The IntOrString holds a string.
)
// MakeIntOrStringFromInt creates an IntOrString object with an int value.
func MakeIntOrStringFromInt(val int) IntOrString {
return IntOrString{Kind: IntstrInt, IntVal: val}
}
// MakeIntOrStringFromInt creates an IntOrString object with a string value.
func MakeIntOrStringFromString(val string) IntOrString {
return IntOrString{Kind: IntstrString, StrVal: val}
}
// SetYAML implements the yaml.Setter interface.
func (intstr *IntOrString) SetYAML(tag string, value interface{}) bool {
switch v := value.(type) {

View File

@ -71,6 +71,20 @@ func TestHandleCrash(t *testing.T) {
}
}
func TestMakeIntOrStringFromInt(t *testing.T) {
i := MakeIntOrStringFromInt(93)
if i.Kind != IntstrInt || i.IntVal != 93 {
t.Errorf("Expected IntVal=93, got %+v", i)
}
}
func TestMakeIntOrStringFromString(t *testing.T) {
i := MakeIntOrStringFromString("76")
if i.Kind != IntstrString || i.StrVal != "76" {
t.Errorf("Expected StrVal=\"76\", got %+v", i)
}
}
type IntOrStringHolder struct {
IOrS IntOrString `json:"val" yaml:"val"`
}