launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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/loggo/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(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", "arm"},
   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  // PrecheckInstance is specified in the environs.Prechecker interface.
   310  func (e *environ) PrecheckInstance(series string, cons constraints.Value) error {
   311  	return nil
   312  }
   313  
   314  // PrecheckContainer is specified in the environs.Prechecker interface.
   315  func (e *environ) PrecheckContainer(series string, kind instance.ContainerType) error {
   316  	// This check can either go away or be relaxed when the ec2
   317  	// provider manages container addressibility.
   318  	return environs.NewContainersUnsupported("ec2 provider does not support containers")
   319  }
   320  
   321  func (e *environ) Name() string {
   322  	return e.name
   323  }
   324  
   325  func (e *environ) Storage() storage.Storage {
   326  	e.ecfgMutex.Lock()
   327  	stor := e.storageUnlocked
   328  	e.ecfgMutex.Unlock()
   329  	return stor
   330  }
   331  
   332  func (e *environ) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error {
   333  	return common.Bootstrap(ctx, e, cons)
   334  }
   335  
   336  func (e *environ) StateInfo() (*state.Info, *api.Info, error) {
   337  	return common.StateInfo(e)
   338  }
   339  
   340  // MetadataLookupParams returns parameters which are used to query simplestreams metadata.
   341  func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   342  	if region == "" {
   343  		region = e.ecfg().region()
   344  	}
   345  	ec2Region, ok := allRegions[region]
   346  	if !ok {
   347  		return nil, fmt.Errorf("unknown region %q", region)
   348  	}
   349  	return &simplestreams.MetadataLookupParams{
   350  		Series:        e.ecfg().DefaultSeries(),
   351  		Region:        region,
   352  		Endpoint:      ec2Region.EC2Endpoint,
   353  		Architectures: []string{"amd64", "i386", "arm"},
   354  	}, nil
   355  }
   356  
   357  // Region is specified in the HasRegion interface.
   358  func (e *environ) Region() (simplestreams.CloudSpec, error) {
   359  	region := e.ecfg().region()
   360  	ec2Region, ok := allRegions[region]
   361  	if !ok {
   362  		return simplestreams.CloudSpec{}, fmt.Errorf("unknown region %q", region)
   363  	}
   364  	return simplestreams.CloudSpec{
   365  		Region:   region,
   366  		Endpoint: ec2Region.EC2Endpoint,
   367  	}, nil
   368  }
   369  
   370  const ebsStorage = "ebs"
   371  
   372  // StartInstance is specified in the InstanceBroker interface.
   373  func (e *environ) StartInstance(cons constraints.Value, possibleTools tools.List,
   374  	machineConfig *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) {
   375  
   376  	arches := possibleTools.Arches()
   377  	stor := ebsStorage
   378  	sources, err := imagemetadata.GetMetadataSources(e)
   379  	if err != nil {
   380  		return nil, nil, err
   381  	}
   382  
   383  	series := possibleTools.OneSeries()
   384  	spec, err := findInstanceSpec(sources, e.Config().ImageStream(), &instances.InstanceConstraint{
   385  		Region:      e.ecfg().region(),
   386  		Series:      series,
   387  		Arches:      arches,
   388  		Constraints: cons,
   389  		Storage:     &stor,
   390  	})
   391  	if err != nil {
   392  		return nil, nil, err
   393  	}
   394  	tools, err := possibleTools.Match(tools.Filter{Arch: spec.Image.Arch})
   395  	if err != nil {
   396  		return nil, nil, fmt.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches)
   397  	}
   398  
   399  	machineConfig.Tools = tools[0]
   400  	if err := environs.FinishMachineConfig(machineConfig, e.Config(), cons); err != nil {
   401  		return nil, nil, err
   402  	}
   403  
   404  	userData, err := environs.ComposeUserData(machineConfig)
   405  	if err != nil {
   406  		return nil, nil, fmt.Errorf("cannot make user data: %v", err)
   407  	}
   408  	logger.Debugf("ec2 user data; %d bytes", len(userData))
   409  	cfg := e.Config()
   410  	groups, err := e.setUpGroups(machineConfig.MachineId, cfg.StatePort(), cfg.APIPort())
   411  	if err != nil {
   412  		return nil, nil, fmt.Errorf("cannot set up groups: %v", err)
   413  	}
   414  	var instResp *ec2.RunInstancesResp
   415  
   416  	device, diskSize := getDiskSize(cons)
   417  	for a := shortAttempt.Start(); a.Next(); {
   418  		instResp, err = e.ec2().RunInstances(&ec2.RunInstances{
   419  			ImageId:             spec.Image.Id,
   420  			MinCount:            1,
   421  			MaxCount:            1,
   422  			UserData:            userData,
   423  			InstanceType:        spec.InstanceType.Name,
   424  			SecurityGroups:      groups,
   425  			BlockDeviceMappings: []ec2.BlockDeviceMapping{device},
   426  		})
   427  		if err == nil || ec2ErrCode(err) != "InvalidGroup.NotFound" {
   428  			break
   429  		}
   430  	}
   431  	if err != nil {
   432  		return nil, nil, fmt.Errorf("cannot run instances: %v", err)
   433  	}
   434  	if len(instResp.Instances) != 1 {
   435  		return nil, nil, fmt.Errorf("expected 1 started instance, got %d", len(instResp.Instances))
   436  	}
   437  
   438  	inst := &ec2Instance{
   439  		e:        e,
   440  		Instance: &instResp.Instances[0],
   441  	}
   442  	logger.Infof("started instance %q", inst.Id())
   443  
   444  	hc := instance.HardwareCharacteristics{
   445  		Arch:     &spec.Image.Arch,
   446  		Mem:      &spec.InstanceType.Mem,
   447  		CpuCores: &spec.InstanceType.CpuCores,
   448  		CpuPower: spec.InstanceType.CpuPower,
   449  		RootDisk: &diskSize,
   450  		// Tags currently not supported by EC2
   451  	}
   452  	return inst, &hc, nil
   453  }
   454  
   455  func (e *environ) StopInstances(insts []instance.Instance) error {
   456  	ids := make([]instance.Id, len(insts))
   457  	for i, inst := range insts {
   458  		ids[i] = inst.(*ec2Instance).Id()
   459  	}
   460  	return e.terminateInstances(ids)
   461  }
   462  
   463  // minDiskSize is the minimum/default size (in megabytes) for ec2 root disks.
   464  const minDiskSize uint64 = 8 * 1024
   465  
   466  // getDiskSize translates a RootDisk constraint (or lackthereof) into a
   467  // BlockDeviceMapping request for EC2.  megs is the size in megabytes of
   468  // the disk that was requested.
   469  func getDiskSize(cons constraints.Value) (dvc ec2.BlockDeviceMapping, megs uint64) {
   470  	diskSize := minDiskSize
   471  
   472  	if cons.RootDisk != nil {
   473  		if *cons.RootDisk >= minDiskSize {
   474  			diskSize = *cons.RootDisk
   475  		} else {
   476  			logger.Infof("Ignoring root-disk constraint of %dM because it is smaller than the EC2 image size of %dM",
   477  				*cons.RootDisk, minDiskSize)
   478  		}
   479  	}
   480  
   481  	// AWS's volume size is in gigabytes, root-disk is in megabytes,
   482  	// so round up to the nearest gigabyte.
   483  	volsize := int64((diskSize + 1023) / 1024)
   484  	return ec2.BlockDeviceMapping{
   485  			DeviceName: "/dev/sda1",
   486  			VolumeSize: volsize,
   487  		},
   488  		uint64(volsize * 1024)
   489  }
   490  
   491  // groupInfoByName returns information on the security group
   492  // with the given name including rules and other details.
   493  func (e *environ) groupInfoByName(groupName string) (ec2.SecurityGroupInfo, error) {
   494  	// Non-default VPC does not support name-based group lookups, can
   495  	// use a filter by group name instead when support is needed.
   496  	limitToGroups := []ec2.SecurityGroup{{Name: groupName}}
   497  	resp, err := e.ec2().SecurityGroups(limitToGroups, nil)
   498  	if err != nil {
   499  		return ec2.SecurityGroupInfo{}, err
   500  	}
   501  	if len(resp.Groups) != 1 {
   502  		return ec2.SecurityGroupInfo{}, fmt.Errorf("expected one security group named %q, got %v", groupName, resp.Groups)
   503  	}
   504  	return resp.Groups[0], nil
   505  }
   506  
   507  // groupByName returns the security group with the given name.
   508  func (e *environ) groupByName(groupName string) (ec2.SecurityGroup, error) {
   509  	groupInfo, err := e.groupInfoByName(groupName)
   510  	return groupInfo.SecurityGroup, err
   511  }
   512  
   513  // addGroupFilter sets a limit an instance filter so only those machines
   514  // with the juju environment wide security group associated will be listed.
   515  //
   516  // An EC2 API call is required to resolve the group name to an id, as VPC
   517  // enabled accounts do not support name based filtering.
   518  // TODO: Detect classic accounts and just filter by name for those.
   519  //
   520  // Callers must handle InvalidGroup.NotFound errors to mean the same as no
   521  // matching instances.
   522  func (e *environ) addGroupFilter(filter *ec2.Filter) error {
   523  	groupName := e.jujuGroupName()
   524  	group, err := e.groupByName(groupName)
   525  	if err != nil {
   526  		return err
   527  	}
   528  	// EC2 should support filtering with and without the 'instance.'
   529  	// prefix, but only the form with seems to work with default VPC.
   530  	filter.Add("instance.group-id", group.Id)
   531  	return nil
   532  }
   533  
   534  // gatherInstances tries to get information on each instance
   535  // id whose corresponding insts slot is nil.
   536  // It returns environs.ErrPartialInstances if the insts
   537  // slice has not been completely filled.
   538  func (e *environ) gatherInstances(ids []instance.Id, insts []instance.Instance) error {
   539  	var need []string
   540  	for i, inst := range insts {
   541  		if inst == nil {
   542  			need = append(need, string(ids[i]))
   543  		}
   544  	}
   545  	if len(need) == 0 {
   546  		return nil
   547  	}
   548  	filter := ec2.NewFilter()
   549  	filter.Add("instance-state-name", "pending", "running")
   550  	err := e.addGroupFilter(filter)
   551  	if err != nil {
   552  		if ec2ErrCode(err) == "InvalidGroup.NotFound" {
   553  			return environs.ErrPartialInstances
   554  		}
   555  		return err
   556  	}
   557  	filter.Add("instance-id", need...)
   558  	resp, err := e.ec2().Instances(nil, filter)
   559  	if err != nil {
   560  		return err
   561  	}
   562  	n := 0
   563  	// For each requested id, add it to the returned instances
   564  	// if we find it in the response.
   565  	for i, id := range ids {
   566  		if insts[i] != nil {
   567  			continue
   568  		}
   569  		for j := range resp.Reservations {
   570  			r := &resp.Reservations[j]
   571  			for k := range r.Instances {
   572  				if r.Instances[k].InstanceId == string(id) {
   573  					inst := r.Instances[k]
   574  					// TODO(wallyworld): lookup the details to fill in the instance type data
   575  					insts[i] = &ec2Instance{e: e, Instance: &inst}
   576  					n++
   577  				}
   578  			}
   579  		}
   580  	}
   581  	if n < len(ids) {
   582  		return environs.ErrPartialInstances
   583  	}
   584  	return nil
   585  }
   586  
   587  func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
   588  	if len(ids) == 0 {
   589  		return nil, nil
   590  	}
   591  	insts := make([]instance.Instance, len(ids))
   592  	// Make a series of requests to cope with eventual consistency.
   593  	// Each request will attempt to add more instances to the requested
   594  	// set.
   595  	var err error
   596  	for a := shortAttempt.Start(); a.Next(); {
   597  		err = e.gatherInstances(ids, insts)
   598  		if err == nil || err != environs.ErrPartialInstances {
   599  			break
   600  		}
   601  	}
   602  	if err == environs.ErrPartialInstances {
   603  		for _, inst := range insts {
   604  			if inst != nil {
   605  				return insts, environs.ErrPartialInstances
   606  			}
   607  		}
   608  		return nil, environs.ErrNoInstances
   609  	}
   610  	if err != nil {
   611  		return nil, err
   612  	}
   613  	return insts, nil
   614  }
   615  
   616  func (e *environ) AllInstances() ([]instance.Instance, error) {
   617  	filter := ec2.NewFilter()
   618  	filter.Add("instance-state-name", "pending", "running")
   619  	err := e.addGroupFilter(filter)
   620  	if err != nil {
   621  		if ec2ErrCode(err) == "InvalidGroup.NotFound" {
   622  			return nil, nil
   623  		}
   624  		return nil, err
   625  	}
   626  	resp, err := e.ec2().Instances(nil, filter)
   627  	if err != nil {
   628  		return nil, err
   629  	}
   630  	var insts []instance.Instance
   631  	for _, r := range resp.Reservations {
   632  		for i := range r.Instances {
   633  			inst := r.Instances[i]
   634  			// TODO(wallyworld): lookup the details to fill in the instance type data
   635  			insts = append(insts, &ec2Instance{e: e, Instance: &inst})
   636  		}
   637  	}
   638  	return insts, nil
   639  }
   640  
   641  func (e *environ) Destroy() error {
   642  	return common.Destroy(e)
   643  }
   644  
   645  func portsToIPPerms(ports []instance.Port) []ec2.IPPerm {
   646  	ipPerms := make([]ec2.IPPerm, len(ports))
   647  	for i, p := range ports {
   648  		ipPerms[i] = ec2.IPPerm{
   649  			Protocol:  p.Protocol,
   650  			FromPort:  p.Number,
   651  			ToPort:    p.Number,
   652  			SourceIPs: []string{"0.0.0.0/0"},
   653  		}
   654  	}
   655  	return ipPerms
   656  }
   657  
   658  func (e *environ) openPortsInGroup(name string, ports []instance.Port) error {
   659  	if len(ports) == 0 {
   660  		return nil
   661  	}
   662  	// Give permissions for anyone to access the given ports.
   663  	g, err := e.groupByName(name)
   664  	if err != nil {
   665  		return err
   666  	}
   667  	ipPerms := portsToIPPerms(ports)
   668  	_, err = e.ec2().AuthorizeSecurityGroup(g, ipPerms)
   669  	if err != nil && ec2ErrCode(err) == "InvalidPermission.Duplicate" {
   670  		if len(ports) == 1 {
   671  			return nil
   672  		}
   673  		// If there's more than one port and we get a duplicate error,
   674  		// then we go through authorizing each port individually,
   675  		// otherwise the ports that were *not* duplicates will have
   676  		// been ignored
   677  		for i := range ipPerms {
   678  			_, err := e.ec2().AuthorizeSecurityGroup(g, ipPerms[i:i+1])
   679  			if err != nil && ec2ErrCode(err) != "InvalidPermission.Duplicate" {
   680  				return fmt.Errorf("cannot open port %v: %v", ipPerms[i], err)
   681  			}
   682  		}
   683  		return nil
   684  	}
   685  	if err != nil {
   686  		return fmt.Errorf("cannot open ports: %v", err)
   687  	}
   688  	return nil
   689  }
   690  
   691  func (e *environ) closePortsInGroup(name string, ports []instance.Port) error {
   692  	if len(ports) == 0 {
   693  		return nil
   694  	}
   695  	// Revoke permissions for anyone to access the given ports.
   696  	// Note that ec2 allows the revocation of permissions that aren't
   697  	// granted, so this is naturally idempotent.
   698  	g, err := e.groupByName(name)
   699  	if err != nil {
   700  		return err
   701  	}
   702  	_, err = e.ec2().RevokeSecurityGroup(g, portsToIPPerms(ports))
   703  	if err != nil {
   704  		return fmt.Errorf("cannot close ports: %v", err)
   705  	}
   706  	return nil
   707  }
   708  
   709  func (e *environ) portsInGroup(name string) (ports []instance.Port, err error) {
   710  	group, err := e.groupInfoByName(name)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  	for _, p := range group.IPPerms {
   715  		if len(p.SourceIPs) != 1 {
   716  			logger.Warningf("unexpected IP permission found: %v", p)
   717  			continue
   718  		}
   719  		for i := p.FromPort; i <= p.ToPort; i++ {
   720  			ports = append(ports, instance.Port{
   721  				Protocol: p.Protocol,
   722  				Number:   i,
   723  			})
   724  		}
   725  	}
   726  	instance.SortPorts(ports)
   727  	return ports, nil
   728  }
   729  
   730  func (e *environ) OpenPorts(ports []instance.Port) error {
   731  	if e.Config().FirewallMode() != config.FwGlobal {
   732  		return fmt.Errorf("invalid firewall mode %q for opening ports on environment",
   733  			e.Config().FirewallMode())
   734  	}
   735  	if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil {
   736  		return err
   737  	}
   738  	logger.Infof("opened ports in global group: %v", ports)
   739  	return nil
   740  }
   741  
   742  func (e *environ) ClosePorts(ports []instance.Port) error {
   743  	if e.Config().FirewallMode() != config.FwGlobal {
   744  		return fmt.Errorf("invalid firewall mode %q for closing ports on environment",
   745  			e.Config().FirewallMode())
   746  	}
   747  	if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil {
   748  		return err
   749  	}
   750  	logger.Infof("closed ports in global group: %v", ports)
   751  	return nil
   752  }
   753  
   754  func (e *environ) Ports() ([]instance.Port, error) {
   755  	if e.Config().FirewallMode() != config.FwGlobal {
   756  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment",
   757  			e.Config().FirewallMode())
   758  	}
   759  	return e.portsInGroup(e.globalGroupName())
   760  }
   761  
   762  func (*environ) Provider() environs.EnvironProvider {
   763  	return &providerInstance
   764  }
   765  
   766  func (e *environ) terminateInstances(ids []instance.Id) error {
   767  	if len(ids) == 0 {
   768  		return nil
   769  	}
   770  	var err error
   771  	ec2inst := e.ec2()
   772  	strs := make([]string, len(ids))
   773  	for i, id := range ids {
   774  		strs[i] = string(id)
   775  	}
   776  	for a := shortAttempt.Start(); a.Next(); {
   777  		_, err = ec2inst.TerminateInstances(strs)
   778  		if err == nil || ec2ErrCode(err) != "InvalidInstanceID.NotFound" {
   779  			return err
   780  		}
   781  	}
   782  	if len(ids) == 1 {
   783  		return err
   784  	}
   785  	// If we get a NotFound error, it means that no instances have been
   786  	// terminated even if some exist, so try them one by one, ignoring
   787  	// NotFound errors.
   788  	var firstErr error
   789  	for _, id := range ids {
   790  		_, err = ec2inst.TerminateInstances([]string{string(id)})
   791  		if ec2ErrCode(err) == "InvalidInstanceID.NotFound" {
   792  			err = nil
   793  		}
   794  		if err != nil && firstErr == nil {
   795  			firstErr = err
   796  		}
   797  	}
   798  	return firstErr
   799  }
   800  
   801  func (e *environ) globalGroupName() string {
   802  	return fmt.Sprintf("%s-global", e.jujuGroupName())
   803  }
   804  
   805  func (e *environ) machineGroupName(machineId string) string {
   806  	return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId)
   807  }
   808  
   809  func (e *environ) jujuGroupName() string {
   810  	return "juju-" + e.name
   811  }
   812  
   813  func (inst *ec2Instance) OpenPorts(machineId string, ports []instance.Port) error {
   814  	if inst.e.Config().FirewallMode() != config.FwInstance {
   815  		return fmt.Errorf("invalid firewall mode %q for opening ports on instance",
   816  			inst.e.Config().FirewallMode())
   817  	}
   818  	name := inst.e.machineGroupName(machineId)
   819  	if err := inst.e.openPortsInGroup(name, ports); err != nil {
   820  		return err
   821  	}
   822  	logger.Infof("opened ports in security group %s: %v", name, ports)
   823  	return nil
   824  }
   825  
   826  func (inst *ec2Instance) ClosePorts(machineId string, ports []instance.Port) error {
   827  	if inst.e.Config().FirewallMode() != config.FwInstance {
   828  		return fmt.Errorf("invalid firewall mode %q for closing ports on instance",
   829  			inst.e.Config().FirewallMode())
   830  	}
   831  	name := inst.e.machineGroupName(machineId)
   832  	if err := inst.e.closePortsInGroup(name, ports); err != nil {
   833  		return err
   834  	}
   835  	logger.Infof("closed ports in security group %s: %v", name, ports)
   836  	return nil
   837  }
   838  
   839  func (inst *ec2Instance) Ports(machineId string) ([]instance.Port, error) {
   840  	if inst.e.Config().FirewallMode() != config.FwInstance {
   841  		return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance",
   842  			inst.e.Config().FirewallMode())
   843  	}
   844  	name := inst.e.machineGroupName(machineId)
   845  	return inst.e.portsInGroup(name)
   846  }
   847  
   848  // setUpGroups creates the security groups for the new machine, and
   849  // returns them.
   850  //
   851  // Instances are tagged with a group so they can be distinguished from
   852  // other instances that might be running on the same EC2 account.  In
   853  // addition, a specific machine security group is created for each
   854  // machine, so that its firewall rules can be configured per machine.
   855  func (e *environ) setUpGroups(machineId string, statePort, apiPort int) ([]ec2.SecurityGroup, error) {
   856  	jujuGroup, err := e.ensureGroup(e.jujuGroupName(),
   857  		[]ec2.IPPerm{
   858  			{
   859  				Protocol:  "tcp",
   860  				FromPort:  22,
   861  				ToPort:    22,
   862  				SourceIPs: []string{"0.0.0.0/0"},
   863  			},
   864  			{
   865  				Protocol:  "tcp",
   866  				FromPort:  statePort,
   867  				ToPort:    statePort,
   868  				SourceIPs: []string{"0.0.0.0/0"},
   869  			},
   870  			{
   871  				Protocol:  "tcp",
   872  				FromPort:  apiPort,
   873  				ToPort:    apiPort,
   874  				SourceIPs: []string{"0.0.0.0/0"},
   875  			},
   876  			{
   877  				Protocol: "tcp",
   878  				FromPort: 0,
   879  				ToPort:   65535,
   880  			},
   881  			{
   882  				Protocol: "udp",
   883  				FromPort: 0,
   884  				ToPort:   65535,
   885  			},
   886  			{
   887  				Protocol: "icmp",
   888  				FromPort: -1,
   889  				ToPort:   -1,
   890  			},
   891  		})
   892  	if err != nil {
   893  		return nil, err
   894  	}
   895  	var machineGroup ec2.SecurityGroup
   896  	switch e.Config().FirewallMode() {
   897  	case config.FwInstance:
   898  		machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil)
   899  	case config.FwGlobal:
   900  		machineGroup, err = e.ensureGroup(e.globalGroupName(), nil)
   901  	}
   902  	if err != nil {
   903  		return nil, err
   904  	}
   905  	return []ec2.SecurityGroup{jujuGroup, machineGroup}, nil
   906  }
   907  
   908  // zeroGroup holds the zero security group.
   909  var zeroGroup ec2.SecurityGroup
   910  
   911  // ensureGroup returns the security group with name and perms.
   912  // If a group with name does not exist, one will be created.
   913  // If it exists, its permissions are set to perms.
   914  // Any entries in perms without SourceIPs will be granted for
   915  // the named group only.
   916  func (e *environ) ensureGroup(name string, perms []ec2.IPPerm) (g ec2.SecurityGroup, err error) {
   917  	ec2inst := e.ec2()
   918  	resp, err := ec2inst.CreateSecurityGroup(name, "juju group")
   919  	if err != nil && ec2ErrCode(err) != "InvalidGroup.Duplicate" {
   920  		return zeroGroup, err
   921  	}
   922  
   923  	var have permSet
   924  	if err == nil {
   925  		g = resp.SecurityGroup
   926  	} else {
   927  		resp, err := ec2inst.SecurityGroups(ec2.SecurityGroupNames(name), nil)
   928  		if err != nil {
   929  			return zeroGroup, err
   930  		}
   931  		info := resp.Groups[0]
   932  		// It's possible that the old group has the wrong
   933  		// description here, but if it does it's probably due
   934  		// to something deliberately playing games with juju,
   935  		// so we ignore it.
   936  		g = info.SecurityGroup
   937  		have = newPermSetForGroup(info.IPPerms, g)
   938  	}
   939  	want := newPermSetForGroup(perms, g)
   940  	revoke := make(permSet)
   941  	for p := range have {
   942  		if !want[p] {
   943  			revoke[p] = true
   944  		}
   945  	}
   946  	if len(revoke) > 0 {
   947  		_, err := ec2inst.RevokeSecurityGroup(g, revoke.ipPerms())
   948  		if err != nil {
   949  			return zeroGroup, fmt.Errorf("cannot revoke security group: %v", err)
   950  		}
   951  	}
   952  
   953  	add := make(permSet)
   954  	for p := range want {
   955  		if !have[p] {
   956  			add[p] = true
   957  		}
   958  	}
   959  	if len(add) > 0 {
   960  		_, err := ec2inst.AuthorizeSecurityGroup(g, add.ipPerms())
   961  		if err != nil {
   962  			return zeroGroup, fmt.Errorf("cannot authorize securityGroup: %v", err)
   963  		}
   964  	}
   965  	return g, nil
   966  }
   967  
   968  // permKey represents a permission for a group or an ip address range
   969  // to access the given range of ports. Only one of groupName or ipAddr
   970  // should be non-empty.
   971  type permKey struct {
   972  	protocol string
   973  	fromPort int
   974  	toPort   int
   975  	groupId  string
   976  	ipAddr   string
   977  }
   978  
   979  type permSet map[permKey]bool
   980  
   981  // newPermSetForGroup returns a set of all the permissions in the
   982  // given slice of IPPerms. It ignores the name and owner
   983  // id in source groups, and any entry with no source ips will
   984  // be granted for the given group only.
   985  func newPermSetForGroup(ps []ec2.IPPerm, group ec2.SecurityGroup) permSet {
   986  	m := make(permSet)
   987  	for _, p := range ps {
   988  		k := permKey{
   989  			protocol: p.Protocol,
   990  			fromPort: p.FromPort,
   991  			toPort:   p.ToPort,
   992  		}
   993  		if len(p.SourceIPs) > 0 {
   994  			for _, ip := range p.SourceIPs {
   995  				k.ipAddr = ip
   996  				m[k] = true
   997  			}
   998  		} else {
   999  			k.groupId = group.Id
  1000  			m[k] = true
  1001  		}
  1002  	}
  1003  	return m
  1004  }
  1005  
  1006  // ipPerms returns m as a slice of permissions usable
  1007  // with the ec2 package.
  1008  func (m permSet) ipPerms() (ps []ec2.IPPerm) {
  1009  	// We could compact the permissions, but it
  1010  	// hardly seems worth it.
  1011  	for p := range m {
  1012  		ipp := ec2.IPPerm{
  1013  			Protocol: p.protocol,
  1014  			FromPort: p.fromPort,
  1015  			ToPort:   p.toPort,
  1016  		}
  1017  		if p.ipAddr != "" {
  1018  			ipp.SourceIPs = []string{p.ipAddr}
  1019  		} else {
  1020  			ipp.SourceGroups = []ec2.UserSecurityGroup{{Id: p.groupId}}
  1021  		}
  1022  		ps = append(ps, ipp)
  1023  	}
  1024  	return
  1025  }
  1026  
  1027  // If the err is of type *ec2.Error, ec2ErrCode returns
  1028  // its code, otherwise it returns the empty string.
  1029  func ec2ErrCode(err error) string {
  1030  	ec2err, _ := err.(*ec2.Error)
  1031  	if ec2err == nil {
  1032  		return ""
  1033  	}
  1034  	return ec2err.Code
  1035  }
  1036  
  1037  // metadataHost holds the address of the instance metadata service.
  1038  // It is a variable so that tests can change it to refer to a local
  1039  // server when needed.
  1040  var metadataHost = "http://169.254.169.254"
  1041  
  1042  // fetchMetadata fetches a single atom of data from the ec2 instance metadata service.
  1043  // http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html
  1044  func fetchMetadata(name string) (value string, err error) {
  1045  	uri := fmt.Sprintf("%s/2011-01-01/meta-data/%s", metadataHost, name)
  1046  	defer utils.ErrorContextf(&err, "cannot get %q", uri)
  1047  	for a := shortAttempt.Start(); a.Next(); {
  1048  		var resp *http.Response
  1049  		resp, err = http.Get(uri)
  1050  		if err != nil {
  1051  			continue
  1052  		}
  1053  		defer resp.Body.Close()
  1054  		if resp.StatusCode != http.StatusOK {
  1055  			err = fmt.Errorf("bad http response %v", resp.Status)
  1056  			continue
  1057  		}
  1058  		var data []byte
  1059  		data, err = ioutil.ReadAll(resp.Body)
  1060  		if err != nil {
  1061  			continue
  1062  		}
  1063  		return strings.TrimSpace(string(data)), nil
  1064  	}
  1065  	return
  1066  }
  1067  
  1068  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
  1069  func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {
  1070  	// Add the simplestreams source off the control bucket.
  1071  	sources := []simplestreams.DataSource{
  1072  		storage.NewStorageSimpleStreamsDataSource(e.Storage(), storage.BaseImagesPath)}
  1073  	return sources, nil
  1074  }
  1075  
  1076  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
  1077  func (e *environ) GetToolsSources() ([]simplestreams.DataSource, error) {
  1078  	// Add the simplestreams source off the control bucket.
  1079  	sources := []simplestreams.DataSource{
  1080  		storage.NewStorageSimpleStreamsDataSource(e.Storage(), storage.BaseToolsPath)}
  1081  	return sources, nil
  1082  }