Merge pull request #8011 from brendandburns/exec

Switch exec to look at exit code not output status.
This commit is contained in:
Vish Kannan 2015-05-12 12:43:11 -07:00
commit a74522a738
8 changed files with 98 additions and 12 deletions

View File

@ -66,6 +66,7 @@ type DockerInterface interface {
Info() (*docker.Env, error) Info() (*docker.Env, error)
CreateExec(docker.CreateExecOptions) (*docker.Exec, error) CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
StartExec(string, docker.StartExecOptions) error StartExec(string, docker.StartExecOptions) error
InspectExec(id string) (*docker.ExecInspect, error)
} }
// KubeletContainerName encapsulates a pod name and a Kubernetes container name. // KubeletContainerName encapsulates a pod name and a Kubernetes container name.

View File

@ -47,6 +47,7 @@ type FakeDockerClient struct {
RemovedImages util.StringSet RemovedImages util.StringSet
VersionInfo docker.Env VersionInfo docker.Env
Information docker.Env Information docker.Env
ExecInspect *docker.ExecInspect
} }
func (f *FakeDockerClient) ClearCalls() { func (f *FakeDockerClient) ClearCalls() {
@ -288,6 +289,10 @@ func (f *FakeDockerClient) StartExec(_ string, _ docker.StartExecOptions) error
return nil return nil
} }
func (f *FakeDockerClient) InspectExec(id string) (*docker.ExecInspect, error) {
return f.ExecInspect, f.popError("inspect_exec")
}
func (f *FakeDockerClient) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) { func (f *FakeDockerClient) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) {
err := f.popError("list_images") err := f.popError("list_images")
return f.Images, err return f.Images, err

View File

@ -153,3 +153,11 @@ func (in instrumentedDockerInterface) StartExec(startExec string, opts docker.St
}() }()
return in.client.StartExec(startExec, opts) return in.client.StartExec(startExec, opts)
} }
func (in instrumentedDockerInterface) InspectExec(id string) (*docker.ExecInspect, error) {
start := time.Now()
defer func() {
metrics.DockerOperationsLatency.WithLabelValues("inspect_exec").Observe(metrics.SinceInMicroseconds(start))
}()
return in.client.InspectExec(id)
}

View File

@ -28,6 +28,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
@ -874,10 +875,47 @@ func (dm *DockerManager) RunInContainer(containerID string, cmd []string) ([]byt
RawTerminal: false, RawTerminal: false,
} }
err = dm.client.StartExec(execObj.ID, startOpts) err = dm.client.StartExec(execObj.ID, startOpts)
if err != nil {
return nil, err
}
tick := time.Tick(2 * time.Second)
for {
inspect, err2 := dm.client.InspectExec(execObj.ID)
if err2 != nil {
return buf.Bytes(), err2
}
if !inspect.Running {
if inspect.ExitCode != 0 {
err = &dockerExitError{inspect}
}
break
}
<-tick
}
return buf.Bytes(), err return buf.Bytes(), err
} }
type dockerExitError struct {
Inspect *docker.ExecInspect
}
func (d *dockerExitError) String() string {
return d.Error()
}
func (d *dockerExitError) Error() string {
return fmt.Sprintf("Error executing in Docker Container: %d", d.Inspect.ExitCode)
}
func (d *dockerExitError) Exited() bool {
return !d.Inspect.Running
}
func (d *dockerExitError) ExitStatus() int {
return d.Inspect.ExitCode
}
// ExecInContainer uses nsenter to run the command inside the container identified by containerID. // ExecInContainer uses nsenter to run the command inside the container identified by containerID.
// //
// TODO: // TODO:

View File

@ -619,3 +619,12 @@ func TestProbeContainer(t *testing.T) {
} }
} }
} }
func TestIsAExitError(t *testing.T) {
var err error
err = &dockerExitError{nil}
_, ok := err.(uexec.ExitError)
if !ok {
t.Error("couldn't cast dockerExitError to exec.ExitError")
}
}

View File

@ -17,34 +17,35 @@ limitations under the License.
package exec package exec
import ( import (
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe" "github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
uexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
"github.com/golang/glog" "github.com/golang/glog"
) )
const defaultHealthyOutput = "ok"
func New() ExecProber { func New() ExecProber {
return execProber{} return execProber{}
} }
type ExecProber interface { type ExecProber interface {
Probe(e uexec.Cmd) (probe.Result, error) Probe(e exec.Cmd) (probe.Result, error)
} }
type execProber struct{} type execProber struct{}
func (pr execProber) Probe(e uexec.Cmd) (probe.Result, error) { func (pr execProber) Probe(e exec.Cmd) (probe.Result, error) {
data, err := e.CombinedOutput() data, err := e.CombinedOutput()
glog.V(4).Infof("health check response: %s", string(data)) glog.V(4).Infof("health check response: %s", string(data))
if err != nil { if err != nil {
return probe.Unknown, err exit, ok := err.(exec.ExitError)
} if ok {
if !strings.HasPrefix(strings.ToLower(string(data)), defaultHealthyOutput) { if exit.ExitStatus() == 0 {
return probe.Success, nil
} else {
return probe.Failure, nil return probe.Failure, nil
} }
}
return probe.Unknown, err
}
return probe.Success, nil return probe.Success, nil
} }

View File

@ -41,16 +41,40 @@ type healthCheckTest struct {
err error err error
} }
type fakeExitError struct {
exited bool
statusCode int
}
func (f *fakeExitError) String() string {
return f.Error()
}
func (f *fakeExitError) Error() string {
return "fake exit"
}
func (f *fakeExitError) Exited() bool {
return f.exited
}
func (f *fakeExitError) ExitStatus() int {
return f.statusCode
}
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
prober := New() prober := New()
fake := FakeCmd{} fake := FakeCmd{}
tests := []healthCheckTest{ tests := []healthCheckTest{
// Ok // Ok
{probe.Success, false, []byte("OK"), nil}, {probe.Success, false, []byte("OK"), nil},
// Ok
{probe.Success, false, []byte("OK"), &fakeExitError{true, 0}},
// Run returns error // Run returns error
{probe.Unknown, true, []byte("OK, NOT"), fmt.Errorf("test error")}, {probe.Unknown, true, []byte("OK, NOT"), fmt.Errorf("test error")},
// Unhealthy // Unhealthy
{probe.Failure, false, []byte("Fail"), nil}, {probe.Failure, false, []byte("Fail"), &fakeExitError{true, 1}},
} }
for _, test := range tests { for _, test := range tests {
fake.out = test.output fake.out = test.output

View File

@ -421,7 +421,7 @@ var _ = Describe("Pods", func() {
{ {
Name: "liveness", Name: "liveness",
Image: "gcr.io/google_containers/busybox", Image: "gcr.io/google_containers/busybox",
Command: []string{"/bin/sh", "-c", "echo ok >/tmp/health; sleep 10; echo fail >/tmp/health; sleep 600"}, Command: []string{"/bin/sh", "-c", "echo ok >/tmp/health; sleep 10; rm -rf /tmp/health; sleep 600"},
LivenessProbe: &api.Probe{ LivenessProbe: &api.Probe{
Handler: api.Handler{ Handler: api.Handler{
Exec: &api.ExecAction{ Exec: &api.ExecAction{