github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/docker.go (about)

     1  package driver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"syscall"
    17  	"time"
    18  
    19  	docker "github.com/fsouza/go-dockerclient"
    20  
    21  	"github.com/hashicorp/go-multierror"
    22  	"github.com/hashicorp/go-plugin"
    23  	"github.com/hashicorp/nomad/client/allocdir"
    24  	"github.com/hashicorp/nomad/client/config"
    25  	"github.com/hashicorp/nomad/client/driver/env"
    26  	"github.com/hashicorp/nomad/client/driver/executor"
    27  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    28  	cstructs "github.com/hashicorp/nomad/client/structs"
    29  	"github.com/hashicorp/nomad/helper/discover"
    30  	"github.com/hashicorp/nomad/helper/fields"
    31  	shelpers "github.com/hashicorp/nomad/helper/stats"
    32  	"github.com/hashicorp/nomad/nomad/structs"
    33  	"github.com/mitchellh/mapstructure"
    34  )
    35  
    36  var (
    37  	// We store the clients globally to cache the connection to the docker daemon.
    38  	createClients sync.Once
    39  
    40  	// client is a docker client with a timeout of 1 minute. This is for doing
    41  	// all operations with the docker daemon besides which are not long running
    42  	// such as creating, killing containers, etc.
    43  	client *docker.Client
    44  
    45  	// waitClient is a docker client with no timeouts. This is used for long
    46  	// running operations such as waiting on containers and collect stats
    47  	waitClient *docker.Client
    48  
    49  	// The statistics the Docker driver exposes
    50  	DockerMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage"}
    51  	DockerMeasuredCpuStats = []string{"Throttled Periods", "Throttled Time", "Percent"}
    52  )
    53  
    54  const (
    55  	// NoSuchContainerError is returned by the docker daemon if the container
    56  	// does not exist.
    57  	NoSuchContainerError = "No such container"
    58  
    59  	// The key populated in Node Attributes to indicate presence of the Docker
    60  	// driver
    61  	dockerDriverAttr = "driver.docker"
    62  
    63  	// dockerSELinuxLabelConfigOption is the key for configuring the
    64  	// SELinux label for binds.
    65  	dockerSELinuxLabelConfigOption = "docker.volumes.selinuxlabel"
    66  
    67  	// dockerVolumesConfigOption is the key for enabling the use of custom
    68  	// bind volumes to arbitrary host paths.
    69  	dockerVolumesConfigOption  = "docker.volumes.enabled"
    70  	dockerVolumesConfigDefault = true
    71  
    72  	// dockerPrivilegedConfigOption is the key for running containers in
    73  	// Docker's privileged mode.
    74  	dockerPrivilegedConfigOption = "docker.privileged.enabled"
    75  
    76  	// dockerTimeout is the length of time a request can be outstanding before
    77  	// it is timed out.
    78  	dockerTimeout = 1 * time.Minute
    79  )
    80  
    81  type DockerDriver struct {
    82  	DriverContext
    83  }
    84  
    85  type DockerDriverAuth struct {
    86  	Username      string `mapstructure:"username"`       // username for the registry
    87  	Password      string `mapstructure:"password"`       // password to access the registry
    88  	Email         string `mapstructure:"email"`          // email address of the user who is allowed to access the registry
    89  	ServerAddress string `mapstructure:"server_address"` // server address of the registry
    90  }
    91  
    92  type DockerLoggingOpts struct {
    93  	Type      string              `mapstructure:"type"`
    94  	ConfigRaw []map[string]string `mapstructure:"config"`
    95  	Config    map[string]string   `mapstructure:"-"`
    96  }
    97  
    98  type DockerDriverConfig struct {
    99  	ImageName        string              `mapstructure:"image"`              // Container's Image Name
   100  	LoadImages       []string            `mapstructure:"load"`               // LoadImage is array of paths to image archive files
   101  	Command          string              `mapstructure:"command"`            // The Command/Entrypoint to run when the container starts up
   102  	Args             []string            `mapstructure:"args"`               // The arguments to the Command/Entrypoint
   103  	IpcMode          string              `mapstructure:"ipc_mode"`           // The IPC mode of the container - host and none
   104  	NetworkMode      string              `mapstructure:"network_mode"`       // The network mode of the container - host, nat and none
   105  	PidMode          string              `mapstructure:"pid_mode"`           // The PID mode of the container - host and none
   106  	UTSMode          string              `mapstructure:"uts_mode"`           // The UTS mode of the container - host and none
   107  	UsernsMode       string              `mapstructure:"userns_mode"`        // The User namespace mode of the container - host and none
   108  	PortMapRaw       []map[string]int    `mapstructure:"port_map"`           //
   109  	PortMap          map[string]int      `mapstructure:"-"`                  // A map of host port labels and the ports exposed on the container
   110  	Privileged       bool                `mapstructure:"privileged"`         // Flag to run the container in privileged mode
   111  	DNSServers       []string            `mapstructure:"dns_servers"`        // DNS Server for containers
   112  	DNSSearchDomains []string            `mapstructure:"dns_search_domains"` // DNS Search domains for containers
   113  	Hostname         string              `mapstructure:"hostname"`           // Hostname for containers
   114  	LabelsRaw        []map[string]string `mapstructure:"labels"`             //
   115  	Labels           map[string]string   `mapstructure:"-"`                  // Labels to set when the container starts up
   116  	Auth             []DockerDriverAuth  `mapstructure:"auth"`               // Authentication credentials for a private Docker registry
   117  	SSL              bool                `mapstructure:"ssl"`                // Flag indicating repository is served via https
   118  	TTY              bool                `mapstructure:"tty"`                // Allocate a Pseudo-TTY
   119  	Interactive      bool                `mapstructure:"interactive"`        // Keep STDIN open even if not attached
   120  	ShmSize          int64               `mapstructure:"shm_size"`           // Size of /dev/shm of the container in bytes
   121  	WorkDir          string              `mapstructure:"work_dir"`           // Working directory inside the container
   122  	Logging          []DockerLoggingOpts `mapstructure:"logging"`            // Logging options for syslog server
   123  	Volumes          []string            `mapstructure:"volumes"`            // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container
   124  }
   125  
   126  // Validate validates a docker driver config
   127  func (c *DockerDriverConfig) Validate() error {
   128  	if c.ImageName == "" {
   129  		return fmt.Errorf("Docker Driver needs an image name")
   130  	}
   131  
   132  	c.PortMap = mapMergeStrInt(c.PortMapRaw...)
   133  	c.Labels = mapMergeStrStr(c.LabelsRaw...)
   134  	if len(c.Logging) > 0 {
   135  		c.Logging[0].Config = mapMergeStrStr(c.Logging[0].ConfigRaw...)
   136  	}
   137  	return nil
   138  }
   139  
   140  // NewDockerDriverConfig returns a docker driver config by parsing the HCL
   141  // config
   142  func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnvironment) (*DockerDriverConfig, error) {
   143  	var dconf DockerDriverConfig
   144  
   145  	// Default to SSL
   146  	dconf.SSL = true
   147  
   148  	if err := mapstructure.WeakDecode(task.Config, &dconf); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	// Interpolate everthing that is a string
   153  	dconf.ImageName = env.ReplaceEnv(dconf.ImageName)
   154  	dconf.Command = env.ReplaceEnv(dconf.Command)
   155  	dconf.IpcMode = env.ReplaceEnv(dconf.IpcMode)
   156  	dconf.NetworkMode = env.ReplaceEnv(dconf.NetworkMode)
   157  	dconf.PidMode = env.ReplaceEnv(dconf.PidMode)
   158  	dconf.UTSMode = env.ReplaceEnv(dconf.UTSMode)
   159  	dconf.Hostname = env.ReplaceEnv(dconf.Hostname)
   160  	dconf.WorkDir = env.ReplaceEnv(dconf.WorkDir)
   161  	dconf.Volumes = env.ParseAndReplace(dconf.Volumes)
   162  	dconf.DNSServers = env.ParseAndReplace(dconf.DNSServers)
   163  	dconf.DNSSearchDomains = env.ParseAndReplace(dconf.DNSSearchDomains)
   164  	dconf.LoadImages = env.ParseAndReplace(dconf.LoadImages)
   165  
   166  	for _, m := range dconf.LabelsRaw {
   167  		for k, v := range m {
   168  			delete(m, k)
   169  			m[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   170  		}
   171  	}
   172  
   173  	for _, a := range dconf.Auth {
   174  		a.Username = env.ReplaceEnv(a.Username)
   175  		a.Password = env.ReplaceEnv(a.Password)
   176  		a.Email = env.ReplaceEnv(a.Email)
   177  		a.ServerAddress = env.ReplaceEnv(a.ServerAddress)
   178  	}
   179  
   180  	for _, l := range dconf.Logging {
   181  		l.Type = env.ReplaceEnv(l.Type)
   182  		for _, c := range l.ConfigRaw {
   183  			for k, v := range c {
   184  				delete(c, k)
   185  				c[env.ReplaceEnv(k)] = env.ReplaceEnv(v)
   186  			}
   187  		}
   188  	}
   189  
   190  	for _, m := range dconf.PortMapRaw {
   191  		for k, v := range m {
   192  			delete(m, k)
   193  			m[env.ReplaceEnv(k)] = v
   194  		}
   195  	}
   196  
   197  	// Remove any http
   198  	if strings.Contains(dconf.ImageName, "https://") {
   199  		dconf.ImageName = strings.Replace(dconf.ImageName, "https://", "", 1)
   200  	}
   201  
   202  	if err := dconf.Validate(); err != nil {
   203  		return nil, err
   204  	}
   205  	return &dconf, nil
   206  }
   207  
   208  type dockerPID struct {
   209  	Version        string
   210  	ImageID        string
   211  	ContainerID    string
   212  	KillTimeout    time.Duration
   213  	MaxKillTimeout time.Duration
   214  	PluginConfig   *PluginReattachConfig
   215  }
   216  
   217  type DockerHandle struct {
   218  	pluginClient      *plugin.Client
   219  	executor          executor.Executor
   220  	client            *docker.Client
   221  	waitClient        *docker.Client
   222  	logger            *log.Logger
   223  	cleanupImage      bool
   224  	imageID           string
   225  	containerID       string
   226  	version           string
   227  	clkSpeed          float64
   228  	killTimeout       time.Duration
   229  	maxKillTimeout    time.Duration
   230  	resourceUsageLock sync.RWMutex
   231  	resourceUsage     *cstructs.TaskResourceUsage
   232  	waitCh            chan *dstructs.WaitResult
   233  	doneCh            chan bool
   234  }
   235  
   236  func NewDockerDriver(ctx *DriverContext) Driver {
   237  	return &DockerDriver{DriverContext: *ctx}
   238  }
   239  
   240  // Validate is used to validate the driver configuration
   241  func (d *DockerDriver) Validate(config map[string]interface{}) error {
   242  	fd := &fields.FieldData{
   243  		Raw: config,
   244  		Schema: map[string]*fields.FieldSchema{
   245  			"image": &fields.FieldSchema{
   246  				Type:     fields.TypeString,
   247  				Required: true,
   248  			},
   249  			"load": &fields.FieldSchema{
   250  				Type: fields.TypeArray,
   251  			},
   252  			"command": &fields.FieldSchema{
   253  				Type: fields.TypeString,
   254  			},
   255  			"args": &fields.FieldSchema{
   256  				Type: fields.TypeArray,
   257  			},
   258  			"ipc_mode": &fields.FieldSchema{
   259  				Type: fields.TypeString,
   260  			},
   261  			"network_mode": &fields.FieldSchema{
   262  				Type: fields.TypeString,
   263  			},
   264  			"pid_mode": &fields.FieldSchema{
   265  				Type: fields.TypeString,
   266  			},
   267  			"uts_mode": &fields.FieldSchema{
   268  				Type: fields.TypeString,
   269  			},
   270  			"userns_mode": &fields.FieldSchema{
   271  				Type: fields.TypeString,
   272  			},
   273  			"port_map": &fields.FieldSchema{
   274  				Type: fields.TypeArray,
   275  			},
   276  			"privileged": &fields.FieldSchema{
   277  				Type: fields.TypeBool,
   278  			},
   279  			"dns_servers": &fields.FieldSchema{
   280  				Type: fields.TypeArray,
   281  			},
   282  			"dns_search_domains": &fields.FieldSchema{
   283  				Type: fields.TypeArray,
   284  			},
   285  			"hostname": &fields.FieldSchema{
   286  				Type: fields.TypeString,
   287  			},
   288  			"labels": &fields.FieldSchema{
   289  				Type: fields.TypeArray,
   290  			},
   291  			"auth": &fields.FieldSchema{
   292  				Type: fields.TypeArray,
   293  			},
   294  			"ssl": &fields.FieldSchema{
   295  				Type: fields.TypeBool,
   296  			},
   297  			"tty": &fields.FieldSchema{
   298  				Type: fields.TypeBool,
   299  			},
   300  			"interactive": &fields.FieldSchema{
   301  				Type: fields.TypeBool,
   302  			},
   303  			"shm_size": &fields.FieldSchema{
   304  				Type: fields.TypeInt,
   305  			},
   306  			"work_dir": &fields.FieldSchema{
   307  				Type: fields.TypeString,
   308  			},
   309  			"logging": &fields.FieldSchema{
   310  				Type: fields.TypeArray,
   311  			},
   312  			"volumes": &fields.FieldSchema{
   313  				Type: fields.TypeArray,
   314  			},
   315  		},
   316  	}
   317  
   318  	if err := fd.Validate(); err != nil {
   319  		return err
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func (d *DockerDriver) Abilities() DriverAbilities {
   326  	return DriverAbilities{
   327  		SendSignals: true,
   328  	}
   329  }
   330  
   331  func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
   332  	// Set environment variables.
   333  	d.taskEnv.SetAllocDir(allocdir.SharedAllocContainerPath).
   334  		SetTaskLocalDir(allocdir.TaskLocalContainerPath).SetSecretsDir(allocdir.TaskSecretsContainerPath).Build()
   335  
   336  	driverConfig, err := NewDockerDriverConfig(task, d.taskEnv)
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	cleanupImage := d.config.ReadBoolDefault("docker.cleanup.image", true)
   342  
   343  	taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
   344  	if !ok {
   345  		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
   346  	}
   347  
   348  	// Initialize docker API clients
   349  	client, waitClient, err := d.dockerClients()
   350  	if err != nil {
   351  		return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err)
   352  	}
   353  
   354  	if err := d.createImage(driverConfig, client, taskDir); err != nil {
   355  		return nil, err
   356  	}
   357  
   358  	image := driverConfig.ImageName
   359  	// Now that we have the image we can get the image id
   360  	dockerImage, err := client.InspectImage(image)
   361  	if err != nil {
   362  		d.logger.Printf("[ERR] driver.docker: failed getting image id for %s: %s", image, err)
   363  		return nil, fmt.Errorf("Failed to determine image id for `%s`: %s", image, err)
   364  	}
   365  	d.logger.Printf("[DEBUG] driver.docker: identified image %s as %s", image, dockerImage.ID)
   366  
   367  	bin, err := discover.NomadExecutable()
   368  	if err != nil {
   369  		return nil, fmt.Errorf("unable to find the nomad binary: %v", err)
   370  	}
   371  	pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name))
   372  	pluginConfig := &plugin.ClientConfig{
   373  		Cmd: exec.Command(bin, "executor", pluginLogFile),
   374  	}
   375  
   376  	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  	executorCtx := &executor.ExecutorContext{
   381  		TaskEnv:        d.taskEnv,
   382  		Task:           task,
   383  		Driver:         "docker",
   384  		AllocDir:       ctx.AllocDir,
   385  		AllocID:        ctx.AllocID,
   386  		PortLowerBound: d.config.ClientMinPort,
   387  		PortUpperBound: d.config.ClientMaxPort,
   388  	}
   389  	if err := exec.SetContext(executorCtx); err != nil {
   390  		pluginClient.Kill()
   391  		return nil, fmt.Errorf("failed to set executor context: %v", err)
   392  	}
   393  
   394  	// Only launch syslog server if we're going to use it!
   395  	syslogAddr := ""
   396  	if runtime.GOOS == "darwin" && len(driverConfig.Logging) == 0 {
   397  		d.logger.Printf("[DEBUG] driver.docker: disabling syslog driver as Docker for Mac workaround")
   398  	} else if len(driverConfig.Logging) == 0 || driverConfig.Logging[0].Type == "syslog" {
   399  		ss, err := exec.LaunchSyslogServer()
   400  		if err != nil {
   401  			pluginClient.Kill()
   402  			return nil, fmt.Errorf("failed to start syslog collector: %v", err)
   403  		}
   404  		syslogAddr = ss.Addr
   405  	}
   406  
   407  	config, err := d.createContainerConfig(ctx, task, driverConfig, syslogAddr)
   408  	if err != nil {
   409  		d.logger.Printf("[ERR] driver.docker: failed to create container configuration for image %s: %s", image, err)
   410  		pluginClient.Kill()
   411  		return nil, fmt.Errorf("Failed to create container configuration for image %s: %s", image, err)
   412  	}
   413  
   414  	container, rerr := d.createContainer(config)
   415  	if rerr != nil {
   416  		d.logger.Printf("[ERR] driver.docker: failed to create container: %s", rerr)
   417  		pluginClient.Kill()
   418  		rerr.Err = fmt.Sprintf("Failed to create container: %s", rerr.Err)
   419  		return nil, rerr
   420  	}
   421  
   422  	d.logger.Printf("[INFO] driver.docker: created container %s", container.ID)
   423  
   424  	// Start the container
   425  	err = client.StartContainer(container.ID, container.HostConfig)
   426  	if err != nil {
   427  		d.logger.Printf("[ERR] driver.docker: failed to start container %s: %s", container.ID, err)
   428  		pluginClient.Kill()
   429  		return nil, fmt.Errorf("Failed to start container %s: %s", container.ID, err)
   430  	}
   431  	d.logger.Printf("[INFO] driver.docker: started container %s", container.ID)
   432  
   433  	// Return a driver handle
   434  	maxKill := d.DriverContext.config.MaxKillTimeout
   435  	h := &DockerHandle{
   436  		client:         client,
   437  		waitClient:     waitClient,
   438  		executor:       exec,
   439  		pluginClient:   pluginClient,
   440  		cleanupImage:   cleanupImage,
   441  		logger:         d.logger,
   442  		imageID:        dockerImage.ID,
   443  		containerID:    container.ID,
   444  		version:        d.config.Version,
   445  		killTimeout:    GetKillTimeout(task.KillTimeout, maxKill),
   446  		maxKillTimeout: maxKill,
   447  		doneCh:         make(chan bool),
   448  		waitCh:         make(chan *dstructs.WaitResult, 1),
   449  	}
   450  	if err := exec.SyncServices(consulContext(d.config, container.ID)); err != nil {
   451  		d.logger.Printf("[ERR] driver.docker: error registering services with consul for task: %q: %v", task.Name, err)
   452  	}
   453  	go h.collectStats()
   454  	go h.run()
   455  	return h, nil
   456  }
   457  
   458  // dockerClients creates two *docker.Client, one for long running operations and
   459  // the other for shorter operations. In test / dev mode we can use ENV vars to
   460  // connect to the docker daemon. In production mode we will read docker.endpoint
   461  // from the config file.
   462  func (d *DockerDriver) dockerClients() (*docker.Client, *docker.Client, error) {
   463  	if client != nil && waitClient != nil {
   464  		return client, waitClient, nil
   465  	}
   466  
   467  	var err error
   468  	var merr multierror.Error
   469  	createClients.Do(func() {
   470  		if err = shelpers.Init(); err != nil {
   471  			d.logger.Printf("[FATAL] driver.docker: unable to initialize stats: %v", err)
   472  			return
   473  		}
   474  
   475  		// Default to using whatever is configured in docker.endpoint. If this is
   476  		// not specified we'll fall back on NewClientFromEnv which reads config from
   477  		// the DOCKER_* environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and
   478  		// DOCKER_CERT_PATH. This allows us to lock down the config in production
   479  		// but also accept the standard ENV configs for dev and test.
   480  		dockerEndpoint := d.config.Read("docker.endpoint")
   481  		if dockerEndpoint != "" {
   482  			cert := d.config.Read("docker.tls.cert")
   483  			key := d.config.Read("docker.tls.key")
   484  			ca := d.config.Read("docker.tls.ca")
   485  
   486  			if cert+key+ca != "" {
   487  				d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", dockerEndpoint)
   488  				client, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca)
   489  				if err != nil {
   490  					merr.Errors = append(merr.Errors, err)
   491  				}
   492  				waitClient, err = docker.NewTLSClient(dockerEndpoint, cert, key, ca)
   493  				if err != nil {
   494  					merr.Errors = append(merr.Errors, err)
   495  				}
   496  			} else {
   497  				d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", dockerEndpoint)
   498  				client, err = docker.NewClient(dockerEndpoint)
   499  				if err != nil {
   500  					merr.Errors = append(merr.Errors, err)
   501  				}
   502  				waitClient, err = docker.NewClient(dockerEndpoint)
   503  				if err != nil {
   504  					merr.Errors = append(merr.Errors, err)
   505  				}
   506  			}
   507  			client.SetTimeout(dockerTimeout)
   508  			return
   509  		}
   510  
   511  		d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment")
   512  		client, err = docker.NewClientFromEnv()
   513  		if err != nil {
   514  			merr.Errors = append(merr.Errors, err)
   515  		}
   516  		client.SetTimeout(dockerTimeout)
   517  
   518  		waitClient, err = docker.NewClientFromEnv()
   519  		if err != nil {
   520  			merr.Errors = append(merr.Errors, err)
   521  		}
   522  	})
   523  	return client, waitClient, merr.ErrorOrNil()
   524  }
   525  
   526  func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
   527  	// Get the current status so that we can log any debug messages only if the
   528  	// state changes
   529  	_, currentlyEnabled := node.Attributes[dockerDriverAttr]
   530  
   531  	// Initialize docker API clients
   532  	client, _, err := d.dockerClients()
   533  	if err != nil {
   534  		delete(node.Attributes, dockerDriverAttr)
   535  		if currentlyEnabled {
   536  			d.logger.Printf("[INFO] driver.docker: failed to initialize client: %s", err)
   537  		}
   538  		return false, nil
   539  	}
   540  
   541  	privileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false)
   542  	if privileged {
   543  		node.Attributes[dockerPrivilegedConfigOption] = "1"
   544  	}
   545  
   546  	// This is the first operation taken on the client so we'll try to
   547  	// establish a connection to the Docker daemon. If this fails it means
   548  	// Docker isn't available so we'll simply disable the docker driver.
   549  	env, err := client.Version()
   550  	if err != nil {
   551  		if currentlyEnabled {
   552  			d.logger.Printf("[DEBUG] driver.docker: could not connect to docker daemon at %s: %s", client.Endpoint(), err)
   553  		}
   554  		delete(node.Attributes, dockerDriverAttr)
   555  		return false, nil
   556  	}
   557  
   558  	node.Attributes[dockerDriverAttr] = "1"
   559  	node.Attributes["driver.docker.version"] = env.Get("Version")
   560  
   561  	// Advertise if this node supports Docker volumes
   562  	if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) {
   563  		node.Attributes["driver."+dockerVolumesConfigOption] = "1"
   564  	}
   565  
   566  	return true, nil
   567  }
   568  
   569  func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, alloc *allocdir.AllocDir,
   570  	task *structs.Task) ([]string, error) {
   571  
   572  	shared := alloc.SharedDir
   573  	taskDir, ok := alloc.TaskDirs[task.Name]
   574  	if !ok {
   575  		return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name)
   576  	}
   577  	local := filepath.Join(taskDir, allocdir.TaskLocal)
   578  
   579  	secret, err := alloc.GetSecretDir(task.Name)
   580  	if err != nil {
   581  		return nil, err
   582  	}
   583  
   584  	allocDirBind := fmt.Sprintf("%s:%s", shared, allocdir.SharedAllocContainerPath)
   585  	taskLocalBind := fmt.Sprintf("%s:%s", local, allocdir.TaskLocalContainerPath)
   586  	secretDirBind := fmt.Sprintf("%s:%s", secret, allocdir.TaskSecretsContainerPath)
   587  	binds := []string{allocDirBind, taskLocalBind, secretDirBind}
   588  
   589  	volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault)
   590  
   591  	for _, userbind := range driverConfig.Volumes {
   592  		parts := strings.Split(userbind, ":")
   593  		if len(parts) < 2 {
   594  			return nil, fmt.Errorf("invalid docker volume: %q", userbind)
   595  		}
   596  
   597  		// Resolve dotted path segments
   598  		parts[0] = filepath.Clean(parts[0])
   599  
   600  		// Absolute paths aren't always supported
   601  		if filepath.IsAbs(parts[0]) {
   602  			if !volumesEnabled {
   603  				// Disallow mounting arbitrary absolute paths
   604  				return nil, fmt.Errorf("%s is false; cannot mount host paths: %+q", dockerVolumesConfigOption, userbind)
   605  			}
   606  			binds = append(binds, userbind)
   607  			continue
   608  		}
   609  
   610  		// Relative paths are always allowed as they mount within a container
   611  		// Expand path relative to alloc dir
   612  		parts[0] = filepath.Join(shared, parts[0])
   613  		binds = append(binds, strings.Join(parts, ":"))
   614  	}
   615  
   616  	if selinuxLabel := d.config.Read(dockerSELinuxLabelConfigOption); selinuxLabel != "" {
   617  		// Apply SELinux Label to each volume
   618  		for i := range binds {
   619  			binds[i] = fmt.Sprintf("%s:%s", binds[i], selinuxLabel)
   620  		}
   621  	}
   622  
   623  	return binds, nil
   624  }
   625  
   626  // createContainerConfig initializes a struct needed to call docker.client.CreateContainer()
   627  func (d *DockerDriver) createContainerConfig(ctx *ExecContext, task *structs.Task,
   628  	driverConfig *DockerDriverConfig, syslogAddr string) (docker.CreateContainerOptions, error) {
   629  	var c docker.CreateContainerOptions
   630  	if task.Resources == nil {
   631  		// Guard against missing resources. We should never have been able to
   632  		// schedule a job without specifying this.
   633  		d.logger.Println("[ERR] driver.docker: task.Resources is empty")
   634  		return c, fmt.Errorf("task.Resources is empty")
   635  	}
   636  
   637  	binds, err := d.containerBinds(driverConfig, ctx.AllocDir, task)
   638  	if err != nil {
   639  		return c, err
   640  	}
   641  
   642  	config := &docker.Config{
   643  		Image:     driverConfig.ImageName,
   644  		Hostname:  driverConfig.Hostname,
   645  		User:      task.User,
   646  		Tty:       driverConfig.TTY,
   647  		OpenStdin: driverConfig.Interactive,
   648  	}
   649  
   650  	if driverConfig.WorkDir != "" {
   651  		config.WorkingDir = driverConfig.WorkDir
   652  	}
   653  
   654  	memLimit := int64(task.Resources.MemoryMB) * 1024 * 1024
   655  
   656  	if len(driverConfig.Logging) == 0 {
   657  		if runtime.GOOS != "darwin" {
   658  			d.logger.Printf("[DEBUG] driver.docker: Setting default logging options to syslog and %s", syslogAddr)
   659  			driverConfig.Logging = []DockerLoggingOpts{
   660  				{Type: "syslog", Config: map[string]string{"syslog-address": syslogAddr}},
   661  			}
   662  		} else {
   663  			d.logger.Printf("[DEBUG] driver.docker: deferring logging to docker on Docker for Mac")
   664  		}
   665  	}
   666  
   667  	hostConfig := &docker.HostConfig{
   668  		// Convert MB to bytes. This is an absolute value.
   669  		Memory:     memLimit,
   670  		MemorySwap: memLimit, // MemorySwap is memory + swap.
   671  		// Convert Mhz to shares. This is a relative value.
   672  		CPUShares: int64(task.Resources.CPU),
   673  
   674  		// Binds are used to mount a host volume into the container. We mount a
   675  		// local directory for storage and a shared alloc directory that can be
   676  		// used to share data between different tasks in the same task group.
   677  		Binds: binds,
   678  	}
   679  
   680  	if len(driverConfig.Logging) != 0 {
   681  		d.logger.Printf("[DEBUG] driver.docker: Using config for logging: %+v", driverConfig.Logging[0])
   682  		hostConfig.LogConfig = docker.LogConfig{
   683  			Type:   driverConfig.Logging[0].Type,
   684  			Config: driverConfig.Logging[0].Config,
   685  		}
   686  	}
   687  
   688  	d.logger.Printf("[DEBUG] driver.docker: using %d bytes memory for %s", hostConfig.Memory, task.Name)
   689  	d.logger.Printf("[DEBUG] driver.docker: using %d cpu shares for %s", hostConfig.CPUShares, task.Name)
   690  	d.logger.Printf("[DEBUG] driver.docker: binding directories %#v for %s", hostConfig.Binds, task.Name)
   691  
   692  	//  set privileged mode
   693  	hostPrivileged := d.config.ReadBoolDefault(dockerPrivilegedConfigOption, false)
   694  	if driverConfig.Privileged && !hostPrivileged {
   695  		return c, fmt.Errorf(`Docker privileged mode is disabled on this Nomad agent`)
   696  	}
   697  	hostConfig.Privileged = driverConfig.Privileged
   698  
   699  	// set SHM size
   700  	if driverConfig.ShmSize != 0 {
   701  		hostConfig.ShmSize = driverConfig.ShmSize
   702  	}
   703  
   704  	// set DNS servers
   705  	for _, ip := range driverConfig.DNSServers {
   706  		if net.ParseIP(ip) != nil {
   707  			hostConfig.DNS = append(hostConfig.DNS, ip)
   708  		} else {
   709  			d.logger.Printf("[ERR] driver.docker: invalid ip address for container dns server: %s", ip)
   710  		}
   711  	}
   712  
   713  	// set DNS search domains
   714  	for _, domain := range driverConfig.DNSSearchDomains {
   715  		hostConfig.DNSSearch = append(hostConfig.DNSSearch, domain)
   716  	}
   717  
   718  	hostConfig.IpcMode = driverConfig.IpcMode
   719  	hostConfig.PidMode = driverConfig.PidMode
   720  	hostConfig.UTSMode = driverConfig.UTSMode
   721  	hostConfig.UsernsMode = driverConfig.UsernsMode
   722  
   723  	hostConfig.NetworkMode = driverConfig.NetworkMode
   724  	if hostConfig.NetworkMode == "" {
   725  		// docker default
   726  		d.logger.Printf("[DEBUG] driver.docker: networking mode not specified; defaulting to %s", defaultNetworkMode)
   727  		hostConfig.NetworkMode = defaultNetworkMode
   728  	}
   729  
   730  	// Setup port mapping and exposed ports
   731  	if len(task.Resources.Networks) == 0 {
   732  		d.logger.Println("[DEBUG] driver.docker: No network interfaces are available")
   733  		if len(driverConfig.PortMap) > 0 {
   734  			return c, fmt.Errorf("Trying to map ports but no network interface is available")
   735  		}
   736  	} else {
   737  		// TODO add support for more than one network
   738  		network := task.Resources.Networks[0]
   739  		publishedPorts := map[docker.Port][]docker.PortBinding{}
   740  		exposedPorts := map[docker.Port]struct{}{}
   741  
   742  		for _, port := range network.ReservedPorts {
   743  			// By default we will map the allocated port 1:1 to the container
   744  			containerPortInt := port.Value
   745  
   746  			// If the user has mapped a port using port_map we'll change it here
   747  			if mapped, ok := driverConfig.PortMap[port.Label]; ok {
   748  				containerPortInt = mapped
   749  			}
   750  
   751  			hostPortStr := strconv.Itoa(port.Value)
   752  			containerPort := docker.Port(strconv.Itoa(containerPortInt))
   753  
   754  			publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr)
   755  			publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr)
   756  			d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)", network.IP, port.Value, port.Value)
   757  
   758  			exposedPorts[containerPort+"/tcp"] = struct{}{}
   759  			exposedPorts[containerPort+"/udp"] = struct{}{}
   760  			d.logger.Printf("[DEBUG] driver.docker: exposed port %d", port.Value)
   761  		}
   762  
   763  		for _, port := range network.DynamicPorts {
   764  			// By default we will map the allocated port 1:1 to the container
   765  			containerPortInt := port.Value
   766  
   767  			// If the user has mapped a port using port_map we'll change it here
   768  			if mapped, ok := driverConfig.PortMap[port.Label]; ok {
   769  				containerPortInt = mapped
   770  			}
   771  
   772  			hostPortStr := strconv.Itoa(port.Value)
   773  			containerPort := docker.Port(strconv.Itoa(containerPortInt))
   774  
   775  			publishedPorts[containerPort+"/tcp"] = getPortBinding(network.IP, hostPortStr)
   776  			publishedPorts[containerPort+"/udp"] = getPortBinding(network.IP, hostPortStr)
   777  			d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (mapped)", network.IP, port.Value, containerPortInt)
   778  
   779  			exposedPorts[containerPort+"/tcp"] = struct{}{}
   780  			exposedPorts[containerPort+"/udp"] = struct{}{}
   781  			d.logger.Printf("[DEBUG] driver.docker: exposed port %s", containerPort)
   782  		}
   783  
   784  		d.taskEnv.SetPortMap(driverConfig.PortMap)
   785  
   786  		hostConfig.PortBindings = publishedPorts
   787  		config.ExposedPorts = exposedPorts
   788  	}
   789  
   790  	d.taskEnv.Build()
   791  	parsedArgs := d.taskEnv.ParseAndReplace(driverConfig.Args)
   792  
   793  	// If the user specified a custom command to run as their entrypoint, we'll
   794  	// inject it here.
   795  	if driverConfig.Command != "" {
   796  		// Validate command
   797  		if err := validateCommand(driverConfig.Command, "args"); err != nil {
   798  			return c, err
   799  		}
   800  
   801  		cmd := []string{driverConfig.Command}
   802  		if len(driverConfig.Args) != 0 {
   803  			cmd = append(cmd, parsedArgs...)
   804  		}
   805  		d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " "))
   806  		config.Cmd = cmd
   807  	} else if len(driverConfig.Args) != 0 {
   808  		config.Cmd = parsedArgs
   809  	}
   810  
   811  	if len(driverConfig.Labels) > 0 {
   812  		config.Labels = driverConfig.Labels
   813  		d.logger.Printf("[DEBUG] driver.docker: applied labels on the container: %+v", config.Labels)
   814  	}
   815  
   816  	config.Env = d.taskEnv.EnvList()
   817  
   818  	containerName := fmt.Sprintf("%s-%s", task.Name, ctx.AllocID)
   819  	d.logger.Printf("[DEBUG] driver.docker: setting container name to: %s", containerName)
   820  
   821  	return docker.CreateContainerOptions{
   822  		Name:       containerName,
   823  		Config:     config,
   824  		HostConfig: hostConfig,
   825  	}, nil
   826  }
   827  
   828  var (
   829  	// imageNotFoundMatcher is a regex expression that matches the image not
   830  	// found error Docker returns.
   831  	imageNotFoundMatcher = regexp.MustCompile(`Error: image .+ not found`)
   832  )
   833  
   834  // recoverablePullError wraps the error gotten when trying to pull and image if
   835  // the error is recoverable.
   836  func (d *DockerDriver) recoverablePullError(err error, image string) error {
   837  	recoverable := true
   838  	if imageNotFoundMatcher.MatchString(err.Error()) {
   839  		recoverable = false
   840  	}
   841  	return structs.NewRecoverableError(fmt.Errorf("Failed to pull `%s`: %s", image, err), recoverable)
   842  }
   843  
   844  func (d *DockerDriver) Periodic() (bool, time.Duration) {
   845  	return true, 15 * time.Second
   846  }
   847  
   848  // createImage creates a docker image either by pulling it from a registry or by
   849  // loading it from the file system
   850  func (d *DockerDriver) createImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir string) error {
   851  	image := driverConfig.ImageName
   852  	repo, tag := docker.ParseRepositoryTag(image)
   853  	if tag == "" {
   854  		tag = "latest"
   855  	}
   856  
   857  	var dockerImage *docker.Image
   858  	var err error
   859  	// We're going to check whether the image is already downloaded. If the tag
   860  	// is "latest" we have to check for a new version every time so we don't
   861  	// bother to check and cache the id here. We'll download first, then cache.
   862  	if tag != "latest" {
   863  		dockerImage, err = client.InspectImage(image)
   864  	}
   865  
   866  	// Download the image
   867  	if dockerImage == nil {
   868  		if len(driverConfig.LoadImages) > 0 {
   869  			return d.loadImage(driverConfig, client, taskDir)
   870  		}
   871  
   872  		return d.pullImage(driverConfig, client, repo, tag)
   873  	}
   874  	return err
   875  }
   876  
   877  // pullImage creates an image by pulling it from a docker registry
   878  func (d *DockerDriver) pullImage(driverConfig *DockerDriverConfig, client *docker.Client, repo string, tag string) error {
   879  	pullOptions := docker.PullImageOptions{
   880  		Repository: repo,
   881  		Tag:        tag,
   882  	}
   883  
   884  	authOptions := docker.AuthConfiguration{}
   885  	if len(driverConfig.Auth) != 0 {
   886  		authOptions = docker.AuthConfiguration{
   887  			Username:      driverConfig.Auth[0].Username,
   888  			Password:      driverConfig.Auth[0].Password,
   889  			Email:         driverConfig.Auth[0].Email,
   890  			ServerAddress: driverConfig.Auth[0].ServerAddress,
   891  		}
   892  	}
   893  
   894  	if authConfigFile := d.config.Read("docker.auth.config"); authConfigFile != "" {
   895  		if f, err := os.Open(authConfigFile); err == nil {
   896  			defer f.Close()
   897  			var authConfigurations *docker.AuthConfigurations
   898  			if authConfigurations, err = docker.NewAuthConfigurations(f); err != nil {
   899  				return fmt.Errorf("Failed to create docker auth object: %v", err)
   900  			}
   901  
   902  			authConfigurationKey := ""
   903  			if driverConfig.SSL {
   904  				authConfigurationKey += "https://"
   905  			}
   906  
   907  			authConfigurationKey += strings.Split(driverConfig.ImageName, "/")[0]
   908  			if authConfiguration, ok := authConfigurations.Configs[authConfigurationKey]; ok {
   909  				authOptions = authConfiguration
   910  			}
   911  		} else {
   912  			return fmt.Errorf("Failed to open auth config file: %v, error: %v", authConfigFile, err)
   913  		}
   914  	}
   915  
   916  	err := client.PullImage(pullOptions, authOptions)
   917  	if err != nil {
   918  		d.logger.Printf("[ERR] driver.docker: failed pulling container %s:%s: %s", repo, tag, err)
   919  		return d.recoverablePullError(err, driverConfig.ImageName)
   920  	}
   921  	d.logger.Printf("[DEBUG] driver.docker: docker pull %s:%s succeeded", repo, tag)
   922  	return nil
   923  }
   924  
   925  // loadImage creates an image by loading it from the file system
   926  func (d *DockerDriver) loadImage(driverConfig *DockerDriverConfig, client *docker.Client, taskDir string) error {
   927  	var errors multierror.Error
   928  	for _, image := range driverConfig.LoadImages {
   929  		archive := filepath.Join(taskDir, allocdir.TaskLocal, image)
   930  		d.logger.Printf("[DEBUG] driver.docker: loading image from: %v", archive)
   931  		f, err := os.Open(archive)
   932  		if err != nil {
   933  			errors.Errors = append(errors.Errors, fmt.Errorf("unable to open image archive: %v", err))
   934  			continue
   935  		}
   936  		if err := client.LoadImage(docker.LoadImageOptions{InputStream: f}); err != nil {
   937  			errors.Errors = append(errors.Errors, err)
   938  		}
   939  		f.Close()
   940  	}
   941  	return errors.ErrorOrNil()
   942  }
   943  
   944  // createContainer creates the container given the passed configuration. It
   945  // attempts to handle any transient Docker errors.
   946  func (d *DockerDriver) createContainer(config docker.CreateContainerOptions) (*docker.Container, *structs.RecoverableError) {
   947  	attempted := 0
   948  
   949  	recoverable := func(err error) *structs.RecoverableError {
   950  		r := false
   951  		if strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") ||
   952  			strings.Contains(err.Error(), "EOF") ||
   953  			strings.Contains(err.Error(), "container already exists") {
   954  			r = true
   955  		}
   956  		return structs.NewRecoverableError(err, r)
   957  	}
   958  
   959  	// Create a container
   960  CREATE:
   961  	container, err := client.CreateContainer(config)
   962  	if err == nil {
   963  		return container, nil
   964  	}
   965  
   966  	if strings.Contains(strings.ToLower(err.Error()), "container already exists") {
   967  		containers, err := client.ListContainers(docker.ListContainersOptions{})
   968  		if err != nil {
   969  			d.logger.Printf("[ERR] driver.docker: failed to query list of containers matching name:%s", config.Name)
   970  			return nil, recoverable(fmt.Errorf("Failed to query list of containers: %s", err))
   971  		}
   972  
   973  		// Delete matching containers
   974  		for _, container := range containers {
   975  			found := false
   976  			for _, name := range container.Names {
   977  				if name == config.Name {
   978  					found = true
   979  					break
   980  				}
   981  			}
   982  
   983  			if !found {
   984  				continue
   985  			}
   986  
   987  			err = client.RemoveContainer(docker.RemoveContainerOptions{
   988  				ID: container.ID,
   989  			})
   990  			if err != nil {
   991  				d.logger.Printf("[ERR] driver.docker: failed to purge container %s", container.ID)
   992  				return nil, recoverable(fmt.Errorf("Failed to purge container %s: %s", container.ID, err))
   993  			} else if err == nil {
   994  				d.logger.Printf("[INFO] driver.docker: purged container %s", container.ID)
   995  			}
   996  		}
   997  
   998  		if attempted < 5 {
   999  			attempted++
  1000  			goto CREATE
  1001  		}
  1002  	}
  1003  
  1004  	return nil, recoverable(err)
  1005  }
  1006  
  1007  func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
  1008  	cleanupImage := d.config.ReadBoolDefault("docker.cleanup.image", true)
  1009  
  1010  	// Split the handle
  1011  	pidBytes := []byte(strings.TrimPrefix(handleID, "DOCKER:"))
  1012  	pid := &dockerPID{}
  1013  	if err := json.Unmarshal(pidBytes, pid); err != nil {
  1014  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
  1015  	}
  1016  	d.logger.Printf("[INFO] driver.docker: re-attaching to docker process: %s", pid.ContainerID)
  1017  	d.logger.Printf("[DEBUG] driver.docker: re-attached to handle: %s", handleID)
  1018  	pluginConfig := &plugin.ClientConfig{
  1019  		Reattach: pid.PluginConfig.PluginConfig(),
  1020  	}
  1021  
  1022  	client, waitClient, err := d.dockerClients()
  1023  	if err != nil {
  1024  		return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err)
  1025  	}
  1026  
  1027  	// Look for a running container with this ID
  1028  	containers, err := client.ListContainers(docker.ListContainersOptions{
  1029  		Filters: map[string][]string{
  1030  			"id": []string{pid.ContainerID},
  1031  		},
  1032  	})
  1033  	if err != nil {
  1034  		return nil, fmt.Errorf("Failed to query for container %s: %v", pid.ContainerID, err)
  1035  	}
  1036  
  1037  	found := false
  1038  	for _, container := range containers {
  1039  		if container.ID == pid.ContainerID {
  1040  			found = true
  1041  		}
  1042  	}
  1043  	if !found {
  1044  		return nil, fmt.Errorf("Failed to find container %s", pid.ContainerID)
  1045  	}
  1046  	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
  1047  	if err != nil {
  1048  		d.logger.Printf("[INFO] driver.docker: couldn't re-attach to the plugin process: %v", err)
  1049  		d.logger.Printf("[DEBUG] driver.docker: stopping container %q", pid.ContainerID)
  1050  		if e := client.StopContainer(pid.ContainerID, uint(pid.KillTimeout.Seconds())); e != nil {
  1051  			d.logger.Printf("[DEBUG] driver.docker: couldn't stop container: %v", e)
  1052  		}
  1053  		return nil, err
  1054  	}
  1055  
  1056  	ver, _ := exec.Version()
  1057  	d.logger.Printf("[DEBUG] driver.docker: version of executor: %v", ver.Version)
  1058  
  1059  	// Return a driver handle
  1060  	h := &DockerHandle{
  1061  		client:         client,
  1062  		waitClient:     waitClient,
  1063  		executor:       exec,
  1064  		pluginClient:   pluginClient,
  1065  		cleanupImage:   cleanupImage,
  1066  		logger:         d.logger,
  1067  		imageID:        pid.ImageID,
  1068  		containerID:    pid.ContainerID,
  1069  		version:        pid.Version,
  1070  		killTimeout:    pid.KillTimeout,
  1071  		maxKillTimeout: pid.MaxKillTimeout,
  1072  		doneCh:         make(chan bool),
  1073  		waitCh:         make(chan *dstructs.WaitResult, 1),
  1074  	}
  1075  	if err := exec.SyncServices(consulContext(d.config, pid.ContainerID)); err != nil {
  1076  		h.logger.Printf("[ERR] driver.docker: error registering services with consul: %v", err)
  1077  	}
  1078  
  1079  	go h.collectStats()
  1080  	go h.run()
  1081  	return h, nil
  1082  }
  1083  
  1084  func (h *DockerHandle) ID() string {
  1085  	// Return a handle to the PID
  1086  	pid := dockerPID{
  1087  		Version:        h.version,
  1088  		ImageID:        h.imageID,
  1089  		ContainerID:    h.containerID,
  1090  		KillTimeout:    h.killTimeout,
  1091  		MaxKillTimeout: h.maxKillTimeout,
  1092  		PluginConfig:   NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
  1093  	}
  1094  	data, err := json.Marshal(pid)
  1095  	if err != nil {
  1096  		h.logger.Printf("[ERR] driver.docker: failed to marshal docker PID to JSON: %s", err)
  1097  	}
  1098  	return fmt.Sprintf("DOCKER:%s", string(data))
  1099  }
  1100  
  1101  func (h *DockerHandle) ContainerID() string {
  1102  	return h.containerID
  1103  }
  1104  
  1105  func (h *DockerHandle) WaitCh() chan *dstructs.WaitResult {
  1106  	return h.waitCh
  1107  }
  1108  
  1109  func (h *DockerHandle) Update(task *structs.Task) error {
  1110  	// Store the updated kill timeout.
  1111  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
  1112  	if err := h.executor.UpdateTask(task); err != nil {
  1113  		h.logger.Printf("[DEBUG] driver.docker: failed to update log config: %v", err)
  1114  	}
  1115  
  1116  	// Update is not possible
  1117  	return nil
  1118  }
  1119  
  1120  func (h *DockerHandle) Signal(s os.Signal) error {
  1121  	// Convert types
  1122  	sysSig, ok := s.(syscall.Signal)
  1123  	if !ok {
  1124  		return fmt.Errorf("Failed to determine signal number")
  1125  	}
  1126  
  1127  	dockerSignal := docker.Signal(sysSig)
  1128  	opts := docker.KillContainerOptions{
  1129  		ID:     h.containerID,
  1130  		Signal: dockerSignal,
  1131  	}
  1132  	return h.client.KillContainer(opts)
  1133  
  1134  }
  1135  
  1136  // Kill is used to terminate the task. This uses `docker stop -t killTimeout`
  1137  func (h *DockerHandle) Kill() error {
  1138  	// Stop the container
  1139  	err := h.client.StopContainer(h.containerID, uint(h.killTimeout.Seconds()))
  1140  	if err != nil {
  1141  		h.executor.Exit()
  1142  		h.pluginClient.Kill()
  1143  
  1144  		// Container has already been removed.
  1145  		if strings.Contains(err.Error(), NoSuchContainerError) {
  1146  			h.logger.Printf("[DEBUG] driver.docker: attempted to stop non-existent container %s", h.containerID)
  1147  			return nil
  1148  		}
  1149  		h.logger.Printf("[ERR] driver.docker: failed to stop container %s: %v", h.containerID, err)
  1150  		return fmt.Errorf("Failed to stop container %s: %s", h.containerID, err)
  1151  	}
  1152  	h.logger.Printf("[INFO] driver.docker: stopped container %s", h.containerID)
  1153  	return nil
  1154  }
  1155  
  1156  func (h *DockerHandle) Stats() (*cstructs.TaskResourceUsage, error) {
  1157  	h.resourceUsageLock.RLock()
  1158  	defer h.resourceUsageLock.RUnlock()
  1159  	var err error
  1160  	if h.resourceUsage == nil {
  1161  		err = fmt.Errorf("stats collection hasn't started yet")
  1162  	}
  1163  	return h.resourceUsage, err
  1164  }
  1165  
  1166  func (h *DockerHandle) run() {
  1167  	// Wait for it...
  1168  	exitCode, werr := h.waitClient.WaitContainer(h.containerID)
  1169  	if werr != nil {
  1170  		h.logger.Printf("[ERR] driver.docker: failed to wait for %s; container already terminated", h.containerID)
  1171  	}
  1172  
  1173  	if exitCode != 0 {
  1174  		werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode)
  1175  	}
  1176  
  1177  	close(h.doneCh)
  1178  
  1179  	// Remove services
  1180  	if err := h.executor.DeregisterServices(); err != nil {
  1181  		h.logger.Printf("[ERR] driver.docker: error deregistering services: %v", err)
  1182  	}
  1183  
  1184  	// Shutdown the syslog collector
  1185  	if err := h.executor.Exit(); err != nil {
  1186  		h.logger.Printf("[ERR] driver.docker: failed to kill the syslog collector: %v", err)
  1187  	}
  1188  	h.pluginClient.Kill()
  1189  
  1190  	// Stop the container just incase the docker daemon's wait returned
  1191  	// incorrectly
  1192  	if err := h.client.StopContainer(h.containerID, 0); err != nil {
  1193  		_, noSuchContainer := err.(*docker.NoSuchContainer)
  1194  		_, containerNotRunning := err.(*docker.ContainerNotRunning)
  1195  		if !containerNotRunning && !noSuchContainer {
  1196  			h.logger.Printf("[ERR] driver.docker: error stopping container: %v", err)
  1197  		}
  1198  	}
  1199  
  1200  	// Remove the container
  1201  	if err := h.client.RemoveContainer(docker.RemoveContainerOptions{ID: h.containerID, RemoveVolumes: true, Force: true}); err != nil {
  1202  		h.logger.Printf("[ERR] driver.docker: error removing container: %v", err)
  1203  	}
  1204  
  1205  	// Cleanup the image
  1206  	if h.cleanupImage {
  1207  		if err := h.client.RemoveImage(h.imageID); err != nil {
  1208  			h.logger.Printf("[DEBUG] driver.docker: error removing image: %v", err)
  1209  		}
  1210  	}
  1211  
  1212  	// Send the results
  1213  	h.waitCh <- dstructs.NewWaitResult(exitCode, 0, werr)
  1214  	close(h.waitCh)
  1215  }
  1216  
  1217  // collectStats starts collecting resource usage stats of a docker container
  1218  func (h *DockerHandle) collectStats() {
  1219  	statsCh := make(chan *docker.Stats)
  1220  	statsOpts := docker.StatsOptions{ID: h.containerID, Done: h.doneCh, Stats: statsCh, Stream: true}
  1221  	go func() {
  1222  		//TODO handle Stats error
  1223  		if err := h.waitClient.Stats(statsOpts); err != nil {
  1224  			h.logger.Printf("[DEBUG] driver.docker: error collecting stats from container %s: %v", h.containerID, err)
  1225  		}
  1226  	}()
  1227  	numCores := runtime.NumCPU()
  1228  	for {
  1229  		select {
  1230  		case s := <-statsCh:
  1231  			if s != nil {
  1232  				ms := &cstructs.MemoryStats{
  1233  					RSS:      s.MemoryStats.Stats.Rss,
  1234  					Cache:    s.MemoryStats.Stats.Cache,
  1235  					Swap:     s.MemoryStats.Stats.Swap,
  1236  					MaxUsage: s.MemoryStats.MaxUsage,
  1237  					Measured: DockerMeasuredMemStats,
  1238  				}
  1239  
  1240  				cs := &cstructs.CpuStats{
  1241  					ThrottledPeriods: s.CPUStats.ThrottlingData.ThrottledPeriods,
  1242  					ThrottledTime:    s.CPUStats.ThrottlingData.ThrottledTime,
  1243  					Measured:         DockerMeasuredCpuStats,
  1244  				}
  1245  
  1246  				// Calculate percentage
  1247  				cores := len(s.CPUStats.CPUUsage.PercpuUsage)
  1248  				cs.Percent = calculatePercent(
  1249  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage,
  1250  					s.CPUStats.SystemCPUUsage, s.PreCPUStats.SystemCPUUsage, cores)
  1251  				cs.SystemMode = calculatePercent(
  1252  					s.CPUStats.CPUUsage.UsageInKernelmode, s.PreCPUStats.CPUUsage.UsageInKernelmode,
  1253  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, cores)
  1254  				cs.UserMode = calculatePercent(
  1255  					s.CPUStats.CPUUsage.UsageInUsermode, s.PreCPUStats.CPUUsage.UsageInUsermode,
  1256  					s.CPUStats.CPUUsage.TotalUsage, s.PreCPUStats.CPUUsage.TotalUsage, cores)
  1257  				cs.TotalTicks = (cs.Percent / 100) * shelpers.TotalTicksAvailable() / float64(numCores)
  1258  
  1259  				h.resourceUsageLock.Lock()
  1260  				h.resourceUsage = &cstructs.TaskResourceUsage{
  1261  					ResourceUsage: &cstructs.ResourceUsage{
  1262  						MemoryStats: ms,
  1263  						CpuStats:    cs,
  1264  					},
  1265  					Timestamp: s.Read.UTC().UnixNano(),
  1266  				}
  1267  				h.resourceUsageLock.Unlock()
  1268  			}
  1269  		case <-h.doneCh:
  1270  			return
  1271  		}
  1272  	}
  1273  }
  1274  
  1275  func calculatePercent(newSample, oldSample, newTotal, oldTotal uint64, cores int) float64 {
  1276  	numerator := newSample - oldSample
  1277  	denom := newTotal - oldTotal
  1278  	if numerator <= 0 || denom <= 0 {
  1279  		return 0.0
  1280  	}
  1281  
  1282  	return (float64(numerator) / float64(denom)) * float64(cores) * 100.0
  1283  }