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