github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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/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/juju/arch" 24 ) 25 26 var ( 27 logger = loggo.GetLogger("juju.container.kvm") 28 29 KvmObjectFactory ContainerFactory = &containerFactory{} 30 DefaultKvmBridge = "virbr0" 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.DefaultLogDir 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 ) (instance.Instance, *instance.HardwareCharacteristics, error) { 110 111 name := names.NewMachineTag(instanceConfig.MachineId).String() 112 if manager.name != "" { 113 name = fmt.Sprintf("%s-%s", manager.name, name) 114 } 115 116 // Set the MachineContainerHostname to match the name returned by virsh list 117 instanceConfig.MachineContainerHostname = name 118 119 // Note here that the kvmObjectFacotry only returns a valid container 120 // object, and doesn't actually construct the underlying kvm container on 121 // disk. 122 kvmContainer := KvmObjectFactory.New(name) 123 124 // Create the cloud-init. 125 directory, err := container.NewDirectory(name) 126 if err != nil { 127 return nil, nil, errors.Annotate(err, "failed to create container directory") 128 } 129 logger.Tracef("write cloud-init") 130 userDataFilename, err := containerinit.WriteUserData(instanceConfig, networkConfig, directory) 131 if err != nil { 132 logger.Infof("machine config api %#v", *instanceConfig.APIInfo) 133 err = errors.Annotate(err, "failed to write user data") 134 logger.Infof(err.Error()) 135 return nil, nil, err 136 } 137 // Create the container. 138 startParams = ParseConstraintsToStartParams(instanceConfig.Constraints) 139 startParams.Arch = arch.HostArch() 140 startParams.Series = series 141 startParams.Network = networkConfig 142 startParams.UserDataFile = userDataFilename 143 144 // If the Simplestream requested is anything but released, update 145 // our StartParams to request it. 146 if instanceConfig.ImageStream != imagemetadata.ReleasedStream { 147 startParams.ImageDownloadUrl = imagemetadata.UbuntuCloudImagesURL + "/" + instanceConfig.ImageStream 148 } 149 150 var hardware instance.HardwareCharacteristics 151 hardware, err = instance.ParseHardware( 152 fmt.Sprintf("arch=%s mem=%vM root-disk=%vG cpu-cores=%v", 153 startParams.Arch, startParams.Memory, startParams.RootDisk, startParams.CpuCores)) 154 if err != nil { 155 logger.Warningf("failed to parse hardware: %v", err) 156 } 157 158 logger.Tracef("create the container, constraints: %v", instanceConfig.Constraints) 159 if err := kvmContainer.Start(startParams); err != nil { 160 err = errors.Annotate(err, "kvm container creation failed") 161 logger.Infof(err.Error()) 162 return nil, nil, err 163 } 164 logger.Tracef("kvm container created") 165 return &kvmInstance{kvmContainer, name}, &hardware, nil 166 } 167 168 func (manager *containerManager) IsInitialized() bool { 169 requiredBinaries := []string{ 170 "virsh", 171 "uvt-kvm", 172 } 173 for _, bin := range requiredBinaries { 174 if _, err := exec.LookPath(bin); err != nil { 175 return false 176 } 177 } 178 return true 179 } 180 181 func (manager *containerManager) DestroyContainer(id instance.Id) error { 182 name := string(id) 183 kvmContainer := KvmObjectFactory.New(name) 184 if err := kvmContainer.Stop(); err != nil { 185 logger.Errorf("failed to stop kvm container: %v", err) 186 return err 187 } 188 return container.RemoveDirectory(name) 189 } 190 191 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 192 containers, err := KvmObjectFactory.List() 193 if err != nil { 194 logger.Errorf("failed getting all instances: %v", err) 195 return 196 } 197 managerPrefix := fmt.Sprintf("%s-", manager.name) 198 for _, container := range containers { 199 // Filter out those not starting with our name. 200 name := container.Name() 201 if !strings.HasPrefix(name, managerPrefix) { 202 continue 203 } 204 if container.IsRunning() { 205 result = append(result, &kvmInstance{container, name}) 206 } 207 } 208 return 209 } 210 211 // ParseConstraintsToStartParams takes a constrants object and returns a bare 212 // StartParams object that has Memory, Cpu, and Disk populated. If there are 213 // no defined values in the constraints for those fields, default values are 214 // used. Other constrains cause a warning to be emitted. 215 func ParseConstraintsToStartParams(cons constraints.Value) StartParams { 216 params := StartParams{ 217 Memory: DefaultMemory, 218 CpuCores: DefaultCpu, 219 RootDisk: DefaultDisk, 220 } 221 222 if cons.Mem != nil { 223 mem := *cons.Mem 224 if mem < MinMemory { 225 params.Memory = MinMemory 226 } else { 227 params.Memory = mem 228 } 229 } 230 if cons.CpuCores != nil { 231 cores := *cons.CpuCores 232 if cores < MinCpu { 233 params.CpuCores = MinCpu 234 } else { 235 params.CpuCores = cores 236 } 237 } 238 if cons.RootDisk != nil { 239 size := *cons.RootDisk / 1024 240 if size < MinDisk { 241 params.RootDisk = MinDisk 242 } else { 243 params.RootDisk = size 244 } 245 } 246 if cons.Arch != nil { 247 logger.Infof("arch constraint of %q being ignored as not supported", *cons.Arch) 248 } 249 if cons.Container != nil { 250 logger.Infof("container constraint of %q being ignored as not supported", *cons.Container) 251 } 252 if cons.CpuPower != nil { 253 logger.Infof("cpu-power constraint of %v being ignored as not supported", *cons.CpuPower) 254 } 255 if cons.Tags != nil { 256 logger.Infof("tags constraint of %q being ignored as not supported", strings.Join(*cons.Tags, ",")) 257 } 258 259 return params 260 }