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