github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/client/driver/executor/checks.go (about)

     1  package executor
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os/exec"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/armon/circbuf"
    12  	docker "github.com/fsouza/go-dockerclient"
    13  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    14  )
    15  
    16  var (
    17  	// We store the client globally to cache the connection to the docker daemon.
    18  	createClient sync.Once
    19  	client       *docker.Client
    20  )
    21  
    22  const (
    23  	// The default check timeout
    24  	defaultCheckTimeout = 30 * time.Second
    25  )
    26  
    27  // DockerScriptCheck runs nagios compatible scripts in a docker container and
    28  // provides the check result
    29  type DockerScriptCheck struct {
    30  	id          string        // id of the check
    31  	interval    time.Duration // interval of the check
    32  	timeout     time.Duration // timeout of the check
    33  	containerID string        // container id in which the check will be invoked
    34  	logger      *log.Logger
    35  	cmd         string   // check command
    36  	args        []string // check command arguments
    37  
    38  	dockerEndpoint string // docker endpoint
    39  	tlsCert        string // path to tls certificate
    40  	tlsCa          string // path to tls ca
    41  	tlsKey         string // path to tls key
    42  }
    43  
    44  // dockerClient creates the client to interact with the docker daemon
    45  func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) {
    46  	if client != nil {
    47  		return client, nil
    48  	}
    49  
    50  	var err error
    51  	createClient.Do(func() {
    52  		if d.dockerEndpoint != "" {
    53  			if d.tlsCert+d.tlsKey+d.tlsCa != "" {
    54  				d.logger.Printf("[DEBUG] executor.checks: using TLS client connection to %s", d.dockerEndpoint)
    55  				client, err = docker.NewTLSClient(d.dockerEndpoint, d.tlsCert, d.tlsKey, d.tlsCa)
    56  			} else {
    57  				d.logger.Printf("[DEBUG] executor.checks: using standard client connection to %s", d.dockerEndpoint)
    58  				client, err = docker.NewClient(d.dockerEndpoint)
    59  			}
    60  			return
    61  		}
    62  
    63  		d.logger.Println("[DEBUG] executor.checks: using client connection initialized from environment")
    64  		client, err = docker.NewClientFromEnv()
    65  	})
    66  	return client, err
    67  }
    68  
    69  // Run runs a script check inside a docker container
    70  func (d *DockerScriptCheck) Run() *cstructs.CheckResult {
    71  	var (
    72  		exec    *docker.Exec
    73  		err     error
    74  		execRes *docker.ExecInspect
    75  		time    = time.Now()
    76  	)
    77  
    78  	if client, err = d.dockerClient(); err != nil {
    79  		return &cstructs.CheckResult{Err: err}
    80  	}
    81  	execOpts := docker.CreateExecOptions{
    82  		AttachStdin:  false,
    83  		AttachStdout: true,
    84  		AttachStderr: true,
    85  		Tty:          false,
    86  		Cmd:          append([]string{d.cmd}, d.args...),
    87  		Container:    d.containerID,
    88  	}
    89  	if exec, err = client.CreateExec(execOpts); err != nil {
    90  		return &cstructs.CheckResult{Err: err}
    91  	}
    92  
    93  	output, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
    94  	startOpts := docker.StartExecOptions{
    95  		Detach:       false,
    96  		Tty:          false,
    97  		OutputStream: output,
    98  		ErrorStream:  output,
    99  	}
   100  
   101  	if err = client.StartExec(exec.ID, startOpts); err != nil {
   102  		return &cstructs.CheckResult{Err: err}
   103  	}
   104  	if execRes, err = client.InspectExec(exec.ID); err != nil {
   105  		return &cstructs.CheckResult{Err: err}
   106  	}
   107  	return &cstructs.CheckResult{
   108  		ExitCode:  execRes.ExitCode,
   109  		Output:    string(output.Bytes()),
   110  		Timestamp: time,
   111  	}
   112  }
   113  
   114  // ID returns the check id
   115  func (d *DockerScriptCheck) ID() string {
   116  	return d.id
   117  }
   118  
   119  // Interval returns the interval at which the check has to run
   120  func (d *DockerScriptCheck) Interval() time.Duration {
   121  	return d.interval
   122  }
   123  
   124  // Timeout returns the duration after which a check is timed out.
   125  func (d *DockerScriptCheck) Timeout() time.Duration {
   126  	if d.timeout == 0 {
   127  		return defaultCheckTimeout
   128  	}
   129  	return d.timeout
   130  }
   131  
   132  // ExecScriptCheck runs a nagios compatible script and returns the check result
   133  type ExecScriptCheck struct {
   134  	id       string        // id of the script check
   135  	interval time.Duration // interval at which the check is invoked
   136  	timeout  time.Duration // timeout duration of the check
   137  	cmd      string        // command of the check
   138  	args     []string      // args passed to the check
   139  	taskDir  string        // the root directory of the check
   140  
   141  	FSIsolation bool // indicates whether the check has to be run within a chroot
   142  }
   143  
   144  // Run runs an exec script check
   145  func (e *ExecScriptCheck) Run() *cstructs.CheckResult {
   146  	buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
   147  	cmd := exec.Command(e.cmd, e.args...)
   148  	cmd.Stdout = buf
   149  	cmd.Stderr = buf
   150  	e.setChroot(cmd)
   151  	ts := time.Now()
   152  	if err := cmd.Start(); err != nil {
   153  		return &cstructs.CheckResult{Err: err}
   154  	}
   155  	errCh := make(chan error, 2)
   156  	go func() {
   157  		errCh <- cmd.Wait()
   158  	}()
   159  
   160  	select {
   161  	case err := <-errCh:
   162  		endTime := time.Now()
   163  		if err == nil {
   164  			return &cstructs.CheckResult{
   165  				ExitCode:  0,
   166  				Output:    string(buf.Bytes()),
   167  				Timestamp: ts,
   168  			}
   169  		}
   170  		exitCode := 1
   171  		if exitErr, ok := err.(*exec.ExitError); ok {
   172  			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
   173  				exitCode = status.ExitStatus()
   174  			}
   175  		}
   176  		return &cstructs.CheckResult{
   177  			ExitCode:  exitCode,
   178  			Output:    string(buf.Bytes()),
   179  			Timestamp: ts,
   180  			Duration:  endTime.Sub(ts),
   181  		}
   182  	case <-time.After(e.Timeout()):
   183  		errCh <- fmt.Errorf("timed out after waiting 30s")
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // ID returns the check id
   190  func (e *ExecScriptCheck) ID() string {
   191  	return e.id
   192  }
   193  
   194  // Interval returns the interval at which the check has to run
   195  func (e *ExecScriptCheck) Interval() time.Duration {
   196  	return e.interval
   197  }
   198  
   199  // Timeout returns the duration after which a check is timed out.
   200  func (e *ExecScriptCheck) Timeout() time.Duration {
   201  	if e.timeout == 0 {
   202  		return defaultCheckTimeout
   203  	}
   204  	return e.timeout
   205  }