github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/constraints"
    18  	"github.com/juju/juju/container"
    19  	"github.com/juju/juju/environs/cloudinit"
    20  	"github.com/juju/juju/environs/imagemetadata"
    21  	"github.com/juju/juju/instance"
    22  	"github.com/juju/juju/version"
    23  )
    24  
    25  var (
    26  	logger = loggo.GetLogger("juju.container.kvm")
    27  
    28  	KvmObjectFactory ContainerFactory = &containerFactory{}
    29  	DefaultKvmBridge                  = "virbr0"
    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, cpu-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. The containers that are created are namespaced by the name
    76  // parameter.
    77  func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) {
    78  	name := conf.PopValue(container.ConfigName)
    79  	if name == "" {
    80  		return nil, fmt.Errorf("name is required")
    81  	}
    82  	logDir := conf.PopValue(container.ConfigLogDir)
    83  	if logDir == "" {
    84  		logDir = agent.DefaultLogDir
    85  	}
    86  	conf.WarnAboutUnused()
    87  	return &containerManager{name: name, logdir: logDir}, nil
    88  }
    89  
    90  // containerManager handles all of the business logic at the juju specific
    91  // level. It makes sure that the necessary directories are in place, that the
    92  // user-data is written out in the right place.
    93  type containerManager struct {
    94  	name   string
    95  	logdir string
    96  }
    97  
    98  var _ container.Manager = (*containerManager)(nil)
    99  
   100  // Exposed so tests can observe our side-effects
   101  var startParams StartParams
   102  
   103  func (manager *containerManager) CreateContainer(
   104  	machineConfig *cloudinit.MachineConfig,
   105  	series string,
   106  	networkConfig *container.NetworkConfig,
   107  ) (instance.Instance, *instance.HardwareCharacteristics, error) {
   108  
   109  	name := names.NewMachineTag(machineConfig.MachineId).String()
   110  	if manager.name != "" {
   111  		name = fmt.Sprintf("%s-%s", manager.name, name)
   112  	}
   113  	// Note here that the kvmObjectFacotry only returns a valid container
   114  	// object, and doesn't actually construct the underlying kvm container on
   115  	// disk.
   116  	kvmContainer := KvmObjectFactory.New(name)
   117  
   118  	// Create the cloud-init.
   119  	directory, err := container.NewDirectory(name)
   120  	if err != nil {
   121  		return nil, nil, errors.Annotate(err, "failed to create container directory")
   122  	}
   123  	logger.Tracef("write cloud-init")
   124  	userDataFilename, err := container.WriteUserData(machineConfig, networkConfig, directory)
   125  	if err != nil {
   126  		logger.Infof("machine config api %#v", *machineConfig.APIInfo)
   127  		err = errors.Annotate(err, "failed to write user data")
   128  		logger.Infof(err.Error())
   129  		return nil, nil, err
   130  	}
   131  	// Create the container.
   132  	startParams = ParseConstraintsToStartParams(machineConfig.Constraints)
   133  	startParams.Arch = version.Current.Arch
   134  	startParams.Series = series
   135  	startParams.Network = networkConfig
   136  	startParams.UserDataFile = userDataFilename
   137  
   138  	// If the Simplestream requested is anything but released, update
   139  	// our StartParams to request it.
   140  	if machineConfig.ImageStream != imagemetadata.ReleasedStream {
   141  		startParams.ImageDownloadUrl = imagemetadata.UbuntuCloudImagesURL + "/" + machineConfig.ImageStream
   142  	}
   143  
   144  	var hardware instance.HardwareCharacteristics
   145  	hardware, err = instance.ParseHardware(
   146  		fmt.Sprintf("arch=%s mem=%vM root-disk=%vG cpu-cores=%v",
   147  			startParams.Arch, startParams.Memory, startParams.RootDisk, startParams.CpuCores))
   148  	if err != nil {
   149  		logger.Warningf("failed to parse hardware: %v", err)
   150  	}
   151  
   152  	logger.Tracef("create the container, constraints: %v", machineConfig.Constraints)
   153  	if err := kvmContainer.Start(startParams); err != nil {
   154  		err = errors.Annotate(err, "kvm container creation failed")
   155  		logger.Infof(err.Error())
   156  		return nil, nil, err
   157  	}
   158  	logger.Tracef("kvm container created")
   159  	return &kvmInstance{kvmContainer, name}, &hardware, nil
   160  }
   161  
   162  func (manager *containerManager) IsInitialized() bool {
   163  	requiredBinaries := []string{
   164  		"virsh",
   165  		"uvt-kvm",
   166  	}
   167  	for _, bin := range requiredBinaries {
   168  		if _, err := exec.LookPath(bin); err != nil {
   169  			return false
   170  		}
   171  	}
   172  	return true
   173  }
   174  
   175  func (manager *containerManager) DestroyContainer(id instance.Id) error {
   176  	name := string(id)
   177  	kvmContainer := KvmObjectFactory.New(name)
   178  	if err := kvmContainer.Stop(); err != nil {
   179  		logger.Errorf("failed to stop kvm container: %v", err)
   180  		return err
   181  	}
   182  	return container.RemoveDirectory(name)
   183  }
   184  
   185  func (manager *containerManager) ListContainers() (result []instance.Instance, err error) {
   186  	containers, err := KvmObjectFactory.List()
   187  	if err != nil {
   188  		logger.Errorf("failed getting all instances: %v", err)
   189  		return
   190  	}
   191  	managerPrefix := fmt.Sprintf("%s-", manager.name)
   192  	for _, container := range containers {
   193  		// Filter out those not starting with our name.
   194  		name := container.Name()
   195  		if !strings.HasPrefix(name, managerPrefix) {
   196  			continue
   197  		}
   198  		if container.IsRunning() {
   199  			result = append(result, &kvmInstance{container, name})
   200  		}
   201  	}
   202  	return
   203  }
   204  
   205  // ParseConstraintsToStartParams takes a constrants object and returns a bare
   206  // StartParams object that has Memory, Cpu, and Disk populated.  If there are
   207  // no defined values in the constraints for those fields, default values are
   208  // used.  Other constrains cause a warning to be emitted.
   209  func ParseConstraintsToStartParams(cons constraints.Value) StartParams {
   210  	params := StartParams{
   211  		Memory:   DefaultMemory,
   212  		CpuCores: DefaultCpu,
   213  		RootDisk: DefaultDisk,
   214  	}
   215  
   216  	if cons.Mem != nil {
   217  		mem := *cons.Mem
   218  		if mem < MinMemory {
   219  			params.Memory = MinMemory
   220  		} else {
   221  			params.Memory = mem
   222  		}
   223  	}
   224  	if cons.CpuCores != nil {
   225  		cores := *cons.CpuCores
   226  		if cores < MinCpu {
   227  			params.CpuCores = MinCpu
   228  		} else {
   229  			params.CpuCores = cores
   230  		}
   231  	}
   232  	if cons.RootDisk != nil {
   233  		size := *cons.RootDisk / 1024
   234  		if size < MinDisk {
   235  			params.RootDisk = MinDisk
   236  		} else {
   237  			params.RootDisk = size
   238  		}
   239  	}
   240  	if cons.Arch != nil {
   241  		logger.Infof("arch constraint of %q being ignored as not supported", *cons.Arch)
   242  	}
   243  	if cons.Container != nil {
   244  		logger.Infof("container constraint of %q being ignored as not supported", *cons.Container)
   245  	}
   246  	if cons.CpuPower != nil {
   247  		logger.Infof("cpu-power constraint of %v being ignored as not supported", *cons.CpuPower)
   248  	}
   249  	if cons.Tags != nil {
   250  		logger.Infof("tags constraint of %q being ignored as not supported", strings.Join(*cons.Tags, ","))
   251  	}
   252  
   253  	return params
   254  }