github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/cluster/executor/container/adapter.go (about)

     1  package container // import "github.com/demonoid81/moby/daemon/cluster/executor/container"
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/docker/distribution/reference"
    15  	"github.com/demonoid81/moby/api/types"
    16  	"github.com/demonoid81/moby/api/types/backend"
    17  	containertypes "github.com/demonoid81/moby/api/types/container"
    18  	"github.com/demonoid81/moby/api/types/events"
    19  	containerpkg "github.com/demonoid81/moby/container"
    20  	"github.com/demonoid81/moby/daemon"
    21  	"github.com/demonoid81/moby/daemon/cluster/convert"
    22  	executorpkg "github.com/demonoid81/moby/daemon/cluster/executor"
    23  	volumeopts "github.com/demonoid81/moby/volume/service/opts"
    24  	"github.com/demonoid81/libnetwork"
    25  	"github.com/docker/swarmkit/agent/exec"
    26  	"github.com/docker/swarmkit/api"
    27  	"github.com/docker/swarmkit/log"
    28  	gogotypes "github.com/gogo/protobuf/types"
    29  	digest "github.com/opencontainers/go-digest"
    30  	"github.com/pkg/errors"
    31  	"github.com/sirupsen/logrus"
    32  	"golang.org/x/time/rate"
    33  )
    34  
    35  // nodeAttachmentReadyInterval is the interval to poll
    36  const nodeAttachmentReadyInterval = 100 * time.Millisecond
    37  
    38  // containerAdapter conducts remote operations for a container. All calls
    39  // are mostly naked calls to the client API, seeded with information from
    40  // containerConfig.
    41  type containerAdapter struct {
    42  	backend       executorpkg.Backend
    43  	imageBackend  executorpkg.ImageBackend
    44  	volumeBackend executorpkg.VolumeBackend
    45  	container     *containerConfig
    46  	dependencies  exec.DependencyGetter
    47  }
    48  
    49  func newContainerAdapter(b executorpkg.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend, task *api.Task, node *api.NodeDescription, dependencies exec.DependencyGetter) (*containerAdapter, error) {
    50  	ctnr, err := newContainerConfig(task, node)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	return &containerAdapter{
    56  		container:     ctnr,
    57  		backend:       b,
    58  		imageBackend:  i,
    59  		volumeBackend: v,
    60  		dependencies:  dependencies,
    61  	}, nil
    62  }
    63  
    64  func (c *containerAdapter) pullImage(ctx context.Context) error {
    65  	spec := c.container.spec()
    66  
    67  	// Skip pulling if the image is referenced by image ID.
    68  	if _, err := digest.Parse(spec.Image); err == nil {
    69  		return nil
    70  	}
    71  
    72  	// Skip pulling if the image is referenced by digest and already
    73  	// exists locally.
    74  	named, err := reference.ParseNormalizedNamed(spec.Image)
    75  	if err == nil {
    76  		if _, ok := named.(reference.Canonical); ok {
    77  			_, err := c.imageBackend.LookupImage(spec.Image)
    78  			if err == nil {
    79  				return nil
    80  			}
    81  		}
    82  	}
    83  
    84  	// if the image needs to be pulled, the auth config will be retrieved and updated
    85  	var encodedAuthConfig string
    86  	if spec.PullOptions != nil {
    87  		encodedAuthConfig = spec.PullOptions.RegistryAuth
    88  	}
    89  
    90  	authConfig := &types.AuthConfig{}
    91  	if encodedAuthConfig != "" {
    92  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil {
    93  			logrus.Warnf("invalid authconfig: %v", err)
    94  		}
    95  	}
    96  
    97  	pr, pw := io.Pipe()
    98  	metaHeaders := map[string][]string{}
    99  	go func() {
   100  		// TODO LCOW Support: This will need revisiting as
   101  		// the stack is built up to include LCOW support for swarm.
   102  		err := c.imageBackend.PullImage(ctx, c.container.image(), "", nil, metaHeaders, authConfig, pw)
   103  		pw.CloseWithError(err)
   104  	}()
   105  
   106  	dec := json.NewDecoder(pr)
   107  	dec.UseNumber()
   108  	m := map[string]interface{}{}
   109  	spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1)
   110  
   111  	lastStatus := ""
   112  	for {
   113  		if err := dec.Decode(&m); err != nil {
   114  			if err == io.EOF {
   115  				break
   116  			}
   117  			return err
   118  		}
   119  		l := log.G(ctx)
   120  		// limit pull progress logs unless the status changes
   121  		if spamLimiter.Allow() || lastStatus != m["status"] {
   122  			// if we have progress details, we have everything we need
   123  			if progress, ok := m["progressDetail"].(map[string]interface{}); ok {
   124  				// first, log the image and status
   125  				l = l.WithFields(logrus.Fields{
   126  					"image":  c.container.image(),
   127  					"status": m["status"],
   128  				})
   129  				// then, if we have progress, log the progress
   130  				if progress["current"] != nil && progress["total"] != nil {
   131  					l = l.WithFields(logrus.Fields{
   132  						"current": progress["current"],
   133  						"total":   progress["total"],
   134  					})
   135  				}
   136  			}
   137  			l.Debug("pull in progress")
   138  		}
   139  		// sometimes, we get no useful information at all, and add no fields
   140  		if status, ok := m["status"].(string); ok {
   141  			lastStatus = status
   142  		}
   143  	}
   144  
   145  	// if the final stream object contained an error, return it
   146  	if errMsg, ok := m["error"]; ok {
   147  		return fmt.Errorf("%v", errMsg)
   148  	}
   149  	return nil
   150  }
   151  
   152  // waitNodeAttachments validates that NetworkAttachments exist on this node
   153  // for every network in use by this task. It blocks until the network
   154  // attachments are ready, or the context times out. If it returns nil, then the
   155  // node's network attachments are all there.
   156  func (c *containerAdapter) waitNodeAttachments(ctx context.Context) error {
   157  	// to do this, we're going to get the attachment store and try getting the
   158  	// IP address for each network. if any network comes back not existing,
   159  	// we'll wait and try again.
   160  	attachmentStore := c.backend.GetAttachmentStore()
   161  	if attachmentStore == nil {
   162  		return fmt.Errorf("error getting attachment store")
   163  	}
   164  
   165  	// essentially, we're long-polling here. this is really sub-optimal, but a
   166  	// better solution based off signaling channels would require a more
   167  	// substantial rearchitecture and probably not be worth our time in terms
   168  	// of performance gains.
   169  	poll := time.NewTicker(nodeAttachmentReadyInterval)
   170  	defer poll.Stop()
   171  	for {
   172  		// set a flag ready to true. if we try to get a network IP that doesn't
   173  		// exist yet, we will set this flag to "false"
   174  		ready := true
   175  		for _, attachment := range c.container.networksAttachments {
   176  			// we only need node attachments (IP address) for overlay networks
   177  			// TODO(dperny): unsure if this will work with other network
   178  			// drivers, but i also don't think other network drivers use the
   179  			// node attachment IP address.
   180  			if attachment.Network.DriverState.Name == "overlay" {
   181  				if _, exists := attachmentStore.GetIPForNetwork(attachment.Network.ID); !exists {
   182  					ready = false
   183  				}
   184  			}
   185  		}
   186  
   187  		// if everything is ready here, then we can just return no error
   188  		if ready {
   189  			return nil
   190  		}
   191  
   192  		// otherwise, try polling again, or wait for context canceled.
   193  		select {
   194  		case <-ctx.Done():
   195  			return fmt.Errorf("node is missing network attachments, ip addresses may be exhausted")
   196  		case <-poll.C:
   197  		}
   198  	}
   199  }
   200  
   201  func (c *containerAdapter) createNetworks(ctx context.Context) error {
   202  	for name := range c.container.networksAttachments {
   203  		ncr, err := c.container.networkCreateRequest(name)
   204  		if err != nil {
   205  			return err
   206  		}
   207  
   208  		if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing
   209  			if _, ok := err.(libnetwork.NetworkNameError); ok {
   210  				continue
   211  			}
   212  			// We will continue if CreateManagedNetwork returns PredefinedNetworkError error.
   213  			// Other callers still can treat it as Error.
   214  			if _, ok := err.(daemon.PredefinedNetworkError); ok {
   215  				continue
   216  			}
   217  			return err
   218  		}
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  func (c *containerAdapter) removeNetworks(ctx context.Context) error {
   225  	var (
   226  		activeEndpointsError *libnetwork.ActiveEndpointsError
   227  		errNoSuchNetwork     libnetwork.ErrNoSuchNetwork
   228  	)
   229  
   230  	for name, v := range c.container.networksAttachments {
   231  		if err := c.backend.DeleteManagedNetwork(v.Network.ID); err != nil {
   232  			switch {
   233  			case errors.As(err, &activeEndpointsError):
   234  				continue
   235  			case errors.As(err, &errNoSuchNetwork):
   236  				continue
   237  			default:
   238  				log.G(ctx).Errorf("network %s remove failed: %v", name, err)
   239  				return err
   240  			}
   241  		}
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func (c *containerAdapter) networkAttach(ctx context.Context) error {
   248  	config := c.container.createNetworkingConfig(c.backend)
   249  
   250  	var (
   251  		networkName string
   252  		networkID   string
   253  	)
   254  
   255  	if config != nil {
   256  		for n, epConfig := range config.EndpointsConfig {
   257  			networkName = n
   258  			networkID = epConfig.NetworkID
   259  			break
   260  		}
   261  	}
   262  
   263  	return c.backend.UpdateAttachment(networkName, networkID, c.container.networkAttachmentContainerID(), config)
   264  }
   265  
   266  func (c *containerAdapter) waitForDetach(ctx context.Context) error {
   267  	config := c.container.createNetworkingConfig(c.backend)
   268  
   269  	var (
   270  		networkName string
   271  		networkID   string
   272  	)
   273  
   274  	if config != nil {
   275  		for n, epConfig := range config.EndpointsConfig {
   276  			networkName = n
   277  			networkID = epConfig.NetworkID
   278  			break
   279  		}
   280  	}
   281  
   282  	return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.networkAttachmentContainerID())
   283  }
   284  
   285  func (c *containerAdapter) create(ctx context.Context) error {
   286  	var cr containertypes.ContainerCreateCreatedBody
   287  	var err error
   288  	if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{
   289  		Name:       c.container.name(),
   290  		Config:     c.container.config(),
   291  		HostConfig: c.container.hostConfig(),
   292  		// Use the first network in container create
   293  		NetworkingConfig: c.container.createNetworkingConfig(c.backend),
   294  	}); err != nil {
   295  		return err
   296  	}
   297  
   298  	// Docker daemon currently doesn't support multiple networks in container create
   299  	// Connect to all other networks
   300  	nc := c.container.connectNetworkingConfig(c.backend)
   301  
   302  	if nc != nil {
   303  		for n, ep := range nc.EndpointsConfig {
   304  			if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
   305  				return err
   306  			}
   307  		}
   308  	}
   309  
   310  	container := c.container.task.Spec.GetContainer()
   311  	if container == nil {
   312  		return errors.New("unable to get container from task spec")
   313  	}
   314  
   315  	if err := c.backend.SetContainerDependencyStore(cr.ID, c.dependencies); err != nil {
   316  		return err
   317  	}
   318  
   319  	// configure secrets
   320  	secretRefs := convert.SecretReferencesFromGRPC(container.Secrets)
   321  	if err := c.backend.SetContainerSecretReferences(cr.ID, secretRefs); err != nil {
   322  		return err
   323  	}
   324  
   325  	configRefs := convert.ConfigReferencesFromGRPC(container.Configs)
   326  	if err := c.backend.SetContainerConfigReferences(cr.ID, configRefs); err != nil {
   327  		return err
   328  	}
   329  
   330  	return c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig())
   331  }
   332  
   333  // checkMounts ensures that the provided mounts won't have any host-specific
   334  // problems at start up. For example, we disallow bind mounts without an
   335  // existing path, which slightly different from the container API.
   336  func (c *containerAdapter) checkMounts() error {
   337  	spec := c.container.spec()
   338  	for _, mount := range spec.Mounts {
   339  		switch mount.Type {
   340  		case api.MountTypeBind:
   341  			if _, err := os.Stat(mount.Source); os.IsNotExist(err) {
   342  				return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source)
   343  			}
   344  		}
   345  	}
   346  
   347  	return nil
   348  }
   349  
   350  func (c *containerAdapter) start(ctx context.Context) error {
   351  	if err := c.checkMounts(); err != nil {
   352  		return err
   353  	}
   354  
   355  	return c.backend.ContainerStart(c.container.name(), nil, "", "")
   356  }
   357  
   358  func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) {
   359  	cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false)
   360  	if ctx.Err() != nil {
   361  		return types.ContainerJSON{}, ctx.Err()
   362  	}
   363  	if err != nil {
   364  		return types.ContainerJSON{}, err
   365  	}
   366  	return *cs, nil
   367  }
   368  
   369  // events issues a call to the events API and returns a channel with all
   370  // events. The stream of events can be shutdown by cancelling the context.
   371  func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
   372  	log.G(ctx).Debugf("waiting on events")
   373  	buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter())
   374  	eventsq := make(chan events.Message, len(buffer))
   375  
   376  	for _, event := range buffer {
   377  		eventsq <- event
   378  	}
   379  
   380  	go func() {
   381  		defer c.backend.UnsubscribeFromEvents(l)
   382  
   383  		for {
   384  			select {
   385  			case ev := <-l:
   386  				jev, ok := ev.(events.Message)
   387  				if !ok {
   388  					log.G(ctx).Warnf("unexpected event message: %q", ev)
   389  					continue
   390  				}
   391  				select {
   392  				case eventsq <- jev:
   393  				case <-ctx.Done():
   394  					return
   395  				}
   396  			case <-ctx.Done():
   397  				return
   398  			}
   399  		}
   400  	}()
   401  
   402  	return eventsq
   403  }
   404  
   405  func (c *containerAdapter) wait(ctx context.Context) (<-chan containerpkg.StateStatus, error) {
   406  	return c.backend.ContainerWait(ctx, c.container.nameOrID(), containerpkg.WaitConditionNotRunning)
   407  }
   408  
   409  func (c *containerAdapter) shutdown(ctx context.Context) error {
   410  	// Default stop grace period to nil (daemon will use the stopTimeout of the container)
   411  	var stopgrace *int
   412  	spec := c.container.spec()
   413  	if spec.StopGracePeriod != nil {
   414  		stopgraceValue := int(spec.StopGracePeriod.Seconds)
   415  		stopgrace = &stopgraceValue
   416  	}
   417  	return c.backend.ContainerStop(c.container.name(), stopgrace)
   418  }
   419  
   420  func (c *containerAdapter) terminate(ctx context.Context) error {
   421  	return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL))
   422  }
   423  
   424  func (c *containerAdapter) remove(ctx context.Context) error {
   425  	return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{
   426  		RemoveVolume: true,
   427  		ForceRemove:  true,
   428  	})
   429  }
   430  
   431  func (c *containerAdapter) createVolumes(ctx context.Context) error {
   432  	// Create plugin volumes that are embedded inside a Mount
   433  	for _, mount := range c.container.task.Spec.GetContainer().Mounts {
   434  		if mount.Type != api.MountTypeVolume {
   435  			continue
   436  		}
   437  
   438  		if mount.VolumeOptions == nil {
   439  			continue
   440  		}
   441  
   442  		if mount.VolumeOptions.DriverConfig == nil {
   443  			continue
   444  		}
   445  
   446  		req := c.container.volumeCreateRequest(&mount)
   447  
   448  		// Check if this volume exists on the engine
   449  		if _, err := c.volumeBackend.Create(ctx, req.Name, req.Driver,
   450  			volumeopts.WithCreateOptions(req.DriverOpts),
   451  			volumeopts.WithCreateLabels(req.Labels),
   452  		); err != nil {
   453  			// TODO(amitshukla): Today, volume create through the engine api does not return an error
   454  			// when the named volume with the same parameters already exists.
   455  			// It returns an error if the driver name is different - that is a valid error
   456  			return err
   457  		}
   458  
   459  	}
   460  
   461  	return nil
   462  }
   463  
   464  func (c *containerAdapter) activateServiceBinding() error {
   465  	return c.backend.ActivateContainerServiceBinding(c.container.name())
   466  }
   467  
   468  func (c *containerAdapter) deactivateServiceBinding() error {
   469  	return c.backend.DeactivateContainerServiceBinding(c.container.name())
   470  }
   471  
   472  func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscriptionOptions) (<-chan *backend.LogMessage, error) {
   473  	apiOptions := &types.ContainerLogsOptions{
   474  		Follow: options.Follow,
   475  
   476  		// Always say yes to Timestamps and Details. we make the decision
   477  		// of whether to return these to the user or not way higher up the
   478  		// stack.
   479  		Timestamps: true,
   480  		Details:    true,
   481  	}
   482  
   483  	if options.Since != nil {
   484  		since, err := gogotypes.TimestampFromProto(options.Since)
   485  		if err != nil {
   486  			return nil, err
   487  		}
   488  		// print since as this formatted string because the docker container
   489  		// logs interface expects it like this.
   490  		// see github.com/demonoid81/moby/api/types/time.ParseTimestamps
   491  		apiOptions.Since = fmt.Sprintf("%d.%09d", since.Unix(), int64(since.Nanosecond()))
   492  	}
   493  
   494  	if options.Tail < 0 {
   495  		// See protobuf documentation for details of how this works.
   496  		apiOptions.Tail = fmt.Sprint(-options.Tail - 1)
   497  	} else if options.Tail > 0 {
   498  		return nil, errors.New("tail relative to start of logs not supported via docker API")
   499  	}
   500  
   501  	if len(options.Streams) == 0 {
   502  		// empty == all
   503  		apiOptions.ShowStdout, apiOptions.ShowStderr = true, true
   504  	} else {
   505  		for _, stream := range options.Streams {
   506  			switch stream {
   507  			case api.LogStreamStdout:
   508  				apiOptions.ShowStdout = true
   509  			case api.LogStreamStderr:
   510  				apiOptions.ShowStderr = true
   511  			}
   512  		}
   513  	}
   514  	msgs, _, err := c.backend.ContainerLogs(ctx, c.container.name(), apiOptions)
   515  	if err != nil {
   516  		return nil, err
   517  	}
   518  	return msgs, nil
   519  }
   520  
   521  // todo: typed/wrapped errors
   522  func isContainerCreateNameConflict(err error) bool {
   523  	return strings.Contains(err.Error(), "Conflict. The name")
   524  }
   525  
   526  func isUnknownContainer(err error) bool {
   527  	return strings.Contains(err.Error(), "No such container:")
   528  }
   529  
   530  func isStoppedContainer(err error) bool {
   531  	return strings.Contains(err.Error(), "is already stopped")
   532  }