|
|
|
@ -1,15 +1,18 @@
|
|
|
|
|
package agent |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"log" |
|
|
|
|
"net" |
|
|
|
|
"net/http" |
|
|
|
|
"net/http/httptest" |
|
|
|
|
"os" |
|
|
|
|
"os/exec" |
|
|
|
|
"testing" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
docker "github.com/fsouza/go-dockerclient" |
|
|
|
|
"github.com/hashicorp/consul/consul/structs" |
|
|
|
|
"github.com/hashicorp/consul/testutil" |
|
|
|
|
) |
|
|
|
@ -393,3 +396,269 @@ func TestCheckTCPPassing(t *testing.T) {
|
|
|
|
|
expectTCPStatus(t, tcpServer.Addr().String(), "passing") |
|
|
|
|
tcpServer.Close() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to test happy path scenario
|
|
|
|
|
type fakeDockerClientWithNoErrors struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithNoErrors) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithNoErrors) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
fmt.Fprint(opts.OutputStream, "output") |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithNoErrors) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return &docker.ExecInspect{ |
|
|
|
|
ID: "123", |
|
|
|
|
ExitCode: 0, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to test truncation of output
|
|
|
|
|
type fakeDockerClientWithLongOutput struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithLongOutput) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithLongOutput) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
b, _ := exec.Command("od", "-N", "81920", "/dev/urandom").Output() |
|
|
|
|
fmt.Fprint(opts.OutputStream, string(b)) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithLongOutput) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return &docker.ExecInspect{ |
|
|
|
|
ID: "123", |
|
|
|
|
ExitCode: 0, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to test non-zero exit codes from exec invocation
|
|
|
|
|
type fakeDockerClientWithExecNonZeroExitCode struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecNonZeroExitCode) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecNonZeroExitCode) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecNonZeroExitCode) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return &docker.ExecInspect{ |
|
|
|
|
ID: "123", |
|
|
|
|
ExitCode: 127, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to test exit code which result into Warning
|
|
|
|
|
type fakeDockerClientWithExecExitCodeOne struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecExitCodeOne) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecExitCodeOne) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
fmt.Fprint(opts.OutputStream, "output") |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecExitCodeOne) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return &docker.ExecInspect{ |
|
|
|
|
ID: "123", |
|
|
|
|
ExitCode: 1, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to simulate create exec failing
|
|
|
|
|
type fakeDockerClientWithCreateExecFailure struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithCreateExecFailure) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return nil, errors.New("Exec Creation Failed") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithCreateExecFailure) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
return errors.New("Exec doesn't exist") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithCreateExecFailure) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return nil, errors.New("Exec doesn't exist") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to simulate start exec failing
|
|
|
|
|
type fakeDockerClientWithStartExecFailure struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithStartExecFailure) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithStartExecFailure) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
return errors.New("Couldn't Start Exec") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithStartExecFailure) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return nil, errors.New("Exec doesn't exist") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// A fake docker client to test exec info query failures
|
|
|
|
|
type fakeDockerClientWithExecInfoErrors struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecInfoErrors) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { |
|
|
|
|
return &docker.Exec{ID: "123"}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecInfoErrors) StartExec(id string, opts docker.StartExecOptions) error { |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (d *fakeDockerClientWithExecInfoErrors) InspectExec(id string) (*docker.ExecInspect, error) { |
|
|
|
|
return nil, errors.New("Unable to query exec info") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func expectDockerCheckStatus(t *testing.T, dockerClient DockerClient, status string, output string) { |
|
|
|
|
mock := &MockNotify{ |
|
|
|
|
state: make(map[string]string), |
|
|
|
|
updates: make(map[string]int), |
|
|
|
|
output: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
check := &CheckDocker{ |
|
|
|
|
Notify: mock, |
|
|
|
|
CheckID: "foo", |
|
|
|
|
Script: "/health.sh", |
|
|
|
|
DockerContainerId: "54432bad1fc7", |
|
|
|
|
Shell: "/bin/sh", |
|
|
|
|
Interval: 10 * time.Millisecond, |
|
|
|
|
Logger: log.New(os.Stderr, "", log.LstdFlags), |
|
|
|
|
dockerClient: dockerClient, |
|
|
|
|
} |
|
|
|
|
check.Start() |
|
|
|
|
defer check.Stop() |
|
|
|
|
|
|
|
|
|
time.Sleep(50 * time.Millisecond) |
|
|
|
|
|
|
|
|
|
// Should have at least 2 updates
|
|
|
|
|
if mock.updates["foo"] < 2 { |
|
|
|
|
t.Fatalf("should have 2 updates %v", mock.updates) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if mock.state["foo"] != status { |
|
|
|
|
t.Fatalf("should be %v %v", status, mock.state) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if mock.output["foo"] != output { |
|
|
|
|
t.Fatalf("should be %v %v", output, mock.output) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExecReturnsSuccessExitCode(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithNoErrors{}, "passing", "output") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExecCreationFails(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithCreateExecFailure{}, "critical", "Unable to create Exec, error: Exec Creation Failed") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExitCodeIsNonZero(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithExecNonZeroExitCode{}, "critical", "") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExitCodeIsone(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithExecExitCodeOne{}, "warning", "output") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExecStartFails(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithStartExecFailure{}, "critical", "Unable to start Exec: Couldn't Start Exec") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckWhenExecInfoFails(t *testing.T) { |
|
|
|
|
expectDockerCheckStatus(t, &fakeDockerClientWithExecInfoErrors{}, "critical", "Unable to inspect Exec: Unable to query exec info") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckDefaultToSh(t *testing.T) { |
|
|
|
|
os.Setenv("SHELL", "") |
|
|
|
|
mock := &MockNotify{ |
|
|
|
|
state: make(map[string]string), |
|
|
|
|
updates: make(map[string]int), |
|
|
|
|
output: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
check := &CheckDocker{ |
|
|
|
|
Notify: mock, |
|
|
|
|
CheckID: "foo", |
|
|
|
|
Script: "/health.sh", |
|
|
|
|
DockerContainerId: "54432bad1fc7", |
|
|
|
|
Interval: 10 * time.Millisecond, |
|
|
|
|
Logger: log.New(os.Stderr, "", log.LstdFlags), |
|
|
|
|
dockerClient: &fakeDockerClientWithNoErrors{}, |
|
|
|
|
} |
|
|
|
|
check.Start() |
|
|
|
|
defer check.Stop() |
|
|
|
|
|
|
|
|
|
time.Sleep(50 * time.Millisecond) |
|
|
|
|
if check.Shell != "/bin/sh" { |
|
|
|
|
t.Fatalf("Shell should be: %v , actual: %v", "/bin/sh", check.Shell) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckUseShellFromEnv(t *testing.T) { |
|
|
|
|
mock := &MockNotify{ |
|
|
|
|
state: make(map[string]string), |
|
|
|
|
updates: make(map[string]int), |
|
|
|
|
output: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
os.Setenv("SHELL", "/bin/bash") |
|
|
|
|
check := &CheckDocker{ |
|
|
|
|
Notify: mock, |
|
|
|
|
CheckID: "foo", |
|
|
|
|
Script: "/health.sh", |
|
|
|
|
DockerContainerId: "54432bad1fc7", |
|
|
|
|
Interval: 10 * time.Millisecond, |
|
|
|
|
Logger: log.New(os.Stderr, "", log.LstdFlags), |
|
|
|
|
dockerClient: &fakeDockerClientWithNoErrors{}, |
|
|
|
|
} |
|
|
|
|
check.Start() |
|
|
|
|
defer check.Stop() |
|
|
|
|
|
|
|
|
|
time.Sleep(50 * time.Millisecond) |
|
|
|
|
if check.Shell != "/bin/bash" { |
|
|
|
|
t.Fatalf("Shell should be: %v , actual: %v", "/bin/bash", check.Shell) |
|
|
|
|
} |
|
|
|
|
os.Setenv("SHELL", "") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestDockerCheckTruncateOutput(t *testing.T) { |
|
|
|
|
mock := &MockNotify{ |
|
|
|
|
state: make(map[string]string), |
|
|
|
|
updates: make(map[string]int), |
|
|
|
|
output: make(map[string]string), |
|
|
|
|
} |
|
|
|
|
check := &CheckDocker{ |
|
|
|
|
Notify: mock, |
|
|
|
|
CheckID: "foo", |
|
|
|
|
Script: "/health.sh", |
|
|
|
|
DockerContainerId: "54432bad1fc7", |
|
|
|
|
Shell: "/bin/sh", |
|
|
|
|
Interval: 10 * time.Millisecond, |
|
|
|
|
Logger: log.New(os.Stderr, "", log.LstdFlags), |
|
|
|
|
dockerClient: &fakeDockerClientWithLongOutput{}, |
|
|
|
|
} |
|
|
|
|
check.Start() |
|
|
|
|
defer check.Stop() |
|
|
|
|
|
|
|
|
|
time.Sleep(50 * time.Millisecond) |
|
|
|
|
|
|
|
|
|
// Allow for extra bytes for the truncation message
|
|
|
|
|
if len(mock.output["foo"]) > CheckBufSize+100 { |
|
|
|
|
t.Fatalf("output size is too long") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|