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