github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/daemon/cluster/executor/container/adapter.go (about)

     1  package container
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/distribution/reference"
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/api/types/backend"
    18  	containertypes "github.com/docker/docker/api/types/container"
    19  	"github.com/docker/docker/api/types/events"
    20  	"github.com/docker/docker/daemon/cluster/convert"
    21  	executorpkg "github.com/docker/docker/daemon/cluster/executor"
    22  	"github.com/docker/libnetwork"
    23  	"github.com/docker/swarmkit/agent/exec"
    24  	"github.com/docker/swarmkit/api"
    25  	"github.com/docker/swarmkit/log"
    26  	gogotypes "github.com/gogo/protobuf/types"
    27  	"github.com/opencontainers/go-digest"
    28  	"golang.org/x/net/context"
    29  	"golang.org/x/time/rate"
    30  )
    31  
    32  // containerAdapter conducts remote operations for a container. All calls
    33  // are mostly naked calls to the client API, seeded with information from
    34  // containerConfig.
    35  type containerAdapter struct {
    36  	backend   executorpkg.Backend
    37  	container *containerConfig
    38  	secrets   exec.SecretGetter
    39  }
    40  
    41  func newContainerAdapter(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*containerAdapter, error) {
    42  	ctnr, err := newContainerConfig(task)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	return &containerAdapter{
    48  		container: ctnr,
    49  		backend:   b,
    50  		secrets:   secrets,
    51  	}, nil
    52  }
    53  
    54  func (c *containerAdapter) pullImage(ctx context.Context) error {
    55  	spec := c.container.spec()
    56  
    57  	// Skip pulling if the image is referenced by image ID.
    58  	if _, err := digest.Parse(spec.Image); err == nil {
    59  		return nil
    60  	}
    61  
    62  	// Skip pulling if the image is referenced by digest and already
    63  	// exists locally.
    64  	named, err := reference.ParseNormalizedNamed(spec.Image)
    65  	if err == nil {
    66  		if _, ok := named.(reference.Canonical); ok {
    67  			_, err := c.backend.LookupImage(spec.Image)
    68  			if err == nil {
    69  				return nil
    70  			}
    71  		}
    72  	}
    73  
    74  	// if the image needs to be pulled, the auth config will be retrieved and updated
    75  	var encodedAuthConfig string
    76  	if spec.PullOptions != nil {
    77  		encodedAuthConfig = spec.PullOptions.RegistryAuth
    78  	}
    79  
    80  	authConfig := &types.AuthConfig{}
    81  	if encodedAuthConfig != "" {
    82  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil {
    83  			logrus.Warnf("invalid authconfig: %v", err)
    84  		}
    85  	}
    86  
    87  	pr, pw := io.Pipe()
    88  	metaHeaders := map[string][]string{}
    89  	go func() {
    90  		err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
    91  		pw.CloseWithError(err)
    92  	}()
    93  
    94  	dec := json.NewDecoder(pr)
    95  	dec.UseNumber()
    96  	m := map[string]interface{}{}
    97  	spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1)
    98  
    99  	lastStatus := ""
   100  	for {
   101  		if err := dec.Decode(&m); err != nil {
   102  			if err == io.EOF {
   103  				break
   104  			}
   105  			return err
   106  		}
   107  		l := log.G(ctx)
   108  		// limit pull progress logs unless the status changes
   109  		if spamLimiter.Allow() || lastStatus != m["status"] {
   110  			// if we have progress details, we have everything we need
   111  			if progress, ok := m["progressDetail"].(map[string]interface{}); ok {
   112  				// first, log the image and status
   113  				l = l.WithFields(logrus.Fields{
   114  					"image":  c.container.image(),
   115  					"status": m["status"],
   116  				})
   117  				// then, if we have progress, log the progress
   118  				if progress["current"] != nil && progress["total"] != nil {
   119  					l = l.WithFields(logrus.Fields{
   120  						"current": progress["current"],
   121  						"total":   progress["total"],
   122  					})
   123  				}
   124  			}
   125  			l.Debug("pull in progress")
   126  		}
   127  		// sometimes, we get no useful information at all, and add no fields
   128  		if status, ok := m["status"].(string); ok {
   129  			lastStatus = status
   130  		}
   131  	}
   132  
   133  	// if the final stream object contained an error, return it
   134  	if errMsg, ok := m["error"]; ok {
   135  		return fmt.Errorf("%v", errMsg)
   136  	}
   137  	return nil
   138  }
   139  
   140  func (c *containerAdapter) createNetworks(ctx context.Context) error {
   141  	for _, network := range c.container.networks() {
   142  		ncr, err := c.container.networkCreateRequest(network)
   143  		if err != nil {
   144  			return err
   145  		}
   146  
   147  		if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing
   148  			if _, ok := err.(libnetwork.NetworkNameError); ok {
   149  				continue
   150  			}
   151  
   152  			return err
   153  		}
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (c *containerAdapter) removeNetworks(ctx context.Context) error {
   160  	for _, nid := range c.container.networks() {
   161  		if err := c.backend.DeleteManagedNetwork(nid); err != nil {
   162  			switch err.(type) {
   163  			case *libnetwork.ActiveEndpointsError:
   164  				continue
   165  			case libnetwork.ErrNoSuchNetwork:
   166  				continue
   167  			default:
   168  				log.G(ctx).Errorf("network %s remove failed: %v", nid, err)
   169  				return err
   170  			}
   171  		}
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func (c *containerAdapter) networkAttach(ctx context.Context) error {
   178  	config := c.container.createNetworkingConfig()
   179  
   180  	var (
   181  		networkName string
   182  		networkID   string
   183  	)
   184  
   185  	if config != nil {
   186  		for n, epConfig := range config.EndpointsConfig {
   187  			networkName = n
   188  			networkID = epConfig.NetworkID
   189  			break
   190  		}
   191  	}
   192  
   193  	return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config)
   194  }
   195  
   196  func (c *containerAdapter) waitForDetach(ctx context.Context) error {
   197  	config := c.container.createNetworkingConfig()
   198  
   199  	var (
   200  		networkName string
   201  		networkID   string
   202  	)
   203  
   204  	if config != nil {
   205  		for n, epConfig := range config.EndpointsConfig {
   206  			networkName = n
   207  			networkID = epConfig.NetworkID
   208  			break
   209  		}
   210  	}
   211  
   212  	return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id())
   213  }
   214  
   215  func (c *containerAdapter) create(ctx context.Context) error {
   216  	var cr containertypes.ContainerCreateCreatedBody
   217  	var err error
   218  
   219  	if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{
   220  		Name:       c.container.name(),
   221  		Config:     c.container.config(),
   222  		HostConfig: c.container.hostConfig(),
   223  		// Use the first network in container create
   224  		NetworkingConfig: c.container.createNetworkingConfig(),
   225  	}); err != nil {
   226  		return err
   227  	}
   228  
   229  	// Docker daemon currently doesn't support multiple networks in container create
   230  	// Connect to all other networks
   231  	nc := c.container.connectNetworkingConfig()
   232  
   233  	if nc != nil {
   234  		for n, ep := range nc.EndpointsConfig {
   235  			if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
   236  				return err
   237  			}
   238  		}
   239  	}
   240  
   241  	container := c.container.task.Spec.GetContainer()
   242  	if container == nil {
   243  		return errors.New("unable to get container from task spec")
   244  	}
   245  
   246  	// configure secrets
   247  	if err := c.backend.SetContainerSecretStore(cr.ID, c.secrets); err != nil {
   248  		return err
   249  	}
   250  
   251  	refs := convert.SecretReferencesFromGRPC(container.Secrets)
   252  	if err := c.backend.SetContainerSecretReferences(cr.ID, refs); err != nil {
   253  		return err
   254  	}
   255  
   256  	if err := c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil {
   257  		return err
   258  	}
   259  
   260  	return nil
   261  }
   262  
   263  // checkMounts ensures that the provided mounts won't have any host-specific
   264  // problems at start up. For example, we disallow bind mounts without an
   265  // existing path, which slightly different from the container API.
   266  func (c *containerAdapter) checkMounts() error {
   267  	spec := c.container.spec()
   268  	for _, mount := range spec.Mounts {
   269  		switch mount.Type {
   270  		case api.MountTypeBind:
   271  			if _, err := os.Stat(mount.Source); os.IsNotExist(err) {
   272  				return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source)
   273  			}
   274  		}
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  func (c *containerAdapter) start(ctx context.Context) error {
   281  	if err := c.checkMounts(); err != nil {
   282  		return err
   283  	}
   284  
   285  	return c.backend.ContainerStart(c.container.name(), nil, "", "")
   286  }
   287  
   288  func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {
   289  	cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false)
   290  	if ctx.Err() != nil {
   291  		return types.ContainerJSON{}, ctx.Err()
   292  	}
   293  	if err != nil {
   294  		return types.ContainerJSON{}, err
   295  	}
   296  	return *cs, nil
   297  }
   298  
   299  // events issues a call to the events API and returns a channel with all
   300  // events. The stream of events can be shutdown by cancelling the context.
   301  func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
   302  	log.G(ctx).Debugf("waiting on events")
   303  	buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter())
   304  	eventsq := make(chan events.Message, len(buffer))
   305  
   306  	for _, event := range buffer {
   307  		eventsq <- event
   308  	}
   309  
   310  	go func() {
   311  		defer c.backend.UnsubscribeFromEvents(l)
   312  
   313  		for {
   314  			select {
   315  			case ev := <-l:
   316  				jev, ok := ev.(events.Message)
   317  				if !ok {
   318  					log.G(ctx).Warnf("unexpected event message: %q", ev)
   319  					continue
   320  				}
   321  				select {
   322  				case eventsq <- jev:
   323  				case <-ctx.Done():
   324  					return
   325  				}
   326  			case <-ctx.Done():
   327  				return
   328  			}
   329  		}
   330  	}()
   331  
   332  	return eventsq
   333  }
   334  
   335  func (c *containerAdapter) wait(ctx context.Context) error {
   336  	return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
   337  }
   338  
   339  func (c *containerAdapter) shutdown(ctx context.Context) error {
   340  	// Default stop grace period to nil (daemon will use the stopTimeout of the container)
   341  	var stopgrace *int
   342  	spec := c.container.spec()
   343  	if spec.StopGracePeriod != nil {
   344  		stopgraceValue := int(spec.StopGracePeriod.Seconds)
   345  		stopgrace = &stopgraceValue
   346  	}
   347  	return c.backend.ContainerStop(c.container.name(), stopgrace)
   348  }
   349  
   350  func (c *containerAdapter) terminate(ctx context.Context) error {
   351  	return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL))
   352  }
   353  
   354  func (c *containerAdapter) remove(ctx context.Context) error {
   355  	return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{
   356  		RemoveVolume: true,
   357  		ForceRemove:  true,
   358  	})
   359  }
   360  
   361  func (c *containerAdapter) createVolumes(ctx context.Context) error {
   362  	// Create plugin volumes that are embedded inside a Mount
   363  	for _, mount := range c.container.task.Spec.GetContainer().Mounts {
   364  		if mount.Type != api.MountTypeVolume {
   365  			continue
   366  		}
   367  
   368  		if mount.VolumeOptions == nil {
   369  			continue
   370  		}
   371  
   372  		if mount.VolumeOptions.DriverConfig == nil {
   373  			continue
   374  		}
   375  
   376  		req := c.container.volumeCreateRequest(&mount)
   377  
   378  		// Check if this volume exists on the engine
   379  		if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
   380  			// TODO(amitshukla): Today, volume create through the engine api does not return an error
   381  			// when the named volume with the same parameters already exists.
   382  			// It returns an error if the driver name is different - that is a valid error
   383  			return err
   384  		}
   385  
   386  	}
   387  
   388  	return nil
   389  }
   390  
   391  func (c *containerAdapter) activateServiceBinding() error {
   392  	return c.backend.ActivateContainerServiceBinding(c.container.name())
   393  }
   394  
   395  func (c *containerAdapter) deactivateServiceBinding() error {
   396  	return c.backend.DeactivateContainerServiceBinding(c.container.name())
   397  }
   398  
   399  func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscriptionOptions) (io.ReadCloser, error) {
   400  	reader, writer := io.Pipe()
   401  
   402  	apiOptions := &backend.ContainerLogsConfig{
   403  		ContainerLogsOptions: types.ContainerLogsOptions{
   404  			Follow: options.Follow,
   405  
   406  			// TODO(stevvooe): Parse timestamp out of message. This
   407  			// absolutely needs to be done before going to production with
   408  			// this, at it is completely redundant.
   409  			Timestamps: true,
   410  			Details:    false, // no clue what to do with this, let's just deprecate it.
   411  		},
   412  		OutStream: writer,
   413  	}
   414  
   415  	if options.Since != nil {
   416  		since, err := gogotypes.TimestampFromProto(options.Since)
   417  		if err != nil {
   418  			return nil, err
   419  		}
   420  		apiOptions.Since = since.Format(time.RFC3339Nano)
   421  	}
   422  
   423  	if options.Tail < 0 {
   424  		// See protobuf documentation for details of how this works.
   425  		apiOptions.Tail = fmt.Sprint(-options.Tail - 1)
   426  	} else if options.Tail > 0 {
   427  		return nil, errors.New("tail relative to start of logs not supported via docker API")
   428  	}
   429  
   430  	if len(options.Streams) == 0 {
   431  		// empty == all
   432  		apiOptions.ShowStdout, apiOptions.ShowStderr = true, true
   433  	} else {
   434  		for _, stream := range options.Streams {
   435  			switch stream {
   436  			case api.LogStreamStdout:
   437  				apiOptions.ShowStdout = true
   438  			case api.LogStreamStderr:
   439  				apiOptions.ShowStderr = true
   440  			}
   441  		}
   442  	}
   443  
   444  	chStarted := make(chan struct{})
   445  	go func() {
   446  		defer writer.Close()
   447  		c.backend.ContainerLogs(ctx, c.container.name(), apiOptions, chStarted)
   448  	}()
   449  
   450  	return reader, nil
   451  }
   452  
   453  // todo: typed/wrapped errors
   454  func isContainerCreateNameConflict(err error) bool {
   455  	return strings.Contains(err.Error(), "Conflict. The name")
   456  }
   457  
   458  func isUnknownContainer(err error) bool {
   459  	return strings.Contains(err.Error(), "No such container:")
   460  }
   461  
   462  func isStoppedContainer(err error) bool {
   463  	return strings.Contains(err.Error(), "is already stopped")
   464  }