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