github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/daemon/cluster/executor/container/adapter.go (about)

     1  package container
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  	"syscall"
    10  	"time"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/api/server/httputils"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/events"
    16  	"github.com/docker/docker/api/types/versions"
    17  	executorpkg "github.com/docker/docker/daemon/cluster/executor"
    18  	"github.com/docker/libnetwork"
    19  	"github.com/docker/swarmkit/api"
    20  	"github.com/docker/swarmkit/log"
    21  	"golang.org/x/net/context"
    22  	"golang.org/x/time/rate"
    23  )
    24  
    25  // containerAdapter conducts remote operations for a container. All calls
    26  // are mostly naked calls to the client API, seeded with information from
    27  // containerConfig.
    28  type containerAdapter struct {
    29  	backend   executorpkg.Backend
    30  	container *containerConfig
    31  }
    32  
    33  func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapter, error) {
    34  	ctnr, err := newContainerConfig(task)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	return &containerAdapter{
    40  		container: ctnr,
    41  		backend:   b,
    42  	}, nil
    43  }
    44  
    45  func (c *containerAdapter) pullImage(ctx context.Context) error {
    46  	spec := c.container.spec()
    47  
    48  	// if the image needs to be pulled, the auth config will be retrieved and updated
    49  	var encodedAuthConfig string
    50  	if spec.PullOptions != nil {
    51  		encodedAuthConfig = spec.PullOptions.RegistryAuth
    52  	}
    53  
    54  	authConfig := &types.AuthConfig{}
    55  	if encodedAuthConfig != "" {
    56  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil {
    57  			logrus.Warnf("invalid authconfig: %v", err)
    58  		}
    59  	}
    60  
    61  	pr, pw := io.Pipe()
    62  	metaHeaders := map[string][]string{}
    63  	go func() {
    64  		err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
    65  		pw.CloseWithError(err)
    66  	}()
    67  
    68  	dec := json.NewDecoder(pr)
    69  	dec.UseNumber()
    70  	m := map[string]interface{}{}
    71  	spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1)
    72  
    73  	lastStatus := ""
    74  	for {
    75  		if err := dec.Decode(&m); err != nil {
    76  			if err == io.EOF {
    77  				break
    78  			}
    79  			return err
    80  		}
    81  		l := log.G(ctx)
    82  		// limit pull progress logs unless the status changes
    83  		if spamLimiter.Allow() || lastStatus != m["status"] {
    84  			// if we have progress details, we have everything we need
    85  			if progress, ok := m["progressDetail"].(map[string]interface{}); ok {
    86  				// first, log the image and status
    87  				l = l.WithFields(logrus.Fields{
    88  					"image":  c.container.image(),
    89  					"status": m["status"],
    90  				})
    91  				// then, if we have progress, log the progress
    92  				if progress["current"] != nil && progress["total"] != nil {
    93  					l = l.WithFields(logrus.Fields{
    94  						"current": progress["current"],
    95  						"total":   progress["total"],
    96  					})
    97  				}
    98  			}
    99  			l.Debug("pull in progress")
   100  		}
   101  		// sometimes, we get no useful information at all, and add no fields
   102  		if status, ok := m["status"].(string); ok {
   103  			lastStatus = status
   104  		}
   105  	}
   106  
   107  	// if the final stream object contained an error, return it
   108  	if errMsg, ok := m["error"]; ok {
   109  		return fmt.Errorf("%v", errMsg)
   110  	}
   111  	return nil
   112  }
   113  
   114  func (c *containerAdapter) createNetworks(ctx context.Context) error {
   115  	for _, network := range c.container.networks() {
   116  		ncr, err := c.container.networkCreateRequest(network)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing
   122  			if _, ok := err.(libnetwork.NetworkNameError); ok {
   123  				continue
   124  			}
   125  
   126  			return err
   127  		}
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  func (c *containerAdapter) removeNetworks(ctx context.Context) error {
   134  	for _, nid := range c.container.networks() {
   135  		if err := c.backend.DeleteManagedNetwork(nid); err != nil {
   136  			switch err.(type) {
   137  			case *libnetwork.ActiveEndpointsError:
   138  				continue
   139  			case libnetwork.ErrNoSuchNetwork:
   140  				continue
   141  			default:
   142  				log.G(ctx).Errorf("network %s remove failed: %v", nid, err)
   143  				return err
   144  			}
   145  		}
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func (c *containerAdapter) networkAttach(ctx context.Context) error {
   152  	config := c.container.createNetworkingConfig()
   153  
   154  	var (
   155  		networkName string
   156  		networkID   string
   157  	)
   158  
   159  	if config != nil {
   160  		for n, epConfig := range config.EndpointsConfig {
   161  			networkName = n
   162  			networkID = epConfig.NetworkID
   163  			break
   164  		}
   165  	}
   166  
   167  	return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config)
   168  }
   169  
   170  func (c *containerAdapter) waitForDetach(ctx context.Context) error {
   171  	config := c.container.createNetworkingConfig()
   172  
   173  	var (
   174  		networkName string
   175  		networkID   string
   176  	)
   177  
   178  	if config != nil {
   179  		for n, epConfig := range config.EndpointsConfig {
   180  			networkName = n
   181  			networkID = epConfig.NetworkID
   182  			break
   183  		}
   184  	}
   185  
   186  	return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id())
   187  }
   188  
   189  func (c *containerAdapter) create(ctx context.Context) error {
   190  	var cr types.ContainerCreateResponse
   191  	var err error
   192  	version := httputils.VersionFromContext(ctx)
   193  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   194  
   195  	if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{
   196  		Name:       c.container.name(),
   197  		Config:     c.container.config(),
   198  		HostConfig: c.container.hostConfig(),
   199  		// Use the first network in container create
   200  		NetworkingConfig: c.container.createNetworkingConfig(),
   201  	}, validateHostname); err != nil {
   202  		return err
   203  	}
   204  
   205  	// Docker daemon currently doesn't support multiple networks in container create
   206  	// Connect to all other networks
   207  	nc := c.container.connectNetworkingConfig()
   208  
   209  	if nc != nil {
   210  		for n, ep := range nc.EndpointsConfig {
   211  			if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
   212  				return err
   213  			}
   214  		}
   215  	}
   216  
   217  	if err := c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil {
   218  		return err
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  func (c *containerAdapter) start(ctx context.Context) error {
   225  	version := httputils.VersionFromContext(ctx)
   226  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   227  	return c.backend.ContainerStart(c.container.name(), nil, validateHostname, "", "")
   228  }
   229  
   230  func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {
   231  	cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false)
   232  	if ctx.Err() != nil {
   233  		return types.ContainerJSON{}, ctx.Err()
   234  	}
   235  	if err != nil {
   236  		return types.ContainerJSON{}, err
   237  	}
   238  	return *cs, nil
   239  }
   240  
   241  // events issues a call to the events API and returns a channel with all
   242  // events. The stream of events can be shutdown by cancelling the context.
   243  func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
   244  	log.G(ctx).Debugf("waiting on events")
   245  	buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter())
   246  	eventsq := make(chan events.Message, len(buffer))
   247  
   248  	for _, event := range buffer {
   249  		eventsq <- event
   250  	}
   251  
   252  	go func() {
   253  		defer c.backend.UnsubscribeFromEvents(l)
   254  
   255  		for {
   256  			select {
   257  			case ev := <-l:
   258  				jev, ok := ev.(events.Message)
   259  				if !ok {
   260  					log.G(ctx).Warnf("unexpected event message: %q", ev)
   261  					continue
   262  				}
   263  				select {
   264  				case eventsq <- jev:
   265  				case <-ctx.Done():
   266  					return
   267  				}
   268  			case <-ctx.Done():
   269  				return
   270  			}
   271  		}
   272  	}()
   273  
   274  	return eventsq
   275  }
   276  
   277  func (c *containerAdapter) wait(ctx context.Context) error {
   278  	return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
   279  }
   280  
   281  func (c *containerAdapter) shutdown(ctx context.Context) error {
   282  	// Default stop grace period to nil (daemon will use the stopTimeout of the container)
   283  	var stopgrace *int
   284  	spec := c.container.spec()
   285  	if spec.StopGracePeriod != nil {
   286  		stopgraceValue := int(spec.StopGracePeriod.Seconds)
   287  		stopgrace = &stopgraceValue
   288  	}
   289  	return c.backend.ContainerStop(c.container.name(), stopgrace)
   290  }
   291  
   292  func (c *containerAdapter) terminate(ctx context.Context) error {
   293  	return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL))
   294  }
   295  
   296  func (c *containerAdapter) remove(ctx context.Context) error {
   297  	return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{
   298  		RemoveVolume: true,
   299  		ForceRemove:  true,
   300  	})
   301  }
   302  
   303  func (c *containerAdapter) createVolumes(ctx context.Context) error {
   304  	// Create plugin volumes that are embedded inside a Mount
   305  	for _, mount := range c.container.task.Spec.GetContainer().Mounts {
   306  		if mount.Type != api.MountTypeVolume {
   307  			continue
   308  		}
   309  
   310  		if mount.VolumeOptions == nil {
   311  			continue
   312  		}
   313  
   314  		if mount.VolumeOptions.DriverConfig == nil {
   315  			continue
   316  		}
   317  
   318  		req := c.container.volumeCreateRequest(&mount)
   319  
   320  		// Check if this volume exists on the engine
   321  		if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
   322  			// TODO(amitshukla): Today, volume create through the engine api does not return an error
   323  			// when the named volume with the same parameters already exists.
   324  			// It returns an error if the driver name is different - that is a valid error
   325  			return err
   326  		}
   327  
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  // todo: typed/wrapped errors
   334  func isContainerCreateNameConflict(err error) bool {
   335  	return strings.Contains(err.Error(), "Conflict. The name")
   336  }
   337  
   338  func isUnknownContainer(err error) bool {
   339  	return strings.Contains(err.Error(), "No such container:")
   340  }
   341  
   342  func isStoppedContainer(err error) bool {
   343  	return strings.Contains(err.Error(), "is already stopped")
   344  }