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