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 }