github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/container/lxc/lxc.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxc
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"text/template"
    19  	"time"
    20  
    21  	"github.com/juju/errors"
    22  	"github.com/juju/loggo"
    23  	"github.com/juju/names"
    24  	"github.com/juju/utils"
    25  	"github.com/juju/utils/symlink"
    26  	"launchpad.net/golxc"
    27  
    28  	"github.com/juju/juju/agent"
    29  	"github.com/juju/juju/container"
    30  	"github.com/juju/juju/environs/cloudinit"
    31  	"github.com/juju/juju/instance"
    32  	"github.com/juju/juju/juju/arch"
    33  	"github.com/juju/juju/version"
    34  	"github.com/juju/utils/keyvalues"
    35  )
    36  
    37  var logger = loggo.GetLogger("juju.container.lxc")
    38  
    39  var (
    40  	defaultTemplate       = "ubuntu-cloud"
    41  	LxcContainerDir       = golxc.GetDefaultLXCContainerDir()
    42  	LxcRestartDir         = "/etc/lxc/auto"
    43  	LxcObjectFactory      = golxc.Factory()
    44  	initProcessCgroupFile = "/proc/1/cgroup"
    45  	runtimeGOOS           = runtime.GOOS
    46  )
    47  
    48  const (
    49  	// DefaultLxcBridge is the package created container bridge
    50  	DefaultLxcBridge = "lxcbr0"
    51  	// Btrfs is special as we treat it differently for create and clone.
    52  	Btrfs = "btrfs"
    53  )
    54  
    55  // DefaultNetworkConfig returns a valid NetworkConfig to use the
    56  // defaultLxcBridge that is created by the lxc package.
    57  func DefaultNetworkConfig() *container.NetworkConfig {
    58  	return container.BridgeNetworkConfig(DefaultLxcBridge, nil)
    59  }
    60  
    61  // FsCommandOutput calls cmd.Output, this is used as an overloading point so
    62  // we can test what *would* be run without actually executing another program
    63  var FsCommandOutput = (*exec.Cmd).CombinedOutput
    64  
    65  func containerDirFilesystem() (string, error) {
    66  	cmd := exec.Command("df", "--output=fstype", LxcContainerDir)
    67  	out, err := FsCommandOutput(cmd)
    68  	if err != nil {
    69  		return "", errors.Trace(err)
    70  	}
    71  	// The filesystem is the second line.
    72  	lines := strings.Split(string(out), "\n")
    73  	if len(lines) < 2 {
    74  		logger.Errorf("unexpected output: %q", out)
    75  		return "", errors.Errorf("could not determine filesystem type")
    76  	}
    77  	return lines[1], nil
    78  }
    79  
    80  // IsLXCSupported returns a boolean value indicating whether or not
    81  // we can run LXC containers
    82  func IsLXCSupported() (bool, error) {
    83  	if runtimeGOOS != "linux" {
    84  		return false, nil
    85  	}
    86  
    87  	file, err := os.Open(initProcessCgroupFile)
    88  	if err != nil {
    89  		return false, errors.Trace(err)
    90  	}
    91  	defer file.Close()
    92  
    93  	scanner := bufio.NewScanner(file)
    94  	for scanner.Scan() {
    95  		line := scanner.Text()
    96  		fields := strings.Split(line, ":")
    97  		if len(fields) != 3 {
    98  			return false, errors.Errorf("Malformed cgroup file")
    99  		}
   100  		if fields[2] != "/" {
   101  			// When running in a container the anchor point will be something
   102  			// other then "/". Return false here as we do not support nested LXC
   103  			// containers
   104  			return false, nil
   105  		}
   106  	}
   107  
   108  	if err := scanner.Err(); err != nil {
   109  		return false, errors.Errorf("Failed to read cgroup file")
   110  	}
   111  	return true, nil
   112  }
   113  
   114  type containerManager struct {
   115  	name              string
   116  	logdir            string
   117  	createWithClone   bool
   118  	useAUFS           bool
   119  	backingFilesystem string
   120  	imageURLGetter    container.ImageURLGetter
   121  }
   122  
   123  // containerManager implements container.Manager.
   124  var _ container.Manager = (*containerManager)(nil)
   125  
   126  // NewContainerManager returns a manager object that can start and
   127  // stop lxc containers. The containers that are created are namespaced
   128  // by the name parameter inside the given ManagerConfig.
   129  func NewContainerManager(conf container.ManagerConfig, imageURLGetter container.ImageURLGetter) (container.Manager, error) {
   130  	name := conf.PopValue(container.ConfigName)
   131  	if name == "" {
   132  		return nil, errors.Errorf("name is required")
   133  	}
   134  	logDir := conf.PopValue(container.ConfigLogDir)
   135  	if logDir == "" {
   136  		logDir = agent.DefaultLogDir
   137  	}
   138  	var useClone bool
   139  	useCloneVal := conf.PopValue("use-clone")
   140  	if useCloneVal != "" {
   141  		// Explicitly ignore the error result from ParseBool.
   142  		// If it fails to parse, the value is false, and this suits
   143  		// us fine.
   144  		useClone, _ = strconv.ParseBool(useCloneVal)
   145  	} else {
   146  		// If no lxc-clone value is explicitly set in config, then
   147  		// see if the Ubuntu series we are running on supports it
   148  		// and if it does, we will use clone.
   149  		useClone = preferFastLXC(releaseVersion())
   150  	}
   151  	useAUFS, _ := strconv.ParseBool(conf.PopValue("use-aufs"))
   152  	backingFS, err := containerDirFilesystem()
   153  	if err != nil {
   154  		// Especially in tests, or a bot, the lxc dir may not exist
   155  		// causing the test to fail. Since we only really care if the
   156  		// backingFS is 'btrfs' and we treat the rest the same, just
   157  		// call it 'unknown'.
   158  		backingFS = "unknown"
   159  	}
   160  	logger.Tracef("backing filesystem: %q", backingFS)
   161  	conf.WarnAboutUnused()
   162  	return &containerManager{
   163  		name:              name,
   164  		logdir:            logDir,
   165  		createWithClone:   useClone,
   166  		useAUFS:           useAUFS,
   167  		backingFilesystem: backingFS,
   168  		imageURLGetter:    imageURLGetter,
   169  	}, nil
   170  }
   171  
   172  // releaseVersion is a function that returns a string representing the
   173  // DISTRIB_RELEASE from the /etc/lsb-release file.
   174  var releaseVersion = version.ReleaseVersion
   175  
   176  // preferFastLXC returns true if the host is capable of
   177  // LXC cloning from a template.
   178  func preferFastLXC(release string) bool {
   179  	if release == "" {
   180  		return false
   181  	}
   182  	value, err := strconv.ParseFloat(release, 64)
   183  	if err != nil {
   184  		return false
   185  	}
   186  	return value >= 14.04
   187  }
   188  
   189  // CreateContainer creates or clones an LXC container.
   190  func (manager *containerManager) CreateContainer(
   191  	machineConfig *cloudinit.MachineConfig,
   192  	series string,
   193  	networkConfig *container.NetworkConfig,
   194  ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) {
   195  	// Check our preconditions
   196  	if manager == nil {
   197  		panic("manager is nil")
   198  	} else if series == "" {
   199  		panic("series not set")
   200  	} else if networkConfig == nil {
   201  		panic("networkConfig is nil")
   202  	}
   203  
   204  	// Log how long the start took
   205  	defer func(start time.Time) {
   206  		if err == nil {
   207  			logger.Tracef("container %q started: %v", inst.Id(), time.Now().Sub(start))
   208  		}
   209  	}(time.Now())
   210  
   211  	name := names.NewMachineTag(machineConfig.MachineId).String()
   212  	if manager.name != "" {
   213  		name = fmt.Sprintf("%s-%s", manager.name, name)
   214  	}
   215  
   216  	// Create the cloud-init.
   217  	directory, err := container.NewDirectory(name)
   218  	if err != nil {
   219  		return nil, nil, errors.Annotate(err, "failed to create a directory for the container")
   220  	}
   221  	logger.Tracef("write cloud-init")
   222  	userDataFilename, err := container.WriteUserData(machineConfig, networkConfig, directory)
   223  	if err != nil {
   224  		return nil, nil, errors.Annotate(err, "failed to write user data")
   225  	}
   226  
   227  	var lxcContainer golxc.Container
   228  	if manager.createWithClone {
   229  		templateContainer, err := EnsureCloneTemplate(
   230  			manager.backingFilesystem,
   231  			series,
   232  			networkConfig,
   233  			machineConfig.AuthorizedKeys,
   234  			machineConfig.AptProxySettings,
   235  			machineConfig.AptMirror,
   236  			machineConfig.EnableOSRefreshUpdate,
   237  			machineConfig.EnableOSUpgrade,
   238  			manager.imageURLGetter,
   239  		)
   240  		if err != nil {
   241  			return nil, nil, errors.Annotate(err, "failed to retrieve the template to clone")
   242  		}
   243  		templateParams := []string{
   244  			"--debug",                      // Debug errors in the cloud image
   245  			"--userdata", userDataFilename, // Our groovey cloud-init
   246  			"--hostid", name, // Use the container name as the hostid
   247  		}
   248  		var extraCloneArgs []string
   249  		if manager.backingFilesystem == Btrfs || manager.useAUFS {
   250  			extraCloneArgs = append(extraCloneArgs, "--snapshot")
   251  		}
   252  		if manager.backingFilesystem != Btrfs && manager.useAUFS {
   253  			extraCloneArgs = append(extraCloneArgs, "--backingstore", "aufs")
   254  		}
   255  
   256  		lock, err := AcquireTemplateLock(templateContainer.Name(), "clone")
   257  		if err != nil {
   258  			return nil, nil, errors.Annotate(err, "failed to acquire lock on template")
   259  		}
   260  		defer lock.Unlock()
   261  
   262  		// Ensure the run-time effective config of the template
   263  		// container has correctly ordered network settings, otherwise
   264  		// Clone() below will fail. This is needed in case we haven't
   265  		// created a new template now but are reusing an existing one.
   266  		// See LP bug #1414016.
   267  		configPath := containerConfigFilename(templateContainer.Name())
   268  		if _, err := reorderNetworkConfig(configPath); err != nil {
   269  			return nil, nil, errors.Annotate(err, "failed to reorder network settings")
   270  		}
   271  
   272  		lxcContainer, err = templateContainer.Clone(name, extraCloneArgs, templateParams)
   273  		if err != nil {
   274  			return nil, nil, errors.Annotate(err, "lxc container cloning failed")
   275  		}
   276  	} else {
   277  		// Note here that the lxcObjectFacotry only returns a valid container
   278  		// object, and doesn't actually construct the underlying lxc container on
   279  		// disk.
   280  		lxcContainer = LxcObjectFactory.New(name)
   281  		templateParams := []string{
   282  			"--debug",                      // Debug errors in the cloud image
   283  			"--userdata", userDataFilename, // Our groovey cloud-init
   284  			"--hostid", name, // Use the container name as the hostid
   285  			"-r", series,
   286  		}
   287  		var caCert []byte
   288  		if manager.imageURLGetter != nil {
   289  			arch := arch.HostArch()
   290  			imageURL, err := manager.imageURLGetter.ImageURL(instance.LXC, series, arch)
   291  			if err != nil {
   292  				return nil, nil, errors.Annotatef(err, "cannot determine cached image URL")
   293  			}
   294  			templateParams = append(templateParams, "-T", imageURL)
   295  			caCert = manager.imageURLGetter.CACert()
   296  		}
   297  		err = createContainer(
   298  			lxcContainer,
   299  			directory,
   300  			networkConfig,
   301  			nil,
   302  			templateParams,
   303  			caCert,
   304  		)
   305  		if err != nil {
   306  			return nil, nil, errors.Trace(err)
   307  		}
   308  	}
   309  
   310  	if err := autostartContainer(name); err != nil {
   311  		return nil, nil, errors.Annotate(err, "failed to configure the container for autostart")
   312  	}
   313  	if err := mountHostLogDir(name, manager.logdir); err != nil {
   314  		return nil, nil, errors.Annotate(err, "failed to mount the directory to log to")
   315  	}
   316  	// Update the network settings inside the run-time config of the
   317  	// container (e.g. /var/lib/lxc/<name>/config) before starting it.
   318  	netConfig := generateNetworkConfig(networkConfig)
   319  	if err := updateContainerConfig(name, netConfig); err != nil {
   320  		return nil, nil, errors.Annotate(err, "failed to update network config")
   321  	}
   322  	configPath := containerConfigFilename(name)
   323  	logger.Tracef("updated network config in %q for container %q", configPath, name)
   324  	// Ensure the run-time config of the new container has correctly
   325  	// ordered network settings, otherwise Start() below will fail. We
   326  	// need this now because after lxc-create or lxc-clone the initial
   327  	// lxc.conf generated inside createContainer gets merged with
   328  	// other settings (e.g. system-wide overrides, changes made by
   329  	// hooks, etc.) and the result can still be incorrectly ordered.
   330  	// See LP bug #1414016.
   331  	if _, err := reorderNetworkConfig(configPath); err != nil {
   332  		return nil, nil, errors.Annotate(err, "failed to reorder network settings")
   333  	}
   334  
   335  	// Start the lxc container with the appropriate settings for grabbing the
   336  	// console output and a log file.
   337  	consoleFile := filepath.Join(directory, "console.log")
   338  	lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug)
   339  	logger.Tracef("start the container")
   340  
   341  	// We explicitly don't pass through the config file to the container.Start
   342  	// method as we have passed it through at container creation time.  This
   343  	// is necessary to get the appropriate rootfs reference without explicitly
   344  	// setting it ourselves.
   345  	if err = lxcContainer.Start("", consoleFile); err != nil {
   346  		logger.Warningf("container failed to start %v", err)
   347  		// if the container fails to start we should try to destroy it
   348  		// check if the container has been constructed
   349  		if lxcContainer.IsConstructed() {
   350  			// if so, then we need to destroy the leftover container
   351  			if derr := lxcContainer.Destroy(); derr != nil {
   352  				// if an error is reported there is probably a leftover
   353  				// container that the user should clean up manually
   354  				logger.Errorf("container failed to start and failed to destroy: %v", derr)
   355  				return nil, nil, errors.Annotate(err, "container failed to start and failed to destroy: manual cleanup of containers needed")
   356  			}
   357  			logger.Warningf("container failed to start and was destroyed - safe to retry")
   358  			return nil, nil, errors.Wrap(err, instance.NewRetryableCreationError("container failed to start and was destroyed: "+lxcContainer.Name()))
   359  		}
   360  		logger.Warningf("container failed to start: %v", err)
   361  		return nil, nil, errors.Annotate(err, "container failed to start")
   362  	}
   363  
   364  	hardware := &instance.HardwareCharacteristics{
   365  		Arch: &version.Current.Arch,
   366  	}
   367  
   368  	return &lxcInstance{lxcContainer, name}, hardware, nil
   369  }
   370  
   371  func createContainer(
   372  	lxcContainer golxc.Container,
   373  	directory string,
   374  	networkConfig *container.NetworkConfig,
   375  	extraCreateArgs, templateParams []string,
   376  	caCert []byte,
   377  ) error {
   378  	// Generate initial lxc.conf with networking settings.
   379  	netConfig := generateNetworkConfig(networkConfig)
   380  	configPath := filepath.Join(directory, "lxc.conf")
   381  	if err := ioutil.WriteFile(configPath, []byte(netConfig), 0644); err != nil {
   382  		return errors.Annotatef(err, "failed to write container config %q", configPath)
   383  	}
   384  	logger.Tracef("wrote initial config %q for container %q", configPath, lxcContainer.Name())
   385  
   386  	var err error
   387  	var execEnv []string = nil
   388  	var closer func()
   389  	if caCert != nil {
   390  		execEnv, closer, err = wgetEnvironment(caCert)
   391  		if err != nil {
   392  			return errors.Annotatef(err, "failed to get environment for wget execution")
   393  		}
   394  		defer closer()
   395  	}
   396  
   397  	// Create the container.
   398  	logger.Debugf("creating lxc container %q", lxcContainer.Name())
   399  	logger.Debugf("lxc-create template params: %v", templateParams)
   400  	if err := lxcContainer.Create(configPath, defaultTemplate, extraCreateArgs, templateParams, execEnv); err != nil {
   401  		return errors.Annotatef(err, "lxc container creation failed")
   402  	}
   403  	return nil
   404  }
   405  
   406  // wgetEnvironment creates a script to call wget with the
   407  // --no-check-certificate argument, patching the PATH to ensure
   408  // the script is invoked by the lxc template bash script.
   409  // It returns a slice of env variables to pass to the lxc create command.
   410  func wgetEnvironment(caCert []byte) (execEnv []string, closer func(), _ error) {
   411  	env := os.Environ()
   412  	kv, err := keyvalues.Parse(env, true)
   413  	if err != nil {
   414  		return nil, nil, errors.Trace(err)
   415  	}
   416  	// Create a wget bash script in a temporary directory.
   417  	tmpDir, err := ioutil.TempDir("", "wget")
   418  	if err != nil {
   419  		return nil, nil, errors.Trace(err)
   420  	}
   421  	closer = func() {
   422  		os.RemoveAll(tmpDir)
   423  	}
   424  	// Write the ca cert.
   425  	caCertPath := filepath.Join(tmpDir, "ca-cert.pem")
   426  	err = ioutil.WriteFile(caCertPath, caCert, 0755)
   427  	if err != nil {
   428  		defer closer()
   429  		return nil, nil, errors.Trace(err)
   430  	}
   431  
   432  	// Write the wget script.
   433  	wgetTmpl := `#!/bin/bash
   434  /usr/bin/wget --ca-certificate=%s $*
   435  `
   436  	wget := fmt.Sprintf(wgetTmpl, caCertPath)
   437  	err = ioutil.WriteFile(filepath.Join(tmpDir, "wget"), []byte(wget), 0755)
   438  	if err != nil {
   439  		defer closer()
   440  		return nil, nil, errors.Trace(err)
   441  	}
   442  
   443  	// Update the path to point to the script.
   444  	for k, v := range kv {
   445  		if strings.ToUpper(k) == "PATH" {
   446  			v = strings.Join([]string{tmpDir, v}, string(os.PathListSeparator))
   447  		}
   448  		execEnv = append(execEnv, fmt.Sprintf("%s=%s", k, v))
   449  	}
   450  	return execEnv, closer, nil
   451  }
   452  
   453  // parseConfigLine tries to parse a line from an LXC config file.
   454  // Empty lines, comments, and lines not starting with "lxc." are
   455  // ignored. If successful the setting and its value are returned
   456  // stripped of leading/trailing whitespace and line comments (e.g.
   457  // "lxc.rootfs" and "/some/path"), otherwise both results are empty.
   458  func parseConfigLine(line string) (setting, value string) {
   459  	input := strings.TrimSpace(line)
   460  	if len(input) == 0 ||
   461  		strings.HasPrefix(input, "#") ||
   462  		!strings.HasPrefix(input, "lxc.") {
   463  		return "", ""
   464  	}
   465  	parts := strings.SplitN(input, "=", 2)
   466  	if len(parts) != 2 {
   467  		// Still not what we're looking for.
   468  		return "", ""
   469  	}
   470  	setting = strings.TrimSpace(parts[0])
   471  	value = strings.TrimSpace(parts[1])
   472  	if strings.Contains(value, "#") {
   473  		parts = strings.SplitN(value, "#", 2)
   474  		if len(parts) == 2 {
   475  			value = strings.TrimSpace(parts[0])
   476  		}
   477  	}
   478  	return setting, value
   479  }
   480  
   481  // updateContainerConfig selectively replaces, deletes, and/or appends
   482  // lines in the named container's current config file, depending on
   483  // the contents of newConfig. First, newConfig is split into multiple
   484  // lines and parsed, ignoring comments, empty lines and spaces. Then
   485  // the occurrence of a setting in a line of the config file will be
   486  // replaced by values from newConfig. Values in newConfig are only
   487  // used once (in the order provided), so multiple replacements must be
   488  // supplied as multiple input values for the same setting in
   489  // newConfig. If the value of a setting is empty, the setting will be
   490  // removed if found. Settings that are not found and have values will
   491  // be appended (also if more values are given than exist).
   492  //
   493  // For example, with existing config like "lxc.foo = off\nlxc.bar=42\n",
   494  // and newConfig like "lxc.bar=\nlxc.foo = bar\nlxc.foo = baz # xx",
   495  // the updated config file contains "lxc.foo = bar\nlxc.foo = baz\n".
   496  // TestUpdateContainerConfig has this example in code.
   497  func updateContainerConfig(name, newConfig string) error {
   498  	lines := strings.Split(newConfig, "\n")
   499  	if len(lines) == 0 {
   500  		return nil
   501  	}
   502  	// Extract unique set of line prefixes to match later. Also, keep
   503  	// a slice of values to replace for each key, as well as a slice
   504  	// of parsed prefixes to preserve the order when replacing or
   505  	// appending.
   506  	parsedLines := make(map[string][]string)
   507  	var parsedPrefixes []string
   508  	for _, line := range lines {
   509  		prefix, value := parseConfigLine(line)
   510  		if prefix == "" {
   511  			// Ignore comments, empty lines, and unknown prefixes.
   512  			continue
   513  		}
   514  		if values, found := parsedLines[prefix]; !found {
   515  			parsedLines[prefix] = []string{value}
   516  		} else {
   517  			values = append(values, value)
   518  			parsedLines[prefix] = values
   519  		}
   520  		parsedPrefixes = append(parsedPrefixes, prefix)
   521  	}
   522  
   523  	path := containerConfigFilename(name)
   524  	currentConfig, err := ioutil.ReadFile(path)
   525  	if err != nil {
   526  		return errors.Annotatef(err, "cannot open config %q for container %q", path, name)
   527  	}
   528  	input := bytes.NewBuffer(currentConfig)
   529  
   530  	// Read the original config and prepare the output to replace it
   531  	// with.
   532  	var output bytes.Buffer
   533  	scanner := bufio.NewScanner(input)
   534  	for scanner.Scan() {
   535  		line := scanner.Text()
   536  		prefix, _ := parseConfigLine(line)
   537  		values, found := parsedLines[prefix]
   538  		if !found || len(values) == 0 {
   539  			// No need to change, just preserve.
   540  			output.WriteString(line + "\n")
   541  			continue
   542  		}
   543  		// We need to change this line. Pop the first value of the
   544  		// list and set it.
   545  		var newValue string
   546  		newValue, values = values[0], values[1:]
   547  		parsedLines[prefix] = values
   548  
   549  		if newValue == "" {
   550  			logger.Tracef("removing %q from container %q config %q", line, name, path)
   551  			continue
   552  		}
   553  		newLine := prefix + " = " + newValue
   554  		if newLine == line {
   555  			// No need to change and pollute the log, just write it.
   556  			output.WriteString(line + "\n")
   557  			continue
   558  		}
   559  		logger.Tracef(
   560  			"replacing %q with %q in container %q config %q",
   561  			line, newLine, name, path,
   562  		)
   563  		output.WriteString(newLine + "\n")
   564  	}
   565  	if err := scanner.Err(); err != nil {
   566  		return errors.Annotatef(err, "cannot read config for container %q", name)
   567  	}
   568  
   569  	// Now process any prefixes with values still left for appending,
   570  	// in the original order.
   571  	for _, prefix := range parsedPrefixes {
   572  		values := parsedLines[prefix]
   573  		for _, value := range values {
   574  			if value == "" {
   575  				// No need to remove what's not there.
   576  				continue
   577  			}
   578  			newLine := prefix + " = " + value + "\n"
   579  			logger.Tracef("appending %q to container %q config %q", newLine, name, path)
   580  			output.WriteString(newLine)
   581  		}
   582  		// Reset the values, so we only append the once per prefix.
   583  		parsedLines[prefix] = []string{}
   584  	}
   585  
   586  	// Reset the original file and overwrite it atomically.
   587  	if err := utils.AtomicWriteFile(path, output.Bytes(), 0644); err != nil {
   588  		return errors.Annotatef(err, "cannot write new config %q for container %q", path, name)
   589  	}
   590  	return nil
   591  }
   592  
   593  // reorderNetworkConfig reads the contents of the given container
   594  // config file and the modifies the contents, if needed, so that any
   595  // lxc.network.* setting comes after the first lxc.network.type
   596  // setting preserving the order. Every line formatting is preserved in
   597  // the modified config, including whitespace and comments. The
   598  // wasReordered flag will be set if the config was modified.
   599  //
   600  // This ensures the lxc tools won't report parsing errors for network
   601  // settings. See also LP bug #1414016.
   602  func reorderNetworkConfig(configFile string) (wasReordered bool, err error) {
   603  	data, err := ioutil.ReadFile(configFile)
   604  	if err != nil {
   605  		return false, errors.Annotatef(err, "cannot read config %q", configFile)
   606  	}
   607  	if len(data) == 0 {
   608  		// Nothing to do.
   609  		return false, nil
   610  	}
   611  	input := bytes.NewBuffer(data)
   612  	scanner := bufio.NewScanner(input)
   613  	var output bytes.Buffer
   614  	firstNetworkType := ""
   615  	var networkLines []string
   616  	mayNeedReordering := true
   617  	foundFirstType := false
   618  	doneReordering := false
   619  	for scanner.Scan() {
   620  		line := scanner.Text()
   621  		prefix, _ := parseConfigLine(line)
   622  		if mayNeedReordering {
   623  			if strings.HasPrefix(prefix, "lxc.network.type") {
   624  				if len(networkLines) == 0 {
   625  					// All good, no need to change.
   626  					logger.Tracef("correct network settings order in config %q", configFile)
   627  					return false, nil
   628  				}
   629  				// We found the first type.
   630  				firstNetworkType = line
   631  				foundFirstType = true
   632  				logger.Tracef(
   633  					"moving line(s) %q after line %q in config %q",
   634  					strings.Join(networkLines, "\n"), firstNetworkType, configFile,
   635  				)
   636  			} else if strings.HasPrefix(prefix, "lxc.network.") {
   637  				if firstNetworkType != "" {
   638  					// All good, no need to change.
   639  					logger.Tracef("correct network settings order in config %q", configFile)
   640  					return false, nil
   641  				}
   642  				networkLines = append(networkLines, line)
   643  				logger.Tracef("need to move line %q in config %q", line, configFile)
   644  				continue
   645  			}
   646  		}
   647  		output.WriteString(line + "\n")
   648  		if foundFirstType && len(networkLines) > 0 {
   649  			// Now add the skipped networkLines.
   650  			output.WriteString(strings.Join(networkLines, "\n") + "\n")
   651  			doneReordering = true
   652  			mayNeedReordering = false
   653  			firstNetworkType = ""
   654  			networkLines = nil
   655  		}
   656  	}
   657  	if err := scanner.Err(); err != nil {
   658  		return false, errors.Annotatef(err, "cannot read config %q", configFile)
   659  	}
   660  	if !doneReordering {
   661  		if len(networkLines) > 0 {
   662  			logger.Errorf("invalid lxc network settings in config %q", configFile)
   663  			return false, errors.Errorf(
   664  				"cannot have line(s) %q without lxc.network.type in config %q",
   665  				strings.Join(networkLines, "\n"), configFile,
   666  			)
   667  		}
   668  		// No networking settings to reorder.
   669  		return false, nil
   670  	}
   671  	// Reset the original file and overwrite it atomically.
   672  	if err := utils.AtomicWriteFile(configFile, output.Bytes(), 0644); err != nil {
   673  		return false, errors.Annotatef(err, "cannot write new config %q", configFile)
   674  	}
   675  	logger.Tracef("reordered network settings in config %q", configFile)
   676  	return true, nil
   677  }
   678  
   679  func appendToContainerConfig(name, line string) error {
   680  	configPath := containerConfigFilename(name)
   681  	file, err := os.OpenFile(configPath, os.O_RDWR|os.O_APPEND, 0644)
   682  	if err != nil {
   683  		return err
   684  	}
   685  	defer file.Close()
   686  	_, err = file.WriteString(line)
   687  	if err != nil {
   688  		return err
   689  	}
   690  	logger.Tracef("appended %q to config %q", line, configPath)
   691  	return nil
   692  }
   693  
   694  func autostartContainer(name string) error {
   695  	// Now symlink the config file into the restart directory, if it exists.
   696  	// This is for backwards compatiblity. From Trusty onwards, the auto start
   697  	// option should be set in the LXC config file, this is done in the networkConfigTemplate
   698  	// function below.
   699  	if useRestartDir() {
   700  		if err := symlink.New(
   701  			containerConfigFilename(name),
   702  			restartSymlink(name),
   703  		); err != nil {
   704  			return err
   705  		}
   706  		logger.Tracef("auto-restart link created")
   707  	} else {
   708  		logger.Tracef("Setting auto start to true in lxc config.")
   709  		return appendToContainerConfig(name, "lxc.start.auto = 1\n")
   710  	}
   711  	return nil
   712  }
   713  
   714  func mountHostLogDir(name, logDir string) error {
   715  	// Make sure that the mount dir has been created.
   716  	internalDir := internalLogDir(name)
   717  	// Ensure that the logDir actually exists.
   718  	if err := os.MkdirAll(logDir, 0777); err != nil {
   719  		return errors.Trace(err)
   720  	}
   721  	logger.Tracef("make the mount dir for the shared logs: %s", internalDir)
   722  	if err := os.MkdirAll(internalDir, 0755); err != nil {
   723  		logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err)
   724  		return err
   725  	}
   726  	line := fmt.Sprintf(
   727  		"lxc.mount.entry = %s var/log/juju none defaults,bind 0 0\n",
   728  		logDir)
   729  	return appendToContainerConfig(name, line)
   730  }
   731  
   732  func (manager *containerManager) DestroyContainer(id instance.Id) error {
   733  	start := time.Now()
   734  	name := string(id)
   735  	lxcContainer := LxcObjectFactory.New(name)
   736  	if useRestartDir() {
   737  		// Remove the autostart link.
   738  		if err := os.Remove(restartSymlink(name)); err != nil {
   739  			logger.Errorf("failed to remove restart symlink: %v", err)
   740  			return err
   741  		}
   742  	}
   743  	if err := lxcContainer.Destroy(); err != nil {
   744  		logger.Errorf("failed to destroy lxc container: %v", err)
   745  		return err
   746  	}
   747  
   748  	err := container.RemoveDirectory(name)
   749  	logger.Tracef("container %q stopped: %v", name, time.Now().Sub(start))
   750  	return err
   751  }
   752  
   753  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   754  	containers, err := LxcObjectFactory.List()
   755  	if err != nil {
   756  		logger.Errorf("failed getting all instances: %v", err)
   757  		return
   758  	}
   759  	managerPrefix := ""
   760  	if manager.name != "" {
   761  		managerPrefix = fmt.Sprintf("%s-", manager.name)
   762  	}
   763  
   764  	for _, container := range containers {
   765  		// Filter out those not starting with our name.
   766  		name := container.Name()
   767  		if !strings.HasPrefix(name, managerPrefix) {
   768  			continue
   769  		}
   770  		if container.IsRunning() {
   771  			result = append(result, &lxcInstance{container, name})
   772  		}
   773  	}
   774  	return
   775  }
   776  
   777  func (manager *containerManager) IsInitialized() bool {
   778  	requiredBinaries := []string{
   779  		"lxc-ls",
   780  	}
   781  	for _, bin := range requiredBinaries {
   782  		if _, err := exec.LookPath(bin); err != nil {
   783  			return false
   784  		}
   785  	}
   786  	return true
   787  }
   788  
   789  const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju"
   790  
   791  func internalLogDir(containerName string) string {
   792  	return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName)
   793  }
   794  
   795  func restartSymlink(name string) string {
   796  	return filepath.Join(LxcRestartDir, name+".conf")
   797  }
   798  
   799  func containerConfigFilename(name string) string {
   800  	return filepath.Join(LxcContainerDir, name, "config")
   801  }
   802  
   803  const singleNICTemplate = `
   804  # network config
   805  # interface "eth0"
   806  lxc.network.type = {{.Type}}
   807  lxc.network.link = {{.Link}}
   808  lxc.network.flags = up{{if .MTU}}
   809  lxc.network.mtu = {{.MTU}}{{end}}
   810  
   811  `
   812  
   813  const multipleNICsTemplate = `
   814  # network config{{$mtu := .MTU}}{{range $nic := .Interfaces}}
   815  {{$nic.Name | printf "# interface %q"}}
   816  lxc.network.type = {{$nic.Type}}{{if $nic.VLANTag}}
   817  lxc.network.vlan.id = {{$nic.VLANTag}}{{end}}
   818  lxc.network.link = {{$nic.Link}}{{if not $nic.NoAutoStart}}
   819  lxc.network.flags = up{{end}}
   820  lxc.network.name = {{$nic.Name}}{{if $nic.MACAddress}}
   821  lxc.network.hwaddr = {{$nic.MACAddress}}{{end}}{{if $nic.IPv4Address}}
   822  lxc.network.ipv4 = {{$nic.IPv4Address}}{{end}}{{if $nic.IPv4Gateway}}
   823  lxc.network.ipv4.gateway = {{$nic.IPv4Gateway}}{{end}}{{if $mtu}}
   824  lxc.network.mtu = {{$mtu}}{{end}}
   825  {{end}}{{/* range */}}
   826  
   827  `
   828  
   829  func networkConfigTemplate(config container.NetworkConfig) string {
   830  	type nicData struct {
   831  		Name        string
   832  		NoAutoStart bool
   833  		Type        string
   834  		Link        string
   835  		VLANTag     int
   836  		MACAddress  string
   837  		IPv4Address string
   838  		IPv4Gateway string
   839  	}
   840  	type configData struct {
   841  		Type       string
   842  		Link       string
   843  		MTU        int
   844  		Interfaces []nicData
   845  	}
   846  	data := configData{
   847  		Link: config.Device,
   848  	}
   849  
   850  	primaryNIC, err := discoverHostNIC()
   851  	if err != nil {
   852  		logger.Warningf("cannot determine primary NIC MTU, not setting for container: %v", err)
   853  	} else if primaryNIC.MTU > 0 {
   854  		logger.Infof("setting MTU to %d for all container network interfaces", primaryNIC.MTU)
   855  		data.MTU = primaryNIC.MTU
   856  	}
   857  
   858  	switch config.NetworkType {
   859  	case container.PhysicalNetwork:
   860  		data.Type = "phys"
   861  	case container.BridgeNetwork:
   862  		data.Type = "veth"
   863  	default:
   864  		logger.Warningf(
   865  			"unknown network type %q, using the default %q config",
   866  			config.NetworkType, container.BridgeNetwork,
   867  		)
   868  		data.Type = "veth"
   869  	}
   870  	for _, iface := range config.Interfaces {
   871  		nic := nicData{
   872  			Type:        data.Type,
   873  			Link:        config.Device,
   874  			Name:        iface.InterfaceName,
   875  			NoAutoStart: iface.NoAutoStart,
   876  			VLANTag:     iface.VLANTag,
   877  			MACAddress:  iface.MACAddress,
   878  			IPv4Address: iface.Address.Value,
   879  			IPv4Gateway: iface.GatewayAddress.Value,
   880  		}
   881  		if iface.VLANTag > 0 {
   882  			nic.Type = "vlan"
   883  		}
   884  		if nic.IPv4Address != "" {
   885  			// LXC expects IPv4 addresses formatted like a CIDR:
   886  			// 1.2.3.4/5 (but without masking the least significant
   887  			// octets). So we need to extract the mask part of the
   888  			// iface.CIDR and append it. If CIDR is empty or invalid
   889  			// "/24" is used as a sane default.
   890  			_, ipNet, err := net.ParseCIDR(iface.CIDR)
   891  			if err != nil {
   892  				logger.Warningf(
   893  					"invalid CIDR %q for interface %q, using /24 as fallback",
   894  					iface.CIDR, nic.Name,
   895  				)
   896  				nic.IPv4Address += "/24"
   897  			} else {
   898  				ones, _ := ipNet.Mask.Size()
   899  				nic.IPv4Address += fmt.Sprintf("/%d", ones)
   900  			}
   901  		}
   902  		if nic.NoAutoStart && nic.IPv4Gateway != "" {
   903  			// LXC refuses to add an ipv4 gateway when the NIC is not
   904  			// brought up.
   905  			logger.Warningf(
   906  				"not setting IPv4 gateway %q for non-auto start interface %q",
   907  				nic.IPv4Gateway, nic.Name,
   908  			)
   909  			nic.IPv4Gateway = ""
   910  		}
   911  
   912  		data.Interfaces = append(data.Interfaces, nic)
   913  	}
   914  	templateName := multipleNICsTemplate
   915  	if len(config.Interfaces) == 0 {
   916  		logger.Tracef("generating default single NIC network config")
   917  		templateName = singleNICTemplate
   918  	} else {
   919  		logger.Tracef("generating network config with %d NIC(s)", len(config.Interfaces))
   920  	}
   921  	tmpl, err := template.New("config").Parse(templateName)
   922  	if err != nil {
   923  		logger.Errorf("cannot parse container config template: %v", err)
   924  		return ""
   925  	}
   926  
   927  	var buf bytes.Buffer
   928  	if err := tmpl.Execute(&buf, data); err != nil {
   929  		logger.Errorf("cannot render container config: %v", err)
   930  		return ""
   931  	}
   932  	return buf.String()
   933  }
   934  
   935  // discoverHostNIC detects and returns the primary network interface
   936  // on the machine. Out of all interfaces, the first non-loopback
   937  // device which is up and has address is considered the primary.
   938  var discoverHostNIC = func() (net.Interface, error) {
   939  	interfaces, err := net.Interfaces()
   940  	if err != nil {
   941  		return net.Interface{}, errors.Annotatef(err, "cannot get network interfaces")
   942  	}
   943  	logger.Tracef("trying to discover primary network interface")
   944  	for _, iface := range interfaces {
   945  		if iface.Flags&net.FlagLoopback != 0 {
   946  			// Skip the loopback.
   947  			logger.Tracef("not using loopback interface %q", iface.Name)
   948  			continue
   949  		}
   950  		if iface.Flags&net.FlagUp != 0 {
   951  			// Possibly the primary, but ensure it has an address as
   952  			// well.
   953  			logger.Tracef("verifying interface %q has addresses", iface.Name)
   954  			addrs, err := iface.Addrs()
   955  			if err != nil {
   956  				return net.Interface{}, errors.Annotatef(err, "cannot get %q addresses", iface.Name)
   957  			}
   958  			if len(addrs) > 0 {
   959  				// We found it.
   960  				logger.Tracef("primary network interface is %q", iface.Name)
   961  				return iface, nil
   962  			}
   963  		}
   964  	}
   965  	return net.Interface{}, errors.Errorf("cannot detect the primary network interface")
   966  }
   967  
   968  func generateNetworkConfig(config *container.NetworkConfig) string {
   969  	if config == nil {
   970  		config = DefaultNetworkConfig()
   971  		logger.Warningf("network type missing, using the default %q config", config.NetworkType)
   972  	}
   973  	return networkConfigTemplate(*config)
   974  }
   975  
   976  // useRestartDir is used to determine whether or not to use a symlink to the
   977  // container config as the restart mechanism.  Older versions of LXC had the
   978  // /etc/lxc/auto directory that would indicate that a container shoud auto-
   979  // restart when the machine boots by having a symlink to the lxc.conf file.
   980  // Newer versions don't do this, but instead have a config value inside the
   981  // lxc.conf file.
   982  func useRestartDir() bool {
   983  	if _, err := os.Stat(LxcRestartDir); err != nil {
   984  		if os.IsNotExist(err) {
   985  			return false
   986  		}
   987  	}
   988  	return true
   989  }