208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
package executor
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/armon/circbuf"
|
|
docker "github.com/fsouza/go-dockerclient"
|
|
|
|
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
|
)
|
|
|
|
var (
|
|
// We store the client globally to cache the connection to the docker daemon.
|
|
createClient sync.Once
|
|
client *docker.Client
|
|
)
|
|
|
|
const (
|
|
// The default check timeout
|
|
defaultCheckTimeout = 30 * time.Second
|
|
)
|
|
|
|
// DockerScriptCheck runs nagios compatible scripts in a docker container and
|
|
// provides the check result
|
|
type DockerScriptCheck struct {
|
|
id string // id of the check
|
|
interval time.Duration // interval of the check
|
|
timeout time.Duration // timeout of the check
|
|
containerID string // container id in which the check will be invoked
|
|
logger *log.Logger
|
|
cmd string // check command
|
|
args []string // check command arguments
|
|
|
|
dockerEndpoint string // docker endpoint
|
|
tlsCert string // path to tls certificate
|
|
tlsCa string // path to tls ca
|
|
tlsKey string // path to tls key
|
|
}
|
|
|
|
// dockerClient creates the client to interact with the docker daemon
|
|
func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) {
|
|
if client != nil {
|
|
return client, nil
|
|
}
|
|
|
|
var err error
|
|
createClient.Do(func() {
|
|
if d.dockerEndpoint != "" {
|
|
if d.tlsCert+d.tlsKey+d.tlsCa != "" {
|
|
d.logger.Printf("[DEBUG] executor.checks: using TLS client connection to %s", d.dockerEndpoint)
|
|
client, err = docker.NewTLSClient(d.dockerEndpoint, d.tlsCert, d.tlsKey, d.tlsCa)
|
|
} else {
|
|
d.logger.Printf("[DEBUG] executor.checks: using standard client connection to %s", d.dockerEndpoint)
|
|
client, err = docker.NewClient(d.dockerEndpoint)
|
|
}
|
|
return
|
|
}
|
|
|
|
d.logger.Println("[DEBUG] executor.checks: using client connection initialized from environment")
|
|
client, err = docker.NewClientFromEnv()
|
|
})
|
|
return client, err
|
|
}
|
|
|
|
// Run runs a script check inside a docker container
|
|
func (d *DockerScriptCheck) Run() *cstructs.CheckResult {
|
|
var (
|
|
exec *docker.Exec
|
|
err error
|
|
execRes *docker.ExecInspect
|
|
time = time.Now()
|
|
)
|
|
|
|
if client, err = d.dockerClient(); err != nil {
|
|
return &cstructs.CheckResult{Err: err}
|
|
}
|
|
client = client
|
|
execOpts := docker.CreateExecOptions{
|
|
AttachStdin: false,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Tty: false,
|
|
Cmd: append([]string{d.cmd}, d.args...),
|
|
Container: d.containerID,
|
|
}
|
|
if exec, err = client.CreateExec(execOpts); err != nil {
|
|
return &cstructs.CheckResult{Err: err}
|
|
}
|
|
|
|
output, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
|
|
startOpts := docker.StartExecOptions{
|
|
Detach: false,
|
|
Tty: false,
|
|
OutputStream: output,
|
|
ErrorStream: output,
|
|
}
|
|
|
|
if err = client.StartExec(exec.ID, startOpts); err != nil {
|
|
return &cstructs.CheckResult{Err: err}
|
|
}
|
|
if execRes, err = client.InspectExec(exec.ID); err != nil {
|
|
return &cstructs.CheckResult{Err: err}
|
|
}
|
|
return &cstructs.CheckResult{
|
|
ExitCode: execRes.ExitCode,
|
|
Output: string(output.Bytes()),
|
|
Timestamp: time,
|
|
}
|
|
}
|
|
|
|
// ID returns the check id
|
|
func (d *DockerScriptCheck) ID() string {
|
|
return d.id
|
|
}
|
|
|
|
// Interval returns the interval at which the check has to run
|
|
func (d *DockerScriptCheck) Interval() time.Duration {
|
|
return d.interval
|
|
}
|
|
|
|
// Timeout returns the duration after which a check is timed out.
|
|
func (d *DockerScriptCheck) Timeout() time.Duration {
|
|
if d.timeout == 0 {
|
|
return defaultCheckTimeout
|
|
}
|
|
return d.timeout
|
|
}
|
|
|
|
// ExecScriptCheck runs a nagios compatible script and returns the check result
|
|
type ExecScriptCheck struct {
|
|
id string // id of the script check
|
|
interval time.Duration // interval at which the check is invoked
|
|
timeout time.Duration // timeout duration of the check
|
|
cmd string // command of the check
|
|
args []string // args passed to the check
|
|
taskDir string // the root directory of the check
|
|
|
|
FSIsolation bool // indicates whether the check has to be run within a chroot
|
|
}
|
|
|
|
// Run runs an exec script check
|
|
func (e *ExecScriptCheck) Run() *cstructs.CheckResult {
|
|
buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
|
|
cmd := exec.Command(e.cmd, e.args...)
|
|
cmd.Stdout = buf
|
|
cmd.Stderr = buf
|
|
e.setChroot(cmd)
|
|
ts := time.Now()
|
|
if err := cmd.Start(); err != nil {
|
|
return &cstructs.CheckResult{Err: err}
|
|
}
|
|
errCh := make(chan error, 2)
|
|
go func() {
|
|
errCh <- cmd.Wait()
|
|
}()
|
|
for {
|
|
select {
|
|
case err := <-errCh:
|
|
endTime := time.Now()
|
|
if err == nil {
|
|
return &cstructs.CheckResult{
|
|
ExitCode: 0,
|
|
Output: string(buf.Bytes()),
|
|
Timestamp: ts,
|
|
}
|
|
}
|
|
exitCode := 1
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
exitCode = status.ExitStatus()
|
|
}
|
|
}
|
|
return &cstructs.CheckResult{
|
|
ExitCode: exitCode,
|
|
Output: string(buf.Bytes()),
|
|
Timestamp: ts,
|
|
Duration: endTime.Sub(ts),
|
|
}
|
|
case <-time.After(e.Timeout()):
|
|
errCh <- fmt.Errorf("timed out after waiting 30s")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ID returns the check id
|
|
func (e *ExecScriptCheck) ID() string {
|
|
return e.id
|
|
}
|
|
|
|
// Interval returns the interval at which the check has to run
|
|
func (e *ExecScriptCheck) Interval() time.Duration {
|
|
return e.interval
|
|
}
|
|
|
|
// Timeout returns the duration after which a check is timed out.
|
|
func (e *ExecScriptCheck) Timeout() time.Duration {
|
|
if e.timeout == 0 {
|
|
return defaultCheckTimeout
|
|
}
|
|
return e.timeout
|
|
}
|