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 }