// StartInstance implements environs.InstanceBroker. func (env *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { // Start a new instance. series := args.Tools.OneSeries() logger.Debugf("StartInstance: %q, %s", args.InstanceConfig.MachineId, series) if err := env.finishInstanceConfig(args); err != nil { return nil, errors.Trace(err) } // TODO(ericsnow) Handle constraints? raw, err := env.newRawInstance(args) if err != nil { if args.StatusCallback != nil { args.StatusCallback(status.ProvisioningError, err.Error(), nil) } return nil, errors.Trace(err) } logger.Infof("started instance %q", raw.Name) inst := newInstance(raw, env) // Build the result. hwc := env.getHardwareCharacteristics(args, inst) result := environs.StartInstanceResult{ Instance: inst, Hardware: hwc, } return &result, nil }
// newRawInstance is where the new physical instance is actually // provisioned, relative to the provided args and spec. Info for that // low-level instance is returned. func (env *environ) newRawInstance(args environs.StartInstanceParams) (*lxdclient.Instance, error) { hostname, err := env.namespace.Hostname(args.InstanceConfig.MachineId) if err != nil { return nil, errors.Trace(err) } // Note: other providers have the ImageMetadata already read for them // and passed in as args.ImageMetadata. However, lxd provider doesn't // use datatype: image-ids, it uses datatype: image-download, and we // don't have a registered cloud/region. imageSources, err := env.getImageSources() if err != nil { return nil, errors.Trace(err) } series := args.InstanceConfig.Series // TODO(jam): We should get this information from EnsureImageExists, or // something given to us from 'raw', not assume it ourselves. image := "ubuntu-" + series // TODO: support args.Constraints.Arch, we'll want to map from // Keep track of StatusCallback output so we may clean up later. // This is implemented here, close to where the StatusCallback calls // are made, instead of at a higher level in the package, so as not to // assume that all providers will have the same need to be implemented // in the same way. longestMsg := 0 statusCallback := func(currentStatus status.Status, msg string) { if args.StatusCallback != nil { args.StatusCallback(currentStatus, msg, nil) } if len(msg) > longestMsg { longestMsg = len(msg) } } cleanupCallback := func() { if args.CleanupCallback != nil { args.CleanupCallback(strings.Repeat(" ", longestMsg)) } } defer cleanupCallback() imageCallback := func(copyProgress string) { statusCallback(status.Allocating, copyProgress) } if err := env.raw.EnsureImageExists(series, imageSources, imageCallback); err != nil { return nil, errors.Trace(err) } cleanupCallback() // Clean out any long line of completed download status cloudcfg, err := cloudinit.New(series) if err != nil { return nil, errors.Trace(err) } var certificateFingerprint string if args.InstanceConfig.Controller != nil { // For controller machines, generate a certificate pair and write // them to the instance's disk in a well-defined location, along // with the server's certificate. certPEM, keyPEM, err := lxdshared.GenerateMemCert(true) if err != nil { return nil, errors.Trace(err) } cert := lxdclient.NewCert(certPEM, keyPEM) cert.Name = hostname // We record the certificate's fingerprint in metadata, so we can // remove the certificate along with the instance. certificateFingerprint, err = cert.Fingerprint() if err != nil { return nil, errors.Trace(err) } if err := env.raw.AddCert(cert); err != nil { return nil, errors.Annotatef(err, "adding certificate %q", cert.Name) } serverState, err := env.raw.ServerStatus() if err != nil { return nil, errors.Annotate(err, "getting server status") } cloudcfg.AddRunTextFile(clientCertPath, string(certPEM), 0600) cloudcfg.AddRunTextFile(clientKeyPath, string(keyPEM), 0600) cloudcfg.AddRunTextFile(serverCertPath, serverState.Environment.Certificate, 0600) } cloudcfg.SetAttr("hostname", hostname) cloudcfg.SetAttr("manage_etc_hosts", true) metadata, err := getMetadata(cloudcfg, args) if err != nil { return nil, errors.Trace(err) } if certificateFingerprint != "" { metadata[metadataKeyCertificateFingerprint] = certificateFingerprint } // TODO(ericsnow) Use the env ID for the network name (instead of default)? // TODO(ericsnow) Make the network name configurable? // TODO(ericsnow) Support multiple networks? // TODO(ericsnow) Use a different net interface name? Configurable? instSpec := lxdclient.InstanceSpec{ Name: hostname, Image: image, //Type: spec.InstanceType.Name, //Disks: getDisks(spec, args.Constraints), //NetworkInterfaces: []string{"ExternalNAT"}, Metadata: metadata, Profiles: []string{ //TODO(wwitzel3) allow the user to specify lxc profiles to apply. This allows the // user to setup any custom devices order config settings for their environment. // Also we must ensure that a device with the parent: lxcbr0 exists in at least // one of the profiles. "default", env.profileName(), }, // Network is omitted (left empty). } logger.Infof("starting instance %q (image %q)...", instSpec.Name, instSpec.Image) statusCallback(status.Allocating, "preparing image") inst, err := env.raw.AddInstance(instSpec) if err != nil { return nil, errors.Trace(err) } statusCallback(status.Running, "container started") return inst, nil }
// newRawInstance is where the new physical instance is actually // provisioned, relative to the provided args and spec. Info for that // low-level instance is returned. func (env *environ) newRawInstance(args environs.StartInstanceParams) (*lxdclient.Instance, error) { machineID := common.MachineFullName(env.Config().UUID(), args.InstanceConfig.MachineId) // Note: other providers have the ImageMetadata already read for them // and passed in as args.ImageMetadata. However, lxd provider doesn't // use datatype: image-ids, it uses datatype: image-download, and we // don't have a registered cloud/region. imageSources, err := env.getImageSources() if err != nil { return nil, errors.Trace(err) } series := args.Tools.OneSeries() // TODO(jam): We should get this information from EnsureImageExists, or // something given to us from 'raw', not assume it ourselves. image := "ubuntu-" + series // TODO: support args.Constraints.Arch, we'll want to map from var callback func(string) if args.StatusCallback != nil { callback = func(copyProgress string) { args.StatusCallback(status.StatusAllocating, copyProgress, nil) } } if err := env.raw.EnsureImageExists(series, imageSources, callback); err != nil { return nil, errors.Trace(err) } metadata, err := getMetadata(args) if err != nil { return nil, errors.Trace(err) } //tags := []string{ // env.globalFirewallName(), // machineID, //} // TODO(ericsnow) Use the env ID for the network name (instead of default)? // TODO(ericsnow) Make the network name configurable? // TODO(ericsnow) Support multiple networks? // TODO(ericsnow) Use a different net interface name? Configurable? instSpec := lxdclient.InstanceSpec{ Name: machineID, Image: image, //Type: spec.InstanceType.Name, //Disks: getDisks(spec, args.Constraints), //NetworkInterfaces: []string{"ExternalNAT"}, Metadata: metadata, Profiles: []string{ //TODO(wwitzel3) allow the user to specify lxc profiles to apply. This allows the // user to setup any custom devices order config settings for their environment. // Also we must ensure that a device with the parent: lxcbr0 exists in at least // one of the profiles. "default", env.profileName(), }, //Tags: tags, // Network is omitted (left empty). } logger.Infof("starting instance %q (image %q)...", instSpec.Name, instSpec.Image) if args.StatusCallback != nil { args.StatusCallback(status.StatusAllocating, "starting instance", nil) } inst, err := env.raw.AddInstance(instSpec) if err != nil { return nil, errors.Trace(err) } if args.StatusCallback != nil { args.StatusCallback(status.StatusRunning, "Container started", nil) } return inst, nil }