github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/ec2/ec2.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/utils"
    15  	"launchpad.net/goamz/aws"
    16  	"launchpad.net/goamz/ec2"
    17  	"launchpad.net/goamz/s3"
    18  
    19  	"github.com/juju/juju/constraints"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/imagemetadata"
    23  	"github.com/juju/juju/environs/instances"
    24  	"github.com/juju/juju/environs/simplestreams"
    25  	"github.com/juju/juju/environs/storage"
    26  	envtools "github.com/juju/juju/environs/tools"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/juju/arch"
    29  	"github.com/juju/juju/network"
    30  	"github.com/juju/juju/provider/common"
    31  	"github.com/juju/juju/state"
    32  	"github.com/juju/juju/state/api"
    33  	"github.com/juju/juju/tools"
    34  )
    35  
    36  var logger = loggo.GetLogger("juju.provider.ec2")
    37  
    38  // Use shortAttempt to poll for short-term events.
    39  var shortAttempt = utils.AttemptStrategy{
    40  	Total: 5 * time.Second,
    41  	Delay: 200 * time.Millisecond,
    42  }
    43  
    44  func init() {
    45  	environs.RegisterProvider("ec2", environProvider{})
    46  }
    47  
    48  type environProvider struct{}
    49  
    50  var providerInstance environProvider
    51  
    52  type environ struct {
    53  	common.SupportsUnitPlacementPolicy
    54  
    55  	name string
    56  
    57  	// archMutex gates access to supportedArchitectures
    58  	archMutex sync.Mutex
    59  	// supportedArchitectures caches the architectures
    60  	// for which images can be instantiated.
    61  	supportedArchitectures []string
    62  
    63  	// ecfgMutex protects the *Unlocked fields below.
    64  	ecfgMutex       sync.Mutex
    65  	ecfgUnlocked    *environConfig
    66  	ec2Unlocked     *ec2.EC2
    67  	s3Unlocked      *s3.S3
    68  	storageUnlocked storage.Storage
    69  
    70  	availabilityZonesMutex sync.Mutex
    71  	availabilityZones      []common.AvailabilityZone
    72  }
    73  
    74  var _ environs.Environ = (*environ)(nil)
    75  var _ simplestreams.HasRegion = (*environ)(nil)
    76  var _ imagemetadata.SupportsCustomSources = (*environ)(nil)
    77  var _ envtools.SupportsCustomSources = (*environ)(nil)
    78  var _ state.Prechecker = (*environ)(nil)
    79  var _ state.InstanceDistributor = (*environ)(nil)
    80  
    81  type ec2Instance struct {
    82  	e *environ
    83  
    84  	mu sync.Mutex
    85  	*ec2.Instance
    86  }
    87  
    88  func (inst *ec2Instance) String() string {
    89  	return string(inst.Id())
    90  }
    91  
    92  var _ instance.Instance = (*ec2Instance)(nil)
    93  
    94  func (inst *ec2Instance) getInstance() *ec2.Instance {
    95  	inst.mu.Lock()
    96  	defer inst.mu.Unlock()
    97  	return inst.Instance
    98  }
    99  
   100  func (inst *ec2Instance) Id() instance.Id {
   101  	return instance.Id(inst.getInstance().InstanceId)
   102  }
   103  
   104  func (inst *ec2Instance) Status() string {
   105  	return inst.getInstance().State.Name
   106  }
   107  
   108  // Refresh implements instance.Refresh(), requerying the
   109  // Instance details over the ec2 api
   110  func (inst *ec2Instance) Refresh() error {
   111  	_, err := inst.refresh()
   112  	return err
   113  }
   114  
   115  // refresh requeries Instance details over the ec2 api.
   116  func (inst *ec2Instance) refresh() (*ec2.Instance, error) {
   117  	id := inst.Id()
   118  	insts, err := inst.e.Instances([]instance.Id{id})
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	inst.mu.Lock()
   123  	defer inst.mu.Unlock()
   124  	inst.Instance = insts[0].(*ec2Instance).Instance
   125  	return inst.Instance, nil
   126  }
   127  
   128  // Addresses implements network.Addresses() returning generic address
   129  // details for the instance, and requerying the ec2 api if required.
   130  func (inst *ec2Instance) Addresses() ([]network.Address, error) {
   131  	// TODO(gz): Stop relying on this requerying logic, maybe remove error
   132  	instInstance := inst.getInstance()
   133  	if instInstance.DNSName == "" {
   134  		// Fetch the instance information again, in case
   135  		// the DNS information has become available.
   136  		var err error
   137  		instInstance, err = inst.refresh()
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  	}
   142  	var addresses []network.Address
   143  	possibleAddresses := []network.Address{
   144  		{
   145  			Value: instInstance.DNSName,
   146  			Type:  network.HostName,
   147  			Scope: network.ScopePublic,
   148  		},
   149  		{
   150  			Value: instInstance.PrivateDNSName,
   151  			Type:  network.HostName,
   152  			Scope: network.ScopeCloudLocal,
   153  		},
   154  		{
   155  			Value: instInstance.IPAddress,
   156  			Type:  network.IPv4Address,
   157  			Scope: network.ScopePublic,
   158  		},
   159  		{
   160  			Value: instInstance.PrivateIPAddress,
   161  			Type:  network.IPv4Address,
   162  			Scope: network.ScopeCloudLocal,
   163  		},
   164  	}
   165  	for _, address := range possibleAddresses {
   166  		if address.Value != "" {
   167  			addresses = append(addresses, address)
   168  		}
   169  	}
   170  	return addresses, nil
   171  }
   172  
   173  func (p environProvider) BoilerplateConfig() string {
   174  	return `
   175  # https://juju.ubuntu.com/docs/config-aws.html
   176  amazon:
   177      type: ec2
   178  
   179      # region specifies the EC2 region. It defaults to us-east-1.
   180      #
   181      # region: us-east-1
   182  
   183      # access-key holds the EC2 access key. It defaults to the
   184      # environment variable AWS_ACCESS_KEY_ID.
   185      #
   186      # access-key: <secret>
   187  
   188      # secret-key holds the EC2 secret key. It defaults to the
   189      # environment variable AWS_SECRET_ACCESS_KEY.
   190      #
   191      # secret-key: <secret>
   192  
   193      # image-stream chooses a simplestreams stream to select OS images
   194      # from, for example daily or released images (or any other stream
   195      # available on simplestreams).
   196      #
   197      # image-stream: "released"
   198  
   199  `[1:]
   200  }
   201  
   202  func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
   203  	logger.Infof("opening environment %q", cfg.Name())
   204  	e := new(environ)
   205  	e.name = cfg.Name()
   206  	err := e.SetConfig(cfg)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	return e, nil
   211  }
   212  
   213  func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
   214  	attrs := cfg.UnknownAttrs()
   215  	if _, ok := attrs["control-bucket"]; !ok {
   216  		uuid, err := utils.NewUUID()
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  		attrs["control-bucket"] = fmt.Sprintf("%x", uuid.Raw())
   221  	}
   222  	cfg, err := cfg.Apply(attrs)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	return p.Open(cfg)
   227  }
   228  
   229  // MetadataLookupParams returns parameters which are used to query image metadata to
   230  // find matching image information.
   231  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   232  	if region == "" {
   233  		fmt.Errorf("region must be specified")
   234  	}
   235  	ec2Region, ok := allRegions[region]
   236  	if !ok {
   237  		return nil, fmt.Errorf("unknown region %q", region)
   238  	}
   239  	return &simplestreams.MetadataLookupParams{
   240  		Region:        region,
   241  		Endpoint:      ec2Region.EC2Endpoint,
   242  		Architectures: arch.AllSupportedArches,
   243  	}, nil
   244  }
   245  
   246  func (environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   247  	m := make(map[string]string)
   248  	ecfg, err := providerInstance.newConfig(cfg)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	m["access-key"] = ecfg.accessKey()
   253  	m["secret-key"] = ecfg.secretKey()
   254  	return m, nil
   255  }
   256  
   257  func (e *environ) Config() *config.Config {
   258  	return e.ecfg().Config
   259  }
   260  
   261  func (e *environ) SetConfig(cfg *config.Config) error {
   262  	ecfg, err := providerInstance.newConfig(cfg)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	e.ecfgMutex.Lock()
   267  	defer e.ecfgMutex.Unlock()
   268  	e.ecfgUnlocked = ecfg
   269  
   270  	auth := aws.Auth{ecfg.accessKey(), ecfg.secretKey()}
   271  	region := aws.Regions[ecfg.region()]
   272  	e.ec2Unlocked = ec2.New(auth, region)
   273  	e.s3Unlocked = s3.New(auth, region)
   274  
   275  	// create new storage instances, existing instances continue
   276  	// to reference their existing configuration.
   277  	e.storageUnlocked = &ec2storage{
   278  		bucket: e.s3Unlocked.Bucket(ecfg.controlBucket()),
   279  	}
   280  	return nil
   281  }
   282  
   283  func (e *environ) ecfg() *environConfig {
   284  	e.ecfgMutex.Lock()
   285  	ecfg := e.ecfgUnlocked
   286  	e.ecfgMutex.Unlock()
   287  	return ecfg
   288  }
   289  
   290  func (e *environ) ec2() *ec2.EC2 {
   291  	e.ecfgMutex.Lock()
   292  	ec2 := e.ec2Unlocked
   293  	e.ecfgMutex.Unlock()
   294  	return ec2
   295  }
   296  
   297  func (e *environ) s3() *s3.S3 {
   298  	e.ecfgMutex.Lock()
   299  	s3 := e.s3Unlocked
   300  	e.ecfgMutex.Unlock()
   301  	return s3
   302  }
   303  
   304  func (e *environ) Name() string {
   305  	return e.name
   306  }
   307  
   308  func (e *environ) Storage() storage.Storage {
   309  	e.ecfgMutex.Lock()
   310  	stor := e.storageUnlocked
   311  	e.ecfgMutex.Unlock()
   312  	return stor
   313  }
   314  
   315  func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) error {
   316  	return common.Bootstrap(ctx, e, args)
   317  }
   318  
   319  func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
   320  	return common.StateInfo(e)
   321  }
   322  
   323  // SupportedArchitectures is specified on the EnvironCapability interface.
   324  func (e *environ) SupportedArchitectures() ([]string, error) {
   325  	e.archMutex.Lock()
   326  	defer e.archMutex.Unlock()
   327  	if e.supportedArchitectures != nil {
   328  		return e.supportedArchitectures, nil
   329  	}
   330  	// Create a filter to get all images from our region and for the correct stream.
   331  	cloudSpec, err := e.Region()
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   336  		CloudSpec: cloudSpec,
   337  		Stream:    e.Config().ImageStream(),
   338  	})
   339  	e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint)
   340  	return e.supportedArchitectures, err
   341  }
   342  
   343  // SupportNetworks is specified on the EnvironCapability interface.
   344  func (e *environ) SupportNetworks() bool {
   345  	// TODO(dimitern) Once we have support for VPCs and advanced
   346  	// networking, return true here.
   347  	return false
   348  }
   349  
   350  var unsupportedConstraints = []string{
   351  	constraints.Tags,
   352  }
   353  
   354  // ConstraintsValidator is defined on the Environs interface.
   355  func (e *environ) ConstraintsValidator() (constraints.Validator, error) {
   356  	validator := constraints.NewValidator()
   357  	validator.RegisterConflicts(
   358  		[]string{constraints.InstanceType},
   359  		[]string{constraints.Mem, constraints.CpuCores, constraints.CpuPower})
   360  	validator.RegisterUnsupported(unsupportedConstraints)
   361  	supportedArches, err := e.SupportedArchitectures()
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   366  	instTypeNames := make([]string, len(allInstanceTypes))
   367  	for i, itype := range allInstanceTypes {
   368  		instTypeNames[i] = itype.Name
   369  	}
   370  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   371  	return validator, nil
   372  }
   373  
   374  func archMatches(arches []string, arch *string) bool {
   375  	if arch == nil {
   376  		return true
   377  	}
   378  	for _, a := range arches {
   379  		if a == *arch {
   380  			return true
   381  		}
   382  	}
   383  	return false
   384  }
   385  
   386  var ec2AvailabilityZones = (*ec2.EC2).AvailabilityZones
   387  
   388  type ec2AvailabilityZone struct {
   389  	ec2.AvailabilityZoneInfo
   390  }
   391  
   392  func (z *ec2AvailabilityZone) Name() string {
   393  	return z.AvailabilityZoneInfo.Name
   394  }
   395  
   396  func (z *ec2AvailabilityZone) Available() bool {
   397  	return z.AvailabilityZoneInfo.State == "available"
   398  }
   399  
   400  // AvailabilityZones returns a slice of availability zones
   401  // for the configured region.
   402  func (e *environ) AvailabilityZones() ([]common.AvailabilityZone, error) {
   403  	e.availabilityZonesMutex.Lock()
   404  	defer e.availabilityZonesMutex.Unlock()
   405  	if e.availabilityZones == nil {
   406  		filter := ec2.NewFilter()
   407  		filter.Add("region-name", e.ecfg().region())
   408  		resp, err := ec2AvailabilityZones(e.ec2(), filter)
   409  		if err != nil {
   410  			return nil, err
   411  		}
   412  		e.availabilityZones = make([]common.AvailabilityZone, len(resp.Zones))
   413  		for i, z := range resp.Zones {
   414  			e.availabilityZones[i] = &ec2AvailabilityZone{z}
   415  		}
   416  	}
   417  	return e.availabilityZones, nil
   418  }
   419  
   420  // InstanceAvailabilityZoneNames returns the availability zone names for each
   421  // of the specified instances.
   422  func (e *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) {
   423  	instances, err := e.Instances(ids)
   424  	if err != nil && err != environs.ErrPartialInstances {
   425  		return nil, err
   426  	}
   427  	zones := make([]string, len(instances))
   428  	for i, inst := range instances {
   429  		if inst == nil {
   430  			continue
   431  		}
   432  		zones[i] = inst.(*ec2Instance).AvailZone
   433  	}
   434  	return zones, err
   435  }
   436  
   437  type ec2Placement struct {
   438  	availabilityZone ec2.AvailabilityZoneInfo
   439  }
   440  
   441  func (e *environ) parsePlacement(placement string) (*ec2Placement, error) {
   442  	pos := strings.IndexRune(placement, '=')
   443  	if pos == -1 {
   444  		return nil, fmt.Errorf("unknown placement directive: %v", placement)
   445  	}
   446  	switch key, value := placement[:pos], placement[pos+1:]; key {
   447  	case "zone":
   448  		availabilityZone := value
   449  		zones, err := e.AvailabilityZones()
   450  		if err != nil {
   451  			return nil, err
   452  		}
   453  		for _, z := range zones {
   454  			if z.Name() == availabilityZone {
   455  				return &ec2Placement{
   456  					z.(*ec2AvailabilityZone).AvailabilityZoneInfo,
   457  				}, nil
   458  			}
   459  		}
   460  		return nil, fmt.Errorf("invalid availability zone %q", availabilityZone)
   461  	}
   462  	return nil, fmt.Errorf("unknown placement directive: %v", placement)
   463  }
   464  
   465  // PrecheckInstance is defined on the state.Prechecker interface.
   466  func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   467  	if placement != "" {
   468  		if _, err := e.parsePlacement(placement); err != nil {
   469  			return err
   470  		}
   471  	}
   472  	if !cons.HasInstanceType() {
   473  		return nil
   474  	}
   475  	// Constraint has an instance-type constraint so let's see if it is valid.
   476  	for _, itype := range allInstanceTypes {
   477  		if itype.Name != *cons.InstanceType {
   478  			continue
   479  		}
   480  		if archMatches(itype.Arches, cons.Arch) {
   481  			return nil
   482  		}
   483  	}
   484  	if cons.Arch == nil {
   485  		return fmt.Errorf("invalid AWS instance type %q specified", *cons.InstanceType)
   486  	}
   487  	return fmt.Errorf("invalid AWS instance type %q and arch %q specified", *cons.InstanceType, *cons.Arch)
   488  }
   489  
   490  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
   491  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   492  	if region == "" {
   493  		region = e.ecfg().region()
   494  	}
   495  	cloudSpec, err := e.cloudSpec(region)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	return &simplestreams.MetadataLookupParams{
   500  		Series:        config.PreferredSeries(e.ecfg()),
   501  		Region:        cloudSpec.Region,
   502  		Endpoint:      cloudSpec.Endpoint,
   503  		Architectures: arch.AllSupportedArches,
   504  	}, nil
   505  }
   506  
   507  // Region is specified in the HasRegion interface.
   508  func (e *environ) Region() (simplestreams.CloudSpec, error) {
   509  	return e.cloudSpec(e.ecfg().region())
   510  }
   511  
   512  func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
   513  	ec2Region, ok := allRegions[region]
   514  	if !ok {
   515  		return simplestreams.CloudSpec{}, fmt.Errorf("unknown region %q", region)
   516  	}
   517  	return simplestreams.CloudSpec{
   518  		Region:   region,
   519  		Endpoint: ec2Region.EC2Endpoint,
   520  	}, nil
   521  }
   522  
   523  const ebsStorage = "ebs"
   524  
   525  // DistributeInstances implements the state.InstanceDistributor policy.
   526  func (e *environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
   527  	return common.DistributeInstances(e, candidates, distributionGroup)
   528  }
   529  
   530  var bestAvailabilityZoneAllocations = common.BestAvailabilityZoneAllocations
   531  
   532  // StartInstance is specified in the InstanceBroker interface.
   533  func (e *environ) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
   534  	var availabilityZone string
   535  	if args.Placement != "" {
   536  		placement, err := e.parsePlacement(args.Placement)
   537  		if err != nil {
   538  			return nil, nil, nil, err
   539  		}
   540  		if placement.availabilityZone.State != "available" {
   541  			return nil, nil, nil, fmt.Errorf("availability zone %q is %s", placement.availabilityZone.Name, placement.availabilityZone.State)
   542  		}
   543  		availabilityZone = placement.availabilityZone.Name
   544  	}
   545  
   546  	// If no availability zone is specified, then automatically spread across
   547  	// the known zones for optimal spread across the instance distribution
   548  	// group.
   549  	if availabilityZone == "" {
   550  		var group []instance.Id
   551  		var err error
   552  		if args.DistributionGroup != nil {
   553  			group, err = args.DistributionGroup()
   554  			if err != nil {
   555  				return nil, nil, nil, err
   556  			}
   557  		}
   558  		bestAvailabilityZones, err := bestAvailabilityZoneAllocations(e, group)
   559  		if err != nil {
   560  			return nil, nil, nil, err
   561  		}
   562  		for availabilityZone = range bestAvailabilityZones {
   563  			break
   564  		}
   565  	}
   566  
   567  	if args.MachineConfig.HasNetworks() {
   568  		return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.")
   569  	}
   570  	arches := args.Tools.Arches()
   571  	stor := ebsStorage
   572  	sources, err := imagemetadata.GetMetadataSources(e)
   573  	if err != nil {
   574  		return nil, nil, nil, err
   575  	}
   576  
   577  	series := args.Tools.OneSeries()
   578  	spec, err := findInstanceSpec(sources, e.Config().ImageStream(), &instances.InstanceConstraint{
   579  		Region:      e.ecfg().region(),
   580  		Series:      series,
   581  		Arches:      arches,
   582  		Constraints: args.Constraints,
   583  		Storage:     &stor,
   584  	})
   585  	if err != nil {
   586  		return nil, nil, nil, err
   587  	}
   588  	tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch})
   589  	if err != nil {
   590  		return nil, nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   591  	}
   592  
   593  	args.MachineConfig.Tools = tools[0]
   594  	if err := environs.FinishMachineConfig(args.MachineConfig, e.Config(), args.Constraints); err != nil {
   595  		return nil, nil, nil, err
   596  	}
   597  
   598  	userData, err := environs.ComposeUserData(args.MachineConfig, nil)
   599  	if err != nil {
   600  		return nil, nil, nil, fmt.Errorf("cannot make user data: %v", err)
   601  	}
   602  	logger.Debugf("ec2 user data; %d bytes", len(userData))
   603  	cfg := e.Config()
   604  	groups, err := e.setUpGroups(args.MachineConfig.MachineId, cfg.StatePort(), cfg.APIPort())
   605  	if err != nil {
   606  		return nil, nil, nil, fmt.Errorf("cannot set up groups: %v", err)
   607  	}
   608  	var instResp *ec2.RunInstancesResp
   609  
   610  	device, diskSize := getDiskSize(args.Constraints)
   611  	for a := shortAttempt.Start(); a.Next(); {
   612  		instResp, err = e.ec2().RunInstances(&ec2.RunInstances{
   613  			AvailZone:           availabilityZone,
   614  			ImageId:             spec.Image.Id,
   615  			MinCount:            1,
   616  			MaxCount:            1,
   617  			UserData:            userData,
   618  			InstanceType:        spec.InstanceType.Name,
   619  			SecurityGroups:      groups,
   620  			BlockDeviceMappings: []ec2.BlockDeviceMapping{device},
   621  		})
   622  		if err == nil || ec2ErrCode(err) != "InvalidGroup.NotFound" {
   623  			break
   624  		}
   625  	}
   626  	if err != nil {
   627  		return nil, nil, nil, fmt.Errorf("cannot run instances: %v", err)
   628  	}
   629  	if len(instResp.Instances) != 1 {
   630  		return nil, nil, nil, fmt.Errorf("expected 1 started instance, got %d", len(instResp.Instances))
   631  	}
   632  
   633  	inst := &ec2Instance{
   634  		e:        e,
   635  		Instance: &instResp.Instances[0],
   636  	}
   637  	logger.Infof("started instance %q", inst.Id())
   638  
   639  	hc := instance.HardwareCharacteristics{
   640  		Arch:     &spec.Image.Arch,
   641  		Mem:      &spec.InstanceType.Mem,
   642  		CpuCores: &spec.InstanceType.CpuCores,
   643  		CpuPower: spec.InstanceType.CpuPower,
   644  		RootDisk: &diskSize,
   645  		// Tags currently not supported by EC2
   646  	}
   647  	return inst, &hc, nil, nil
   648  }
   649  
   650  func (e *environ) StopInstances(ids ...instance.Id) error {
   651  	return e.terminateInstances(ids)
   652  }
   653  
   654  // minDiskSize is the minimum/default size (in megabytes) for ec2 root disks.
   655  const minDiskSize uint64 = 8 * 1024
   656  
   657  // getDiskSize translates a RootDisk constraint (or lackthereof) into a
   658  // BlockDeviceMapping request for EC2.  megs is the size in megabytes of
   659  // the disk that was requested.
   660  func getDiskSize(cons constraints.Value) (dvc ec2.BlockDeviceMapping, megs uint64) {
   661  	diskSize := minDiskSize
   662  
   663  	if cons.RootDisk != nil {
   664  		if *cons.RootDisk >= minDiskSize {
   665  			diskSize = *cons.RootDisk
   666  		} else {
   667  			logger.Infof("Ignoring root-disk constraint of %dM because it is smaller than the EC2 image size of %dM",
   668  				*cons.RootDisk, minDiskSize)
   669  		}
   670  	}
   671  
   672  	// AWS's volume size is in gigabytes, root-disk is in megabytes,
   673  	// so round up to the nearest gigabyte.
   674  	volsize := int64((diskSize + 1023) / 1024)
   675  	return ec2.BlockDeviceMapping{
   676  			DeviceName: "/dev/sda1",
   677  			VolumeSize: volsize,
   678  		},
   679  		uint64(volsize * 1024)
   680  }
   681  
   682  // groupInfoByName returns information on the security group
   683  // with the given name including rules and other details.
   684  func (e *environ) groupInfoByName(groupName string) (ec2.SecurityGroupInfo, error) {
   685  	// Non-default VPC does not support name-based group lookups, can
   686  	// use a filter by group name instead when support is needed.
   687  	limitToGroups := []ec2.SecurityGroup{{Name: groupName}}
   688  	resp, err := e.ec2().SecurityGroups(limitToGroups, nil)
   689  	if err != nil {
   690  		return ec2.SecurityGroupInfo{}, err
   691  	}
   692  	if len(resp.Groups) != 1 {
   693  		return ec2.SecurityGroupInfo{}, fmt.Errorf("expected one security group named %q, got %v", groupName, resp.Groups)
   694  	}
   695  	return resp.Groups[0], nil
   696  }
   697  
   698  // groupByName returns the security group with the given name.
   699  func (e *environ) groupByName(groupName string) (ec2.SecurityGroup, error) {
   700  	groupInfo, err := e.groupInfoByName(groupName)
   701  	return groupInfo.SecurityGroup, err
   702  }
   703  
   704  // addGroupFilter sets a limit an instance filter so only those machines
   705  // with the juju environment wide security group associated will be listed.
   706  //
   707  // An EC2 API call is required to resolve the group name to an id, as VPC
   708  // enabled accounts do not support name based filtering.
   709  // TODO: Detect classic accounts and just filter by name for those.
   710  //
   711  // Callers must handle InvalidGroup.NotFound errors to mean the same as no
   712  // matching instances.
   713  func (e *environ) addGroupFilter(filter *ec2.Filter) error {
   714  	groupName := e.jujuGroupName()
   715  	group, err := e.groupByName(groupName)
   716  	if err != nil {
   717  		return err
   718  	}
   719  	// EC2 should support filtering with and without the 'instance.'
   720  	// prefix, but only the form with seems to work with default VPC.
   721  	filter.Add("instance.group-id", group.Id)
   722  	return nil
   723  }
   724  
   725  // gatherInstances tries to get information on each instance
   726  // id whose corresponding insts slot is nil.
   727  // It returns environs.ErrPartialInstances if the insts
   728  // slice has not been completely filled.
   729  func (e *environ) gatherInstances(ids []instance.Id, insts []instance.Instance) error {
   730  	var need []string
   731  	for i, inst := range insts {
   732  		if inst == nil {
   733  			need = append(need, string(ids[i]))
   734  		}
   735  	}
   736  	if len(need) == 0 {
   737  		return nil
   738  	}
   739  	filter := ec2.NewFilter()
   740  	filter.Add("instance-state-name", "pending", "running")
   741  	err := e.addGroupFilter(filter)
   742  	if err != nil {
   743  		if ec2ErrCode(err) == "InvalidGroup.NotFound" {
   744  			return environs.ErrPartialInstances
   745  		}
   746  		return err
   747  	}
   748  	filter.Add("instance-id", need...)
   749  	resp, err := e.ec2().Instances(nil, filter)
   750  	if err != nil {
   751  		return err
   752  	}
   753  	n := 0
   754  	// For each requested id, add it to the returned instances
   755  	// if we find it in the response.
   756  	for i, id := range ids {
   757  		if insts[i] != nil {
   758  			continue
   759  		}
   760  		for j := range resp.Reservations {
   761  			r := &resp.Reservations[j]
   762  			for k := range r.Instances {
   763  				if r.Instances[k].InstanceId == string(id) {
   764  					inst := r.Instances[k]
   765  					// TODO(wallyworld): lookup the details to fill in the instance type data
   766  					insts[i] = &ec2Instance{e: e, Instance: &inst}
   767  					n++
   768  				}
   769  			}
   770  		}
   771  	}
   772  	if n < len(ids) {
   773  		return environs.ErrPartialInstances
   774  	}
   775  	return nil
   776  }
   777  
   778  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
   779  	if len(ids) == 0 {
   780  		return nil, nil
   781  	}
   782  	insts := make([]instance.Instance, len(ids))
   783  	// Make a series of requests to cope with eventual consistency.
   784  	// Each request will attempt to add more instances to the requested
   785  	// set.
   786  	var err error
   787  	for a := shortAttempt.Start(); a.Next(); {
   788  		err = e.gatherInstances(ids, insts)
   789  		if err == nil || err != environs.ErrPartialInstances {
   790  			break
   791  		}
   792  	}
   793  	if err == environs.ErrPartialInstances {
   794  		for _, inst := range insts {
   795  			if inst != nil {
   796  				return insts, environs.ErrPartialInstances
   797  			}
   798  		}
   799  		return nil, environs.ErrNoInstances
   800  	}
   801  	if err != nil {
   802  		return nil, err
   803  	}
   804  	return insts, nil
   805  }
   806  
   807  // AllocateAddress requests a new address to be allocated for the
   808  // given instance on the given network. This is not implemented by the
   809  // EC2 provider yet.
   810  func (*environ) AllocateAddress(_ instance.Id, _ network.Id) (network.Address, error) {
   811  	// TODO(dimitern) This will be implemented in a follow-up.
   812  	return network.Address{}, errors.NotImplementedf("AllocateAddress")
   813  }
   814  
   815  // ListNetworks returns basic information about all networks known
   816  // by the provider for the environment. They may be unknown to juju
   817  // yet (i.e. when called initially or when a new network was created).
   818  // This is not implemented by the EC2 provider yet.
   819  func (*environ) ListNetworks() ([]network.BasicInfo, error) {
   820  	return nil, errors.NotImplementedf("ListNetworks")
   821  }
   822  
   823  func (e *environ) AllInstances() ([]instance.Instance, error) {
   824  	filter := ec2.NewFilter()
   825  	filter.Add("instance-state-name", "pending", "running")
   826  	err := e.addGroupFilter(filter)
   827  	if err != nil {
   828  		if ec2ErrCode(err) == "InvalidGroup.NotFound" {
   829  			return nil, nil
   830  		}
   831  		return nil, err
   832  	}
   833  	resp, err := e.ec2().Instances(nil, filter)
   834  	if err != nil {
   835  		return nil, err
   836  	}
   837  	var insts []instance.Instance
   838  	for _, r := range resp.Reservations {
   839  		for i := range r.Instances {
   840  			inst := r.Instances[i]
   841  			// TODO(wallyworld): lookup the details to fill in the instance type data
   842  			insts = append(insts, &ec2Instance{e: e, Instance: &inst})
   843  		}
   844  	}
   845  	return insts, nil
   846  }
   847  
   848  func (e *environ) Destroy() error {
   849  	return common.Destroy(e)
   850  }
   851  
   852  func portsToIPPerms(ports []network.Port) []ec2.IPPerm {
   853  	ipPerms := make([]ec2.IPPerm, len(ports))
   854  	for i, p := range ports {
   855  		ipPerms[i] = ec2.IPPerm{
   856  			Protocol:  p.Protocol,
   857  			FromPort:  p.Number,
   858  			ToPort:    p.Number,
   859  			SourceIPs: []string{"0.0.0.0/0"},
   860  		}
   861  	}
   862  	return ipPerms
   863  }
   864  
   865  func (e *environ) openPortsInGroup(name string, ports []network.Port) error {
   866  	if len(ports) == 0 {
   867  		return nil
   868  	}
   869  	// Give permissions for anyone to access the given ports.
   870  	g, err := e.groupByName(name)
   871  	if err != nil {
   872  		return err
   873  	}
   874  	ipPerms := portsToIPPerms(ports)
   875  	_, err = e.ec2().AuthorizeSecurityGroup(g, ipPerms)
   876  	if err != nil && ec2ErrCode(err) == "InvalidPermission.Duplicate" {
   877  		if len(ports) == 1 {
   878  			return nil
   879  		}
   880  		// If there's more than one port and we get a duplicate error,
   881  		// then we go through authorizing each port individually,
   882  		// otherwise the ports that were *not* duplicates will have
   883  		// been ignored
   884  		for i := range ipPerms {
   885  			_, err := e.ec2().AuthorizeSecurityGroup(g, ipPerms[i:i+1])
   886  			if err != nil && ec2ErrCode(err) != "InvalidPermission.Duplicate" {
   887  				return fmt.Errorf("cannot open port %v: %v", ipPerms[i], err)
   888  			}
   889  		}
   890  		return nil
   891  	}
   892  	if err != nil {
   893  		return fmt.Errorf("cannot open ports: %v", err)
   894  	}
   895  	return nil
   896  }
   897  
   898  func (e *environ) closePortsInGroup(name string, ports []network.Port) error {
   899  	if len(ports) == 0 {
   900  		return nil
   901  	}
   902  	// Revoke permissions for anyone to access the given ports.
   903  	// Note that ec2 allows the revocation of permissions that aren't
   904  	// granted, so this is naturally idempotent.
   905  	g, err := e.groupByName(name)
   906  	if err != nil {
   907  		return err
   908  	}
   909  	_, err = e.ec2().RevokeSecurityGroup(g, portsToIPPerms(ports))
   910  	if err != nil {
   911  		return fmt.Errorf("cannot close ports: %v", err)
   912  	}
   913  	return nil
   914  }
   915  
   916  func (e *environ) portsInGroup(name string) (ports []network.Port, err error) {
   917  	group, err := e.groupInfoByName(name)
   918  	if err != nil {
   919  		return nil, err
   920  	}
   921  	for _, p := range group.IPPerms {
   922  		if len(p.SourceIPs) != 1 {
   923  			logger.Warningf("unexpected IP permission found: %v", p)
   924  			continue
   925  		}
   926  		for i := p.FromPort; i <= p.ToPort; i++ {
   927  			ports = append(ports, network.Port{
   928  				Protocol: p.Protocol,
   929  				Number:   i,
   930  			})
   931  		}
   932  	}
   933  	network.SortPorts(ports)
   934  	return ports, nil
   935  }
   936  
   937  func (e *environ) OpenPorts(ports []network.Port) error {
   938  	if e.Config().FirewallMode() != config.FwGlobal {
   939  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
   940  			e.Config().FirewallMode())
   941  	}
   942  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
   943  		return err
   944  	}
   945  	logger.Infof("opened ports in global group: %v", ports)
   946  	return nil
   947  }
   948  
   949  func (e *environ) ClosePorts(ports []network.Port) error {
   950  	if e.Config().FirewallMode() != config.FwGlobal {
   951  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
   952  			e.Config().FirewallMode())
   953  	}
   954  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
   955  		return err
   956  	}
   957  	logger.Infof("closed ports in global group: %v", ports)
   958  	return nil
   959  }
   960  
   961  func (e *environ) Ports() ([]network.Port, error) {
   962  	if e.Config().FirewallMode() != config.FwGlobal {
   963  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
   964  			e.Config().FirewallMode())
   965  	}
   966  	return e.portsInGroup(e.globalGroupName())
   967  }
   968  
   969  func (*environ) Provider() environs.EnvironProvider {
   970  	return &providerInstance
   971  }
   972  
   973  func (e *environ) terminateInstances(ids []instance.Id) error {
   974  	if len(ids) == 0 {
   975  		return nil
   976  	}
   977  	var err error
   978  	ec2inst := e.ec2()
   979  	strs := make([]string, len(ids))
   980  	for i, id := range ids {
   981  		strs[i] = string(id)
   982  	}
   983  	for a := shortAttempt.Start(); a.Next(); {
   984  		_, err = ec2inst.TerminateInstances(strs)
   985  		if err == nil || ec2ErrCode(err) != "InvalidInstanceID.NotFound" {
   986  			return err
   987  		}
   988  	}
   989  	if len(ids) == 1 {
   990  		return err
   991  	}
   992  	// If we get a NotFound error, it means that no instances have been
   993  	// terminated even if some exist, so try them one by one, ignoring
   994  	// NotFound errors.
   995  	var firstErr error
   996  	for _, id := range ids {
   997  		_, err = ec2inst.TerminateInstances([]string{string(id)})
   998  		if ec2ErrCode(err) == "InvalidInstanceID.NotFound" {
   999  			err = nil
  1000  		}
  1001  		if err != nil && firstErr == nil {
  1002  			firstErr = err
  1003  		}
  1004  	}
  1005  	return firstErr
  1006  }
  1007  
  1008  func (e *environ) globalGroupName() string {
  1009  	return fmt.Sprintf("%s-global", e.jujuGroupName())
  1010  }
  1011  
  1012  func (e *environ) machineGroupName(machineId string) string {
  1013  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
  1014  }
  1015  
  1016  func (e *environ) jujuGroupName() string {
  1017  	return "juju-" + e.name
  1018  }
  1019  
  1020  func (inst *ec2Instance) OpenPorts(machineId string, ports []network.Port) error {
  1021  	if inst.e.Config().FirewallMode() != config.FwInstance {
  1022  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
  1023  			inst.e.Config().FirewallMode())
  1024  	}
  1025  	name := inst.e.machineGroupName(machineId)
  1026  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
  1027  		return err
  1028  	}
  1029  	logger.Infof("opened ports in security group %s: %v", name, ports)
  1030  	return nil
  1031  }
  1032  
  1033  func (inst *ec2Instance) ClosePorts(machineId string, ports []network.Port) error {
  1034  	if inst.e.Config().FirewallMode() != config.FwInstance {
  1035  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
  1036  			inst.e.Config().FirewallMode())
  1037  	}
  1038  	name := inst.e.machineGroupName(machineId)
  1039  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
  1040  		return err
  1041  	}
  1042  	logger.Infof("closed ports in security group %s: %v", name, ports)
  1043  	return nil
  1044  }
  1045  
  1046  func (inst *ec2Instance) Ports(machineId string) ([]network.Port, error) {
  1047  	if inst.e.Config().FirewallMode() != config.FwInstance {
  1048  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
  1049  			inst.e.Config().FirewallMode())
  1050  	}
  1051  	name := inst.e.machineGroupName(machineId)
  1052  	return inst.e.portsInGroup(name)
  1053  }
  1054  
  1055  // setUpGroups creates the security groups for the new machine, and
  1056  // returns them.
  1057  //
  1058  // Instances are tagged with a group so they can be distinguished from
  1059  // other instances that might be running on the same EC2 account.  In
  1060  // addition, a specific machine security group is created for each
  1061  // machine, so that its firewall rules can be configured per machine.
  1062  func (e *environ) setUpGroups(machineId string, statePort, apiPort int) ([]ec2.SecurityGroup, error) {
  1063  	jujuGroup, err := e.ensureGroup(e.jujuGroupName(),
  1064  		[]ec2.IPPerm{
  1065  			{
  1066  				Protocol:  "tcp",
  1067  				FromPort:  22,
  1068  				ToPort:    22,
  1069  				SourceIPs: []string{"0.0.0.0/0"},
  1070  			},
  1071  			{
  1072  				Protocol:  "tcp",
  1073  				FromPort:  statePort,
  1074  				ToPort:    statePort,
  1075  				SourceIPs: []string{"0.0.0.0/0"},
  1076  			},
  1077  			{
  1078  				Protocol:  "tcp",
  1079  				FromPort:  apiPort,
  1080  				ToPort:    apiPort,
  1081  				SourceIPs: []string{"0.0.0.0/0"},
  1082  			},
  1083  			{
  1084  				Protocol: "tcp",
  1085  				FromPort: 0,
  1086  				ToPort:   65535,
  1087  			},
  1088  			{
  1089  				Protocol: "udp",
  1090  				FromPort: 0,
  1091  				ToPort:   65535,
  1092  			},
  1093  			{
  1094  				Protocol: "icmp",
  1095  				FromPort: -1,
  1096  				ToPort:   -1,
  1097  			},
  1098  		})
  1099  	if err != nil {
  1100  		return nil, err
  1101  	}
  1102  	var machineGroup ec2.SecurityGroup
  1103  	switch e.Config().FirewallMode() {
  1104  	case config.FwInstance:
  1105  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
  1106  	case config.FwGlobal:
  1107  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
  1108  	}
  1109  	if err != nil {
  1110  		return nil, err
  1111  	}
  1112  	return []ec2.SecurityGroup{jujuGroup, machineGroup}, nil
  1113  }
  1114  
  1115  // zeroGroup holds the zero security group.
  1116  var zeroGroup ec2.SecurityGroup
  1117  
  1118  // ensureGroup returns the security group with name and perms.
  1119  // If a group with name does not exist, one will be created.
  1120  // If it exists, its permissions are set to perms.
  1121  // Any entries in perms without SourceIPs will be granted for
  1122  // the named group only.
  1123  func (e *environ) ensureGroup(name string, perms []ec2.IPPerm) (g ec2.SecurityGroup, err error) {
  1124  	ec2inst := e.ec2()
  1125  	resp, err := ec2inst.CreateSecurityGroup(name, "juju group")
  1126  	if err != nil && ec2ErrCode(err) != "InvalidGroup.Duplicate" {
  1127  		return zeroGroup, err
  1128  	}
  1129  
  1130  	var have permSet
  1131  	if err == nil {
  1132  		g = resp.SecurityGroup
  1133  	} else {
  1134  		resp, err := ec2inst.SecurityGroups(ec2.SecurityGroupNames(name), nil)
  1135  		if err != nil {
  1136  			return zeroGroup, err
  1137  		}
  1138  		info := resp.Groups[0]
  1139  		// It's possible that the old group has the wrong
  1140  		// description here, but if it does it's probably due
  1141  		// to something deliberately playing games with juju,
  1142  		// so we ignore it.
  1143  		g = info.SecurityGroup
  1144  		have = newPermSetForGroup(info.IPPerms, g)
  1145  	}
  1146  	want := newPermSetForGroup(perms, g)
  1147  	revoke := make(permSet)
  1148  	for p := range have {
  1149  		if !want[p] {
  1150  			revoke[p] = true
  1151  		}
  1152  	}
  1153  	if len(revoke) > 0 {
  1154  		_, err := ec2inst.RevokeSecurityGroup(g, revoke.ipPerms())
  1155  		if err != nil {
  1156  			return zeroGroup, fmt.Errorf("cannot revoke security group: %v", err)
  1157  		}
  1158  	}
  1159  
  1160  	add := make(permSet)
  1161  	for p := range want {
  1162  		if !have[p] {
  1163  			add[p] = true
  1164  		}
  1165  	}
  1166  	if len(add) > 0 {
  1167  		_, err := ec2inst.AuthorizeSecurityGroup(g, add.ipPerms())
  1168  		if err != nil {
  1169  			return zeroGroup, fmt.Errorf("cannot authorize securityGroup: %v", err)
  1170  		}
  1171  	}
  1172  	return g, nil
  1173  }
  1174  
  1175  // permKey represents a permission for a group or an ip address range
  1176  // to access the given range of ports. Only one of groupName or ipAddr
  1177  // should be non-empty.
  1178  type permKey struct {
  1179  	protocol string
  1180  	fromPort int
  1181  	toPort   int
  1182  	groupId  string
  1183  	ipAddr   string
  1184  }
  1185  
  1186  type permSet map[permKey]bool
  1187  
  1188  // newPermSetForGroup returns a set of all the permissions in the
  1189  // given slice of IPPerms. It ignores the name and owner
  1190  // id in source groups, and any entry with no source ips will
  1191  // be granted for the given group only.
  1192  func newPermSetForGroup(ps []ec2.IPPerm, group ec2.SecurityGroup) permSet {
  1193  	m := make(permSet)
  1194  	for _, p := range ps {
  1195  		k := permKey{
  1196  			protocol: p.Protocol,
  1197  			fromPort: p.FromPort,
  1198  			toPort:   p.ToPort,
  1199  		}
  1200  		if len(p.SourceIPs) > 0 {
  1201  			for _, ip := range p.SourceIPs {
  1202  				k.ipAddr = ip
  1203  				m[k] = true
  1204  			}
  1205  		} else {
  1206  			k.groupId = group.Id
  1207  			m[k] = true
  1208  		}
  1209  	}
  1210  	return m
  1211  }
  1212  
  1213  // ipPerms returns m as a slice of permissions usable
  1214  // with the ec2 package.
  1215  func (m permSet) ipPerms() (ps []ec2.IPPerm) {
  1216  	// We could compact the permissions, but it
  1217  	// hardly seems worth it.
  1218  	for p := range m {
  1219  		ipp := ec2.IPPerm{
  1220  			Protocol: p.protocol,
  1221  			FromPort: p.fromPort,
  1222  			ToPort:   p.toPort,
  1223  		}
  1224  		if p.ipAddr != "" {
  1225  			ipp.SourceIPs = []string{p.ipAddr}
  1226  		} else {
  1227  			ipp.SourceGroups = []ec2.UserSecurityGroup{{Id: p.groupId}}
  1228  		}
  1229  		ps = append(ps, ipp)
  1230  	}
  1231  	return
  1232  }
  1233  
  1234  // If the err is of type *ec2.Error, ec2ErrCode returns
  1235  // its code, otherwise it returns the empty string.
  1236  func ec2ErrCode(err error) string {
  1237  	ec2err, _ := err.(*ec2.Error)
  1238  	if ec2err == nil {
  1239  		return ""
  1240  	}
  1241  	return ec2err.Code
  1242  }
  1243  
  1244  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
  1245  func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {
  1246  	// Add the simplestreams source off the control bucket.
  1247  	sources := []simplestreams.DataSource{
  1248  		storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseImagesPath)}
  1249  	return sources, nil
  1250  }
  1251  
  1252  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
  1253  func (e *environ) GetToolsSources() ([]simplestreams.DataSource, error) {
  1254  	// Add the simplestreams source off the control bucket.
  1255  	sources := []simplestreams.DataSource{
  1256  		storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath)}
  1257  	return sources, nil
  1258  }