launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "strings" 10 11 "github.com/loggo/loggo" 12 "launchpad.net/errgo/errors" 13 14 "launchpad.net/juju-core/constraints" 15 "launchpad.net/juju-core/container" 16 "launchpad.net/juju-core/environs/cloudinit" 17 "launchpad.net/juju-core/instance" 18 "launchpad.net/juju-core/log" 19 "launchpad.net/juju-core/names" 20 "launchpad.net/juju-core/version" 21 ) 22 23 var ( 24 logger = loggo.GetLogger("juju.container.kvm") 25 26 mask = errors.Mask 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 // IsKVMSupported calls into the kvm-ok executable from the cpu-checkers package. 46 // It is a variable to allow us to overrid behaviour in the tests. 47 var IsKVMSupported = func() (bool, error) { 48 command := exec.Command("kvm-ok") 49 output, err := command.CombinedOutput() 50 if err != nil { 51 return false, err 52 } 53 logger.Debugf("kvm-ok output:\n%s", output) 54 return command.ProcessState.Success(), nil 55 } 56 57 // NewContainerManager returns a manager object that can start and stop kvm 58 // containers. The containers that are created are namespaced by the name 59 // parameter. 60 func NewContainerManager(conf container.ManagerConfig) (container.Manager, error) { 61 if conf.Name == "" { 62 return nil, fmt.Errorf("name is required") 63 } 64 if conf.LogDir == "" { 65 conf.LogDir = "/var/log/juju" 66 } 67 return &containerManager{name: conf.Name, logdir: conf.LogDir}, nil 68 } 69 70 // containerManager handles all of the business logic at the juju specific 71 // level. It makes sure that the necessary directories are in place, that the 72 // user-data is written out in the right place. 73 type containerManager struct { 74 name string 75 logdir string 76 } 77 78 var _ container.Manager = (*containerManager)(nil) 79 80 func (manager *containerManager) StartContainer( 81 machineConfig *cloudinit.MachineConfig, 82 series string, 83 network *container.NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 84 85 name := names.MachineTag(machineConfig.MachineId) 86 if manager.name != "" { 87 name = fmt.Sprintf("%s-%s", manager.name, name) 88 } 89 // Note here that the kvmObjectFacotry only returns a valid container 90 // object, and doesn't actually construct the underlying kvm container on 91 // disk. 92 kvmContainer := KvmObjectFactory.New(name) 93 94 // Create the cloud-init. 95 directory, err := container.NewDirectory(name) 96 if err != nil { 97 return nil, nil, fmt.Errorf("failed to create container directory: %v", err) 98 } 99 logger.Tracef("write cloud-init") 100 userDataFilename, err := container.WriteUserData(machineConfig, directory) 101 if err != nil { 102 return nil, nil, log.LoggedErrorf(logger, "failed to write user data: %v", err) 103 } 104 // Create the container. 105 startParams := ParseConstraintsToStartParams(machineConfig.Constraints) 106 startParams.Arch = version.Current.Arch 107 startParams.Series = series 108 startParams.Network = network 109 startParams.UserDataFile = userDataFilename 110 111 var hardware instance.HardwareCharacteristics 112 hardware, err = instance.ParseHardware( 113 fmt.Sprintf("arch=%s mem=%vM root-disk=%vG cpu-cores=%v", 114 startParams.Arch, startParams.Memory, startParams.RootDisk, startParams.CpuCores)) 115 if err != nil { 116 logger.Warningf("failed to parse hardware: %v", err) 117 } 118 119 logger.Tracef("create the container, constraints: %v", machineConfig.Constraints) 120 if err := kvmContainer.Start(startParams); err != nil { 121 return nil, nil, log.LoggedErrorf(logger, "kvm container creation failed: %v", err) 122 } 123 logger.Tracef("kvm container created") 124 return &kvmInstance{kvmContainer, name}, &hardware, nil 125 } 126 127 func (manager *containerManager) StopContainer(instance instance.Instance) error { 128 name := string(instance.Id()) 129 kvmContainer := KvmObjectFactory.New(name) 130 if err := kvmContainer.Stop(); err != nil { 131 logger.Errorf("failed to stop kvm container: %v", err) 132 return err 133 } 134 return container.RemoveDirectory(name) 135 } 136 137 func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { 138 containers, err := KvmObjectFactory.List() 139 if err != nil { 140 logger.Errorf("failed getting all instances: %v", err) 141 return 142 } 143 managerPrefix := fmt.Sprintf("%s-", manager.name) 144 for _, container := range containers { 145 // Filter out those not starting with our name. 146 name := container.Name() 147 if !strings.HasPrefix(name, managerPrefix) { 148 continue 149 } 150 if container.IsRunning() { 151 result = append(result, &kvmInstance{container, name}) 152 } 153 } 154 return 155 } 156 157 // ParseConstraintsToStartParams takes a constrants object and returns a bare 158 // StartParams object that has Memory, Cpu, and Disk populated. If there are 159 // no defined values in the constraints for those fields, default values are 160 // used. Other constrains cause a warning to be emitted. 161 func ParseConstraintsToStartParams(cons constraints.Value) StartParams { 162 params := StartParams{ 163 Memory: DefaultMemory, 164 CpuCores: DefaultCpu, 165 RootDisk: DefaultDisk, 166 } 167 168 if cons.Mem != nil { 169 mem := *cons.Mem 170 if mem < MinMemory { 171 params.Memory = MinMemory 172 } else { 173 params.Memory = mem 174 } 175 } 176 if cons.CpuCores != nil { 177 cores := *cons.CpuCores 178 if cores < MinCpu { 179 params.CpuCores = MinCpu 180 } else { 181 params.CpuCores = cores 182 } 183 } 184 if cons.RootDisk != nil { 185 size := *cons.RootDisk / 1024 186 if size < MinDisk { 187 params.RootDisk = MinDisk 188 } else { 189 params.RootDisk = size 190 } 191 } 192 if cons.Arch != nil { 193 logger.Infof("arch constraint of %q being ignored as not supported", *cons.Arch) 194 } 195 if cons.Container != nil { 196 logger.Infof("container constraint of %q being ignored as not supported", *cons.Container) 197 } 198 if cons.CpuPower != nil { 199 logger.Infof("cpu-power constraint of %v being ignored as not supported", *cons.CpuPower) 200 } 201 if cons.Tags != nil { 202 logger.Infof("tags constraint of %q being ignored as not supported", strings.Join(*cons.Tags, ",")) 203 } 204 205 return params 206 }