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