github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/utils/arch"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/cloudconfig/containerinit"
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  	"github.com/juju/juju/constraints"
    20  	"github.com/juju/juju/container"
    21  	"github.com/juju/juju/environs/imagemetadata"
    22  	"github.com/juju/juju/instance"
    23  	"github.com/juju/juju/status"
    24  )
    25  
    26  var (
    27  	logger = loggo.GetLogger("juju.container.kvm")
    28  
    29  	KvmObjectFactory ContainerFactory = &containerFactory{}
    30  
    31  	// In order for Juju to be able to create the hardware characteristics of
    32  	// the kvm machines it creates, we need to be explicit in our definition
    33  	// of memory, cores and root-disk.  The defaults here have been
    34  	// extracted from the uvt-kvm executable.
    35  	DefaultMemory uint64 = 512 // MB
    36  	DefaultCpu    uint64 = 1
    37  	DefaultDisk   uint64 = 8 // GB
    38  
    39  	// There are some values where it doesn't make sense to go below.
    40  	MinMemory uint64 = 512 // MB
    41  	MinCpu    uint64 = 1
    42  	MinDisk   uint64 = 2 // GB
    43  )
    44  
    45  // Utilized to provide a hard-coded path to kvm-ok
    46  var kvmPath = "/usr/sbin"
    47  
    48  // IsKVMSupported calls into the kvm-ok executable from the cpu-checkers package.
    49  // It is a variable to allow us to overrid behaviour in the tests.
    50  var IsKVMSupported = func() (bool, error) {
    51  
    52  	// Prefer the user's $PATH first, but check /usr/sbin if we can't
    53  	// find kvm-ok there
    54  	var foundPath string
    55  	const binName = "kvm-ok"
    56  	if path, err := exec.LookPath(binName); err == nil {
    57  		foundPath = path
    58  	} else if path, err := exec.LookPath(filepath.Join(kvmPath, binName)); err == nil {
    59  		foundPath = path
    60  	} else {
    61  		return false, errors.NotFoundf("%s executable", binName)
    62  	}
    63  
    64  	command := exec.Command(foundPath)
    65  	output, err := command.CombinedOutput()
    66  
    67  	if err != nil {
    68  		return false, errors.Annotate(err, string(output))
    69  	}
    70  	logger.Debugf("%s output:\n%s", binName, output)
    71  	return command.ProcessState.Success(), nil
    72  }
    73  
    74  // NewContainerManager returns a manager object that can start and stop kvm
    75  // containers.
    76  func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
    77  	modelUUID := conf.PopValue(container.ConfigModelUUID)
    78  	if modelUUID == "" {
    79  		return nil, errors.Errorf("model UUID is required")
    80  	}
    81  	namespace, err := instance.NewNamespace(modelUUID)
    82  	if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  	logDir := conf.PopValue(container.ConfigLogDir)
    86  	if logDir == "" {
    87  		logDir = agent.DefaultPaths.LogDir
    88  	}
    89  	conf.WarnAboutUnused()
    90  	return &containerManager{namespace: namespace, logdir: logDir}, nil
    91  }
    92  
    93  // containerManager handles all of the business logic at the juju specific
    94  // level. It makes sure that the necessary directories are in place, that the
    95  // user-data is written out in the right place.
    96  type containerManager struct {
    97  	namespace instance.Namespace
    98  	logdir    string
    99  }
   100  
   101  var _ container.Manager = (*containerManager)(nil)
   102  
   103  // Namespace implements container.Manager.
   104  func (manager *containerManager) Namespace() instance.Namespace {
   105  	return manager.namespace
   106  }
   107  
   108  // Exposed so tests can observe our side-effects
   109  var startParams StartParams
   110  
   111  func (manager *containerManager) CreateContainer(
   112  	instanceConfig *instancecfg.InstanceConfig,
   113  	cons constraints.Value,
   114  	series string,
   115  	networkConfig *container.NetworkConfig,
   116  	storageConfig *container.StorageConfig,
   117  	callback container.StatusCallback,
   118  ) (_ instance.Instance, _ *instance.HardwareCharacteristics, err error) {
   119  
   120  	name, err := manager.namespace.Hostname(instanceConfig.MachineId)
   121  	if err != nil {
   122  		return nil, nil, errors.Trace(err)
   123  	}
   124  
   125  	defer func() {
   126  		if err != nil {
   127  			callback(status.ProvisioningError, fmt.Sprintf("Creating container: %v", err), nil)
   128  		}
   129  	}()
   130  
   131  	// Set the MachineContainerHostname to match the name returned by virsh list
   132  	instanceConfig.MachineContainerHostname = name
   133  
   134  	// Note here that the kvmObjectFactory only returns a valid container
   135  	// object, and doesn't actually construct the underlying kvm container on
   136  	// disk.
   137  	kvmContainer := KvmObjectFactory.New(name)
   138  
   139  	// Create the cloud-init.
   140  	directory, err := container.NewDirectory(name)
   141  	if err != nil {
   142  		return nil, nil, errors.Annotate(err, "failed to create container directory")
   143  	}
   144  	logger.Tracef("write cloud-init")
   145  	userDataFilename, err := containerinit.WriteUserData(instanceConfig, networkConfig, directory)
   146  	if err != nil {
   147  		logger.Infof("machine config api %#v", *instanceConfig.APIInfo)
   148  		err = errors.Annotate(err, "failed to write user data")
   149  		logger.Infof(err.Error())
   150  		return nil, nil, err
   151  	}
   152  	// Create the container.
   153  	startParams = ParseConstraintsToStartParams(cons)
   154  	startParams.Arch = arch.HostArch()
   155  	startParams.Series = series
   156  	startParams.Network = networkConfig
   157  	startParams.UserDataFile = userDataFilename
   158  
   159  	// If the Simplestream requested is anything but released, update
   160  	// our StartParams to request it.
   161  	if instanceConfig.ImageStream != imagemetadata.ReleasedStream {
   162  		startParams.ImageDownloadURL = imagemetadata.UbuntuCloudImagesURL + "/" + instanceConfig.ImageStream
   163  	}
   164  
   165  	var hardware instance.HardwareCharacteristics
   166  	hardware, err = instance.ParseHardware(
   167  		fmt.Sprintf("arch=%s mem=%vM root-disk=%vG cores=%v",
   168  			startParams.Arch, startParams.Memory, startParams.RootDisk, startParams.CpuCores))
   169  	if err != nil {
   170  		return nil, nil, errors.Annotate(err, "failed to parse hardware")
   171  	}
   172  
   173  	callback(status.Allocating, "Creating container; it might take some time", nil)
   174  	logger.Tracef("create the container, constraints: %v", cons)
   175  	if err := kvmContainer.Start(startParams); err != nil {
   176  		err = errors.Annotate(err, "kvm container creation failed")
   177  		logger.Infof(err.Error())
   178  		return nil, nil, err
   179  	}
   180  	logger.Tracef("kvm container created")
   181  	return &kvmInstance{kvmContainer, name}, &hardware, nil
   182  }
   183  
   184  func (manager *containerManager) IsInitialized() bool {
   185  	requiredBinaries := []string{
   186  		"virsh",
   187  		"uvt-kvm",
   188  	}
   189  	for _, bin := range requiredBinaries {
   190  		if _, err := exec.LookPath(bin); err != nil {
   191  			return false
   192  		}
   193  	}
   194  	return true
   195  }
   196  
   197  func (manager *containerManager) DestroyContainer(id instance.Id) error {
   198  	name := string(id)
   199  	kvmContainer := KvmObjectFactory.New(name)
   200  	if err := kvmContainer.Stop(); err != nil {
   201  		logger.Errorf("failed to stop kvm container: %v", err)
   202  		return err
   203  	}
   204  	return container.RemoveDirectory(name)
   205  }
   206  
   207  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   208  	containers, err := KvmObjectFactory.List()
   209  	if err != nil {
   210  		logger.Errorf("failed getting all instances: %v", err)
   211  		return
   212  	}
   213  	managerPrefix := manager.namespace.Prefix()
   214  	for _, container := range containers {
   215  		// Filter out those not starting with our name.
   216  		name := container.Name()
   217  		if !strings.HasPrefix(name, managerPrefix) {
   218  			continue
   219  		}
   220  		if container.IsRunning() {
   221  			result = append(result, &kvmInstance{container, name})
   222  		}
   223  	}
   224  	return
   225  }
   226  
   227  // ParseConstraintsToStartParams takes a constrants object and returns a bare
   228  // StartParams object that has Memory, Cpu, and Disk populated.  If there are
   229  // no defined values in the constraints for those fields, default values are
   230  // used.  Other constrains cause a warning to be emitted.
   231  func ParseConstraintsToStartParams(cons constraints.Value) StartParams {
   232  	params := StartParams{
   233  		Memory:   DefaultMemory,
   234  		CpuCores: DefaultCpu,
   235  		RootDisk: DefaultDisk,
   236  	}
   237  
   238  	if cons.Mem != nil {
   239  		mem := *cons.Mem
   240  		if mem < MinMemory {
   241  			params.Memory = MinMemory
   242  		} else {
   243  			params.Memory = mem
   244  		}
   245  	}
   246  	if cons.CpuCores != nil {
   247  		cores := *cons.CpuCores
   248  		if cores < MinCpu {
   249  			params.CpuCores = MinCpu
   250  		} else {
   251  			params.CpuCores = cores
   252  		}
   253  	}
   254  	if cons.RootDisk != nil {
   255  		size := *cons.RootDisk / 1024
   256  		if size < MinDisk {
   257  			params.RootDisk = MinDisk
   258  		} else {
   259  			params.RootDisk = size
   260  		}
   261  	}
   262  	if cons.Arch != nil {
   263  		logger.Infof("arch constraint of %q being ignored as not supported", *cons.Arch)
   264  	}
   265  	if cons.Container != nil {
   266  		logger.Infof("container constraint of %q being ignored as not supported", *cons.Container)
   267  	}
   268  	if cons.CpuPower != nil {
   269  		logger.Infof("cpu-power constraint of %v being ignored as not supported", *cons.CpuPower)
   270  	}
   271  	if cons.Tags != nil {
   272  		logger.Infof("tags constraint of %q being ignored as not supported", strings.Join(*cons.Tags, ","))
   273  	}
   274  
   275  	return params
   276  }