github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/container/kvm/kvm.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package kvm
     5  
     6  import (
     7  	"fmt"
     8  	"os/exec"
     9  	"strings"
    10  
    11  	"github.com/juju/loggo"
    12  
    13  	"launchpad.net/juju-core/agent"
    14  	"launchpad.net/juju-core/constraints"
    15  	"launchpad.net/juju-core/container"
    16  	"launchpad.net/juju-core/environs/cloudinit"
    17  	"launchpad.net/juju-core/instance"
    18  	"launchpad.net/juju-core/log"
    19  	"launchpad.net/juju-core/names"
    20  	"launchpad.net/juju-core/version"
    21  )
    22  
    23  var (
    24  	logger = loggo.GetLogger("juju.container.kvm")
    25  
    26  	KvmObjectFactory ContainerFactory = &containerFactory{}
    27  	DefaultKvmBridge                  = "virbr0"
    28  
    29  	// In order for Juju to be able to create the hardware characteristics of
    30  	// the kvm machines it creates, we need to be explicit in our definition
    31  	// of memory, cpu-cores and root-disk.  The defaults here have been
    32  	// extracted from the uvt-kvm executable.
    33  	DefaultMemory uint64 = 512 // MB
    34  	DefaultCpu    uint64 = 1
    35  	DefaultDisk   uint64 = 8 // GB
    36  
    37  	// There are some values where it doesn't make sense to go below.
    38  	MinMemory uint64 = 512 // MB
    39  	MinCpu    uint64 = 1
    40  	MinDisk   uint64 = 2 // GB
    41  )
    42  
    43  // IsKVMSupported calls into the kvm-ok executable from the cpu-checkers package.
    44  // It is a variable to allow us to overrid behaviour in the tests.
    45  var IsKVMSupported = func() (bool, error) {
    46  	command := exec.Command("kvm-ok")
    47  	output, err := command.CombinedOutput()
    48  	if err != nil {
    49  		return false, err
    50  	}
    51  	logger.Debugf("kvm-ok output:\n%s", output)
    52  	return command.ProcessState.Success(), nil
    53  }
    54  
    55  // NewContainerManager returns a manager object that can start and stop kvm
    56  // containers. The containers that are created are namespaced by the name
    57  // parameter.
    58  func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
    59  	name := conf[container.ConfigName]
    60  	delete(conf, container.ConfigName)
    61  	if name == "" {
    62  		return nil, fmt.Errorf("name is required")
    63  	}
    64  	logDir := conf[container.ConfigLogDir]
    65  	delete(conf, container.ConfigLogDir)
    66  	if logDir == "" {
    67  		logDir = agent.DefaultLogDir
    68  	}
    69  	for k, v := range conf {
    70  		logger.Warningf(`Found unused config option with key: "%v" and value: "%v"`, k, v)
    71  	}
    72  
    73  	return &containerManager{name: name, logdir: logDir}, nil
    74  }
    75  
    76  // containerManager handles all of the business logic at the juju specific
    77  // level. It makes sure that the necessary directories are in place, that the
    78  // user-data is written out in the right place.
    79  type containerManager struct {
    80  	name   string
    81  	logdir string
    82  }
    83  
    84  var _ container.Manager = (*containerManager)(nil)
    85  
    86  func (manager *containerManager) StartContainer(
    87  	machineConfig *cloudinit.MachineConfig,
    88  	series string,
    89  	network *container.NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) {
    90  
    91  	name := names.MachineTag(machineConfig.MachineId)
    92  	if manager.name != "" {
    93  		name = fmt.Sprintf("%s-%s", manager.name, name)
    94  	}
    95  	// Note here that the kvmObjectFacotry only returns a valid container
    96  	// object, and doesn't actually construct the underlying kvm container on
    97  	// disk.
    98  	kvmContainer := KvmObjectFactory.New(name)
    99  
   100  	// Create the cloud-init.
   101  	directory, err := container.NewDirectory(name)
   102  	if err != nil {
   103  		return nil, nil, fmt.Errorf("failed to create container directory: %v", err)
   104  	}
   105  	logger.Tracef("write cloud-init")
   106  	userDataFilename, err := container.WriteUserData(machineConfig, directory)
   107  	if err != nil {
   108  		return nil, nil, log.LoggedErrorf(logger, "failed to write user data: %v", err)
   109  	}
   110  	// Create the container.
   111  	startParams := ParseConstraintsToStartParams(machineConfig.Constraints)
   112  	startParams.Arch = version.Current.Arch
   113  	startParams.Series = series
   114  	startParams.Network = network
   115  	startParams.UserDataFile = userDataFilename
   116  
   117  	var hardware instance.HardwareCharacteristics
   118  	hardware, err = instance.ParseHardware(
   119  		fmt.Sprintf("arch=%s mem=%vM root-disk=%vG cpu-cores=%v",
   120  			startParams.Arch, startParams.Memory, startParams.RootDisk, startParams.CpuCores))
   121  	if err != nil {
   122  		logger.Warningf("failed to parse hardware: %v", err)
   123  	}
   124  
   125  	logger.Tracef("create the container, constraints: %v", machineConfig.Constraints)
   126  	if err := kvmContainer.Start(startParams); err != nil {
   127  		return nil, nil, log.LoggedErrorf(logger, "kvm container creation failed: %v", err)
   128  	}
   129  	logger.Tracef("kvm container created")
   130  	return &kvmInstance{kvmContainer, name}, &hardware, nil
   131  }
   132  
   133  func (manager *containerManager) StopContainer(instance instance.Instance) error {
   134  	name := string(instance.Id())
   135  	kvmContainer := KvmObjectFactory.New(name)
   136  	if err := kvmContainer.Stop(); err != nil {
   137  		logger.Errorf("failed to stop kvm container: %v", err)
   138  		return err
   139  	}
   140  	return container.RemoveDirectory(name)
   141  }
   142  
   143  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   144  	containers, err := KvmObjectFactory.List()
   145  	if err != nil {
   146  		logger.Errorf("failed getting all instances: %v", err)
   147  		return
   148  	}
   149  	managerPrefix := fmt.Sprintf("%s-", manager.name)
   150  	for _, container := range containers {
   151  		// Filter out those not starting with our name.
   152  		name := container.Name()
   153  		if !strings.HasPrefix(name, managerPrefix) {
   154  			continue
   155  		}
   156  		if container.IsRunning() {
   157  			result = append(result, &kvmInstance{container, name})
   158  		}
   159  	}
   160  	return
   161  }
   162  
   163  // ParseConstraintsToStartParams takes a constrants object and returns a bare
   164  // StartParams object that has Memory, Cpu, and Disk populated.  If there are
   165  // no defined values in the constraints for those fields, default values are
   166  // used.  Other constrains cause a warning to be emitted.
   167  func ParseConstraintsToStartParams(cons constraints.Value) StartParams {
   168  	params := StartParams{
   169  		Memory:   DefaultMemory,
   170  		CpuCores: DefaultCpu,
   171  		RootDisk: DefaultDisk,
   172  	}
   173  
   174  	if cons.Mem != nil {
   175  		mem := *cons.Mem
   176  		if mem < MinMemory {
   177  			params.Memory = MinMemory
   178  		} else {
   179  			params.Memory = mem
   180  		}
   181  	}
   182  	if cons.CpuCores != nil {
   183  		cores := *cons.CpuCores
   184  		if cores < MinCpu {
   185  			params.CpuCores = MinCpu
   186  		} else {
   187  			params.CpuCores = cores
   188  		}
   189  	}
   190  	if cons.RootDisk != nil {
   191  		size := *cons.RootDisk / 1024
   192  		if size < MinDisk {
   193  			params.RootDisk = MinDisk
   194  		} else {
   195  			params.RootDisk = size
   196  		}
   197  	}
   198  	if cons.Arch != nil {
   199  		logger.Infof("arch constraint of %q being ignored as not supported", *cons.Arch)
   200  	}
   201  	if cons.Container != nil {
   202  		logger.Infof("container constraint of %q being ignored as not supported", *cons.Container)
   203  	}
   204  	if cons.CpuPower != nil {
   205  		logger.Infof("cpu-power constraint of %v being ignored as not supported", *cons.CpuPower)
   206  	}
   207  	if cons.Tags != nil {
   208  		logger.Infof("tags constraint of %q being ignored as not supported", strings.Join(*cons.Tags, ","))
   209  	}
   210  
   211  	return params
   212  }