github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"strconv"
    15  	"strings"
    16  	"text/template"
    17  	"time"
    18  
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/names"
    22  	"github.com/juju/utils"
    23  	"github.com/juju/utils/arch"
    24  	"github.com/juju/utils/keyvalues"
    25  	"github.com/juju/utils/series"
    26  	"github.com/juju/utils/symlink"
    27  	"launchpad.net/golxc"
    28  
    29  	"github.com/juju/juju/agent"
    30  	"github.com/juju/juju/cloudconfig/containerinit"
    31  	"github.com/juju/juju/cloudconfig/instancecfg"
    32  	"github.com/juju/juju/container"
    33  	"github.com/juju/juju/instance"
    34  	"github.com/juju/juju/status"
    35  	"github.com/juju/juju/storage/looputil"
    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  	writeWgetTmpFile = ioutil.WriteFile
    46  )
    47  
    48  const (
    49  	// Btrfs is special as we treat it differently for create and clone.
    50  	Btrfs = "btrfs"
    51  
    52  	// etcNetworkInterfaces here is the path (inside the container's
    53  	// rootfs) where the network config is stored.
    54  	etcNetworkInterfaces = "/etc/network/interfaces"
    55  )
    56  
    57  // DefaultNetworkConfig returns a valid NetworkConfig to use the
    58  // defaultLxcBridge that is created by the lxc package.
    59  func DefaultNetworkConfig() *container.NetworkConfig {
    60  	return container.BridgeNetworkConfig(container.DefaultLxcBridge, 0, nil)
    61  }
    62  
    63  // FsCommandOutput calls cmd.Output, this is used as an overloading point so
    64  // we can test what *would* be run without actually executing another program
    65  var FsCommandOutput = (*exec.Cmd).CombinedOutput
    66  
    67  func containerDirFilesystem() (string, error) {
    68  	cmd := exec.Command("df", "--output=fstype", LxcContainerDir)
    69  	out, err := FsCommandOutput(cmd)
    70  	if err != nil {
    71  		return "", errors.Trace(err)
    72  	}
    73  	// The filesystem is the second line.
    74  	lines := strings.Split(string(out), "\n")
    75  	if len(lines) < 2 {
    76  		logger.Errorf("unexpected output: %q", out)
    77  		return "", errors.Errorf("could not determine filesystem type")
    78  	}
    79  	return lines[1], nil
    80  }
    81  
    82  type containerManager struct {
    83  	name              string
    84  	logdir            string
    85  	createWithClone   bool
    86  	useAUFS           bool
    87  	backingFilesystem string
    88  	imageURLGetter    container.ImageURLGetter
    89  	loopDeviceManager looputil.LoopDeviceManager
    90  }
    91  
    92  // NewContainerManager returns a manager object that can start and
    93  // stop lxc containers. The containers that are created are namespaced
    94  // by the name parameter inside the given ManagerConfig.
    95  func NewContainerManager(conf container.ManagerConfig, imageURLGetter container.ImageURLGetter) (container.Manager, error) {
    96  	return newContainerManager(conf, imageURLGetter, looputil.NewLoopDeviceManager())
    97  }
    98  
    99  func newContainerManager(
   100  	conf container.ManagerConfig,
   101  	imageURLGetter container.ImageURLGetter,
   102  	loopDeviceManager looputil.LoopDeviceManager,
   103  ) (container.Manager, error) {
   104  	name := conf.PopValue(container.ConfigName)
   105  	if name == "" {
   106  		return nil, errors.Errorf("name is required")
   107  	}
   108  	logDir := conf.PopValue(container.ConfigLogDir)
   109  	if logDir == "" {
   110  		logDir = agent.DefaultPaths.LogDir
   111  	}
   112  	var useClone bool
   113  	useCloneVal := conf.PopValue("use-clone")
   114  	if useCloneVal != "" {
   115  		// Explicitly ignore the error result from ParseBool.
   116  		// If it fails to parse, the value is false, and this suits
   117  		// us fine.
   118  		useClone, _ = strconv.ParseBool(useCloneVal)
   119  	} else {
   120  		// If no lxc-clone value is explicitly set in config, then
   121  		// see if the Ubuntu series we are running on supports it
   122  		// and if it does, we will use clone.
   123  		useClone = preferFastLXC(releaseVersion())
   124  	}
   125  	useAUFS, _ := strconv.ParseBool(conf.PopValue("use-aufs"))
   126  	backingFS, err := containerDirFilesystem()
   127  	if err != nil {
   128  		// Especially in tests, or a bot, the lxc dir may not exist
   129  		// causing the test to fail. Since we only really care if the
   130  		// backingFS is 'btrfs' and we treat the rest the same, just
   131  		// call it 'unknown'.
   132  		backingFS = "unknown"
   133  	}
   134  	logger.Tracef("backing filesystem: %q", backingFS)
   135  	conf.WarnAboutUnused()
   136  	return &containerManager{
   137  		name:              name,
   138  		logdir:            logDir,
   139  		createWithClone:   useClone,
   140  		useAUFS:           useAUFS,
   141  		backingFilesystem: backingFS,
   142  		imageURLGetter:    imageURLGetter,
   143  		loopDeviceManager: loopDeviceManager,
   144  	}, nil
   145  }
   146  
   147  // releaseVersion is a function that returns a string representing the
   148  // DISTRIB_RELEASE from the /etc/lsb-release file.
   149  var releaseVersion = series.ReleaseVersion
   150  
   151  // preferFastLXC returns true if the host is capable of
   152  // LXC cloning from a template.
   153  func preferFastLXC(release string) bool {
   154  	if release == "" {
   155  		return false
   156  	}
   157  	value, err := strconv.ParseFloat(release, 64)
   158  	if err != nil {
   159  		return false
   160  	}
   161  	return value >= 14.04
   162  }
   163  
   164  // CreateContainer creates or clones an LXC container.
   165  func (manager *containerManager) CreateContainer(
   166  	instanceConfig *instancecfg.InstanceConfig,
   167  	series string,
   168  	networkConfig *container.NetworkConfig,
   169  	storageConfig *container.StorageConfig,
   170  	callback container.StatusCallback,
   171  ) (inst instance.Instance, _ *instance.HardwareCharacteristics, err error) {
   172  	// Check our preconditions
   173  	if manager == nil {
   174  		panic("manager is nil")
   175  	} else if series == "" {
   176  		panic("series not set")
   177  	} else if networkConfig == nil {
   178  		panic("networkConfig is nil")
   179  	} else if storageConfig == nil {
   180  		panic("storageConfig is nil")
   181  	} else if callback == nil {
   182  		panic("status callback is nil")
   183  	}
   184  
   185  	// Log how long the start took
   186  	defer func(start time.Time) {
   187  		if err == nil {
   188  			logger.Tracef("container %q started: %v", inst.Id(), time.Now().Sub(start))
   189  		}
   190  	}(time.Now())
   191  
   192  	name := names.NewMachineTag(instanceConfig.MachineId).String()
   193  	if manager.name != "" {
   194  		name = fmt.Sprintf("%s-%s", manager.name, name)
   195  	}
   196  
   197  	// Create the cloud-init.
   198  	directory, err := container.NewDirectory(name)
   199  	if err != nil {
   200  		return nil, nil, errors.Annotate(err, "failed to create a directory for the container")
   201  	}
   202  	logger.Tracef("write cloud-init")
   203  	userDataFilename, err := containerinit.WriteUserData(instanceConfig, networkConfig, directory)
   204  	if err != nil {
   205  		return nil, nil, errors.Annotate(err, "failed to write user data")
   206  	}
   207  
   208  	var lxcContainer golxc.Container
   209  	if manager.createWithClone {
   210  		templateContainer, err := EnsureCloneTemplate(
   211  			manager.backingFilesystem,
   212  			series,
   213  			networkConfig,
   214  			instanceConfig.AuthorizedKeys,
   215  			instanceConfig.AptProxySettings,
   216  			instanceConfig.AptMirror,
   217  			instanceConfig.EnableOSRefreshUpdate,
   218  			instanceConfig.EnableOSUpgrade,
   219  			manager.imageURLGetter,
   220  			manager.useAUFS,
   221  			callback,
   222  		)
   223  		if err != nil {
   224  			return nil, nil, errors.Annotate(err, "failed to retrieve the template to clone")
   225  		}
   226  		templateParams := []string{
   227  			"--debug",                      // Debug errors in the cloud image
   228  			"--userdata", userDataFilename, // Our groovey cloud-init
   229  			"--hostid", name, // Use the container name as the hostid
   230  		}
   231  		var extraCloneArgs []string
   232  		if manager.backingFilesystem == Btrfs || manager.useAUFS {
   233  			extraCloneArgs = append(extraCloneArgs, "--snapshot")
   234  		}
   235  		if manager.backingFilesystem != Btrfs && manager.useAUFS {
   236  			extraCloneArgs = append(extraCloneArgs, "--backingstore", "aufs")
   237  		}
   238  
   239  		lock, err := AcquireTemplateLock(templateContainer.Name(), "clone")
   240  		if err != nil {
   241  			return nil, nil, errors.Annotate(err, "failed to acquire lock on template")
   242  		}
   243  		defer lock.Unlock()
   244  
   245  		// Ensure the run-time effective config of the template
   246  		// container has correctly ordered network settings, otherwise
   247  		// Clone() below will fail. This is needed in case we haven't
   248  		// created a new template now but are reusing an existing one.
   249  		// See LP bug #1414016.
   250  		configPath := containerConfigFilename(templateContainer.Name())
   251  		if _, err := reorderNetworkConfig(configPath); err != nil {
   252  			return nil, nil, errors.Annotate(err, "failed to reorder network settings")
   253  		}
   254  
   255  		err = callback(status.StatusProvisioning, "Cloning template container", nil)
   256  		if err != nil {
   257  			logger.Warningf("Cannot set instance status for container: %v", err)
   258  		}
   259  		lxcContainer, err = templateContainer.Clone(name, extraCloneArgs, templateParams)
   260  		if err != nil {
   261  			return nil, nil, errors.Wrap(err, instance.NewRetryableCreationError("lxc container cloning failed"))
   262  		}
   263  	} else {
   264  		// Note here that the lxcObjectFactory only returns a valid container
   265  		// object, and doesn't actually construct the underlying lxc container on
   266  		// disk.
   267  		lxcContainer = LxcObjectFactory.New(name)
   268  		templateParams := []string{
   269  			"--debug",                      // Debug errors in the cloud image
   270  			"--userdata", userDataFilename, // Our groovey cloud-init
   271  			"--hostid", name, // Use the container name as the hostid
   272  			"-r", series,
   273  		}
   274  		var caCert []byte
   275  		if manager.imageURLGetter != nil {
   276  			arch := arch.HostArch()
   277  			imageURL, err := manager.imageURLGetter.ImageURL(instance.LXC, series, arch)
   278  			if err != nil {
   279  				return nil, nil, errors.Annotatef(err, "cannot determine cached image URL")
   280  			}
   281  			templateParams = append(templateParams, "-T", imageURL)
   282  			caCert = manager.imageURLGetter.CACert()
   283  		}
   284  		err = createContainer(
   285  			lxcContainer,
   286  			directory,
   287  			networkConfig,
   288  			nil,
   289  			templateParams,
   290  			caCert,
   291  		)
   292  		if err != nil {
   293  			return nil, nil, errors.Trace(err)
   294  		}
   295  	}
   296  
   297  	callback(status.StatusProvisioning, "Configuring container", nil)
   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  	callback(status.StatusProvisioning, "Starting container", nil)
   356  
   357  	// We explicitly don't pass through the config file to the container.Start
   358  	// method as we have passed it through at container creation time.  This
   359  	// is necessary to get the appropriate rootfs reference without explicitly
   360  	// setting it ourselves.
   361  	if err := lxcContainer.Start("", consoleFile); err != nil {
   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  				return nil, nil, errors.Annotatef(err, "container failed to start and failed to destroy: manual cleanup of containers needed: %v", derr)
   370  			}
   371  			return nil, nil, errors.Wrap(err, instance.NewRetryableCreationError("container failed to start and was destroyed: "+lxcContainer.Name()))
   372  		}
   373  		return nil, nil, errors.Annotate(err, "container failed to start")
   374  	}
   375  
   376  	arch := arch.HostArch()
   377  	hardware := &instance.HardwareCharacteristics{
   378  		Arch: &arch,
   379  	}
   380  
   381  	callback(status.StatusRunning, "Container started", nil)
   382  	return &lxcInstance{lxcContainer, name}, hardware, nil
   383  }
   384  
   385  func createContainer(
   386  	lxcContainer golxc.Container,
   387  	directory string,
   388  	networkConfig *container.NetworkConfig,
   389  	extraCreateArgs, templateParams []string,
   390  	caCert []byte,
   391  ) error {
   392  	// Generate initial lxc.conf with networking settings.
   393  	netConfig := generateNetworkConfig(networkConfig)
   394  	configPath := filepath.Join(directory, "lxc.conf")
   395  	if err := ioutil.WriteFile(configPath, []byte(netConfig), 0644); err != nil {
   396  		return errors.Annotatef(err, "failed to write container config %q", configPath)
   397  	}
   398  	logger.Tracef("wrote initial config %q for container %q: %+v", configPath, lxcContainer.Name(), netConfig)
   399  
   400  	var err error
   401  	var execEnv []string = nil
   402  	var closer func()
   403  	if caCert != nil {
   404  		execEnv, closer, err = wgetEnvironment(caCert)
   405  		if err != nil {
   406  			return errors.Annotatef(err, "failed to get model for wget execution")
   407  		}
   408  		defer closer()
   409  	}
   410  
   411  	// Create the container.
   412  	logger.Debugf("creating lxc container %q", lxcContainer.Name())
   413  	logger.Debugf("lxc-create template params: %v", templateParams)
   414  	if err := lxcContainer.Create(configPath, defaultTemplate, extraCreateArgs, templateParams, execEnv); err != nil {
   415  		return errors.Wrap(err, instance.NewRetryableCreationError("lxc container creation failed: "+lxcContainer.Name()))
   416  	}
   417  	return nil
   418  }
   419  
   420  // wgetEnvironment creates a script to call wget with the
   421  // --no-check-certificate argument, patching the PATH to ensure
   422  // the script is invoked by the lxc template bash script.
   423  // It returns a slice of env variables to pass to the lxc create command.
   424  func wgetEnvironment(caCert []byte) (execEnv []string, closer func(), _ error) {
   425  	env := os.Environ()
   426  	kv, err := keyvalues.Parse(env, true)
   427  	if err != nil {
   428  		return nil, nil, errors.Trace(err)
   429  	}
   430  	// Create a wget bash script in a temporary directory.
   431  	tmpDir, err := ioutil.TempDir("", "wget")
   432  	if err != nil {
   433  		return nil, nil, errors.Trace(err)
   434  	}
   435  	closer = func() {
   436  		os.RemoveAll(tmpDir)
   437  	}
   438  	// Write the ca cert.
   439  	caCertPath := filepath.Join(tmpDir, "ca-cert.pem")
   440  	err = ioutil.WriteFile(caCertPath, caCert, 0755)
   441  	if err != nil {
   442  		defer closer()
   443  		return nil, nil, errors.Trace(err)
   444  	}
   445  
   446  	// Write the wget script.  Don't use a proxy when getting
   447  	// the image as it's going through the controller.
   448  	wgetTmpl := `#!/bin/bash
   449  /usr/bin/wget --no-proxy --ca-certificate=%s $*
   450  `
   451  	wget := fmt.Sprintf(wgetTmpl, caCertPath)
   452  	err = writeWgetTmpFile(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  
   768  	// Detach loop devices backed by files inside the container's rootfs.
   769  	rootfs := filepath.Join(LxcContainerDir, name, "rootfs")
   770  	if err := manager.loopDeviceManager.DetachLoopDevices(rootfs, "/"); err != nil {
   771  		logger.Errorf("failed to detach loop devices from lxc container: %v", err)
   772  		return err
   773  	}
   774  
   775  	if err := lxcContainer.Destroy(); err != nil {
   776  		logger.Errorf("failed to destroy lxc container: %v", err)
   777  		return err
   778  	}
   779  
   780  	err := container.RemoveDirectory(name)
   781  	logger.Tracef("container %q stopped: %v", name, time.Now().Sub(start))
   782  	return err
   783  }
   784  
   785  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   786  	containers, err := LxcObjectFactory.List()
   787  	if err != nil {
   788  		logger.Errorf("failed getting all instances: %v", err)
   789  		return
   790  	}
   791  	managerPrefix := ""
   792  	if manager.name != "" {
   793  		managerPrefix = fmt.Sprintf("%s-", manager.name)
   794  	}
   795  
   796  	for _, container := range containers {
   797  		// Filter out those not starting with our name.
   798  		name := container.Name()
   799  		if !strings.HasPrefix(name, managerPrefix) {
   800  			continue
   801  		}
   802  		if container.IsRunning() {
   803  			result = append(result, &lxcInstance{container, name})
   804  		}
   805  	}
   806  	return
   807  }
   808  
   809  func (manager *containerManager) IsInitialized() bool {
   810  	requiredBinaries := []string{
   811  		"lxc-ls",
   812  	}
   813  	for _, bin := range requiredBinaries {
   814  		if _, err := exec.LookPath(bin); err != nil {
   815  			return false
   816  		}
   817  	}
   818  	return true
   819  }
   820  
   821  const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju"
   822  
   823  func internalLogDir(containerName string) string {
   824  	return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName)
   825  }
   826  
   827  func restartSymlink(name string) string {
   828  	return filepath.Join(LxcRestartDir, name+".conf")
   829  }
   830  
   831  func containerConfigFilename(name string) string {
   832  	return filepath.Join(LxcContainerDir, name, "config")
   833  }
   834  
   835  const singleNICTemplate = `
   836  # network config
   837  # interface "eth0"
   838  lxc.network.type = {{.Type}}
   839  lxc.network.link = {{.Link}}
   840  lxc.network.flags = up{{if .MTU}}
   841  lxc.network.mtu = {{.MTU}}{{end}}{{if .MACAddress}}
   842  lxc.network.hwaddr = {{.MACAddress}}{{end}}
   843  
   844  `
   845  
   846  const multipleNICsTemplate = `
   847  # network config{{$mtu := .MTU}}{{range $nic := .Interfaces}}
   848  {{$nic.Name | printf "# interface %q"}}
   849  lxc.network.type = {{$nic.Type}}
   850  lxc.network.link = {{$nic.Link}}{{if not $nic.NoAutoStart}}
   851  lxc.network.flags = up{{end}}{{if $nic.Name}}
   852  lxc.network.name = {{$nic.Name}}{{end}}{{if $nic.MACAddress}}
   853  lxc.network.hwaddr = {{$nic.MACAddress}}{{end}}{{if $nic.IPv4Address}}
   854  lxc.network.ipv4 = {{$nic.IPv4Address}}{{end}}{{if $nic.IPv4Gateway}}
   855  lxc.network.ipv4.gateway = {{$nic.IPv4Gateway}}{{end}}{{if $nic.MTU}}
   856  lxc.network.mtu = {{$nic.MTU}}{{end}}
   857  {{end}}{{/* range */}}
   858  
   859  `
   860  
   861  func networkConfigTemplate(config container.NetworkConfig) string {
   862  	logger.Debugf("preparing to render container network config from %+v", config)
   863  	type nicData struct {
   864  		Name        string
   865  		NoAutoStart bool
   866  		Type        string
   867  		Link        string
   868  		MTU         int
   869  		VLANTag     int
   870  		MACAddress  string
   871  		IPv4Address string
   872  		IPv4Gateway string
   873  	}
   874  	type configData struct {
   875  		Type       string
   876  		Link       string
   877  		Interfaces []nicData
   878  		// The following are used only with a single NIC config.
   879  		MTU        int
   880  		MACAddress string
   881  	}
   882  	data := configData{
   883  		Link: config.Device,
   884  		MTU:  config.MTU,
   885  	}
   886  
   887  	switch config.NetworkType {
   888  	case container.PhysicalNetwork:
   889  		data.Type = "phys"
   890  	case container.BridgeNetwork:
   891  		data.Type = "veth"
   892  	default:
   893  		logger.Warningf(
   894  			"unknown network type %q, using the default %q config",
   895  			config.NetworkType, container.BridgeNetwork,
   896  		)
   897  		data.Type = "veth"
   898  	}
   899  	for _, iface := range config.Interfaces {
   900  		linkName := data.Link
   901  		if iface.ParentInterfaceName != "" {
   902  			linkName = iface.ParentInterfaceName
   903  		}
   904  		nic := nicData{
   905  			Type:        data.Type,
   906  			Link:        linkName,
   907  			Name:        iface.InterfaceName,
   908  			MTU:         iface.MTU,
   909  			NoAutoStart: iface.NoAutoStart,
   910  			MACAddress:  iface.MACAddress,
   911  			IPv4Address: iface.CIDRAddress(),
   912  		}
   913  		if nic.Name == "eth0" {
   914  			// Only the primary NIC needs a gateway otherwise lxc and/or ifup
   915  			// inside the container will fail.
   916  			nic.IPv4Gateway = iface.GatewayAddress.Value
   917  		}
   918  		if iface.MACAddress == "" || nic.MACAddress == "" {
   919  			logger.Warningf(
   920  				"empty MAC address %q from config for %q (rendered as %q)",
   921  				iface.MACAddress, iface.InterfaceName, nic.MACAddress,
   922  			)
   923  		}
   924  
   925  		data.Interfaces = append(data.Interfaces, nic)
   926  	}
   927  	templateName := multipleNICsTemplate
   928  	if len(config.Interfaces) == 0 {
   929  		logger.Tracef("generating default single NIC network config")
   930  		templateName = singleNICTemplate
   931  	} else {
   932  		logger.Tracef("generating network config with %d NIC(s)", len(config.Interfaces))
   933  	}
   934  	tmpl, err := template.New("config").Parse(templateName)
   935  	if err != nil {
   936  		logger.Errorf("cannot parse container config template: %v", err)
   937  		return ""
   938  	}
   939  
   940  	var buf bytes.Buffer
   941  	if err := tmpl.Execute(&buf, data); err != nil {
   942  		logger.Errorf("cannot render container config: %v", err)
   943  		return ""
   944  	}
   945  	return buf.String()
   946  }
   947  
   948  func generateNetworkConfig(config *container.NetworkConfig) string {
   949  	if config == nil {
   950  		config = DefaultNetworkConfig()
   951  		logger.Warningf("network type missing, using the default %q config", config.NetworkType)
   952  	}
   953  	return networkConfigTemplate(*config)
   954  }
   955  
   956  // useRestartDir is used to determine whether or not to use a symlink to the
   957  // container config as the restart mechanism.  Older versions of LXC had the
   958  // /etc/lxc/auto directory that would indicate that a container shoud auto-
   959  // restart when the machine boots by having a symlink to the lxc.conf file.
   960  // Newer versions don't do this, but instead have a config value inside the
   961  // lxc.conf file.
   962  func useRestartDir() bool {
   963  	if _, err := os.Stat(LxcRestartDir); err != nil {
   964  		if os.IsNotExist(err) {
   965  			return false
   966  		}
   967  	}
   968  	return true
   969  }