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 }