launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/loggo/loggo"
    14  	"launchpad.net/golxc"
    15  
    16  	"launchpad.net/juju-core/container"
    17  	"launchpad.net/juju-core/environs/cloudinit"
    18  	"launchpad.net/juju-core/instance"
    19  	"launchpad.net/juju-core/names"
    20  	"launchpad.net/juju-core/version"
    21  )
    22  
    23  var logger = loggo.GetLogger("juju.container.lxc")
    24  
    25  var (
    26  	defaultTemplate  = "ubuntu-cloud"
    27  	LxcContainerDir  = "/var/lib/lxc"
    28  	LxcRestartDir    = "/etc/lxc/auto"
    29  	LxcObjectFactory = golxc.Factory()
    30  )
    31  
    32  const (
    33  	// DefaultLxcBridge is the package created container bridge
    34  	DefaultLxcBridge = "lxcbr0"
    35  )
    36  
    37  // DefaultNetworkConfig returns a valid NetworkConfig to use the
    38  // defaultLxcBridge that is created by the lxc package.
    39  func DefaultNetworkConfig() *container.NetworkConfig {
    40  	return container.BridgeNetworkConfig(DefaultLxcBridge)
    41  }
    42  
    43  type containerManager struct {
    44  	name   string
    45  	logdir string
    46  }
    47  
    48  // containerManager implements container.Manager.
    49  var _ container.Manager = (*containerManager)(nil)
    50  
    51  // NewContainerManager returns a manager object that can start and stop lxc
    52  // containers. The containers that are created are namespaced by the name
    53  // parameter.
    54  func NewContainerManager(conf container.ManagerConfig) container.Manager {
    55  	logdir := "/var/log/juju"
    56  	if conf.LogDir != "" {
    57  		logdir = conf.LogDir
    58  	}
    59  	return &containerManager{name: conf.Name, logdir: logdir}
    60  }
    61  
    62  func (manager *containerManager) StartContainer(
    63  	machineConfig *cloudinit.MachineConfig,
    64  	series string,
    65  	network *container.NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) {
    66  
    67  	name := names.MachineTag(machineConfig.MachineId)
    68  	if manager.name != "" {
    69  		name = fmt.Sprintf("%s-%s", manager.name, name)
    70  	}
    71  	// Note here that the lxcObjectFacotry only returns a valid container
    72  	// object, and doesn't actually construct the underlying lxc container on
    73  	// disk.
    74  	lxcContainer := LxcObjectFactory.New(name)
    75  
    76  	// Create the cloud-init.
    77  	directory, err := container.NewDirectory(name)
    78  	if err != nil {
    79  		return nil, nil, err
    80  	}
    81  	logger.Tracef("write cloud-init")
    82  	userDataFilename, err := container.WriteUserData(machineConfig, directory)
    83  	if err != nil {
    84  		logger.Errorf("failed to write user data: %v", err)
    85  		return nil, nil, err
    86  	}
    87  	logger.Tracef("write the lxc.conf file")
    88  	configFile, err := writeLxcConfig(network, directory, manager.logdir)
    89  	if err != nil {
    90  		logger.Errorf("failed to write config file: %v", err)
    91  		return nil, nil, err
    92  	}
    93  	templateParams := []string{
    94  		"--debug",                      // Debug errors in the cloud image
    95  		"--userdata", userDataFilename, // Our groovey cloud-init
    96  		"--hostid", name, // Use the container name as the hostid
    97  		"-r", series,
    98  	}
    99  	// Create the container.
   100  	logger.Tracef("create the container")
   101  	if err := lxcContainer.Create(configFile, defaultTemplate, templateParams...); err != nil {
   102  		logger.Errorf("lxc container creation failed: %v", err)
   103  		return nil, nil, err
   104  	}
   105  	// Make sure that the mount dir has been created.
   106  	logger.Tracef("make the mount dir for the shard logs")
   107  	if err := os.MkdirAll(internalLogDir(name), 0755); err != nil {
   108  		logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err)
   109  		return nil, nil, err
   110  	}
   111  	logger.Tracef("lxc container created")
   112  	// Now symlink the config file into the restart directory, if it exists.
   113  	// This is for backwards compatiblity. From Trusty onwards, the auto start
   114  	// option should be set in the LXC config file, this is done in the networkConfigTemplate
   115  	// function below.
   116  	if useRestartDir() {
   117  		containerConfigFile := filepath.Join(LxcContainerDir, name, "config")
   118  		if err := os.Symlink(containerConfigFile, restartSymlink(name)); err != nil {
   119  			return nil, nil, err
   120  		}
   121  		logger.Tracef("auto-restart link created")
   122  	}
   123  
   124  	// Start the lxc container with the appropriate settings for grabbing the
   125  	// console output and a log file.
   126  	consoleFile := filepath.Join(directory, "console.log")
   127  	lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug)
   128  	logger.Tracef("start the container")
   129  	// We explicitly don't pass through the config file to the container.Start
   130  	// method as we have passed it through at container creation time.  This
   131  	// is necessary to get the appropriate rootfs reference without explicitly
   132  	// setting it ourselves.
   133  	if err = lxcContainer.Start("", consoleFile); err != nil {
   134  		logger.Errorf("container failed to start: %v", err)
   135  		return nil, nil, err
   136  	}
   137  	arch := version.Current.Arch
   138  	hardware := &instance.HardwareCharacteristics{
   139  		Arch: &arch,
   140  	}
   141  	logger.Tracef("container started")
   142  	return &lxcInstance{lxcContainer, name}, hardware, nil
   143  }
   144  
   145  func (manager *containerManager) StopContainer(instance instance.Instance) error {
   146  	name := string(instance.Id())
   147  	lxcContainer := LxcObjectFactory.New(name)
   148  	if useRestartDir() {
   149  		// Remove the autostart link.
   150  		if err := os.Remove(restartSymlink(name)); err != nil {
   151  			logger.Errorf("failed to remove restart symlink: %v", err)
   152  			return err
   153  		}
   154  	}
   155  	if err := lxcContainer.Destroy(); err != nil {
   156  		logger.Errorf("failed to destroy lxc container: %v", err)
   157  		return err
   158  	}
   159  
   160  	return container.RemoveDirectory(name)
   161  }
   162  
   163  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   164  	containers, err := LxcObjectFactory.List()
   165  	if err != nil {
   166  		logger.Errorf("failed getting all instances: %v", err)
   167  		return
   168  	}
   169  	managerPrefix := ""
   170  	if manager.name != "" {
   171  		managerPrefix = fmt.Sprintf("%s-", manager.name)
   172  	}
   173  
   174  	for _, container := range containers {
   175  		// Filter out those not starting with our name.
   176  		name := container.Name()
   177  		if !strings.HasPrefix(name, managerPrefix) {
   178  			continue
   179  		}
   180  		if container.IsRunning() {
   181  			result = append(result, &lxcInstance{container, name})
   182  		}
   183  	}
   184  	return
   185  }
   186  
   187  const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju"
   188  
   189  func internalLogDir(containerName string) string {
   190  	return fmt.Sprintf(internalLogDirTemplate, LxcContainerDir, containerName)
   191  }
   192  
   193  func restartSymlink(name string) string {
   194  	return filepath.Join(LxcRestartDir, name+".conf")
   195  }
   196  
   197  const localConfig = `%s
   198  lxc.mount.entry=%s var/log/juju none defaults,bind 0 0
   199  `
   200  
   201  const networkTemplate = `
   202  lxc.network.type = %s
   203  lxc.network.link = %s
   204  lxc.network.flags = up
   205  `
   206  
   207  func networkConfigTemplate(networkType, networkLink string) string {
   208  	networkConfig := fmt.Sprintf(networkTemplate, networkType, networkLink)
   209  	if !useRestartDir() {
   210  		networkConfig += "lxc.start.auto = 1\n"
   211  		logger.Tracef("Setting auto start to true in lxc config.")
   212  	}
   213  	return networkConfig
   214  }
   215  
   216  func generateNetworkConfig(network *container.NetworkConfig) string {
   217  	if network == nil {
   218  		logger.Warningf("network unspecified, using default networking config")
   219  		network = DefaultNetworkConfig()
   220  	}
   221  	switch network.NetworkType {
   222  	case container.PhysicalNetwork:
   223  		return networkConfigTemplate("phys", network.Device)
   224  	default:
   225  		logger.Warningf("Unknown network config type %q: using bridge", network.NetworkType)
   226  		fallthrough
   227  	case container.BridgeNetwork:
   228  		return networkConfigTemplate("veth", network.Device)
   229  	}
   230  }
   231  
   232  func writeLxcConfig(network *container.NetworkConfig, directory, logdir string) (string, error) {
   233  	networkConfig := generateNetworkConfig(network)
   234  	configFilename := filepath.Join(directory, "lxc.conf")
   235  	configContent := fmt.Sprintf(localConfig, networkConfig, logdir)
   236  	if err := ioutil.WriteFile(configFilename, []byte(configContent), 0644); err != nil {
   237  		return "", err
   238  	}
   239  	return configFilename, nil
   240  }
   241  
   242  // useRestartDir is used to determine whether or not to use a symlink to the
   243  // container config as the restart mechanism.  Older versions of LXC had the
   244  // /etc/lxc/auto directory that would indicate that a container shoud auto-
   245  // restart when the machine boots by having a symlink to the lxc.conf file.
   246  // Newer versions don't do this, but instead have a config value inside the
   247  // lxc.conf file.
   248  func useRestartDir() bool {
   249  	if _, err := os.Stat(LxcRestartDir); err != nil {
   250  		if os.IsNotExist(err) {
   251  			return false
   252  		}
   253  	}
   254  	return true
   255  }