github.com/portworx/docker@v1.12.1/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  	executorpkg "github.com/docker/docker/daemon/cluster/executor"
    15  	"github.com/docker/engine-api/types"
    16  	"github.com/docker/engine-api/types/events"
    17  	"github.com/docker/engine-api/types/versions"
    18  	"github.com/docker/libnetwork"
    19  	"github.com/docker/swarmkit/api"
    20  	"github.com/docker/swarmkit/log"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  // containerAdapter conducts remote operations for a container. All calls
    25  // are mostly naked calls to the client API, seeded with information from
    26  // containerConfig.
    27  type containerAdapter struct {
    28  	backend   executorpkg.Backend
    29  	container *containerConfig
    30  }
    31  
    32  func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapter, error) {
    33  	ctnr, err := newContainerConfig(task)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	return &containerAdapter{
    39  		container: ctnr,
    40  		backend:   b,
    41  	}, nil
    42  }
    43  
    44  func (c *containerAdapter) pullImage(ctx context.Context) error {
    45  	spec := c.container.spec()
    46  
    47  	// if the image needs to be pulled, the auth config will be retrieved and updated
    48  	var encodedAuthConfig string
    49  	if spec.PullOptions != nil {
    50  		encodedAuthConfig = spec.PullOptions.RegistryAuth
    51  	}
    52  
    53  	authConfig := &types.AuthConfig{}
    54  	if encodedAuthConfig != "" {
    55  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil {
    56  			logrus.Warnf("invalid authconfig: %v", err)
    57  		}
    58  	}
    59  
    60  	pr, pw := io.Pipe()
    61  	metaHeaders := map[string][]string{}
    62  	go func() {
    63  		err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw)
    64  		pw.CloseWithError(err)
    65  	}()
    66  
    67  	dec := json.NewDecoder(pr)
    68  	m := map[string]interface{}{}
    69  	for {
    70  		if err := dec.Decode(&m); err != nil {
    71  			if err == io.EOF {
    72  				break
    73  			}
    74  			return err
    75  		}
    76  		// TODO(stevvooe): Report this status somewhere.
    77  		logrus.Debugln("pull progress", m)
    78  	}
    79  	// if the final stream object contained an error, return it
    80  	if errMsg, ok := m["error"]; ok {
    81  		return fmt.Errorf("%v", errMsg)
    82  	}
    83  	return nil
    84  }
    85  
    86  func (c *containerAdapter) createNetworks(ctx context.Context) error {
    87  	for _, network := range c.container.networks() {
    88  		ncr, err := c.container.networkCreateRequest(network)
    89  		if err != nil {
    90  			return err
    91  		}
    92  
    93  		if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing
    94  			if _, ok := err.(libnetwork.NetworkNameError); ok {
    95  				continue
    96  			}
    97  
    98  			return err
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func (c *containerAdapter) removeNetworks(ctx context.Context) error {
   106  	for _, nid := range c.container.networks() {
   107  		if err := c.backend.DeleteManagedNetwork(nid); err != nil {
   108  			if _, ok := err.(*libnetwork.ActiveEndpointsError); ok {
   109  				continue
   110  			}
   111  			log.G(ctx).Errorf("network %s remove failed: %v", nid, err)
   112  			return err
   113  		}
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backend) error {
   120  	var cr types.ContainerCreateResponse
   121  	var err error
   122  	version := httputils.VersionFromContext(ctx)
   123  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   124  
   125  	if cr, err = backend.CreateManagedContainer(types.ContainerCreateConfig{
   126  		Name:       c.container.name(),
   127  		Config:     c.container.config(),
   128  		HostConfig: c.container.hostConfig(),
   129  		// Use the first network in container create
   130  		NetworkingConfig: c.container.createNetworkingConfig(),
   131  	}, validateHostname); err != nil {
   132  		return err
   133  	}
   134  
   135  	// Docker daemon currently doesn't support multiple networks in container create
   136  	// Connect to all other networks
   137  	nc := c.container.connectNetworkingConfig()
   138  
   139  	if nc != nil {
   140  		for n, ep := range nc.EndpointsConfig {
   141  			if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
   142  				return err
   143  			}
   144  		}
   145  	}
   146  
   147  	if err := backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil {
   148  		return err
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func (c *containerAdapter) start(ctx context.Context) error {
   155  	version := httputils.VersionFromContext(ctx)
   156  	validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
   157  	return c.backend.ContainerStart(c.container.name(), nil, validateHostname)
   158  }
   159  
   160  func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {
   161  	cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false)
   162  	if ctx.Err() != nil {
   163  		return types.ContainerJSON{}, ctx.Err()
   164  	}
   165  	if err != nil {
   166  		return types.ContainerJSON{}, err
   167  	}
   168  	return *cs, nil
   169  }
   170  
   171  // events issues a call to the events API and returns a channel with all
   172  // events. The stream of events can be shutdown by cancelling the context.
   173  func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
   174  	log.G(ctx).Debugf("waiting on events")
   175  	buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter())
   176  	eventsq := make(chan events.Message, len(buffer))
   177  
   178  	for _, event := range buffer {
   179  		eventsq <- event
   180  	}
   181  
   182  	go func() {
   183  		defer c.backend.UnsubscribeFromEvents(l)
   184  
   185  		for {
   186  			select {
   187  			case ev := <-l:
   188  				jev, ok := ev.(events.Message)
   189  				if !ok {
   190  					log.G(ctx).Warnf("unexpected event message: %q", ev)
   191  					continue
   192  				}
   193  				select {
   194  				case eventsq <- jev:
   195  				case <-ctx.Done():
   196  					return
   197  				}
   198  			case <-ctx.Done():
   199  				return
   200  			}
   201  		}
   202  	}()
   203  
   204  	return eventsq
   205  }
   206  
   207  func (c *containerAdapter) wait(ctx context.Context) error {
   208  	return c.backend.ContainerWaitWithContext(ctx, c.container.name())
   209  }
   210  
   211  func (c *containerAdapter) shutdown(ctx context.Context) error {
   212  	// Default stop grace period to 10s.
   213  	stopgrace := 10
   214  	spec := c.container.spec()
   215  	if spec.StopGracePeriod != nil {
   216  		stopgrace = int(spec.StopGracePeriod.Seconds)
   217  	}
   218  	return c.backend.ContainerStop(c.container.name(), stopgrace)
   219  }
   220  
   221  func (c *containerAdapter) terminate(ctx context.Context) error {
   222  	return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL))
   223  }
   224  
   225  func (c *containerAdapter) remove(ctx context.Context) error {
   226  	return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{
   227  		RemoveVolume: true,
   228  		ForceRemove:  true,
   229  	})
   230  }
   231  
   232  func (c *containerAdapter) createVolumes(ctx context.Context, backend executorpkg.Backend) error {
   233  	// Create plugin volumes that are embedded inside a Mount
   234  	for _, mount := range c.container.task.Spec.GetContainer().Mounts {
   235  		if mount.Type != api.MountTypeVolume {
   236  			continue
   237  		}
   238  
   239  		if mount.VolumeOptions == nil {
   240  			continue
   241  		}
   242  
   243  		if mount.VolumeOptions.DriverConfig == nil {
   244  			continue
   245  		}
   246  
   247  		req := c.container.volumeCreateRequest(&mount)
   248  
   249  		// Check if this volume exists on the engine
   250  		if _, err := backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil {
   251  			// TODO(amitshukla): Today, volume create through the engine api does not return an error
   252  			// when the named volume with the same parameters already exists.
   253  			// It returns an error if the driver name is different - that is a valid error
   254  			return err
   255  		}
   256  
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  // todo: typed/wrapped errors
   263  func isContainerCreateNameConflict(err error) bool {
   264  	return strings.Contains(err.Error(), "Conflict. The name")
   265  }
   266  
   267  func isUnknownContainer(err error) bool {
   268  	return strings.Contains(err.Error(), "No such container:")
   269  }
   270  
   271  func isStoppedContainer(err error) bool {
   272  	return strings.Contains(err.Error(), "is already stopped")
   273  }