github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/ec2/environ.go (about) 1 // Copyright 2011-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2 5 6 import ( 7 "fmt" 8 "net" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/names" 15 "github.com/juju/utils" 16 "github.com/juju/utils/set" 17 "gopkg.in/amz.v3/aws" 18 "gopkg.in/amz.v3/ec2" 19 "gopkg.in/amz.v3/s3" 20 21 "github.com/juju/juju/cloudconfig/instancecfg" 22 "github.com/juju/juju/cloudconfig/providerinit" 23 "github.com/juju/juju/constraints" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/imagemetadata" 27 "github.com/juju/juju/environs/instances" 28 "github.com/juju/juju/environs/simplestreams" 29 envstorage "github.com/juju/juju/environs/storage" 30 "github.com/juju/juju/environs/tags" 31 "github.com/juju/juju/instance" 32 "github.com/juju/juju/juju/arch" 33 "github.com/juju/juju/network" 34 "github.com/juju/juju/provider/common" 35 "github.com/juju/juju/state" 36 "github.com/juju/juju/state/multiwatcher" 37 "github.com/juju/juju/tools" 38 ) 39 40 const ( 41 none = "none" 42 invalidParameterValue = "InvalidParameterValue" 43 privateAddressLimitExceeded = "PrivateIpAddressLimitExceeded" 44 45 // tagName is the AWS-specific tag key that populates resources' 46 // name columns in the console. 47 tagName = "Name" 48 ) 49 50 // Use shortAttempt to poll for short-term events or for retrying API calls. 51 var shortAttempt = utils.AttemptStrategy{ 52 Total: 5 * time.Second, 53 Delay: 200 * time.Millisecond, 54 } 55 56 type environ struct { 57 common.SupportsUnitPlacementPolicy 58 59 name string 60 61 // archMutex gates access to supportedArchitectures 62 archMutex sync.Mutex 63 // supportedArchitectures caches the architectures 64 // for which images can be instantiated. 65 supportedArchitectures []string 66 67 // ecfgMutex protects the *Unlocked fields below. 68 ecfgMutex sync.Mutex 69 ecfgUnlocked *environConfig 70 ec2Unlocked *ec2.EC2 71 s3Unlocked *s3.S3 72 storageUnlocked envstorage.Storage 73 74 availabilityZonesMutex sync.Mutex 75 availabilityZones []common.AvailabilityZone 76 77 // cachedDefaultVpc caches the id of the ec2 default vpc 78 cachedDefaultVpc *defaultVpc 79 } 80 81 // Ensure EC2 provider supports environs.NetworkingEnviron. 82 var _ environs.NetworkingEnviron = (*environ)(nil) 83 var _ simplestreams.HasRegion = (*environ)(nil) 84 var _ state.Prechecker = (*environ)(nil) 85 var _ state.InstanceDistributor = (*environ)(nil) 86 87 type defaultVpc struct { 88 hasDefaultVpc bool 89 id network.Id 90 } 91 92 // AssignPrivateIPAddress is a wrapper around ec2Inst.AssignPrivateIPAddresses. 93 var AssignPrivateIPAddress = assignPrivateIPAddress 94 95 // assignPrivateIPAddress should not be called directly so tests can patch it (use 96 // AssignPrivateIPAddress). 97 func assignPrivateIPAddress(ec2Inst *ec2.EC2, netId string, addr network.Address) error { 98 _, err := ec2Inst.AssignPrivateIPAddresses(netId, []string{addr.Value}, 0, false) 99 return err 100 } 101 102 func (e *environ) Config() *config.Config { 103 return e.ecfg().Config 104 } 105 106 func awsClients(cfg *config.Config) (*ec2.EC2, *s3.S3, *environConfig, error) { 107 ecfg, err := providerInstance.newConfig(cfg) 108 if err != nil { 109 return nil, nil, nil, err 110 } 111 112 auth := aws.Auth{ecfg.accessKey(), ecfg.secretKey()} 113 region := aws.Regions[ecfg.region()] 114 signer := aws.SignV4Factory(region.Name, "ec2") 115 return ec2.New(auth, region, signer), s3.New(auth, region), ecfg, nil 116 } 117 118 func (e *environ) SetConfig(cfg *config.Config) error { 119 ec2Client, s3Client, ecfg, err := awsClients(cfg) 120 if err != nil { 121 return err 122 } 123 e.ecfgMutex.Lock() 124 defer e.ecfgMutex.Unlock() 125 e.ecfgUnlocked = ecfg 126 e.ec2Unlocked = ec2Client 127 e.s3Unlocked = s3Client 128 129 bucket, err := e.s3Unlocked.Bucket(ecfg.controlBucket()) 130 if err != nil { 131 return err 132 } 133 134 // create new storage instances, existing instances continue 135 // to reference their existing configuration. 136 e.storageUnlocked = &ec2storage{bucket: bucket} 137 return nil 138 } 139 140 func (e *environ) defaultVpc() (network.Id, bool, error) { 141 if e.cachedDefaultVpc != nil { 142 defaultVpc := e.cachedDefaultVpc 143 return defaultVpc.id, defaultVpc.hasDefaultVpc, nil 144 } 145 ec2 := e.ec2() 146 resp, err := ec2.AccountAttributes("default-vpc") 147 if err != nil { 148 return "", false, errors.Trace(err) 149 } 150 151 hasDefault := true 152 defaultVpcId := "" 153 154 if len(resp.Attributes) == 0 || len(resp.Attributes[0].Values) == 0 { 155 hasDefault = false 156 defaultVpcId = "" 157 } else { 158 defaultVpcId = resp.Attributes[0].Values[0] 159 if defaultVpcId == none { 160 hasDefault = false 161 defaultVpcId = "" 162 } 163 } 164 defaultVpc := &defaultVpc{ 165 id: network.Id(defaultVpcId), 166 hasDefaultVpc: hasDefault, 167 } 168 e.cachedDefaultVpc = defaultVpc 169 return defaultVpc.id, defaultVpc.hasDefaultVpc, nil 170 } 171 172 func (e *environ) ecfg() *environConfig { 173 e.ecfgMutex.Lock() 174 ecfg := e.ecfgUnlocked 175 e.ecfgMutex.Unlock() 176 return ecfg 177 } 178 179 func (e *environ) ec2() *ec2.EC2 { 180 e.ecfgMutex.Lock() 181 ec2 := e.ec2Unlocked 182 e.ecfgMutex.Unlock() 183 return ec2 184 } 185 186 func (e *environ) s3() *s3.S3 { 187 e.ecfgMutex.Lock() 188 s3 := e.s3Unlocked 189 e.ecfgMutex.Unlock() 190 return s3 191 } 192 193 func (e *environ) Name() string { 194 return e.name 195 } 196 197 func (e *environ) Storage() envstorage.Storage { 198 e.ecfgMutex.Lock() 199 stor := e.storageUnlocked 200 e.ecfgMutex.Unlock() 201 return stor 202 } 203 204 func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) { 205 return common.Bootstrap(ctx, e, args) 206 } 207 208 func (e *environ) StateServerInstances() ([]instance.Id, error) { 209 return common.ProviderStateInstances(e, e.Storage()) 210 } 211 212 // SupportedArchitectures is specified on the EnvironCapability interface. 213 func (e *environ) SupportedArchitectures() ([]string, error) { 214 e.archMutex.Lock() 215 defer e.archMutex.Unlock() 216 if e.supportedArchitectures != nil { 217 return e.supportedArchitectures, nil 218 } 219 // Create a filter to get all images from our region and for the correct stream. 220 cloudSpec, err := e.Region() 221 if err != nil { 222 return nil, err 223 } 224 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ 225 CloudSpec: cloudSpec, 226 Stream: e.Config().ImageStream(), 227 }) 228 e.supportedArchitectures, err = common.SupportedArchitectures(e, imageConstraint) 229 return e.supportedArchitectures, err 230 } 231 232 // SupportsAddressAllocation is specified on environs.Networking. 233 func (e *environ) SupportsAddressAllocation(_ network.Id) (bool, error) { 234 if !environs.AddressAllocationEnabled() { 235 return false, errors.NotSupportedf("address allocation") 236 } 237 _, hasDefaultVpc, err := e.defaultVpc() 238 if err != nil { 239 return false, errors.Trace(err) 240 } 241 return hasDefaultVpc, nil 242 } 243 244 var unsupportedConstraints = []string{ 245 constraints.Tags, 246 } 247 248 // ConstraintsValidator is defined on the Environs interface. 249 func (e *environ) ConstraintsValidator() (constraints.Validator, error) { 250 validator := constraints.NewValidator() 251 validator.RegisterConflicts( 252 []string{constraints.InstanceType}, 253 []string{constraints.Mem, constraints.CpuCores, constraints.CpuPower}) 254 validator.RegisterUnsupported(unsupportedConstraints) 255 supportedArches, err := e.SupportedArchitectures() 256 if err != nil { 257 return nil, err 258 } 259 validator.RegisterVocabulary(constraints.Arch, supportedArches) 260 instTypeNames := make([]string, len(allInstanceTypes)) 261 for i, itype := range allInstanceTypes { 262 instTypeNames[i] = itype.Name 263 } 264 validator.RegisterVocabulary(constraints.InstanceType, instTypeNames) 265 return validator, nil 266 } 267 268 func archMatches(arches []string, arch *string) bool { 269 if arch == nil { 270 return true 271 } 272 for _, a := range arches { 273 if a == *arch { 274 return true 275 } 276 } 277 return false 278 } 279 280 var ec2AvailabilityZones = (*ec2.EC2).AvailabilityZones 281 282 type ec2AvailabilityZone struct { 283 ec2.AvailabilityZoneInfo 284 } 285 286 func (z *ec2AvailabilityZone) Name() string { 287 return z.AvailabilityZoneInfo.Name 288 } 289 290 func (z *ec2AvailabilityZone) Available() bool { 291 return z.AvailabilityZoneInfo.State == "available" 292 } 293 294 // AvailabilityZones returns a slice of availability zones 295 // for the configured region. 296 func (e *environ) AvailabilityZones() ([]common.AvailabilityZone, error) { 297 e.availabilityZonesMutex.Lock() 298 defer e.availabilityZonesMutex.Unlock() 299 if e.availabilityZones == nil { 300 filter := ec2.NewFilter() 301 filter.Add("region-name", e.ecfg().region()) 302 resp, err := ec2AvailabilityZones(e.ec2(), filter) 303 if err != nil { 304 return nil, err 305 } 306 logger.Debugf("availability zones: %+v", resp) 307 e.availabilityZones = make([]common.AvailabilityZone, len(resp.Zones)) 308 for i, z := range resp.Zones { 309 e.availabilityZones[i] = &ec2AvailabilityZone{z} 310 } 311 } 312 return e.availabilityZones, nil 313 } 314 315 // InstanceAvailabilityZoneNames returns the availability zone names for each 316 // of the specified instances. 317 func (e *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) { 318 instances, err := e.Instances(ids) 319 if err != nil && err != environs.ErrPartialInstances { 320 return nil, err 321 } 322 zones := make([]string, len(instances)) 323 for i, inst := range instances { 324 if inst == nil { 325 continue 326 } 327 zones[i] = inst.(*ec2Instance).AvailZone 328 } 329 return zones, err 330 } 331 332 type ec2Placement struct { 333 availabilityZone ec2.AvailabilityZoneInfo 334 } 335 336 func (e *environ) parsePlacement(placement string) (*ec2Placement, error) { 337 pos := strings.IndexRune(placement, '=') 338 if pos == -1 { 339 return nil, fmt.Errorf("unknown placement directive: %v", placement) 340 } 341 switch key, value := placement[:pos], placement[pos+1:]; key { 342 case "zone": 343 availabilityZone := value 344 zones, err := e.AvailabilityZones() 345 if err != nil { 346 return nil, err 347 } 348 for _, z := range zones { 349 if z.Name() == availabilityZone { 350 return &ec2Placement{ 351 z.(*ec2AvailabilityZone).AvailabilityZoneInfo, 352 }, nil 353 } 354 } 355 return nil, fmt.Errorf("invalid availability zone %q", availabilityZone) 356 } 357 return nil, fmt.Errorf("unknown placement directive: %v", placement) 358 } 359 360 // PrecheckInstance is defined on the state.Prechecker interface. 361 func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { 362 if placement != "" { 363 if _, err := e.parsePlacement(placement); err != nil { 364 return err 365 } 366 } 367 if !cons.HasInstanceType() { 368 return nil 369 } 370 // Constraint has an instance-type constraint so let's see if it is valid. 371 for _, itype := range allInstanceTypes { 372 if itype.Name != *cons.InstanceType { 373 continue 374 } 375 if archMatches(itype.Arches, cons.Arch) { 376 return nil 377 } 378 } 379 if cons.Arch == nil { 380 return fmt.Errorf("invalid AWS instance type %q specified", *cons.InstanceType) 381 } 382 return fmt.Errorf("invalid AWS instance type %q and arch %q specified", *cons.InstanceType, *cons.Arch) 383 } 384 385 // MetadataLookupParams returns parameters which are used to query simplestreams metadata. 386 func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { 387 if region == "" { 388 region = e.ecfg().region() 389 } 390 cloudSpec, err := e.cloudSpec(region) 391 if err != nil { 392 return nil, err 393 } 394 return &simplestreams.MetadataLookupParams{ 395 Series: config.PreferredSeries(e.ecfg()), 396 Region: cloudSpec.Region, 397 Endpoint: cloudSpec.Endpoint, 398 Architectures: arch.AllSupportedArches, 399 }, nil 400 } 401 402 // Region is specified in the HasRegion interface. 403 func (e *environ) Region() (simplestreams.CloudSpec, error) { 404 return e.cloudSpec(e.ecfg().region()) 405 } 406 407 func (e *environ) cloudSpec(region string) (simplestreams.CloudSpec, error) { 408 ec2Region, ok := allRegions[region] 409 if !ok { 410 return simplestreams.CloudSpec{}, fmt.Errorf("unknown region %q", region) 411 } 412 return simplestreams.CloudSpec{ 413 Region: region, 414 Endpoint: ec2Region.EC2Endpoint, 415 }, nil 416 } 417 418 const ( 419 ebsStorage = "ebs" 420 ssdStorage = "ssd" 421 ) 422 423 // DistributeInstances implements the state.InstanceDistributor policy. 424 func (e *environ) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) { 425 return common.DistributeInstances(e, candidates, distributionGroup) 426 } 427 428 var availabilityZoneAllocations = common.AvailabilityZoneAllocations 429 430 // MaintainInstance is specified in the InstanceBroker interface. 431 func (*environ) MaintainInstance(args environs.StartInstanceParams) error { 432 return nil 433 } 434 435 // resourceName returns the string to use for a resource's Name tag, 436 // to help users identify Juju-managed resources in the AWS console. 437 func resourceName(tag names.Tag, envName string) string { 438 return fmt.Sprintf("juju-%s-%s", envName, tag) 439 } 440 441 // StartInstance is specified in the InstanceBroker interface. 442 func (e *environ) StartInstance(args environs.StartInstanceParams) (_ *environs.StartInstanceResult, resultErr error) { 443 var inst *ec2Instance 444 defer func() { 445 if resultErr == nil || inst == nil { 446 return 447 } 448 if err := e.StopInstances(inst.Id()); err != nil { 449 logger.Errorf("error stopping failed instance: %v", err) 450 } 451 }() 452 453 var availabilityZones []string 454 if args.Placement != "" { 455 placement, err := e.parsePlacement(args.Placement) 456 if err != nil { 457 return nil, err 458 } 459 if placement.availabilityZone.State != "available" { 460 return nil, errors.Errorf("availability zone %q is %s", placement.availabilityZone.Name, placement.availabilityZone.State) 461 } 462 availabilityZones = append(availabilityZones, placement.availabilityZone.Name) 463 } 464 465 // If no availability zone is specified, then automatically spread across 466 // the known zones for optimal spread across the instance distribution 467 // group. 468 if len(availabilityZones) == 0 { 469 var group []instance.Id 470 var err error 471 if args.DistributionGroup != nil { 472 group, err = args.DistributionGroup() 473 if err != nil { 474 return nil, err 475 } 476 } 477 zoneInstances, err := availabilityZoneAllocations(e, group) 478 if err != nil { 479 return nil, err 480 } 481 for _, z := range zoneInstances { 482 availabilityZones = append(availabilityZones, z.ZoneName) 483 } 484 if len(availabilityZones) == 0 { 485 return nil, errors.New("failed to determine availability zones") 486 } 487 } 488 489 // If spaces= constraints is also set, then filter availabilityZones to only 490 // contain zones matching the space's subnets' zones 491 if len(args.SubnetsToZones) > 0 { 492 // find all the available zones that match the subnets that match our 493 // space/subnet constraints 494 zonesSet := set.NewStrings(availabilityZones...) 495 subnetZones := set.NewStrings() 496 497 for _, zones := range args.SubnetsToZones { 498 for _, zone := range zones { 499 subnetZones.Add(zone) 500 } 501 } 502 503 if len(zonesSet.Intersection(subnetZones).SortedValues()) == 0 { 504 return nil, errors.Errorf( 505 "unable to resolve constraints: space and/or subnet unavailable in zones %v", 506 availabilityZones) 507 } 508 509 availabilityZones = zonesSet.Intersection(subnetZones).SortedValues() 510 } 511 512 if args.InstanceConfig.HasNetworks() { 513 return nil, errors.New("starting instances with networks is not supported yet") 514 } 515 arches := args.Tools.Arches() 516 sources, err := environs.ImageMetadataSources(e) 517 if err != nil { 518 return nil, err 519 } 520 521 series := args.Tools.OneSeries() 522 spec, err := findInstanceSpec(sources, e.Config().ImageStream(), &instances.InstanceConstraint{ 523 Region: e.ecfg().region(), 524 Series: series, 525 Arches: arches, 526 Constraints: args.Constraints, 527 Storage: []string{ssdStorage, ebsStorage}, 528 }) 529 if err != nil { 530 return nil, err 531 } 532 tools, err := args.Tools.Match(tools.Filter{Arch: spec.Image.Arch}) 533 if err != nil { 534 return nil, errors.Errorf("chosen architecture %v not present in %v", spec.Image.Arch, arches) 535 } 536 537 args.InstanceConfig.Tools = tools[0] 538 if err := instancecfg.FinishInstanceConfig(args.InstanceConfig, e.Config()); err != nil { 539 return nil, err 540 } 541 542 userData, err := providerinit.ComposeUserData(args.InstanceConfig, nil) 543 if err != nil { 544 return nil, errors.Annotate(err, "cannot make user data") 545 } 546 logger.Debugf("ec2 user data; %d bytes", len(userData)) 547 cfg := e.Config() 548 groups, err := e.setUpGroups(args.InstanceConfig.MachineId, cfg.APIPort()) 549 if err != nil { 550 return nil, errors.Annotate(err, "cannot set up groups") 551 } 552 var instResp *ec2.RunInstancesResp 553 554 blockDeviceMappings, err := getBlockDeviceMappings(args.Constraints) 555 if err != nil { 556 return nil, errors.Annotate(err, "cannot create block device mappings") 557 } 558 rootDiskSize := uint64(blockDeviceMappings[0].VolumeSize) * 1024 559 560 for _, availZone := range availabilityZones { 561 instResp, err = runInstances(e.ec2(), &ec2.RunInstances{ 562 AvailZone: availZone, 563 // TODO: SubnetId: <a subnet in the AZ that conforms to our constraints> 564 ImageId: spec.Image.Id, 565 MinCount: 1, 566 MaxCount: 1, 567 UserData: userData, 568 InstanceType: spec.InstanceType.Name, 569 SecurityGroups: groups, 570 BlockDeviceMappings: blockDeviceMappings, 571 }) 572 if isZoneConstrainedError(err) { 573 logger.Infof("%q is constrained, trying another availability zone", availZone) 574 } else { 575 break 576 } 577 } 578 if err != nil { 579 return nil, errors.Annotate(err, "cannot run instances") 580 } 581 if len(instResp.Instances) != 1 { 582 return nil, errors.Errorf("expected 1 started instance, got %d", len(instResp.Instances)) 583 } 584 585 inst = &ec2Instance{ 586 e: e, 587 Instance: &instResp.Instances[0], 588 } 589 logger.Infof("started instance %q in %q", inst.Id(), inst.Instance.AvailZone) 590 591 // Tag instance, for accounting and identification. 592 instanceName := resourceName( 593 names.NewMachineTag(args.InstanceConfig.MachineId), e.Config().Name(), 594 ) 595 args.InstanceConfig.Tags[tagName] = instanceName 596 if err := tagResources(e.ec2(), args.InstanceConfig.Tags, string(inst.Id())); err != nil { 597 return nil, errors.Annotate(err, "tagging instance") 598 } 599 600 // Tag the machine's root EBS volume, if it has one. 601 if inst.Instance.RootDeviceType == "ebs" { 602 uuid, _ := cfg.UUID() 603 tags := tags.ResourceTags(names.NewEnvironTag(uuid), cfg) 604 tags[tagName] = instanceName + "-root" 605 if err := tagRootDisk(e.ec2(), tags, inst.Instance); err != nil { 606 return nil, errors.Annotate(err, "tagging root disk") 607 } 608 } 609 610 if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) { 611 if err := common.AddStateInstance(e.Storage(), inst.Id()); err != nil { 612 return nil, errors.Annotate(err, "recording instance in provider-state") 613 } 614 } 615 616 hc := instance.HardwareCharacteristics{ 617 Arch: &spec.Image.Arch, 618 Mem: &spec.InstanceType.Mem, 619 CpuCores: &spec.InstanceType.CpuCores, 620 CpuPower: spec.InstanceType.CpuPower, 621 RootDisk: &rootDiskSize, 622 // Tags currently not supported by EC2 623 AvailabilityZone: &inst.Instance.AvailZone, 624 } 625 return &environs.StartInstanceResult{ 626 Instance: inst, 627 Hardware: &hc, 628 }, nil 629 } 630 631 // tagResources calls ec2.CreateTags, tagging each of the specified resources 632 // with the given tags. tagResources will retry for a short period of time 633 // if it receives a *.NotFound error response from EC2. 634 func tagResources(e *ec2.EC2, tags map[string]string, resourceIds ...string) error { 635 if len(tags) == 0 { 636 return nil 637 } 638 ec2Tags := make([]ec2.Tag, 0, len(tags)) 639 for k, v := range tags { 640 ec2Tags = append(ec2Tags, ec2.Tag{k, v}) 641 } 642 var err error 643 for a := shortAttempt.Start(); a.Next(); { 644 _, err = e.CreateTags(resourceIds, ec2Tags) 645 if err == nil || !strings.HasSuffix(ec2ErrCode(err), ".NotFound") { 646 return err 647 } 648 } 649 return err 650 } 651 652 func tagRootDisk(e *ec2.EC2, tags map[string]string, inst *ec2.Instance) error { 653 if len(tags) == 0 { 654 return nil 655 } 656 findVolumeId := func(inst *ec2.Instance) string { 657 for _, m := range inst.BlockDeviceMappings { 658 if m.DeviceName != inst.RootDeviceName { 659 continue 660 } 661 return m.VolumeId 662 } 663 return "" 664 } 665 // Wait until the instance has an associated EBS volume in the 666 // block-device-mapping. 667 volumeId := findVolumeId(inst) 668 for a := shortAttempt.Start(); volumeId == "" && a.Next(); { 669 resp, err := e.Instances([]string{inst.InstanceId}, nil) 670 if err != nil { 671 return err 672 } 673 if len(resp.Reservations) > 0 && len(resp.Reservations[0].Instances) > 0 { 674 inst = &resp.Reservations[0].Instances[0] 675 volumeId = findVolumeId(inst) 676 } 677 } 678 if volumeId == "" { 679 return errors.New("timed out waiting for EBS volume to be associated") 680 } 681 return tagResources(e, tags, volumeId) 682 } 683 684 var runInstances = _runInstances 685 686 // runInstances calls ec2.RunInstances for a fixed number of attempts until 687 // RunInstances returns an error code that does not indicate an error that 688 // may be caused by eventual consistency. 689 func _runInstances(e *ec2.EC2, ri *ec2.RunInstances) (resp *ec2.RunInstancesResp, err error) { 690 for a := shortAttempt.Start(); a.Next(); { 691 resp, err = e.RunInstances(ri) 692 if err == nil || ec2ErrCode(err) != "InvalidGroup.NotFound" { 693 break 694 } 695 } 696 return resp, err 697 } 698 699 func (e *environ) StopInstances(ids ...instance.Id) error { 700 if err := e.terminateInstances(ids); err != nil { 701 return errors.Trace(err) 702 } 703 return common.RemoveStateInstances(e.Storage(), ids...) 704 } 705 706 // groupInfoByName returns information on the security group 707 // with the given name including rules and other details. 708 func (e *environ) groupInfoByName(groupName string) (ec2.SecurityGroupInfo, error) { 709 // Non-default VPC does not support name-based group lookups, can 710 // use a filter by group name instead when support is needed. 711 limitToGroups := []ec2.SecurityGroup{{Name: groupName}} 712 resp, err := e.ec2().SecurityGroups(limitToGroups, nil) 713 if err != nil { 714 return ec2.SecurityGroupInfo{}, err 715 } 716 if len(resp.Groups) != 1 { 717 return ec2.SecurityGroupInfo{}, fmt.Errorf("expected one security group named %q, got %v", groupName, resp.Groups) 718 } 719 return resp.Groups[0], nil 720 } 721 722 // groupByName returns the security group with the given name. 723 func (e *environ) groupByName(groupName string) (ec2.SecurityGroup, error) { 724 groupInfo, err := e.groupInfoByName(groupName) 725 return groupInfo.SecurityGroup, err 726 } 727 728 // addGroupFilter sets a limit an instance filter so only those machines 729 // with the juju environment wide security group associated will be listed. 730 // 731 // An EC2 API call is required to resolve the group name to an id, as VPC 732 // enabled accounts do not support name based filtering. 733 // TODO: Detect classic accounts and just filter by name for those. 734 // 735 // Callers must handle InvalidGroup.NotFound errors to mean the same as no 736 // matching instances. 737 func (e *environ) addGroupFilter(filter *ec2.Filter) error { 738 groupName := e.jujuGroupName() 739 group, err := e.groupByName(groupName) 740 if err != nil { 741 return err 742 } 743 // EC2 should support filtering with and without the 'instance.' 744 // prefix, but only the form with seems to work with default VPC. 745 filter.Add("instance.group-id", group.Id) 746 return nil 747 } 748 749 // gatherInstances tries to get information on each instance 750 // id whose corresponding insts slot is nil. 751 // It returns environs.ErrPartialInstances if the insts 752 // slice has not been completely filled. 753 func (e *environ) gatherInstances(ids []instance.Id, insts []instance.Instance) error { 754 var need []string 755 for i, inst := range insts { 756 if inst == nil { 757 need = append(need, string(ids[i])) 758 } 759 } 760 if len(need) == 0 { 761 return nil 762 } 763 filter := ec2.NewFilter() 764 filter.Add("instance-state-name", "pending", "running") 765 err := e.addGroupFilter(filter) 766 if err != nil { 767 if ec2ErrCode(err) == "InvalidGroup.NotFound" { 768 return environs.ErrPartialInstances 769 } 770 return err 771 } 772 filter.Add("instance-id", need...) 773 resp, err := e.ec2().Instances(nil, filter) 774 if err != nil { 775 return err 776 } 777 n := 0 778 // For each requested id, add it to the returned instances 779 // if we find it in the response. 780 for i, id := range ids { 781 if insts[i] != nil { 782 continue 783 } 784 for j := range resp.Reservations { 785 r := &resp.Reservations[j] 786 for k := range r.Instances { 787 if r.Instances[k].InstanceId == string(id) { 788 inst := r.Instances[k] 789 // TODO(wallyworld): lookup the details to fill in the instance type data 790 insts[i] = &ec2Instance{e: e, Instance: &inst} 791 n++ 792 } 793 } 794 } 795 } 796 if n < len(ids) { 797 return environs.ErrPartialInstances 798 } 799 return nil 800 } 801 802 func (e *environ) Instances(ids []instance.Id) ([]instance.Instance, error) { 803 if len(ids) == 0 { 804 return nil, nil 805 } 806 insts := make([]instance.Instance, len(ids)) 807 // Make a series of requests to cope with eventual consistency. 808 // Each request will attempt to add more instances to the requested 809 // set. 810 var err error 811 for a := shortAttempt.Start(); a.Next(); { 812 err = e.gatherInstances(ids, insts) 813 if err == nil || err != environs.ErrPartialInstances { 814 break 815 } 816 } 817 if err == environs.ErrPartialInstances { 818 for _, inst := range insts { 819 if inst != nil { 820 return insts, environs.ErrPartialInstances 821 } 822 } 823 return nil, environs.ErrNoInstances 824 } 825 if err != nil { 826 return nil, err 827 } 828 return insts, nil 829 } 830 831 func (e *environ) fetchNetworkInterfaceId(ec2Inst *ec2.EC2, instId instance.Id) (string, error) { 832 var err error 833 var instancesResp *ec2.InstancesResp 834 for a := shortAttempt.Start(); a.Next(); { 835 instancesResp, err = ec2Inst.Instances([]string{string(instId)}, nil) 836 if err == nil { 837 break 838 } 839 logger.Tracef("Instances(%q) returned: %v", instId, err) 840 } 841 if err != nil { 842 // either the instance doesn't exist or we couldn't get through to 843 // the ec2 api 844 return "", err 845 } 846 847 if len(instancesResp.Reservations) == 0 { 848 return "", errors.New("unexpected AWS response: reservation not found") 849 } 850 if len(instancesResp.Reservations[0].Instances) == 0 { 851 return "", errors.New("unexpected AWS response: instance not found") 852 } 853 if len(instancesResp.Reservations[0].Instances[0].NetworkInterfaces) == 0 { 854 return "", errors.New("unexpected AWS response: network interface not found") 855 } 856 networkInterfaceId := instancesResp.Reservations[0].Instances[0].NetworkInterfaces[0].Id 857 return networkInterfaceId, nil 858 } 859 860 // AllocateAddress requests an address to be allocated for the given 861 // instance on the given subnet. Implements NetworkingEnviron.AllocateAddress. 862 func (e *environ) AllocateAddress(instId instance.Id, _ network.Id, addr network.Address, _, _ string) (err error) { 863 if !environs.AddressAllocationEnabled() { 864 return errors.NotSupportedf("address allocation") 865 } 866 867 defer errors.DeferredAnnotatef(&err, "failed to allocate address %q for instance %q", addr, instId) 868 869 var nicId string 870 ec2Inst := e.ec2() 871 nicId, err = e.fetchNetworkInterfaceId(ec2Inst, instId) 872 if err != nil { 873 return errors.Trace(err) 874 } 875 for a := shortAttempt.Start(); a.Next(); { 876 err = AssignPrivateIPAddress(ec2Inst, nicId, addr) 877 logger.Tracef("AssignPrivateIPAddresses(%v, %v) returned: %v", nicId, addr, err) 878 if err == nil { 879 logger.Tracef("allocated address %v for instance %v, NIC %v", addr, instId, nicId) 880 break 881 } 882 if ec2Err, ok := err.(*ec2.Error); ok { 883 if ec2Err.Code == invalidParameterValue { 884 // Note: this Code is also used if we specify 885 // an IP address outside the subnet. Take care! 886 logger.Tracef("address %q not available for allocation", addr) 887 return environs.ErrIPAddressUnavailable 888 } else if ec2Err.Code == privateAddressLimitExceeded { 889 logger.Tracef("no more addresses available on the subnet") 890 return environs.ErrIPAddressesExhausted 891 } 892 } 893 894 } 895 return err 896 } 897 898 // ReleaseAddress releases a specific address previously allocated with 899 // AllocateAddress. Implements NetworkingEnviron.ReleaseAddress. 900 func (e *environ) ReleaseAddress(instId instance.Id, _ network.Id, addr network.Address, _ string) (err error) { 901 if !environs.AddressAllocationEnabled() { 902 return errors.NotSupportedf("address allocation") 903 } 904 905 defer errors.DeferredAnnotatef(&err, "failed to release address %q from instance %q", addr, instId) 906 907 // If the instance ID is unknown the address has already been released 908 // and we can ignore this request. 909 if instId == instance.UnknownId { 910 logger.Debugf("release address %q with an unknown instance ID is a no-op (ignoring)", addr.Value) 911 return nil 912 } 913 914 var nicId string 915 ec2Inst := e.ec2() 916 nicId, err = e.fetchNetworkInterfaceId(ec2Inst, instId) 917 if err != nil { 918 return errors.Trace(err) 919 } 920 for a := shortAttempt.Start(); a.Next(); { 921 _, err = ec2Inst.UnassignPrivateIPAddresses(nicId, []string{addr.Value}) 922 logger.Tracef("UnassignPrivateIPAddresses(%q, %q) returned: %v", nicId, addr, err) 923 if err == nil { 924 logger.Tracef("released address %q from instance %q, NIC %q", addr, instId, nicId) 925 break 926 } 927 } 928 return err 929 } 930 931 // NetworkInterfaces implements NetworkingEnviron.NetworkInterfaces. 932 func (e *environ) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) { 933 ec2Client := e.ec2() 934 var err error 935 var networkInterfacesResp *ec2.NetworkInterfacesResp 936 for a := shortAttempt.Start(); a.Next(); { 937 logger.Tracef("retrieving NICs for instance %q", instId) 938 filter := ec2.NewFilter() 939 filter.Add("attachment.instance-id", string(instId)) 940 networkInterfacesResp, err = ec2Client.NetworkInterfaces(nil, filter) 941 logger.Tracef("instance %q NICs: %#v (err: %v)", instId, networkInterfacesResp, err) 942 if err != nil { 943 logger.Warningf("failed to get instance %q interfaces: %v (retrying)", instId, err) 944 continue 945 } 946 if len(networkInterfacesResp.Interfaces) == 0 { 947 logger.Tracef("instance %q has no NIC attachment yet, retrying...", instId) 948 continue 949 } 950 logger.Tracef("found instance %q NICS: %#v", instId, networkInterfacesResp.Interfaces) 951 break 952 } 953 if err != nil { 954 // either the instance doesn't exist or we couldn't get through to 955 // the ec2 api 956 return nil, errors.Annotatef(err, "cannot get instance %q network interfaces", instId) 957 } 958 ec2Interfaces := networkInterfacesResp.Interfaces 959 result := make([]network.InterfaceInfo, len(ec2Interfaces)) 960 for i, iface := range ec2Interfaces { 961 resp, err := ec2Client.Subnets([]string{iface.SubnetId}, nil) 962 if err != nil { 963 return nil, errors.Annotatef(err, "failed to retrieve subnet %q info", iface.SubnetId) 964 } 965 if len(resp.Subnets) != 1 { 966 return nil, errors.Errorf("expected 1 subnet, got %d", len(resp.Subnets)) 967 } 968 subnet := resp.Subnets[0] 969 cidr := subnet.CIDRBlock 970 971 result[i] = network.InterfaceInfo{ 972 DeviceIndex: iface.Attachment.DeviceIndex, 973 MACAddress: iface.MACAddress, 974 CIDR: cidr, 975 NetworkName: "", // Not needed for now. 976 ProviderId: network.Id(iface.Id), 977 ProviderSubnetId: network.Id(iface.SubnetId), 978 VLANTag: 0, // Not supported on EC2. 979 // Getting the interface name is not supported on EC2, so fake it. 980 InterfaceName: fmt.Sprintf("unsupported%d", iface.Attachment.DeviceIndex), 981 Disabled: false, 982 NoAutoStart: false, 983 ConfigType: network.ConfigDHCP, 984 Address: network.NewScopedAddress(iface.PrivateIPAddress, network.ScopeCloudLocal), 985 } 986 } 987 return result, nil 988 } 989 990 // Subnets returns basic information about the specified subnets known 991 // by the provider for the specified instance or list of ids. instId 992 // equal to instance.UnknownId is the only supported value, other ones 993 // result in NotSupportedError. subnetIds can be empty, in which case 994 // all known are returned. Implements NetworkingEnviron.Subnets. 995 func (e *environ) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 996 if instId != instance.UnknownId { 997 return nil, errors.NotSupportedf("instId") 998 } 999 ec2Inst := e.ec2() 1000 // We can't filter by instance id here, unfortunately. 1001 resp, err := ec2Inst.Subnets(nil, nil) 1002 if err != nil { 1003 return nil, errors.Annotatef(err, "failed to retrieve subnets") 1004 } 1005 if len(subnetIds) == 0 { 1006 for _, subnet := range resp.Subnets { 1007 subnetIds = append(subnetIds, network.Id(subnet.Id)) 1008 } 1009 } 1010 1011 subIdSet := make(map[string]bool) 1012 for _, subId := range subnetIds { 1013 subIdSet[string(subId)] = false 1014 } 1015 1016 var results []network.SubnetInfo 1017 for _, subnet := range resp.Subnets { 1018 _, ok := subIdSet[subnet.Id] 1019 if !ok { 1020 logger.Tracef("subnet %q not in %v, skipping", subnet.Id, subnetIds) 1021 continue 1022 } 1023 subIdSet[subnet.Id] = true 1024 1025 cidr := subnet.CIDRBlock 1026 ip, ipnet, err := net.ParseCIDR(cidr) 1027 if err != nil { 1028 logger.Warningf("skipping subnet %q, invalid CIDR: %v", cidr, err) 1029 continue 1030 } 1031 // ec2 only uses IPv4 addresses for subnets 1032 start, err := network.IPv4ToDecimal(ip) 1033 if err != nil { 1034 logger.Warningf("skipping subnet %q, invalid IP: %v", cidr, err) 1035 continue 1036 } 1037 // First four addresses in a subnet are reserved, see 1038 // http://goo.gl/rrWTIo 1039 allocatableLow := network.DecimalToIPv4(start + 4) 1040 1041 ones, bits := ipnet.Mask.Size() 1042 zeros := bits - ones 1043 numIPs := uint32(1) << uint32(zeros) 1044 highIP := start + numIPs - 1 1045 // The last address in a subnet is also reserved (see same ref). 1046 allocatableHigh := network.DecimalToIPv4(highIP - 1) 1047 1048 info := network.SubnetInfo{ 1049 CIDR: cidr, 1050 ProviderId: network.Id(subnet.Id), 1051 VLANTag: 0, // Not supported on EC2 1052 AllocatableIPLow: allocatableLow, 1053 AllocatableIPHigh: allocatableHigh, 1054 AvailabilityZones: []string{subnet.AvailZone}, 1055 } 1056 logger.Tracef("found subnet with info %#v", info) 1057 results = append(results, info) 1058 } 1059 1060 notFound := []string{} 1061 for subId, found := range subIdSet { 1062 if !found { 1063 notFound = append(notFound, subId) 1064 } 1065 } 1066 if len(notFound) != 0 { 1067 return nil, errors.Errorf("failed to find the following subnet ids: %v", notFound) 1068 } 1069 1070 return results, nil 1071 } 1072 1073 func (e *environ) AllInstances() ([]instance.Instance, error) { 1074 filter := ec2.NewFilter() 1075 filter.Add("instance-state-name", "pending", "running") 1076 err := e.addGroupFilter(filter) 1077 if err != nil { 1078 if ec2ErrCode(err) == "InvalidGroup.NotFound" { 1079 return nil, nil 1080 } 1081 return nil, err 1082 } 1083 resp, err := e.ec2().Instances(nil, filter) 1084 if err != nil { 1085 return nil, err 1086 } 1087 var insts []instance.Instance 1088 for _, r := range resp.Reservations { 1089 for i := range r.Instances { 1090 inst := r.Instances[i] 1091 // TODO(wallyworld): lookup the details to fill in the instance type data 1092 insts = append(insts, &ec2Instance{e: e, Instance: &inst}) 1093 } 1094 } 1095 return insts, nil 1096 } 1097 1098 func (e *environ) Destroy() error { 1099 if err := common.Destroy(e); err != nil { 1100 return errors.Trace(err) 1101 } 1102 return e.Storage().RemoveAll() 1103 } 1104 1105 func portsToIPPerms(ports []network.PortRange) []ec2.IPPerm { 1106 ipPerms := make([]ec2.IPPerm, len(ports)) 1107 for i, p := range ports { 1108 ipPerms[i] = ec2.IPPerm{ 1109 Protocol: p.Protocol, 1110 FromPort: p.FromPort, 1111 ToPort: p.ToPort, 1112 SourceIPs: []string{"0.0.0.0/0"}, 1113 } 1114 } 1115 return ipPerms 1116 } 1117 1118 func (e *environ) openPortsInGroup(name string, ports []network.PortRange) error { 1119 if len(ports) == 0 { 1120 return nil 1121 } 1122 // Give permissions for anyone to access the given ports. 1123 g, err := e.groupByName(name) 1124 if err != nil { 1125 return err 1126 } 1127 ipPerms := portsToIPPerms(ports) 1128 _, err = e.ec2().AuthorizeSecurityGroup(g, ipPerms) 1129 if err != nil && ec2ErrCode(err) == "InvalidPermission.Duplicate" { 1130 if len(ports) == 1 { 1131 return nil 1132 } 1133 // If there's more than one port and we get a duplicate error, 1134 // then we go through authorizing each port individually, 1135 // otherwise the ports that were *not* duplicates will have 1136 // been ignored 1137 for i := range ipPerms { 1138 _, err := e.ec2().AuthorizeSecurityGroup(g, ipPerms[i:i+1]) 1139 if err != nil && ec2ErrCode(err) != "InvalidPermission.Duplicate" { 1140 return fmt.Errorf("cannot open port %v: %v", ipPerms[i], err) 1141 } 1142 } 1143 return nil 1144 } 1145 if err != nil { 1146 return fmt.Errorf("cannot open ports: %v", err) 1147 } 1148 return nil 1149 } 1150 1151 func (e *environ) closePortsInGroup(name string, ports []network.PortRange) error { 1152 if len(ports) == 0 { 1153 return nil 1154 } 1155 // Revoke permissions for anyone to access the given ports. 1156 // Note that ec2 allows the revocation of permissions that aren't 1157 // granted, so this is naturally idempotent. 1158 g, err := e.groupByName(name) 1159 if err != nil { 1160 return err 1161 } 1162 _, err = e.ec2().RevokeSecurityGroup(g, portsToIPPerms(ports)) 1163 if err != nil { 1164 return fmt.Errorf("cannot close ports: %v", err) 1165 } 1166 return nil 1167 } 1168 1169 func (e *environ) portsInGroup(name string) (ports []network.PortRange, err error) { 1170 group, err := e.groupInfoByName(name) 1171 if err != nil { 1172 return nil, err 1173 } 1174 for _, p := range group.IPPerms { 1175 if len(p.SourceIPs) != 1 { 1176 logger.Warningf("unexpected IP permission found: %v", p) 1177 continue 1178 } 1179 ports = append(ports, network.PortRange{ 1180 Protocol: p.Protocol, 1181 FromPort: p.FromPort, 1182 ToPort: p.ToPort, 1183 }) 1184 } 1185 network.SortPortRanges(ports) 1186 return ports, nil 1187 } 1188 1189 func (e *environ) OpenPorts(ports []network.PortRange) error { 1190 if e.Config().FirewallMode() != config.FwGlobal { 1191 return fmt.Errorf("invalid firewall mode %q for opening ports on environment", 1192 e.Config().FirewallMode()) 1193 } 1194 if err := e.openPortsInGroup(e.globalGroupName(), ports); err != nil { 1195 return err 1196 } 1197 logger.Infof("opened ports in global group: %v", ports) 1198 return nil 1199 } 1200 1201 func (e *environ) ClosePorts(ports []network.PortRange) error { 1202 if e.Config().FirewallMode() != config.FwGlobal { 1203 return fmt.Errorf("invalid firewall mode %q for closing ports on environment", 1204 e.Config().FirewallMode()) 1205 } 1206 if err := e.closePortsInGroup(e.globalGroupName(), ports); err != nil { 1207 return err 1208 } 1209 logger.Infof("closed ports in global group: %v", ports) 1210 return nil 1211 } 1212 1213 func (e *environ) Ports() ([]network.PortRange, error) { 1214 if e.Config().FirewallMode() != config.FwGlobal { 1215 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment", 1216 e.Config().FirewallMode()) 1217 } 1218 return e.portsInGroup(e.globalGroupName()) 1219 } 1220 1221 func (*environ) Provider() environs.EnvironProvider { 1222 return &providerInstance 1223 } 1224 1225 func (e *environ) terminateInstances(ids []instance.Id) error { 1226 if len(ids) == 0 { 1227 return nil 1228 } 1229 var err error 1230 ec2inst := e.ec2() 1231 strs := make([]string, len(ids)) 1232 for i, id := range ids { 1233 strs[i] = string(id) 1234 } 1235 for a := shortAttempt.Start(); a.Next(); { 1236 _, err = ec2inst.TerminateInstances(strs) 1237 if err == nil || ec2ErrCode(err) != "InvalidInstanceID.NotFound" { 1238 return err 1239 } 1240 } 1241 if len(ids) == 1 { 1242 return err 1243 } 1244 // If we get a NotFound error, it means that no instances have been 1245 // terminated even if some exist, so try them one by one, ignoring 1246 // NotFound errors. 1247 var firstErr error 1248 for _, id := range ids { 1249 _, err = ec2inst.TerminateInstances([]string{string(id)}) 1250 if ec2ErrCode(err) == "InvalidInstanceID.NotFound" { 1251 err = nil 1252 } 1253 if err != nil && firstErr == nil { 1254 firstErr = err 1255 } 1256 } 1257 return firstErr 1258 } 1259 1260 func (e *environ) globalGroupName() string { 1261 return fmt.Sprintf("%s-global", e.jujuGroupName()) 1262 } 1263 1264 func (e *environ) machineGroupName(machineId string) string { 1265 return fmt.Sprintf("%s-%s", e.jujuGroupName(), machineId) 1266 } 1267 1268 func (e *environ) jujuGroupName() string { 1269 return "juju-" + e.name 1270 } 1271 1272 // setUpGroups creates the security groups for the new machine, and 1273 // returns them. 1274 // 1275 // Instances are tagged with a group so they can be distinguished from 1276 // other instances that might be running on the same EC2 account. In 1277 // addition, a specific machine security group is created for each 1278 // machine, so that its firewall rules can be configured per machine. 1279 func (e *environ) setUpGroups(machineId string, apiPort int) ([]ec2.SecurityGroup, error) { 1280 jujuGroup, err := e.ensureGroup(e.jujuGroupName(), 1281 []ec2.IPPerm{ 1282 { 1283 Protocol: "tcp", 1284 FromPort: 22, 1285 ToPort: 22, 1286 SourceIPs: []string{"0.0.0.0/0"}, 1287 }, 1288 { 1289 Protocol: "tcp", 1290 FromPort: apiPort, 1291 ToPort: apiPort, 1292 SourceIPs: []string{"0.0.0.0/0"}, 1293 }, 1294 { 1295 Protocol: "tcp", 1296 FromPort: 0, 1297 ToPort: 65535, 1298 }, 1299 { 1300 Protocol: "udp", 1301 FromPort: 0, 1302 ToPort: 65535, 1303 }, 1304 { 1305 Protocol: "icmp", 1306 FromPort: -1, 1307 ToPort: -1, 1308 }, 1309 }) 1310 if err != nil { 1311 return nil, err 1312 } 1313 var machineGroup ec2.SecurityGroup 1314 switch e.Config().FirewallMode() { 1315 case config.FwInstance: 1316 machineGroup, err = e.ensureGroup(e.machineGroupName(machineId), nil) 1317 case config.FwGlobal: 1318 machineGroup, err = e.ensureGroup(e.globalGroupName(), nil) 1319 } 1320 if err != nil { 1321 return nil, err 1322 } 1323 return []ec2.SecurityGroup{jujuGroup, machineGroup}, nil 1324 } 1325 1326 // zeroGroup holds the zero security group. 1327 var zeroGroup ec2.SecurityGroup 1328 1329 // ensureGroup returns the security group with name and perms. 1330 // If a group with name does not exist, one will be created. 1331 // If it exists, its permissions are set to perms. 1332 // Any entries in perms without SourceIPs will be granted for 1333 // the named group only. 1334 func (e *environ) ensureGroup(name string, perms []ec2.IPPerm) (g ec2.SecurityGroup, err error) { 1335 ec2inst := e.ec2() 1336 resp, err := ec2inst.CreateSecurityGroup("", name, "juju group") 1337 if err != nil && ec2ErrCode(err) != "InvalidGroup.Duplicate" { 1338 return zeroGroup, err 1339 } 1340 1341 var have permSet 1342 if err == nil { 1343 g = resp.SecurityGroup 1344 } else { 1345 resp, err := ec2inst.SecurityGroups(ec2.SecurityGroupNames(name), nil) 1346 if err != nil { 1347 return zeroGroup, err 1348 } 1349 info := resp.Groups[0] 1350 // It's possible that the old group has the wrong 1351 // description here, but if it does it's probably due 1352 // to something deliberately playing games with juju, 1353 // so we ignore it. 1354 g = info.SecurityGroup 1355 have = newPermSetForGroup(info.IPPerms, g) 1356 } 1357 want := newPermSetForGroup(perms, g) 1358 revoke := make(permSet) 1359 for p := range have { 1360 if !want[p] { 1361 revoke[p] = true 1362 } 1363 } 1364 if len(revoke) > 0 { 1365 _, err := ec2inst.RevokeSecurityGroup(g, revoke.ipPerms()) 1366 if err != nil { 1367 return zeroGroup, fmt.Errorf("cannot revoke security group: %v", err) 1368 } 1369 } 1370 1371 add := make(permSet) 1372 for p := range want { 1373 if !have[p] { 1374 add[p] = true 1375 } 1376 } 1377 if len(add) > 0 { 1378 _, err := ec2inst.AuthorizeSecurityGroup(g, add.ipPerms()) 1379 if err != nil { 1380 return zeroGroup, fmt.Errorf("cannot authorize securityGroup: %v", err) 1381 } 1382 } 1383 return g, nil 1384 } 1385 1386 // permKey represents a permission for a group or an ip address range 1387 // to access the given range of ports. Only one of groupName or ipAddr 1388 // should be non-empty. 1389 type permKey struct { 1390 protocol string 1391 fromPort int 1392 toPort int 1393 groupId string 1394 ipAddr string 1395 } 1396 1397 type permSet map[permKey]bool 1398 1399 // newPermSetForGroup returns a set of all the permissions in the 1400 // given slice of IPPerms. It ignores the name and owner 1401 // id in source groups, and any entry with no source ips will 1402 // be granted for the given group only. 1403 func newPermSetForGroup(ps []ec2.IPPerm, group ec2.SecurityGroup) permSet { 1404 m := make(permSet) 1405 for _, p := range ps { 1406 k := permKey{ 1407 protocol: p.Protocol, 1408 fromPort: p.FromPort, 1409 toPort: p.ToPort, 1410 } 1411 if len(p.SourceIPs) > 0 { 1412 for _, ip := range p.SourceIPs { 1413 k.ipAddr = ip 1414 m[k] = true 1415 } 1416 } else { 1417 k.groupId = group.Id 1418 m[k] = true 1419 } 1420 } 1421 return m 1422 } 1423 1424 // ipPerms returns m as a slice of permissions usable 1425 // with the ec2 package. 1426 func (m permSet) ipPerms() (ps []ec2.IPPerm) { 1427 // We could compact the permissions, but it 1428 // hardly seems worth it. 1429 for p := range m { 1430 ipp := ec2.IPPerm{ 1431 Protocol: p.protocol, 1432 FromPort: p.fromPort, 1433 ToPort: p.toPort, 1434 } 1435 if p.ipAddr != "" { 1436 ipp.SourceIPs = []string{p.ipAddr} 1437 } else { 1438 ipp.SourceGroups = []ec2.UserSecurityGroup{{Id: p.groupId}} 1439 } 1440 ps = append(ps, ipp) 1441 } 1442 return 1443 } 1444 1445 // isZoneConstrainedError reports whether or not the error indicates 1446 // RunInstances failed due to the specified availability zone being 1447 // constrained for the instance type being provisioned, or is 1448 // otherwise unusable for the specific request made. 1449 func isZoneConstrainedError(err error) bool { 1450 switch err := err.(type) { 1451 case *ec2.Error: 1452 switch err.Code { 1453 case "Unsupported", "InsufficientInstanceCapacity": 1454 // A big hammer, but we've now seen several different error messages 1455 // for constrained zones, and who knows how many more there might 1456 // be. If the message contains "Availability Zone", it's a fair 1457 // bet that it's constrained or otherwise unusable. 1458 return strings.Contains(err.Message, "Availability Zone") 1459 case "InvalidInput": 1460 // If the region has a default VPC, then we will receive an error 1461 // if the AZ does not have a default subnet. Until we have proper 1462 // support for networks, we'll skip over these. 1463 return strings.HasPrefix(err.Message, "No default subnet for availability zone") 1464 case "VolumeTypeNotAvailableInZone": 1465 return true 1466 } 1467 } 1468 return false 1469 } 1470 1471 // If the err is of type *ec2.Error, ec2ErrCode returns 1472 // its code, otherwise it returns the empty string. 1473 func ec2ErrCode(err error) string { 1474 ec2err, _ := err.(*ec2.Error) 1475 if ec2err == nil { 1476 return "" 1477 } 1478 return ec2err.Code 1479 }