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