github.com/kobeld/docker@v1.12.0-rc1/daemon/cluster/executor/container/controller.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	executorpkg "github.com/docker/docker/daemon/cluster/executor"
     8  	"github.com/docker/engine-api/types"
     9  	"github.com/docker/swarmkit/agent/exec"
    10  	"github.com/docker/swarmkit/api"
    11  	"github.com/docker/swarmkit/log"
    12  	"golang.org/x/net/context"
    13  )
    14  
    15  // controller implements agent.Controller against docker's API.
    16  //
    17  // Most operations against docker's API are done through the container name,
    18  // which is unique to the task.
    19  type controller struct {
    20  	backend executorpkg.Backend
    21  	task    *api.Task
    22  	adapter *containerAdapter
    23  	closed  chan struct{}
    24  	err     error
    25  }
    26  
    27  var _ exec.Controller = &controller{}
    28  
    29  // NewController returns a dockerexec runner for the provided task.
    30  func newController(b executorpkg.Backend, task *api.Task) (*controller, error) {
    31  	adapter, err := newContainerAdapter(b, task)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	return &controller{
    37  		backend: b,
    38  		task:    task,
    39  		adapter: adapter,
    40  		closed:  make(chan struct{}),
    41  	}, nil
    42  }
    43  
    44  func (r *controller) Task() (*api.Task, error) {
    45  	return r.task, nil
    46  }
    47  
    48  // ContainerStatus returns the container-specific status for the task.
    49  func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) {
    50  	ctnr, err := r.adapter.inspect(ctx)
    51  	if err != nil {
    52  		if isUnknownContainer(err) {
    53  			return nil, nil
    54  		}
    55  		return nil, err
    56  	}
    57  	return parseContainerStatus(ctnr)
    58  }
    59  
    60  // Update tasks a recent task update and applies it to the container.
    61  func (r *controller) Update(ctx context.Context, t *api.Task) error {
    62  	log.G(ctx).Warnf("task updates not yet supported")
    63  	// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
    64  	// updates of metadata, such as labelling, as well as any other properties
    65  	// that make sense.
    66  	return nil
    67  }
    68  
    69  // Prepare creates a container and ensures the image is pulled.
    70  //
    71  // If the container has already be created, exec.ErrTaskPrepared is returned.
    72  func (r *controller) Prepare(ctx context.Context) error {
    73  	if err := r.checkClosed(); err != nil {
    74  		return err
    75  	}
    76  
    77  	// Make sure all the networks that the task needs are created.
    78  	if err := r.adapter.createNetworks(ctx); err != nil {
    79  		return err
    80  	}
    81  
    82  	// Make sure all the volumes that the task needs are created.
    83  	if err := r.adapter.createVolumes(ctx, r.backend); err != nil {
    84  		return err
    85  	}
    86  
    87  	for {
    88  		if err := r.checkClosed(); err != nil {
    89  			return err
    90  		}
    91  		if err := r.adapter.create(ctx, r.backend); err != nil {
    92  			if isContainerCreateNameConflict(err) {
    93  				if _, err := r.adapter.inspect(ctx); err != nil {
    94  					return err
    95  				}
    96  
    97  				// container is already created. success!
    98  				return exec.ErrTaskPrepared
    99  			}
   100  
   101  			if !strings.Contains(err.Error(), "No such image") { // todo: better error detection
   102  				return err
   103  			}
   104  			if err := r.adapter.pullImage(ctx); err != nil {
   105  				return err
   106  			}
   107  
   108  			continue // retry to create the container
   109  		}
   110  
   111  		break
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // Start the container. An error will be returned if the container is already started.
   118  func (r *controller) Start(ctx context.Context) error {
   119  	if err := r.checkClosed(); err != nil {
   120  		return err
   121  	}
   122  
   123  	ctnr, err := r.adapter.inspect(ctx)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	// Detect whether the container has *ever* been started. If so, we don't
   129  	// issue the start.
   130  	//
   131  	// TODO(stevvooe): This is very racy. While reading inspect, another could
   132  	// start the process and we could end up starting it twice.
   133  	if ctnr.State.Status != "created" {
   134  		return exec.ErrTaskStarted
   135  	}
   136  
   137  	if err := r.adapter.start(ctx); err != nil {
   138  		return err
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  // Wait on the container to exit.
   145  func (r *controller) Wait(pctx context.Context) error {
   146  	if err := r.checkClosed(); err != nil {
   147  		return err
   148  	}
   149  
   150  	ctx, cancel := context.WithCancel(pctx)
   151  	defer cancel()
   152  
   153  	err := r.adapter.wait(ctx)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	if ctx.Err() != nil {
   158  		return ctx.Err()
   159  	}
   160  	if err != nil {
   161  		ee := &exitError{}
   162  		if err.Error() != "" {
   163  			ee.cause = err
   164  		}
   165  		if ec, ok := err.(exec.ExitCoder); ok {
   166  			ee.code = ec.ExitCode()
   167  		}
   168  	}
   169  	return nil
   170  }
   171  
   172  // Shutdown the container cleanly.
   173  func (r *controller) Shutdown(ctx context.Context) error {
   174  	if err := r.checkClosed(); err != nil {
   175  		return err
   176  	}
   177  
   178  	if err := r.adapter.shutdown(ctx); err != nil {
   179  		if isUnknownContainer(err) || isStoppedContainer(err) {
   180  			return nil
   181  		}
   182  
   183  		return err
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // Terminate the container, with force.
   190  func (r *controller) Terminate(ctx context.Context) error {
   191  	if err := r.checkClosed(); err != nil {
   192  		return err
   193  	}
   194  
   195  	if err := r.adapter.terminate(ctx); err != nil {
   196  		if isUnknownContainer(err) {
   197  			return nil
   198  		}
   199  
   200  		return err
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  // Remove the container and its resources.
   207  func (r *controller) Remove(ctx context.Context) error {
   208  	if err := r.checkClosed(); err != nil {
   209  		return err
   210  	}
   211  
   212  	// It may be necessary to shut down the task before removing it.
   213  	if err := r.Shutdown(ctx); err != nil {
   214  		if isUnknownContainer(err) {
   215  			return nil
   216  		}
   217  		// This may fail if the task was already shut down.
   218  		log.G(ctx).WithError(err).Debug("shutdown failed on removal")
   219  	}
   220  
   221  	// Try removing networks referenced in this task in case this
   222  	// task is the last one referencing it
   223  	if err := r.adapter.removeNetworks(ctx); err != nil {
   224  		if isUnknownContainer(err) {
   225  			return nil
   226  		}
   227  		return err
   228  	}
   229  
   230  	if err := r.adapter.remove(ctx); err != nil {
   231  		if isUnknownContainer(err) {
   232  			return nil
   233  		}
   234  
   235  		return err
   236  	}
   237  	return nil
   238  }
   239  
   240  // Close the runner and clean up any ephemeral resources.
   241  func (r *controller) Close() error {
   242  	select {
   243  	case <-r.closed:
   244  		return r.err
   245  	default:
   246  		r.err = exec.ErrControllerClosed
   247  		close(r.closed)
   248  	}
   249  	return nil
   250  }
   251  
   252  func (r *controller) checkClosed() error {
   253  	select {
   254  	case <-r.closed:
   255  		return r.err
   256  	default:
   257  		return nil
   258  	}
   259  }
   260  
   261  func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) {
   262  	status := &api.ContainerStatus{
   263  		ContainerID: ctnr.ID,
   264  		PID:         int32(ctnr.State.Pid),
   265  		ExitCode:    int32(ctnr.State.ExitCode),
   266  	}
   267  
   268  	return status, nil
   269  }
   270  
   271  type exitError struct {
   272  	code  int
   273  	cause error
   274  }
   275  
   276  func (e *exitError) Error() string {
   277  	if e.cause != nil {
   278  		return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause)
   279  	}
   280  
   281  	return fmt.Sprintf("task: non-zero exit (%v)", e.code)
   282  }
   283  
   284  func (e *exitError) ExitCode() int {
   285  	return int(e.code)
   286  }
   287  
   288  func (e *exitError) Cause() error {
   289  	return e.cause
   290  }