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