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