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