github.com/anuvu/nomad@v0.8.7-atom1/client/driver/lxc.go (about)

     1  //+build linux,lxc
     2  
     3  package driver
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"time"
    18  
    19  	"github.com/hashicorp/nomad/client/fingerprint"
    20  	"github.com/hashicorp/nomad/client/stats"
    21  	"github.com/hashicorp/nomad/helper/fields"
    22  	"github.com/hashicorp/nomad/nomad/structs"
    23  	"github.com/mitchellh/mapstructure"
    24  
    25  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    26  	cstructs "github.com/hashicorp/nomad/client/structs"
    27  	lxc "gopkg.in/lxc/go-lxc.v2"
    28  )
    29  
    30  const (
    31  	// lxcConfigOption is the key for enabling the LXC driver in the
    32  	// Config.Options map.
    33  	lxcConfigOption = "driver.lxc.enable"
    34  
    35  	// lxcVolumesConfigOption is the key for enabling the use of
    36  	// custom bind volumes to arbitrary host paths
    37  	lxcVolumesConfigOption  = "lxc.volumes.enabled"
    38  	lxcVolumesConfigDefault = true
    39  
    40  	// containerMonitorIntv is the interval at which the driver checks if the
    41  	// container is still alive
    42  	containerMonitorIntv = 2 * time.Second
    43  )
    44  
    45  var (
    46  	LXCMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"}
    47  
    48  	LXCMeasuredMemStats = []string{"RSS", "Cache", "Swap", "Max Usage", "Kernel Usage", "Kernel Max Usage"}
    49  )
    50  
    51  // Add the lxc driver to the list of builtin drivers
    52  func init() {
    53  	BuiltinDrivers["lxc"] = NewLxcDriver
    54  }
    55  
    56  // LxcDriver allows users to run LXC Containers
    57  type LxcDriver struct {
    58  	DriverContext
    59  	fingerprint.StaticFingerprinter
    60  	lxcPath string
    61  }
    62  
    63  // LxcCommonDriverConfig is configuration that's common between
    64  // container types - new containers created with Create and run with
    65  // Start; and containers created from a rootfs clone and started using
    66  // StartExecute.
    67  type LxcCommonDriverConfig struct {
    68  	LogLevel   string              `mapstructure:"log_level"`
    69  	Verbosity  string              `mapstructure:"verbosity"`
    70  	UseExecute bool                `mapstructure:"use_execute"`
    71  	Volumes    []map[string]string `mapstructure:"volumes"`
    72  }
    73  
    74  // LxcStartDriverConfig is the configuration for containers that will
    75  // be created with Create and run with Start
    76  type LxcStartDriverConfig struct {
    77  	Template             string
    78  	Distro               string
    79  	Release              string
    80  	Arch                 string
    81  	ImageVariant         string   `mapstructure:"image_variant"`
    82  	ImageServer          string   `mapstructure:"image_server"`
    83  	GPGKeyID             string   `mapstructure:"gpg_key_id"`
    84  	GPGKeyServer         string   `mapstructure:"gpg_key_server"`
    85  	DisableGPGValidation bool     `mapstructure:"disable_gpg"`
    86  	FlushCache           bool     `mapstructure:"flush_cache"`
    87  	ForceCache           bool     `mapstructure:"force_cache"`
    88  	TemplateArgs         []string `mapstructure:"template_args"`
    89  	LxcCommonDriverConfig
    90  }
    91  
    92  // LxcExecuteDriverConfig is configuration for containers that will be
    93  // created by cloning a rootfs and run using StartExecute
    94  type LxcExecuteDriverConfig struct {
    95  	LxcCommonDriverConfig
    96  	BaseRootFsPath string   `mapstructure:"base_rootfs_path"`
    97  	BaseConfigPath string   `mapstructure:"base_config_path"`
    98  	NetworkMode    string   `mapstructure:"network_mode"` // The network mode of the container - host or bridge
    99  	CmdArgs        []string `mapstructure:"cmd_args"`
   100  }
   101  
   102  // NewLxcDriver returns a new instance of the LXC driver
   103  func NewLxcDriver(ctx *DriverContext) Driver {
   104  	d := &LxcDriver{DriverContext: *ctx}
   105  	return d
   106  }
   107  
   108  // Validate validates the lxc driver configuration
   109  func (d *LxcDriver) Validate(config map[string]interface{}) error {
   110  	commonFieldSchema := map[string]*fields.FieldSchema{
   111  		"log_level": {
   112  			Type:     fields.TypeString,
   113  			Required: false,
   114  		},
   115  		"verbosity": {
   116  			Type:     fields.TypeString,
   117  			Required: false,
   118  		},
   119  		"use_execute": {
   120  			Type:     fields.TypeBool,
   121  			Required: false,
   122  		},
   123  		"volumes": {
   124  			Type:     fields.TypeArray,
   125  			Required: false,
   126  		},
   127  	}
   128  	fd := &fields.FieldData{
   129  		Raw: config,
   130  		Schema: map[string]*fields.FieldSchema{
   131  			"template": {
   132  				Type:     fields.TypeString,
   133  				Required: true,
   134  			},
   135  			"distro": {
   136  				Type:     fields.TypeString,
   137  				Required: false,
   138  			},
   139  			"release": {
   140  				Type:     fields.TypeString,
   141  				Required: false,
   142  			},
   143  			"arch": {
   144  				Type:     fields.TypeString,
   145  				Required: false,
   146  			},
   147  			"image_variant": {
   148  				Type:     fields.TypeString,
   149  				Required: false,
   150  			},
   151  			"image_server": {
   152  				Type:     fields.TypeString,
   153  				Required: false,
   154  			},
   155  			"gpg_key_id": {
   156  				Type:     fields.TypeString,
   157  				Required: false,
   158  			},
   159  			"gpg_key_server": {
   160  				Type:     fields.TypeString,
   161  				Required: false,
   162  			},
   163  			"disable_gpg": {
   164  				Type:     fields.TypeString,
   165  				Required: false,
   166  			},
   167  			"flush_cache": {
   168  				Type:     fields.TypeString,
   169  				Required: false,
   170  			},
   171  			"force_cache": {
   172  				Type:     fields.TypeString,
   173  				Required: false,
   174  			},
   175  			"template_args": {
   176  				Type:     fields.TypeArray,
   177  				Required: false,
   178  			},
   179  		},
   180  	}
   181  	for k, v := range commonFieldSchema {
   182  		fd.Schema[k] = v
   183  	}
   184  
   185  	execFd := &fields.FieldData{
   186  		Raw: config,
   187  		Schema: map[string]*fields.FieldSchema{
   188  			"base_config_path": {
   189  				Type:     fields.TypeString,
   190  				Required: true,
   191  			},
   192  			"base_rootfs_path": {
   193  				Type:     fields.TypeString,
   194  				Required: true,
   195  			},
   196  			"cmd_args": {
   197  				Type:     fields.TypeArray,
   198  				Required: false,
   199  				Default:  []string{},
   200  			},
   201  			"network_mode": {
   202  				Type:     fields.TypeString,
   203  				Required: false,
   204  				Default:  "bridge",
   205  			},
   206  		},
   207  	}
   208  
   209  	for k, v := range commonFieldSchema {
   210  		execFd.Schema[k] = v
   211  	}
   212  
   213  	// default is to use lxc-start
   214  	useExecute := false
   215  	execConfig, ok := config["use_execute"]
   216  	if ok {
   217  		useExecute, ok = execConfig.(bool)
   218  		if !ok {
   219  			return fmt.Errorf("invalid value for use_execute config: %v", execConfig)
   220  		}
   221  	}
   222  	var volumes interface{}
   223  	if useExecute {
   224  		if err := execFd.Validate(); err != nil {
   225  			return err
   226  		}
   227  		volumes, ok = execFd.GetOk("volumes")
   228  	} else {
   229  		if err := fd.Validate(); err != nil {
   230  			return err
   231  		}
   232  		volumes, ok = fd.GetOk("volumes")
   233  	}
   234  	if ok {
   235  		return d.validateVolumesConfig(volumes.([]interface{}))
   236  	} else {
   237  		return nil
   238  	}
   239  }
   240  
   241  func (d *LxcDriver) validateVolumesConfig(volumes []interface{}) error {
   242  	for _, volSpecI := range volumes {
   243  		volSpec := volSpecI.(map[string]interface{})
   244  		_, sourcePresent := volSpec["source"]
   245  		dest, destPresent := volSpec["dest"]
   246  		if !sourcePresent || !destPresent {
   247  			return fmt.Errorf("invalid volume bind mount entry: empty src or dest %v", volSpec)
   248  		}
   249  
   250  		if dest.(string)[0] == '/' {
   251  			return fmt.Errorf("unsupported absolute container mount point: '%s'", dest)
   252  		}
   253  		mode, modePresent := volSpec["mode"]
   254  		if modePresent && mode.(string) != "ro" && mode.(string) != "rw" {
   255  			return fmt.Errorf("invalid mode in bind vol: '%s'", mode.(string))
   256  		}
   257  	}
   258  	return nil
   259  }
   260  
   261  func (d *LxcDriver) Abilities() DriverAbilities {
   262  	return DriverAbilities{
   263  		SendSignals: false,
   264  		Exec:        false,
   265  	}
   266  }
   267  
   268  func (d *LxcDriver) FSIsolation() cstructs.FSIsolation {
   269  	return cstructs.FSIsolationImage
   270  }
   271  
   272  // Fingerprint fingerprints the lxc driver configuration
   273  func (d *LxcDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error {
   274  	cfg := req.Config
   275  
   276  	enabled := cfg.ReadBoolDefault(lxcConfigOption, true)
   277  	if !enabled && !cfg.DevMode {
   278  		return nil
   279  	}
   280  	version := lxc.Version()
   281  	if version == "" {
   282  		return nil
   283  	}
   284  	resp.AddAttribute("driver.lxc.version", version)
   285  	resp.AddAttribute("driver.lxc", "1")
   286  	resp.Detected = true
   287  
   288  	// Advertise if this node supports lxc volumes
   289  	if d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault) {
   290  		resp.AddAttribute("driver."+lxcVolumesConfigOption, "1")
   291  	}
   292  	resp.AddAttribute("driver.lxc.execute", "true")
   293  
   294  	return nil
   295  }
   296  
   297  func (d *LxcDriver) Prestart(_ *ExecContext, task *structs.Task) (*PrestartResponse, error) {
   298  	return nil, nil
   299  }
   300  
   301  func (d *LxcDriver) getContainerName(task *structs.Task) string {
   302  	return fmt.Sprintf("%s-%s", task.Name, d.DriverContext.allocID)
   303  }
   304  
   305  // Start starts the LXC Driver
   306  func (d *LxcDriver) Start(ctx *ExecContext, task *structs.Task) (*StartResponse, error) {
   307  	sresp, err, errCleanup := d.startWithCleanup(ctx, task)
   308  	if err != nil {
   309  		if cleanupErr := errCleanup(); cleanupErr != nil {
   310  			d.logger.Printf("[ERR] error occurred while cleaning up from error in Start: %v", cleanupErr)
   311  		}
   312  	}
   313  	return sresp, err
   314  }
   315  
   316  func (d *LxcDriver) startWithCleanup(ctx *ExecContext, task *structs.Task) (*StartResponse, error, func() error) {
   317  	noCleanup := func() error { return nil }
   318  
   319  	var commonConfig LxcCommonDriverConfig
   320  	if err := mapstructure.WeakDecode(task.Config, &commonConfig); err != nil {
   321  		return nil, err, noCleanup
   322  	}
   323  
   324  	d.lxcPath = lxc.DefaultConfigPath()
   325  	if path := d.config.Read("driver.lxc.path"); path != "" {
   326  		d.lxcPath = path
   327  	}
   328  
   329  	containerName := d.getContainerName(task)
   330  	c, err := lxc.NewContainer(containerName, d.lxcPath)
   331  	if err != nil {
   332  		return nil, fmt.Errorf("unable to initialize container: %v", err), noCleanup
   333  	}
   334  
   335  	var verbosity lxc.Verbosity
   336  	switch commonConfig.Verbosity {
   337  	case "verbose":
   338  		verbosity = lxc.Verbose
   339  	case "", "quiet":
   340  		verbosity = lxc.Quiet
   341  	default:
   342  		return nil, fmt.Errorf("lxc driver config 'verbosity' can only be either quiet or verbose"), noCleanup
   343  	}
   344  	c.SetVerbosity(verbosity)
   345  
   346  	var logLevel lxc.LogLevel
   347  	switch commonConfig.LogLevel {
   348  	case "trace":
   349  		logLevel = lxc.TRACE
   350  	case "debug":
   351  		logLevel = lxc.DEBUG
   352  	case "info":
   353  		logLevel = lxc.INFO
   354  	case "warn":
   355  		logLevel = lxc.WARN
   356  	case "", "error":
   357  		logLevel = lxc.ERROR
   358  	default:
   359  		return nil, fmt.Errorf("lxc driver config 'log_level' can only be trace, debug, info, warn or error"), noCleanup
   360  	}
   361  	c.SetLogLevel(logLevel)
   362  
   363  	logFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.log", task.Name))
   364  	c.SetLogFile(logFile)
   365  
   366  	if commonConfig.UseExecute {
   367  		d.logger.Printf("[INFO] Using lxc-execute to start application container")
   368  		return d.executeContainer(ctx, c, task, &commonConfig)
   369  	} else {
   370  		d.logger.Printf("[INFO] Using lxc-start to start system container")
   371  		return d.startContainer(ctx, c, task, &commonConfig)
   372  	}
   373  }
   374  
   375  func (d *LxcDriver) startContainer(ctx *ExecContext, c *lxc.Container, task *structs.Task, commonConfig *LxcCommonDriverConfig) (*StartResponse, error, func() error) {
   376  	noCleanup := func() error { return nil }
   377  
   378  	var startConfig LxcStartDriverConfig
   379  	if err := mapstructure.WeakDecode(task.Config, &startConfig); err != nil {
   380  		return nil, err, noCleanup
   381  	}
   382  
   383  	options := lxc.TemplateOptions{
   384  		Template:             startConfig.Template,
   385  		Distro:               startConfig.Distro,
   386  		Release:              startConfig.Release,
   387  		Arch:                 startConfig.Arch,
   388  		FlushCache:           startConfig.FlushCache,
   389  		DisableGPGValidation: startConfig.DisableGPGValidation,
   390  		ExtraArgs:            startConfig.TemplateArgs,
   391  	}
   392  
   393  	if err := c.Create(options); err != nil {
   394  		return nil, fmt.Errorf("unable to create container: %v", err), noCleanup
   395  	}
   396  
   397  	if err := d.setCommonContainerConfig(ctx, c, commonConfig); err != nil {
   398  		return nil, err, c.Destroy
   399  	}
   400  
   401  	// Start the container
   402  	if err := c.Start(); err != nil {
   403  		return nil, fmt.Errorf("unable to start container: %v", err), c.Destroy
   404  	}
   405  
   406  	stopAndDestroyCleanup := func() error {
   407  		if err := c.Stop(); err != nil {
   408  			return err
   409  		}
   410  		return c.Destroy()
   411  	}
   412  
   413  	if err := setLimitsOnContainer(c, task); err != nil {
   414  		return nil, err, stopAndDestroyCleanup
   415  	}
   416  
   417  	h := lxcDriverHandle{
   418  		container:      c,
   419  		initPid:        c.InitPid(),
   420  		lxcPath:        d.lxcPath,
   421  		logger:         d.logger,
   422  		killTimeout:    GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout),
   423  		maxKillTimeout: d.DriverContext.config.MaxKillTimeout,
   424  		totalCpuStats:  stats.NewCpuStats(),
   425  		userCpuStats:   stats.NewCpuStats(),
   426  		systemCpuStats: stats.NewCpuStats(),
   427  		waitCh:         make(chan *dstructs.WaitResult, 1),
   428  		doneCh:         make(chan bool, 1),
   429  	}
   430  
   431  	go h.run()
   432  
   433  	return &StartResponse{Handle: &h}, nil, noCleanup
   434  }
   435  
   436  func (d *LxcDriver) executeContainer(ctx *ExecContext, c *lxc.Container, task *structs.Task, commonConfig *LxcCommonDriverConfig) (*StartResponse, error, func() error) {
   437  	noCleanup := func() error { return nil }
   438  	var executeConfig LxcExecuteDriverConfig
   439  	if err := mapstructure.WeakDecode(task.Config, &executeConfig); err != nil {
   440  		return nil, err, noCleanup
   441  	}
   442  
   443  	containerPath := filepath.Join(d.lxcPath, c.Name())
   444  	containerRootfsPath := filepath.Join(containerPath, "rootfs")
   445  	if err := os.MkdirAll(containerRootfsPath, 0711); err != nil {
   446  		return nil, fmt.Errorf("unable to create container directory at %s", containerPath), noCleanup
   447  	}
   448  
   449  	if executeConfig.BaseRootFsPath[:4] != "lvm:" {
   450  		return nil, fmt.Errorf("only LVM is supported as a base to clone from"), noCleanup
   451  	}
   452  
   453  	baseLvName := executeConfig.BaseRootFsPath[4:]
   454  	lvCreateCmd := exec.Command("lvcreate", "-kn", "-n", c.Name(), "-s", baseLvName)
   455  	if err := lvCreateCmd.Run(); err != nil {
   456  		return nil, fmt.Errorf("could not create thin pool snapshot with cmd '%v': %v: %s", lvCreateCmd.Args, err, err.(*exec.ExitError).Stderr), noCleanup
   457  	}
   458  
   459  	vgName, err := extractVgName(baseLvName)
   460  	if err != nil {
   461  		return nil, fmt.Errorf("Could not parse LVM Volume Group name from '%s'", baseLvName), noCleanup
   462  	}
   463  
   464  	removeLVCleanup := func() error {
   465  		lvRemoveCmd := exec.Command("lvremove", "-f", fmt.Sprintf("%s/%s", vgName, c.Name()))
   466  		if err := lvRemoveCmd.Run(); err != nil {
   467  			return fmt.Errorf("could not remove thin pool snapshot with cmd '%v': %v: %s", lvRemoveCmd.Args, err, err.(*exec.ExitError).Stderr)
   468  		}
   469  		return nil
   470  	}
   471  
   472  	tr := func(s string) string {
   473  		return strings.Replace(s, "-", "--", -1)
   474  	}
   475  
   476  	storageName := fmt.Sprintf("lvm:/dev/mapper/%s-%s", tr(vgName), tr(c.Name()))
   477  
   478  	initialConfigFilePath := filepath.Join(d.lxcPath, c.Name(), "config.initial")
   479  
   480  	if err := exec.Command("cp", executeConfig.BaseConfigPath, initialConfigFilePath).Run(); err != nil {
   481  		return nil, fmt.Errorf("could not copy initial container config from '%s' to '%s': %v",
   482  			executeConfig.BaseConfigPath, initialConfigFilePath, err), removeLVCleanup
   483  	}
   484  
   485  	finalConfigFilePath := filepath.Join(d.lxcPath, c.Name(), "config")
   486  	d.logger.Printf("[INFO] %s initial config path is %s, final config path is %s", c.Name(), initialConfigFilePath, finalConfigFilePath)
   487  
   488  	if err := c.LoadConfigFile(initialConfigFilePath); err != nil {
   489  		return nil, fmt.Errorf("unable to read config file for container: %v", err), removeLVCleanup
   490  	}
   491  
   492  	if err := d.setCommonContainerConfig(ctx, c, commonConfig); err != nil {
   493  		return nil, err, removeLVCleanup
   494  	}
   495  
   496  	if err := c.SetConfigItem("lxc.rootfs.path", storageName); err != nil {
   497  		return nil, fmt.Errorf("unable to set lxc.rootfs.path to '%s': %v", storageName, err), removeLVCleanup
   498  	}
   499  
   500  	// Replace any env vars in Cmd with value from Nomad env:
   501  	parsedArgs := ctx.TaskEnv.ParseAndReplace(executeConfig.CmdArgs)
   502  	d.logger.Printf("[INFO] env vars substituted in command \"%#v\" - new command is \"%#v\"", executeConfig.CmdArgs, parsedArgs)
   503  
   504  	if err := c.SetConfigItem("lxc.execute.cmd", strings.Join(parsedArgs, " ")); err != nil {
   505  		return nil, fmt.Errorf("unable to set final parsed command to \"%s\"", parsedArgs), removeLVCleanup
   506  	}
   507  
   508  	// if networkmode is host, clear the start-host and stop hooks (which we assume are just being used for CNI setup)
   509  	// we assume if the network mode is not host, it has been configured elsewhere (yes, including 'bridge')
   510  	if executeConfig.NetworkMode == "host" {
   511  		keysToClear := []string{"lxc.hook.stop", "lxc.hook.start-host", "lxc.net.0.type", "lxc.net.0.flags", "lxc.net.0.ipv4.address"}
   512  		d.logger.Printf("[INFO] network_mode=host, so clearing all of %v and setting lxc.net.0.type=none.", keysToClear)
   513  
   514  		for _, item := range keysToClear {
   515  			if err := c.ClearConfigItem(item); err != nil {
   516  				return nil, fmt.Errorf("Unable to clear hook config '%s': %v", item, err), removeLVCleanup
   517  			}
   518  		}
   519  
   520  		if err := c.SetConfigItem("lxc.net.0.type", "none"); err != nil {
   521  			return nil, fmt.Errorf("Unable to set net.1.type=none: %v", err), removeLVCleanup
   522  		}
   523  	}
   524  
   525  	// Write out final config file for debugging and use with lxc-attach:
   526  	// Do not edit config after this.
   527  
   528  	savedConfigFile := filepath.Join(ctx.TaskDir.Dir, fmt.Sprintf("%v-lxc.config", task.Name))
   529  	for _, fp := range []string{finalConfigFilePath, savedConfigFile} {
   530  		if err := c.SaveConfigFile(fp); err != nil {
   531  			return nil, fmt.Errorf("unable to write final config file to \"%s\"", fp), removeLVCleanup
   532  		}
   533  	}
   534  
   535  	if err := c.StartExecute(parsedArgs); err != nil {
   536  		return nil, fmt.Errorf("unable to execute with args %#v: %v", parsedArgs, err), removeLVCleanup
   537  	}
   538  
   539  	stopAndRemoveLVCleanup := func() error {
   540  		removeLVCleanup()
   541  		if err := c.Stop(); err != nil {
   542  			return err
   543  		}
   544  		return nil
   545  	}
   546  
   547  	if err := setLimitsOnContainer(c, task); err != nil {
   548  		return nil, err, stopAndRemoveLVCleanup
   549  	}
   550  
   551  	h := lxcDriverHandle{
   552  		container:      c,
   553  		initPid:        c.InitPid(),
   554  		lxcPath:        d.lxcPath,
   555  		logger:         d.logger,
   556  		killTimeout:    GetKillTimeout(task.KillTimeout, d.DriverContext.config.MaxKillTimeout),
   557  		maxKillTimeout: d.DriverContext.config.MaxKillTimeout,
   558  		totalCpuStats:  stats.NewCpuStats(),
   559  		userCpuStats:   stats.NewCpuStats(),
   560  		systemCpuStats: stats.NewCpuStats(),
   561  		waitCh:         make(chan *dstructs.WaitResult, 1),
   562  		doneCh:         make(chan bool, 1),
   563  	}
   564  
   565  	go h.run()
   566  
   567  	net := cstructs.DriverNetwork{}
   568  	ipv4Addrs, err := c.IPv4Addresses()
   569  	if err != nil {
   570  		d.logger.Printf("[ERROR] error getting IPv4Addresses for container %s: %v", c.Name(), err)
   571  	} else if len(ipv4Addrs) == 0 {
   572  		d.logger.Printf("[INFO] No ipv4 address found for container %s", c.Name())
   573  	} else {
   574  		d.logger.Printf("[INFO] Found ipv4 address %#v for container %s", ipv4Addrs[0], c.Name())
   575  		net = cstructs.DriverNetwork{
   576  			IP:            ipv4Addrs[0],
   577  			AutoAdvertise: true,
   578  		}
   579  	}
   580  
   581  	resp := &StartResponse{
   582  		Handle:  &h,
   583  		Network: &net,
   584  	}
   585  	return resp, nil, noCleanup
   586  }
   587  
   588  func extractVgName(baseLvName string) (string, error) {
   589  	vgName := ""
   590  	devMapperRE := regexp.MustCompile("/dev/mapper/([a-zA-Z0-9_+.-]*[^-])-{1}[^-]")
   591  	if !strings.HasPrefix(baseLvName, "/") {
   592  		// vgname/lvname
   593  		c := strings.Split(baseLvName, "/")
   594  		if len(c) != 2 {
   595  			return "", fmt.Errorf("unexpected number of components in baseLvName '%s': %d", baseLvName, len(c))
   596  		}
   597  		vgName = c[0]
   598  	} else {
   599  		// /dev/mapper/vg--name-lv--name
   600  		matches := devMapperRE.FindAllStringSubmatch(baseLvName, 1)
   601  		if matches != nil {
   602  			vgName = matches[0][1]
   603  		} else {
   604  			if strings.HasPrefix(baseLvName, "/dev/") {
   605  				// /dev/vg/lv
   606  				vgName = baseLvName[len("/dev/"):strings.LastIndex(baseLvName, "/")]
   607  			}
   608  		}
   609  	}
   610  	if len(vgName) == 0 {
   611  		return "", fmt.Errorf("could not parse volume group name from '%v':, baseLvName")
   612  	}
   613  	return vgName, nil
   614  }
   615  
   616  func (d *LxcDriver) setCommonContainerConfig(ctx *ExecContext, c *lxc.Container, commonConfig *LxcCommonDriverConfig) error {
   617  
   618  	if err := c.SetConfigItem("lxc.uts.name", c.Name()); err != nil {
   619  		return fmt.Errorf("unable to set lxc.uts.name to '%s': %v", c.Name(), err)
   620  	}
   621  
   622  	// Set the network type to none if not previously set
   623  	netZeroType := c.ConfigItem("lxc.net.0.type")
   624  	if len(netZeroType) == 0 || netZeroType[0] == "" {
   625  		if err := c.SetConfigItem("lxc.net.0.type", "none"); err != nil {
   626  			return fmt.Errorf("error setting network type configuration: %v", err)
   627  		}
   628  	} else {
   629  
   630  		d.logger.Printf("[INFO] driver.lxc: lxc.net.0.type set as %#v, not overriding.", netZeroType)
   631  	}
   632  
   633  	// Bind mount the shared alloc dir and task local dir in the container
   634  	mounts := []string{
   635  		fmt.Sprintf("%s local none rw,bind,create=dir", ctx.TaskDir.LocalDir),
   636  		fmt.Sprintf("%s alloc none rw,bind,create=dir", ctx.TaskDir.SharedAllocDir),
   637  		fmt.Sprintf("%s secrets none rw,bind,create=dir", ctx.TaskDir.SecretsDir),
   638  	}
   639  	for _, mnt := range mounts {
   640  		if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil {
   641  			return fmt.Errorf("error setting bind mount %q error: %v", mnt, err)
   642  		}
   643  	}
   644  
   645  	volumesEnabled := d.config.ReadBoolDefault(lxcVolumesConfigOption, lxcVolumesConfigDefault)
   646  
   647  	for _, volSpec := range commonConfig.Volumes {
   648  		mode, modePresent := volSpec["mode"]
   649  
   650  		if !modePresent {
   651  			mode = "rw"
   652  		}
   653  
   654  		srcPath := volSpec["source"]
   655  		if filepath.IsAbs(srcPath) {
   656  			if !volumesEnabled {
   657  				return fmt.Errorf("absolute bind-mount volume in config but '%v' is false", lxcVolumesConfigOption)
   658  			}
   659  		} else {
   660  			// Relative source paths are treated as relative to alloc dir
   661  			srcPath = filepath.Join(ctx.TaskDir.Dir, srcPath)
   662  		}
   663  
   664  		createType := "dir"
   665  		srcFileInfo, err := os.Stat(srcPath)
   666  		if err != nil {
   667  			return fmt.Errorf("couldn't Stat src='%s' in mount directive: %v", srcPath, err)
   668  		}
   669  
   670  		if srcFileInfo.Mode().IsRegular() {
   671  			createType = "file"
   672  		}
   673  
   674  		mounts = append(mounts, fmt.Sprintf("%s %s none %s,bind,create=%s", srcPath, volSpec["dest"], mode, createType))
   675  	}
   676  
   677  	for _, mnt := range mounts {
   678  		if err := c.SetConfigItem("lxc.mount.entry", mnt); err != nil {
   679  			return fmt.Errorf("error setting bind mount %q error: %v", mnt, err)
   680  		}
   681  	}
   682  
   683  	for _, envVar := range ctx.TaskEnv.List() {
   684  		if err := c.SetConfigItem("lxc.environment", envVar); err != nil {
   685  			return fmt.Errorf("error setting environment variable '%s': %v", envVar, err)
   686  		}
   687  	}
   688  
   689  	return nil
   690  }
   691  
   692  func setLimitsOnContainer(c *lxc.Container, task *structs.Task) error {
   693  	// Set the resource limits
   694  	if err := c.SetMemoryLimit(lxc.ByteSize(task.Resources.MemoryMB) * lxc.MB); err != nil {
   695  		return fmt.Errorf("unable to set memory limits: %v", err)
   696  	}
   697  	if err := c.SetCgroupItem("cpu.shares", strconv.Itoa(task.Resources.CPU)); err != nil {
   698  		return fmt.Errorf("unable to set cpu shares: %v", err)
   699  	}
   700  	return nil
   701  }
   702  
   703  func (d *LxcDriver) Cleanup(ctx *ExecContext, resources *CreatedResources) error {
   704  
   705  	return nil
   706  }
   707  
   708  // Open creates the driver to monitor an existing LXC container
   709  func (d *LxcDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
   710  	pid := &lxcPID{}
   711  	if err := json.Unmarshal([]byte(handleID), pid); err != nil {
   712  		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
   713  	}
   714  
   715  	var container *lxc.Container
   716  	containers := lxc.Containers(pid.LxcPath)
   717  	for _, c := range containers {
   718  		if c.Name() == pid.ContainerName {
   719  			container = c
   720  			break
   721  		}
   722  	}
   723  
   724  	if container == nil {
   725  		return nil, fmt.Errorf("container %v not found", pid.ContainerName)
   726  	}
   727  
   728  	handle := lxcDriverHandle{
   729  		container:      container,
   730  		initPid:        container.InitPid(),
   731  		lxcPath:        pid.LxcPath,
   732  		logger:         d.logger,
   733  		killTimeout:    pid.KillTimeout,
   734  		maxKillTimeout: d.DriverContext.config.MaxKillTimeout,
   735  		totalCpuStats:  stats.NewCpuStats(),
   736  		userCpuStats:   stats.NewCpuStats(),
   737  		systemCpuStats: stats.NewCpuStats(),
   738  		waitCh:         make(chan *dstructs.WaitResult, 1),
   739  		doneCh:         make(chan bool, 1),
   740  	}
   741  	go handle.run()
   742  
   743  	return &handle, nil
   744  }
   745  
   746  // lxcDriverHandle allows controlling the lifecycle of an lxc container
   747  type lxcDriverHandle struct {
   748  	container *lxc.Container
   749  	initPid   int
   750  	lxcPath   string
   751  
   752  	logger *log.Logger
   753  
   754  	killTimeout    time.Duration
   755  	maxKillTimeout time.Duration
   756  
   757  	totalCpuStats  *stats.CpuStats
   758  	userCpuStats   *stats.CpuStats
   759  	systemCpuStats *stats.CpuStats
   760  
   761  	waitCh chan *dstructs.WaitResult
   762  	doneCh chan bool
   763  }
   764  
   765  type lxcPID struct {
   766  	ContainerName string
   767  	InitPid       int
   768  	LxcPath       string
   769  	KillTimeout   time.Duration
   770  }
   771  
   772  func (h *lxcDriverHandle) ID() string {
   773  	pid := lxcPID{
   774  		ContainerName: h.container.Name(),
   775  		InitPid:       h.initPid,
   776  		LxcPath:       h.lxcPath,
   777  		KillTimeout:   h.killTimeout,
   778  	}
   779  	data, err := json.Marshal(pid)
   780  	if err != nil {
   781  		h.logger.Printf("[ERR] driver.lxc: failed to marshal lxc PID to JSON: %v", err)
   782  	}
   783  	return string(data)
   784  }
   785  
   786  func (h *lxcDriverHandle) WaitCh() chan *dstructs.WaitResult {
   787  	return h.waitCh
   788  }
   789  
   790  func (h *lxcDriverHandle) Update(task *structs.Task) error {
   791  	h.killTimeout = GetKillTimeout(task.KillTimeout, h.killTimeout)
   792  	return nil
   793  }
   794  
   795  func (h *lxcDriverHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   796  	return nil, 0, fmt.Errorf("lxc driver cannot execute commands")
   797  }
   798  
   799  func (h *lxcDriverHandle) Kill() error {
   800  	if h.container.Running() {
   801  		name := h.container.Name()
   802  		if err := h.container.Stop(); err != nil {
   803  			h.logger.Printf("[WARN] driver.lxc: error stopping container %q: %v", name, err)
   804  			return fmt.Errorf("could not stop container: %v", err)
   805  		}
   806  		h.logger.Printf("[INFO] driver.lxc: stopped running container %q", name)
   807  	}
   808  
   809  	close(h.doneCh)
   810  	return nil
   811  }
   812  
   813  func (h *lxcDriverHandle) Signal(s os.Signal) error {
   814  	return fmt.Errorf("LXC does not support signals")
   815  }
   816  
   817  func (h *lxcDriverHandle) Stats() (*cstructs.TaskResourceUsage, error) {
   818  	cpuStats, err := h.container.CPUStats()
   819  	if err != nil {
   820  		return nil, nil
   821  	}
   822  	total, err := h.container.CPUTime()
   823  	if err != nil {
   824  		return nil, nil
   825  	}
   826  
   827  	t := time.Now()
   828  
   829  	// Get the cpu stats
   830  	system := cpuStats["system"]
   831  	user := cpuStats["user"]
   832  	cs := &cstructs.CpuStats{
   833  		SystemMode: h.systemCpuStats.Percent(float64(system)),
   834  		UserMode:   h.systemCpuStats.Percent(float64(user)),
   835  		Percent:    h.totalCpuStats.Percent(float64(total)),
   836  		TotalTicks: float64(user + system),
   837  		Measured:   LXCMeasuredCpuStats,
   838  	}
   839  
   840  	// Get the Memory Stats
   841  	memData := map[string]uint64{
   842  		"rss":   0,
   843  		"cache": 0,
   844  		"swap":  0,
   845  	}
   846  	rawMemStats := h.container.CgroupItem("memory.stat")
   847  	for _, rawMemStat := range rawMemStats {
   848  		key, val, err := keysToVal(rawMemStat)
   849  		if err != nil {
   850  			h.logger.Printf("[ERR] driver.lxc: error getting stat for line %q", rawMemStat)
   851  			continue
   852  		}
   853  		if _, ok := memData[key]; ok {
   854  			memData[key] = val
   855  
   856  		}
   857  	}
   858  	ms := &cstructs.MemoryStats{
   859  		RSS:      memData["rss"],
   860  		Cache:    memData["cache"],
   861  		Swap:     memData["swap"],
   862  		Measured: LXCMeasuredMemStats,
   863  	}
   864  
   865  	mu := h.container.CgroupItem("memory.max_usage_in_bytes")
   866  	for _, rawMemMaxUsage := range mu {
   867  		val, err := strconv.ParseUint(rawMemMaxUsage, 10, 64)
   868  		if err != nil {
   869  			h.logger.Printf("[ERR] driver.lxc: unable to get max memory usage: %v", err)
   870  			continue
   871  		}
   872  		ms.MaxUsage = val
   873  	}
   874  	ku := h.container.CgroupItem("memory.kmem.usage_in_bytes")
   875  	for _, rawKernelUsage := range ku {
   876  		val, err := strconv.ParseUint(rawKernelUsage, 10, 64)
   877  		if err != nil {
   878  			h.logger.Printf("[ERR] driver.lxc: unable to get kernel memory usage: %v", err)
   879  			continue
   880  		}
   881  		ms.KernelUsage = val
   882  	}
   883  
   884  	mku := h.container.CgroupItem("memory.kmem.max_usage_in_bytes")
   885  	for _, rawMaxKernelUsage := range mku {
   886  		val, err := strconv.ParseUint(rawMaxKernelUsage, 10, 64)
   887  		if err != nil {
   888  			h.logger.Printf("[ERR] driver.lxc: unable to get max kernel memory usage: %v", err)
   889  			continue
   890  		}
   891  		ms.KernelMaxUsage = val
   892  	}
   893  
   894  	taskResUsage := cstructs.TaskResourceUsage{
   895  		ResourceUsage: &cstructs.ResourceUsage{
   896  			CpuStats:    cs,
   897  			MemoryStats: ms,
   898  		},
   899  		Timestamp: t.UTC().UnixNano(),
   900  	}
   901  
   902  	return &taskResUsage, nil
   903  }
   904  
   905  func (h *lxcDriverHandle) run() {
   906  	defer close(h.waitCh)
   907  	timer := time.NewTimer(containerMonitorIntv)
   908  	for {
   909  		select {
   910  		case <-timer.C:
   911  			process, err := os.FindProcess(h.initPid)
   912  			if err != nil {
   913  				h.waitCh <- &dstructs.WaitResult{Err: err}
   914  				goto DESTROY
   915  			}
   916  			if err := process.Signal(syscall.Signal(0)); err != nil {
   917  				h.waitCh <- &dstructs.WaitResult{}
   918  				goto DESTROY
   919  			}
   920  			timer.Reset(containerMonitorIntv)
   921  		case <-h.doneCh:
   922  			h.waitCh <- &dstructs.WaitResult{}
   923  			goto DESTROY
   924  		}
   925  	}
   926  
   927  DESTROY:
   928  	name := h.container.Name()
   929  	if err := h.container.Destroy(); err != nil {
   930  		h.logger.Printf("[ERR] driver.lxc: error destroying container %q: %v.", name, err)
   931  	} else {
   932  		if err := h.container.Release(); err != nil {
   933  			h.logger.Printf("[ERR] driver.lxc: failed to release container %q after successful destroy: %v", name, err)
   934  		} else {
   935  			h.logger.Printf("[INFO] driver.lxc: successfully destroyed and released container %q.", name)
   936  		}
   937  	}
   938  }
   939  
   940  func keysToVal(line string) (string, uint64, error) {
   941  	tokens := strings.Split(line, " ")
   942  	if len(tokens) != 2 {
   943  		return "", 0, fmt.Errorf("line isn't a k/v pair")
   944  	}
   945  	key := tokens[0]
   946  	val, err := strconv.ParseUint(tokens[1], 10, 64)
   947  	return key, val, err
   948  }