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