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