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