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