From ee56a1d3e39e4f834cf14026b6774578c768ce7a Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Fri, 23 Jan 2015 10:03:04 -0800 Subject: [PATCH] create pkg/probe as successor to pkg/health. --- pkg/kubelet/probe.go | 112 +++++++++++++++++++++++++++++++++++++++++ pkg/probe/doc.go | 18 +++++++ pkg/probe/exec/exec.go | 40 +++++++++++++++ pkg/probe/http/http.go | 67 ++++++++++++++++++++++++ pkg/probe/probe.go | 37 ++++++++++++++ pkg/probe/tcp/tcp.go | 46 +++++++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 pkg/kubelet/probe.go create mode 100644 pkg/probe/doc.go create mode 100644 pkg/probe/exec/exec.go create mode 100644 pkg/probe/http/http.go create mode 100644 pkg/probe/probe.go create mode 100644 pkg/probe/tcp/tcp.go diff --git a/pkg/kubelet/probe.go b/pkg/kubelet/probe.go new file mode 100644 index 0000000000..413a14e18e --- /dev/null +++ b/pkg/kubelet/probe.go @@ -0,0 +1,112 @@ +/* +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 kubelet + +import ( + "fmt" + "strconv" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" + execprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/exec" + httprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/http" + tcprobe "github.com/GoogleCloudPlatform/kubernetes/pkg/probe/tcp" + "github.com/GoogleCloudPlatform/kubernetes/pkg/types" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" + + "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) { + if p.Exec != nil { + return execprobe.Probe(kl.newExecInContainer(podFullName, podUID, container)) + } + if p.HTTPGet != nil { + port, err := extractPort(p.HTTPGet.Port, container) + if err != nil { + return probe.Unknown, err + } + return httprobe.Probe(extractGetParams(p.HTTPGet, status, port)) + } + if p.TCPSocket != nil { + port, err := extractPort(p.TCPSocket.Port, container) + if err != nil { + return probe.Unknown, err + } + return tcprobe.Probe(status.PodIP, port) + } + glog.Warningf("Failed to find probe builder for %s %+v", container.Name, container.LivenessProbe) + return probe.Unknown, nil +} + +func extractGetParams(action *api.HTTPGetAction, status api.PodStatus, port int) (string, int, string) { + host := action.Host + if host == "" { + host = status.PodIP + } + return host, port, action.Path +} + +func extractPort(param util.IntOrString, container api.Container) (int, error) { + port := -1 + var err error + switch param.Kind { + case util.IntstrInt: + return param.IntVal, nil + case util.IntstrString: + port = findPortByName(container, param.StrVal) + if port == -1 { + // Last ditch effort - maybe it was an int stored as string? + if port, err = strconv.Atoi(param.StrVal); err != nil { + return port, err + } + } + return port, nil + default: + return port, fmt.Errorf("IntOrString had no kind: %+v", param) + } +} + +// 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 +} + +type execInContainer struct { + run func() ([]byte, error) +} + +func (kl *Kubelet) newExecInContainer(podFullName string, podUID types.UID, container api.Container) exec.Cmd { + return execInContainer{func() ([]byte, error) { + return kl.RunInContainer(podFullName, podUID, container.Name, container.LivenessProbe.Exec.Command) + }} +} + +func (eic execInContainer) CombinedOutput() ([]byte, error) { + return eic.run() +} + +func (eic execInContainer) SetDir(dir string) { + //unimplemented +} diff --git a/pkg/probe/doc.go b/pkg/probe/doc.go new file mode 100644 index 0000000000..dded05f852 --- /dev/null +++ b/pkg/probe/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 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 probe contains utilities for health probing, as well as health status information. +package probe diff --git a/pkg/probe/exec/exec.go b/pkg/probe/exec/exec.go new file mode 100644 index 0000000000..73d7283302 --- /dev/null +++ b/pkg/probe/exec/exec.go @@ -0,0 +1,40 @@ +/* +Copyright 2015 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 exec + +import ( + "strings" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" + uexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" + + "github.com/golang/glog" +) + +const defaultHealthyOutput = "ok" + +func Probe(e uexec.Cmd) (probe.Status, error) { + data, err := e.CombinedOutput() + glog.V(4).Infof("health check response: %s", string(data)) + if err != nil { + return probe.Unknown, err + } + if strings.ToLower(string(data)) != defaultHealthyOutput { + return probe.Unhealthy, nil + } + return probe.Healthy, nil +} diff --git a/pkg/probe/http/http.go b/pkg/probe/http/http.go new file mode 100644 index 0000000000..b9c406aa16 --- /dev/null +++ b/pkg/probe/http/http.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 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 http + +import ( + "net" + "net/http" + "net/url" + "strconv" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" + + "github.com/golang/glog" +) + +var client = &http.Client{} + +// Probe returns a ProbeRunner capable of running an http check. +func Probe(host string, port int, path string) (probe.Status, error) { + return DoHTTPProbe(formatURL(host, port, path), client) +} + +type HTTPGetInterface interface { + Get(u string) (*http.Response, error) +} + +// DoHTTPProbe 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 probes. +func DoHTTPProbe(url string, client HTTPGetInterface) (probe.Status, error) { + res, err := client.Get(url) + if err != nil { + glog.V(1).Infof("HTTP probe error: %v", err) + return probe.Unhealthy, nil + } + defer res.Body.Close() + if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { + return probe.Healthy, nil + } + glog.V(1).Infof("Health check failed for %s, Response: %v", url, *res) + return probe.Unhealthy, 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() +} diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go new file mode 100644 index 0000000000..85881e3a90 --- /dev/null +++ b/pkg/probe/probe.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 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 probe + +type Status int + +// Status values must be one of these constants. +const ( + Healthy Status = iota + Unhealthy + Unknown +) + +func (s Status) String() string { + switch s { + case Healthy: + return "healthy" + case Unhealthy: + return "unhealthy" + default: + return "unknown" + } +} diff --git a/pkg/probe/tcp/tcp.go b/pkg/probe/tcp/tcp.go new file mode 100644 index 0000000000..0dc2e343e7 --- /dev/null +++ b/pkg/probe/tcp/tcp.go @@ -0,0 +1,46 @@ +/* +Copyright 2015 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 tcp + +import ( + "net" + "strconv" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" + + "github.com/golang/glog" +) + +func Probe(host string, port int) (probe.Status, error) { + return DoTCPProbe(net.JoinHostPort(host, strconv.Itoa(port))) +} + +// DoTCPProbe 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 probes. +func DoTCPProbe(addr string) (probe.Status, error) { + conn, err := net.Dial("tcp", addr) + if err != nil { + return probe.Unhealthy, nil + } + err = conn.Close() + if err != nil { + glog.Errorf("unexpected error closing health check socket: %v (%#v)", err, err) + } + return probe.Healthy, nil +}