github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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(ctx context.Context, 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 Context: ctx, 122 } 123 return h.client.KillContainer(opts) 124 125 } 126 127 // Kill is used to terminate the task. 128 func (h *taskHandle) Kill(killTimeout time.Duration, signal os.Signal) error { 129 // Only send signal if killTimeout is set, otherwise stop container 130 if killTimeout > 0 { 131 ctx, cancel := context.WithTimeout(context.Background(), killTimeout) 132 defer cancel() 133 134 if err := h.Signal(ctx, signal); err != nil { 135 // Container has already been removed. 136 if strings.Contains(err.Error(), NoSuchContainerError) { 137 h.logger.Debug("attempted to signal nonexistent container") 138 return nil 139 } 140 // Container has already been stopped. 141 if strings.Contains(err.Error(), ContainerNotRunningError) { 142 h.logger.Debug("attempted to signal a not-running container") 143 return nil 144 } 145 146 h.logger.Error("failed to signal container while killing", "error", err) 147 return fmt.Errorf("Failed to signal container %q while killing: %v", h.containerID, err) 148 } 149 150 select { 151 case <-h.waitCh: 152 return nil 153 case <-ctx.Done(): 154 } 155 } 156 157 // Stop the container 158 err := h.client.StopContainer(h.containerID, 0) 159 if err != nil { 160 161 // Container has already been removed. 162 if strings.Contains(err.Error(), NoSuchContainerError) { 163 h.logger.Debug("attempted to stop nonexistent container") 164 return nil 165 } 166 // Container has already been stopped. 167 if strings.Contains(err.Error(), ContainerNotRunningError) { 168 h.logger.Debug("attempted to stop an not-running container") 169 return nil 170 } 171 172 h.logger.Error("failed to stop container", "error", err) 173 return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err) 174 } 175 176 h.logger.Info("stopped container") 177 return nil 178 } 179 180 func (h *taskHandle) shutdownLogger() { 181 if h.dlogger == nil { 182 return 183 } 184 185 if err := h.dlogger.Stop(); err != nil { 186 h.logger.Error("failed to stop docker logger process during StopTask", 187 "error", err, "logger_pid", h.dloggerPluginClient.ReattachConfig().Pid) 188 } 189 h.dloggerPluginClient.Kill() 190 } 191 192 func (h *taskHandle) run() { 193 defer h.shutdownLogger() 194 195 exitCode, werr := h.waitClient.WaitContainer(h.containerID) 196 if werr != nil { 197 h.logger.Error("failed to wait for container; already terminated") 198 } 199 200 if exitCode != 0 { 201 werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode) 202 } 203 204 container, ierr := h.waitClient.InspectContainerWithOptions(docker.InspectContainerOptions{ 205 ID: h.containerID, 206 }) 207 oom := false 208 if ierr != nil { 209 h.logger.Error("failed to inspect container", "error", ierr) 210 } else if container.State.OOMKilled { 211 oom = true 212 werr = fmt.Errorf("OOM Killed") 213 } 214 215 // Shutdown stats collection 216 close(h.doneCh) 217 218 // Stop the container just incase the docker daemon's wait returned 219 // incorrectly 220 if err := h.client.StopContainer(h.containerID, 0); err != nil { 221 _, noSuchContainer := err.(*docker.NoSuchContainer) 222 _, containerNotRunning := err.(*docker.ContainerNotRunning) 223 if !containerNotRunning && !noSuchContainer { 224 h.logger.Error("error stopping container", "error", err) 225 } 226 } 227 228 // Set the result 229 h.exitResultLock.Lock() 230 h.exitResult = &drivers.ExitResult{ 231 ExitCode: exitCode, 232 Signal: 0, 233 OOMKilled: oom, 234 Err: werr, 235 } 236 h.exitResultLock.Unlock() 237 close(h.waitCh) 238 }