github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/lxd/environ_broker.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // +build go1.3 5 6 package lxd 7 8 import ( 9 "fmt" 10 "strings" 11 12 "github.com/juju/errors" 13 "github.com/juju/utils/arch" 14 15 "github.com/juju/juju/agent" 16 "github.com/juju/juju/cloudconfig/instancecfg" 17 "github.com/juju/juju/cloudconfig/providerinit" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/tags" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/provider/common" 22 "github.com/juju/juju/state/multiwatcher" 23 "github.com/juju/juju/status" 24 "github.com/juju/juju/tools" 25 "github.com/juju/juju/tools/lxdclient" 26 ) 27 28 func isController(icfg *instancecfg.InstanceConfig) bool { 29 return multiwatcher.AnyJobNeedsState(icfg.Jobs...) 30 } 31 32 // MaintainInstance is specified in the InstanceBroker interface. 33 func (*environ) MaintainInstance(args environs.StartInstanceParams) error { 34 return nil 35 } 36 37 // StartInstance implements environs.InstanceBroker. 38 func (env *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 39 // Start a new instance. 40 41 series := args.Tools.OneSeries() 42 logger.Debugf("StartInstance: %q, %s", args.InstanceConfig.MachineId, series) 43 44 if err := env.finishInstanceConfig(args); err != nil { 45 return nil, errors.Trace(err) 46 } 47 48 // TODO(ericsnow) Handle constraints? 49 50 raw, err := env.newRawInstance(args) 51 if err != nil { 52 if args.StatusCallback != nil { 53 args.StatusCallback(status.StatusProvisioningError, err.Error(), nil) 54 } 55 return nil, errors.Trace(err) 56 } 57 logger.Infof("started instance %q", raw.Name) 58 inst := newInstance(raw, env) 59 60 // Build the result. 61 hwc := env.getHardwareCharacteristics(args, inst) 62 result := environs.StartInstanceResult{ 63 Instance: inst, 64 Hardware: hwc, 65 } 66 return &result, nil 67 } 68 69 func (env *environ) finishInstanceConfig(args environs.StartInstanceParams) error { 70 // TODO(natefinch): This is only correct so long as the lxd is running on 71 // the local machine. If/when we support a remote lxd environment, we'll 72 // need to change this to match the arch of the remote machine. 73 tools, err := args.Tools.Match(tools.Filter{Arch: arch.HostArch()}) 74 if err != nil { 75 return errors.Trace(err) 76 } 77 if len(tools) == 0 { 78 return errors.Errorf("No tools available for architecture %q", arch.HostArch()) 79 } 80 if err := args.InstanceConfig.SetTools(tools); err != nil { 81 return errors.Trace(err) 82 } 83 logger.Debugf("tools: %#v", args.InstanceConfig.ToolsList()) 84 85 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, env.ecfg.Config); err != nil { 86 return errors.Trace(err) 87 } 88 89 // TODO: evaluate the impact of setting the constraints on the 90 // instanceConfig for all machines rather than just controller nodes. 91 // This limitation is why the constraints are assigned directly here. 92 args.InstanceConfig.Constraints = args.Constraints 93 94 args.InstanceConfig.AgentEnvironment[agent.Namespace] = env.ecfg.namespace() 95 96 return nil 97 } 98 99 func (env *environ) getImageSources() ([]lxdclient.Remote, error) { 100 metadataSources, err := environs.ImageMetadataSources(env) 101 if err != nil { 102 return nil, errors.Trace(err) 103 } 104 remotes := make([]lxdclient.Remote, 0) 105 for _, source := range metadataSources { 106 url, err := source.URL("") 107 if err != nil { 108 logger.Debugf("failed to get the URL for metadataSource: %s", err) 109 continue 110 } 111 // NOTE(jam) LXD only allows you to pass HTTPS URLs. So strip 112 // off http:// and replace it with https:// 113 // Arguably we could give the user a direct error if 114 // env.ImageMetadataURL is http instead of https, but we also 115 // get http from the DefaultImageSources, which is why we 116 // replace it. 117 // TODO(jam) Maybe we could add a Validate step that ensures 118 // image-metadata-url is an "https://" URL, so that Users get a 119 // "your configuration is wrong" error, rather than silently 120 // changing it and having them get confused. 121 // https://github.com/lxc/lxd/issues/1763 122 if strings.HasPrefix(url, "http://") { 123 url = strings.TrimPrefix(url, "http://") 124 url = "https://" + url 125 logger.Debugf("LXD requires https://, using: %s", url) 126 } 127 remotes = append(remotes, lxdclient.Remote{ 128 Name: source.Description(), 129 Host: url, 130 Protocol: lxdclient.SimplestreamsProtocol, 131 Cert: nil, 132 ServerPEMCert: "", 133 }) 134 } 135 return remotes, nil 136 } 137 138 // newRawInstance is where the new physical instance is actually 139 // provisioned, relative to the provided args and spec. Info for that 140 // low-level instance is returned. 141 func (env *environ) newRawInstance(args environs.StartInstanceParams) (*lxdclient.Instance, error) { 142 machineID := common.MachineFullName(env.Config().UUID(), args.InstanceConfig.MachineId) 143 144 // Note: other providers have the ImageMetadata already read for them 145 // and passed in as args.ImageMetadata. However, lxd provider doesn't 146 // use datatype: image-ids, it uses datatype: image-download, and we 147 // don't have a registered cloud/region. 148 imageSources, err := env.getImageSources() 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 153 series := args.Tools.OneSeries() 154 // TODO(jam): We should get this information from EnsureImageExists, or 155 // something given to us from 'raw', not assume it ourselves. 156 image := "ubuntu-" + series 157 // TODO: support args.Constraints.Arch, we'll want to map from 158 159 var callback func(string) 160 if args.StatusCallback != nil { 161 callback = func(copyProgress string) { 162 args.StatusCallback(status.StatusAllocating, copyProgress, nil) 163 } 164 } 165 if err := env.raw.EnsureImageExists(series, imageSources, callback); err != nil { 166 return nil, errors.Trace(err) 167 } 168 169 metadata, err := getMetadata(args) 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 //tags := []string{ 174 // env.globalFirewallName(), 175 // machineID, 176 //} 177 // TODO(ericsnow) Use the env ID for the network name (instead of default)? 178 // TODO(ericsnow) Make the network name configurable? 179 // TODO(ericsnow) Support multiple networks? 180 // TODO(ericsnow) Use a different net interface name? Configurable? 181 instSpec := lxdclient.InstanceSpec{ 182 Name: machineID, 183 Image: image, 184 //Type: spec.InstanceType.Name, 185 //Disks: getDisks(spec, args.Constraints), 186 //NetworkInterfaces: []string{"ExternalNAT"}, 187 Metadata: metadata, 188 Profiles: []string{ 189 //TODO(wwitzel3) allow the user to specify lxc profiles to apply. This allows the 190 // user to setup any custom devices order config settings for their environment. 191 // Also we must ensure that a device with the parent: lxcbr0 exists in at least 192 // one of the profiles. 193 "default", 194 env.profileName(), 195 }, 196 //Tags: tags, 197 // Network is omitted (left empty). 198 } 199 200 logger.Infof("starting instance %q (image %q)...", instSpec.Name, instSpec.Image) 201 if args.StatusCallback != nil { 202 args.StatusCallback(status.StatusAllocating, "starting instance", nil) 203 } 204 inst, err := env.raw.AddInstance(instSpec) 205 if err != nil { 206 return nil, errors.Trace(err) 207 } 208 if args.StatusCallback != nil { 209 args.StatusCallback(status.StatusRunning, "Container started", nil) 210 } 211 return inst, nil 212 } 213 214 // getMetadata builds the raw "user-defined" metadata for the new 215 // instance (relative to the provided args) and returns it. 216 func getMetadata(args environs.StartInstanceParams) (map[string]string, error) { 217 renderer := lxdRenderer{} 218 uncompressed, err := providerinit.ComposeUserData(args.InstanceConfig, nil, renderer) 219 if err != nil { 220 return nil, errors.Annotate(err, "cannot make user data") 221 } 222 logger.Debugf("LXD user data; %d bytes", len(uncompressed)) 223 224 // TODO(ericsnow) Looks like LXD does not handle gzipped userdata 225 // correctly. It likely has to do with the HTTP transport, much 226 // as we have to b64encode the userdata for GCE. Until that is 227 // resolved we simply pass the plain text. 228 //compressed := utils.Gzip(compressed) 229 userdata := string(uncompressed) 230 231 metadata := map[string]string{ 232 // store the cloud-config userdata for cloud-init. 233 metadataKeyCloudInit: userdata, 234 } 235 for k, v := range args.InstanceConfig.Tags { 236 if !strings.HasPrefix(k, tags.JujuTagPrefix) { 237 // Since some metadata is interpreted by LXD, 238 // we cannot allow arbitrary tags to be passed 239 // in by the user. We currently only pass through 240 // Juju-defined tags. 241 // 242 // TODO(axw) 2016-04-11 #1568666 243 // We should reject non-juju tags in config validation. 244 logger.Debugf("ignoring non-juju tag: %s=%s", k, v) 245 continue 246 } 247 metadata[k] = v 248 } 249 250 return metadata, nil 251 } 252 253 // getHardwareCharacteristics compiles hardware-related details about 254 // the given instance and relative to the provided spec and returns it. 255 func (env *environ) getHardwareCharacteristics(args environs.StartInstanceParams, inst *environInstance) *instance.HardwareCharacteristics { 256 raw := inst.raw.Hardware 257 258 archStr := raw.Architecture 259 if archStr == "unknown" || !arch.IsSupportedArch(archStr) { 260 // TODO(ericsnow) This special-case should be improved. 261 archStr = arch.HostArch() 262 } 263 264 hwc, err := instance.ParseHardware( 265 "arch="+archStr, 266 fmt.Sprintf("cpu-cores=%d", raw.NumCores), 267 fmt.Sprintf("mem=%dM", raw.MemoryMB), 268 //"root-disk=", 269 //"tags=", 270 ) 271 if err != nil { 272 logger.Errorf("unexpected problem parsing hardware info: %v", err) 273 // Keep moving... 274 } 275 return &hwc 276 } 277 278 // AllInstances implements environs.InstanceBroker. 279 func (env *environ) AllInstances() ([]instance.Instance, error) { 280 environInstances, err := env.allInstances() 281 instances := make([]instance.Instance, len(environInstances)) 282 for i, inst := range environInstances { 283 if inst == nil { 284 continue 285 } 286 instances[i] = inst 287 } 288 return instances, err 289 } 290 291 // StopInstances implements environs.InstanceBroker. 292 func (env *environ) StopInstances(instances ...instance.Id) error { 293 var ids []string 294 for _, id := range instances { 295 ids = append(ids, string(id)) 296 } 297 298 prefix := common.MachineFullName(env.Config().UUID(), "") 299 err := env.raw.RemoveInstances(prefix, ids...) 300 return errors.Trace(err) 301 }