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  }