github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/handle.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync" 8 "syscall" 9 "time" 10 11 "github.com/armon/circbuf" 12 docker "github.com/fsouza/go-dockerclient" 13 hclog "github.com/hashicorp/go-hclog" 14 plugin "github.com/hashicorp/go-plugin" 15 "github.com/hashicorp/nomad/drivers/docker/docklog" 16 "github.com/hashicorp/nomad/plugins/drivers" 17 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 18 "golang.org/x/net/context" 19 ) 20 21 type taskHandle struct { 22 client *docker.Client 23 waitClient *docker.Client 24 logger hclog.Logger 25 dlogger docklog.DockerLogger 26 dloggerPluginClient *plugin.Client 27 task *drivers.TaskConfig 28 containerID string 29 containerImage string 30 doneCh chan bool 31 waitCh chan struct{} 32 removeContainerOnExit bool 33 net *drivers.DriverNetwork 34 35 exitResult *drivers.ExitResult 36 exitResultLock sync.Mutex 37 } 38 39 func (h *taskHandle) ExitResult() *drivers.ExitResult { 40 h.exitResultLock.Lock() 41 defer h.exitResultLock.Unlock() 42 return h.exitResult.Copy() 43 } 44 45 type taskHandleState struct { 46 // ReattachConfig for the docker logger plugin 47 ReattachConfig *pstructs.ReattachConfig 48 49 ContainerID string 50 DriverNetwork *drivers.DriverNetwork 51 } 52 53 func (h *taskHandle) buildState() *taskHandleState { 54 return &taskHandleState{ 55 ReattachConfig: pstructs.ReattachConfigFromGoPlugin(h.dloggerPluginClient.ReattachConfig()), 56 ContainerID: h.containerID, 57 DriverNetwork: h.net, 58 } 59 } 60 61 func (h *taskHandle) Exec(ctx context.Context, cmd string, args []string) (*drivers.ExecTaskResult, error) { 62 fullCmd := make([]string, len(args)+1) 63 fullCmd[0] = cmd 64 copy(fullCmd[1:], args) 65 createExecOpts := docker.CreateExecOptions{ 66 AttachStdin: false, 67 AttachStdout: true, 68 AttachStderr: true, 69 Tty: false, 70 Cmd: fullCmd, 71 Container: h.containerID, 72 Context: ctx, 73 } 74 exec, err := h.client.CreateExec(createExecOpts) 75 if err != nil { 76 return nil, err 77 } 78 79 execResult := &drivers.ExecTaskResult{ExitResult: &drivers.ExitResult{}} 80 stdout, _ := circbuf.NewBuffer(int64(drivers.CheckBufSize)) 81 stderr, _ := circbuf.NewBuffer(int64(drivers.CheckBufSize)) 82 startOpts := docker.StartExecOptions{ 83 Detach: false, 84 Tty: false, 85 OutputStream: stdout, 86 ErrorStream: stderr, 87 Context: ctx, 88 } 89 if err := client.StartExec(exec.ID, startOpts); err != nil { 90 return nil, err 91 } 92 execResult.Stdout = stdout.Bytes() 93 execResult.Stderr = stderr.Bytes() 94 res, err := client.InspectExec(exec.ID) 95 if err != nil { 96 return execResult, err 97 } 98 99 execResult.ExitResult.ExitCode = res.ExitCode 100 return execResult, nil 101 } 102 103 func (h *taskHandle) Signal(s os.Signal) error { 104 // Convert types 105 sysSig, ok := s.(syscall.Signal) 106 if !ok { 107 return fmt.Errorf("Failed to determine signal number") 108 } 109 110 // TODO When we expose signals we will need a mapping layer that converts 111 // MacOS signals to the correct signal number for docker. Or we change the 112 // interface to take a signal string and leave it up to driver to map? 113 114 dockerSignal := docker.Signal(sysSig) 115 opts := docker.KillContainerOptions{ 116 ID: h.containerID, 117 Signal: dockerSignal, 118 } 119 return h.client.KillContainer(opts) 120 121 } 122 123 // Kill is used to terminate the task. 124 func (h *taskHandle) Kill(killTimeout time.Duration, signal os.Signal) error { 125 // Only send signal if killTimeout is set, otherwise stop container 126 if killTimeout > 0 { 127 if err := h.Signal(signal); err != nil { 128 // Container has already been removed. 129 if strings.Contains(err.Error(), NoSuchContainerError) { 130 h.logger.Debug("attempted to signal nonexistent container") 131 return nil 132 } 133 // Container has already been stopped. 134 if strings.Contains(err.Error(), ContainerNotRunningError) { 135 h.logger.Debug("attempted to signal a not-running container") 136 return nil 137 } 138 139 h.logger.Error("failed to signal container while killing", "error", err) 140 return fmt.Errorf("Failed to signal container %q while killing: %v", h.containerID, err) 141 } 142 143 select { 144 case <-h.waitCh: 145 return nil 146 case <-time.After(killTimeout): 147 } 148 } 149 150 // Stop the container 151 err := h.client.StopContainer(h.containerID, 0) 152 if err != nil { 153 154 // Container has already been removed. 155 if strings.Contains(err.Error(), NoSuchContainerError) { 156 h.logger.Debug("attempted to stop nonexistent container") 157 return nil 158 } 159 // Container has already been stopped. 160 if strings.Contains(err.Error(), ContainerNotRunningError) { 161 h.logger.Debug("attempted to stop an not-running container") 162 return nil 163 } 164 165 h.logger.Error("failed to stop container", "error", err) 166 return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err) 167 } 168 169 h.logger.Info("stopped container") 170 return nil 171 } 172 173 func (h *taskHandle) shutdownLogger() { 174 if err := h.dlogger.Stop(); err != nil { 175 h.logger.Error("failed to stop docker logger process during StopTask", 176 "error", err, "logger_pid", h.dloggerPluginClient.ReattachConfig().Pid) 177 } 178 h.dloggerPluginClient.Kill() 179 } 180 181 func (h *taskHandle) run() { 182 defer h.shutdownLogger() 183 184 exitCode, werr := h.waitClient.WaitContainer(h.containerID) 185 if werr != nil { 186 h.logger.Error("failed to wait for container; already terminated") 187 } 188 189 if exitCode != 0 { 190 werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode) 191 } 192 193 container, ierr := h.waitClient.InspectContainer(h.containerID) 194 oom := false 195 if ierr != nil { 196 h.logger.Error("failed to inspect container", "error", ierr) 197 } else if container.State.OOMKilled { 198 oom = true 199 werr = fmt.Errorf("OOM Killed") 200 } 201 202 // Shutdown stats collection 203 close(h.doneCh) 204 205 // Stop the container just incase the docker daemon's wait returned 206 // incorrectly 207 if err := h.client.StopContainer(h.containerID, 0); err != nil { 208 _, noSuchContainer := err.(*docker.NoSuchContainer) 209 _, containerNotRunning := err.(*docker.ContainerNotRunning) 210 if !containerNotRunning && !noSuchContainer { 211 h.logger.Error("error stopping container", "error", err) 212 } 213 } 214 215 // Set the result 216 h.exitResultLock.Lock() 217 h.exitResult = &drivers.ExitResult{ 218 ExitCode: exitCode, 219 Signal: 0, 220 OOMKilled: oom, 221 Err: werr, 222 } 223 h.exitResultLock.Unlock() 224 close(h.waitCh) 225 }