github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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  	client = client
    82  	execOpts := docker.CreateExecOptions{
    83  		AttachStdin:  false,
    84  		AttachStdout: true,
    85  		AttachStderr: true,
    86  		Tty:          false,
    87  		Cmd:          append([]string{d.cmd}, d.args...),
    88  		Container:    d.containerID,
    89  	}
    90  	if exec, err = client.CreateExec(execOpts); err != nil {
    91  		return &cstructs.CheckResult{Err: err}
    92  	}
    93  
    94  	output, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
    95  	startOpts := docker.StartExecOptions{
    96  		Detach:       false,
    97  		Tty:          false,
    98  		OutputStream: output,
    99  		ErrorStream:  output,
   100  	}
   101  
   102  	if err = client.StartExec(exec.ID, startOpts); err != nil {
   103  		return &cstructs.CheckResult{Err: err}
   104  	}
   105  	if execRes, err = client.InspectExec(exec.ID); err != nil {
   106  		return &cstructs.CheckResult{Err: err}
   107  	}
   108  	return &cstructs.CheckResult{
   109  		ExitCode:  execRes.ExitCode,
   110  		Output:    string(output.Bytes()),
   111  		Timestamp: time,
   112  	}
   113  }
   114  
   115  // ID returns the check id
   116  func (d *DockerScriptCheck) ID() string {
   117  	return d.id
   118  }
   119  
   120  // Interval returns the interval at which the check has to run
   121  func (d *DockerScriptCheck) Interval() time.Duration {
   122  	return d.interval
   123  }
   124  
   125  // Timeout returns the duration after which a check is timed out.
   126  func (d *DockerScriptCheck) Timeout() time.Duration {
   127  	if d.timeout == 0 {
   128  		return defaultCheckTimeout
   129  	}
   130  	return d.timeout
   131  }
   132  
   133  // ExecScriptCheck runs a nagios compatible script and returns the check result
   134  type ExecScriptCheck struct {
   135  	id       string        // id of the script check
   136  	interval time.Duration // interval at which the check is invoked
   137  	timeout  time.Duration // timeout duration of the check
   138  	cmd      string        // command of the check
   139  	args     []string      // args passed to the check
   140  	taskDir  string        // the root directory of the check
   141  
   142  	FSIsolation bool // indicates whether the check has to be run within a chroot
   143  }
   144  
   145  // Run runs an exec script check
   146  func (e *ExecScriptCheck) Run() *cstructs.CheckResult {
   147  	buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
   148  	cmd := exec.Command(e.cmd, e.args...)
   149  	cmd.Stdout = buf
   150  	cmd.Stderr = buf
   151  	e.setChroot(cmd)
   152  	ts := time.Now()
   153  	if err := cmd.Start(); err != nil {
   154  		return &cstructs.CheckResult{Err: err}
   155  	}
   156  	errCh := make(chan error, 2)
   157  	go func() {
   158  		errCh <- cmd.Wait()
   159  	}()
   160  	for {
   161  		select {
   162  		case err := <-errCh:
   163  			endTime := time.Now()
   164  			if err == nil {
   165  				return &cstructs.CheckResult{
   166  					ExitCode:  0,
   167  					Output:    string(buf.Bytes()),
   168  					Timestamp: ts,
   169  				}
   170  			}
   171  			exitCode := 1
   172  			if exitErr, ok := err.(*exec.ExitError); ok {
   173  				if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
   174  					exitCode = status.ExitStatus()
   175  				}
   176  			}
   177  			return &cstructs.CheckResult{
   178  				ExitCode:  exitCode,
   179  				Output:    string(buf.Bytes()),
   180  				Timestamp: ts,
   181  				Duration:  endTime.Sub(ts),
   182  			}
   183  		case <-time.After(e.Timeout()):
   184  			errCh <- fmt.Errorf("timed out after waiting 30s")
   185  		}
   186  	}
   187  	return nil
   188  }
   189  
   190  // ID returns the check id
   191  func (e *ExecScriptCheck) ID() string {
   192  	return e.id
   193  }
   194  
   195  // Interval returns the interval at which the check has to run
   196  func (e *ExecScriptCheck) Interval() time.Duration {
   197  	return e.interval
   198  }
   199  
   200  // Timeout returns the duration after which a check is timed out.
   201  func (e *ExecScriptCheck) Timeout() time.Duration {
   202  	if e.timeout == 0 {
   203  		return defaultCheckTimeout
   204  	}
   205  	return e.timeout
   206  }