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