func (e *Environ) Instances(ids []instance.Id) ([]instance.Instance, error) { if len(ids) == 0 { return nil, nil } // Make a series of requests to cope with eventual consistency. // Each request will attempt to add more instances to the requested // set. var foundServers []nova.ServerDetail for a := shortAttempt.Start(); a.Next(); { var err error foundServers, err = e.listServers(ids) if err != nil { logger.Debugf("error listing servers: %v", err) if !gooseerrors.IsNotFound(err) { return nil, err } } if len(foundServers) == len(ids) { break } } logger.Tracef("%d/%d live servers found", len(foundServers), len(ids)) if len(foundServers) == 0 { return nil, environs.ErrNoInstances } instsById := make(map[string]instance.Instance, len(foundServers)) for i, server := range foundServers { // TODO(wallyworld): lookup the flavor details to fill in the // instance type data instsById[server.Id] = &openstackInstance{ e: e, serverDetail: &foundServers[i], } } // Update the instance structs with any floating IP address that has been assigned to the instance. if e.ecfg().useFloatingIP() { if err := e.updateFloatingIPAddresses(instsById); err != nil { return nil, err } } insts := make([]instance.Instance, len(ids)) var err error for i, id := range ids { if inst := instsById[string(id)]; inst != nil { insts[i] = inst } else { err = environs.ErrPartialInstances } } return insts, err }
// DiscardSecurityGroup cleans up a security group, it is not an error to // delete something that doesn't exist. func DiscardSecurityGroup(e environs.Environ, name string) error { env := e.(*environ) novaClient := env.nova() group, err := novaClient.SecurityGroupByName(name) if err != nil { if errors.IsNotFound(err) { // Group already deleted, done return nil } } err = novaClient.DeleteSecurityGroup(group.Id) if err != nil { return err } return nil }
func (e *Environ) terminateInstances(ids []instance.Id) error { if len(ids) == 0 { return nil } var firstErr error novaClient := e.nova() for _, id := range ids { err := novaClient.DeleteServer(string(id)) if gooseerrors.IsNotFound(err) { err = nil } if err != nil && firstErr == nil { logger.Debugf("error terminating instance %q: %v", id, err) firstErr = err } } return firstErr }
// StartInstance is specified in the InstanceBroker interface. func (e *Environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { var availabilityZones []string if args.Placement != "" { placement, err := e.parsePlacement(args.Placement) if err != nil { return nil, err } if !placement.availabilityZone.State.Available { return nil, errors.Errorf("availability zone %q is unavailable", placement.availabilityZone.Name) } availabilityZones = append(availabilityZones, placement.availabilityZone.Name) } // If no availability zone is specified, then automatically spread across // the known zones for optimal spread across the instance distribution // group. if len(availabilityZones) == 0 { var group []instance.Id var err error if args.DistributionGroup != nil { group, err = args.DistributionGroup() if err != nil { return nil, err } } zoneInstances, err := availabilityZoneAllocations(e, group) if errors.IsNotImplemented(err) { // Availability zones are an extension, so we may get a // not implemented error; ignore these. } else if err != nil { return nil, err } else { for _, zone := range zoneInstances { availabilityZones = append(availabilityZones, zone.ZoneName) } } if len(availabilityZones) == 0 { // No explicitly selectable zones available, so use an unspecified zone. availabilityZones = []string{""} } } series := args.Tools.OneSeries() arches := args.Tools.Arches() spec, err := findInstanceSpec(e, &instances.InstanceConstraint{ Region: e.ecfg().region(), Series: series, Arches: arches, Constraints: args.Constraints, }, args.ImageMetadata) if err != nil { return nil, err } tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) if err != nil { return nil, errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) } if err := args.InstanceConfig.SetTools(tools); err != nil { return nil, errors.Trace(err) } if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil { return nil, err } cloudcfg, err := e.configurator.GetCloudConfig(args) if err != nil { return nil, errors.Trace(err) } userData, err := providerinit.ComposeUserData(args.InstanceConfig, cloudcfg, OpenstackRenderer{}) if err != nil { return nil, errors.Annotate(err, "cannot make user data") } logger.Debugf("openstack user data; %d bytes", len(userData)) var networks = e.firewaller.InitialNetworks() usingNetwork := e.ecfg().network() if usingNetwork != "" { networkId, err := e.resolveNetwork(usingNetwork) if err != nil { return nil, err } logger.Debugf("using network id %q", networkId) networks = append(networks, nova.ServerNetworks{NetworkId: networkId}) } withPublicIP := e.ecfg().useFloatingIP() var publicIP *nova.FloatingIP if withPublicIP { logger.Debugf("allocating public IP address for openstack node") if fip, err := e.allocatePublicIP(); err != nil { return nil, errors.Annotate(err, "cannot allocate a public IP as needed") } else { publicIP = fip logger.Infof("allocated public IP %s", publicIP.IP) } } cfg := e.Config() var groupNames = make([]nova.SecurityGroupName, 0) groups, err := e.firewaller.SetUpGroups(args.InstanceConfig.MachineId, cfg.APIPort()) if err != nil { return nil, errors.Annotate(err, "cannot set up groups") } for _, g := range groups { groupNames = append(groupNames, nova.SecurityGroupName{g.Name}) } machineName := resourceName( names.NewMachineTag(args.InstanceConfig.MachineId), e.Config().UUID(), ) tryStartNovaInstance := func( attempts utils.AttemptStrategy, client *nova.Client, instanceOpts nova.RunServerOpts, ) (server *nova.Entity, err error) { for a := attempts.Start(); a.Next(); { server, err = client.RunServer(instanceOpts) if err == nil || gooseerrors.IsNotFound(err) == false { break } } return server, err } tryStartNovaInstanceAcrossAvailZones := func( attempts utils.AttemptStrategy, client *nova.Client, instanceOpts nova.RunServerOpts, availabilityZones []string, ) (server *nova.Entity, err error) { for _, zone := range availabilityZones { instanceOpts.AvailabilityZone = zone e.configurator.ModifyRunServerOptions(&instanceOpts) server, err = tryStartNovaInstance(attempts, client, instanceOpts) if err == nil || isNoValidHostsError(err) == false { break } logger.Infof("no valid hosts available in zone %q, trying another availability zone", zone) } if err != nil { err = errors.Annotate(err, "cannot run instance") } return server, err } var opts = nova.RunServerOpts{ Name: machineName, FlavorId: spec.InstanceType.Id, ImageId: spec.Image.Id, UserData: userData, SecurityGroupNames: groupNames, Networks: networks, Metadata: args.InstanceConfig.Tags, } server, err := tryStartNovaInstanceAcrossAvailZones(shortAttempt, e.nova(), opts, availabilityZones) if err != nil { return nil, errors.Trace(err) } detail, err := e.nova().GetServer(server.Id) if err != nil { return nil, errors.Annotate(err, "cannot get started instance") } inst := &openstackInstance{ e: e, serverDetail: detail, arch: &spec.Image.Arch, instType: &spec.InstanceType, } logger.Infof("started instance %q", inst.Id()) if withPublicIP { if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil { if err := e.terminateInstances([]instance.Id{inst.Id()}); err != nil { // ignore the failure at this stage, just log it logger.Debugf("failed to terminate instance %q: %v", inst.Id(), err) } return nil, errors.Annotatef(err, "cannot assign public address %s to instance %q", publicIP.IP, inst.Id()) } inst.floatingIP = publicIP logger.Infof("assigned public IP %s to %q", publicIP.IP, inst.Id()) } return &environs.StartInstanceResult{ Instance: inst, Hardware: inst.hardwareCharacteristics(), }, nil }
// maybeNotFound returns a errors.NotFoundError if the root cause of the specified error is due to a file or // container not being found. func maybeNotFound(err error) (error, bool) { if err != nil && gooseerrors.IsNotFound(err) { return jujuerrors.NewNotFound(err, ""), true } return err, false }