2014-09-09 21:25:35 +00:00
|
|
|
/*
|
|
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package aws_cloud
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"regexp"
|
2015-03-04 21:52:49 +00:00
|
|
|
"strings"
|
2014-09-09 21:25:35 +00:00
|
|
|
|
|
|
|
"code.google.com/p/gcfg"
|
|
|
|
"github.com/mitchellh/goamz/aws"
|
|
|
|
"github.com/mitchellh/goamz/ec2"
|
|
|
|
|
2014-09-26 23:28:30 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2014-09-09 21:25:35 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
2015-03-04 21:52:49 +00:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2014-09-09 21:25:35 +00:00
|
|
|
)
|
|
|
|
|
2015-03-05 14:05:11 +00:00
|
|
|
// Abstraction over EC2, to allow mocking/other implementations
|
2014-09-09 21:25:35 +00:00
|
|
|
type EC2 interface {
|
2015-03-05 14:05:11 +00:00
|
|
|
// Query EC2 for instances matching the filter
|
|
|
|
Instances(instIds []string, filter *ec2InstanceFilter) (resp *ec2.InstancesResp, err error)
|
2015-03-10 04:15:53 +00:00
|
|
|
|
|
|
|
// Query the EC2 metadata service (used to discover instance-id etc)
|
|
|
|
GetMetaData(key string) ([]byte, error)
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AWSCloud is an implementation of Interface, TCPLoadBalancer and Instances for Amazon Web Services.
|
|
|
|
type AWSCloud struct {
|
2015-03-10 04:15:53 +00:00
|
|
|
ec2 EC2
|
|
|
|
cfg *AWSCloudConfig
|
|
|
|
availabilityZone string
|
|
|
|
region aws.Region
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type AWSCloudConfig struct {
|
|
|
|
Global struct {
|
2015-03-10 04:15:53 +00:00
|
|
|
// TODO: Is there any use for this? We can get it from the instance metadata service
|
2014-09-09 21:25:35 +00:00
|
|
|
Region string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-05 14:05:11 +00:00
|
|
|
// Similar to ec2.Filter, but the filter values can be read from tests
|
|
|
|
// (ec2.Filter only has private members)
|
|
|
|
type ec2InstanceFilter struct {
|
|
|
|
PrivateDNSName string
|
|
|
|
}
|
|
|
|
|
|
|
|
// True if the passed instance matches the filter
|
|
|
|
func (f *ec2InstanceFilter) Matches(instance ec2.Instance) bool {
|
|
|
|
if f.PrivateDNSName != "" && instance.PrivateDNSName != f.PrivateDNSName {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// goamzEC2 is an implementation of the EC2 interface, backed by goamz
|
|
|
|
type GoamzEC2 struct {
|
|
|
|
ec2 *ec2.EC2
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implementation of EC2.Instances
|
|
|
|
func (self *GoamzEC2) Instances(instanceIds []string, filter *ec2InstanceFilter) (resp *ec2.InstancesResp, err error) {
|
|
|
|
var goamzFilter *ec2.Filter
|
|
|
|
if filter != nil {
|
|
|
|
goamzFilter = ec2.NewFilter()
|
|
|
|
if filter.PrivateDNSName != "" {
|
|
|
|
goamzFilter.Add("private-dns-name", filter.PrivateDNSName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return self.ec2.Instances(instanceIds, goamzFilter)
|
|
|
|
}
|
|
|
|
|
2015-03-10 04:15:53 +00:00
|
|
|
func (self *GoamzEC2) GetMetaData(key string) ([]byte, error) {
|
|
|
|
v, err := aws.GetMetaData(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error querying AWS metadata for key %s: %v", key, err)
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
2014-09-09 21:25:35 +00:00
|
|
|
type AuthFunc func() (auth aws.Auth, err error)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
cloudprovider.RegisterCloudProvider("aws", func(config io.Reader) (cloudprovider.Interface, error) {
|
|
|
|
return newAWSCloud(config, getAuth)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAuth() (auth aws.Auth, err error) {
|
|
|
|
return aws.GetAuth("", "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// readAWSCloudConfig reads an instance of AWSCloudConfig from config reader.
|
|
|
|
func readAWSCloudConfig(config io.Reader) (*AWSCloudConfig, error) {
|
|
|
|
if config == nil {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("no AWS cloud provider config file given")
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var cfg AWSCloudConfig
|
|
|
|
err := gcfg.ReadInto(&cfg, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.Global.Region == "" {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("no region specified in configuration file")
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &cfg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newAWSCloud creates a new instance of AWSCloud.
|
|
|
|
func newAWSCloud(config io.Reader, authFunc AuthFunc) (*AWSCloud, error) {
|
|
|
|
cfg, err := readAWSCloudConfig(config)
|
|
|
|
if err != nil {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("unable to read AWS cloud provider config file: %v", err)
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auth, err := authFunc()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-03-10 04:15:53 +00:00
|
|
|
// TODO: We can get the region very easily from the instance-metadata service
|
2014-09-09 21:25:35 +00:00
|
|
|
region, ok := aws.Regions[cfg.Global.Region]
|
|
|
|
if !ok {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("not a valid AWS region: %s", cfg.Global.Region)
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &AWSCloud{
|
2015-03-10 04:15:53 +00:00
|
|
|
ec2: &GoamzEC2{ec2: ec2.New(auth, region)},
|
|
|
|
cfg: cfg,
|
|
|
|
region: region,
|
2014-09-09 21:25:35 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2015-03-10 04:15:53 +00:00
|
|
|
func (self *AWSCloud) getAvailabilityZone() (string, error) {
|
|
|
|
// TODO: Do we need sync.Mutex here?
|
|
|
|
availabilityZone := self.availabilityZone
|
|
|
|
if self.availabilityZone == "" {
|
|
|
|
availabilityZoneBytes, err := self.ec2.GetMetaData("placement/availability-zone")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if availabilityZoneBytes == nil || len(availabilityZoneBytes) == 0 {
|
|
|
|
return "", fmt.Errorf("Unable to determine availability-zone from instance metadata")
|
|
|
|
}
|
|
|
|
availabilityZone = string(availabilityZoneBytes)
|
|
|
|
self.availabilityZone = availabilityZone
|
|
|
|
}
|
|
|
|
return availabilityZone, nil
|
|
|
|
}
|
|
|
|
|
2014-11-13 20:35:03 +00:00
|
|
|
func (aws *AWSCloud) Clusters() (cloudprovider.Clusters, bool) {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2014-09-09 21:25:35 +00:00
|
|
|
// TCPLoadBalancer returns an implementation of TCPLoadBalancer for Amazon Web Services.
|
|
|
|
func (aws *AWSCloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instances returns an implementation of Instances for Amazon Web Services.
|
|
|
|
func (aws *AWSCloud) Instances() (cloudprovider.Instances, bool) {
|
|
|
|
return aws, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Zones returns an implementation of Zones for Amazon Web Services.
|
|
|
|
func (aws *AWSCloud) Zones() (cloudprovider.Zones, bool) {
|
2015-03-10 04:15:53 +00:00
|
|
|
return aws, true
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IPAddress is an implementation of Instances.IPAddress.
|
|
|
|
func (aws *AWSCloud) IPAddress(name string) (net.IP, error) {
|
2015-02-16 16:54:04 +00:00
|
|
|
inst, err := aws.getInstancesByDnsName(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ip := net.ParseIP(inst.PrivateIpAddress)
|
|
|
|
if ip == nil {
|
|
|
|
return nil, fmt.Errorf("invalid network IP: %s", inst.PrivateIpAddress)
|
|
|
|
}
|
|
|
|
return ip, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExternalID returns the cloud provider ID of the specified instance.
|
|
|
|
func (aws *AWSCloud) ExternalID(name string) (string, error) {
|
|
|
|
inst, err := aws.getInstancesByDnsName(name)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return inst.InstanceId, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the instances matching the relevant private dns name.
|
|
|
|
func (aws *AWSCloud) getInstancesByDnsName(name string) (*ec2.Instance, error) {
|
2015-03-05 14:05:11 +00:00
|
|
|
f := &ec2InstanceFilter{}
|
|
|
|
f.PrivateDNSName = name
|
2014-09-09 21:25:35 +00:00
|
|
|
|
|
|
|
resp, err := aws.ec2.Instances(nil, f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-04 22:58:36 +00:00
|
|
|
|
|
|
|
instances := []*ec2.Instance{}
|
|
|
|
for _, reservation := range resp.Reservations {
|
|
|
|
for _, instance := range reservation.Instances {
|
|
|
|
// TODO: Push running logic down into filter?
|
|
|
|
if !isAlive(&instance) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if instance.PrivateDNSName != name {
|
|
|
|
// TODO: Should we warn here? - the filter should have caught this
|
|
|
|
// (this will happen in the tests if they don't fully mock the EC2 API)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
instances = append(instances, &instance)
|
|
|
|
}
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
2015-03-04 22:58:36 +00:00
|
|
|
|
|
|
|
if len(instances) == 0 {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("no instances found for host: %s", name)
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
2015-03-04 22:58:36 +00:00
|
|
|
if len(instances) > 1 {
|
2014-11-20 10:00:36 +00:00
|
|
|
return nil, fmt.Errorf("multiple instances found for host: %s", name)
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
2015-03-04 22:58:36 +00:00
|
|
|
return instances[0], nil
|
|
|
|
}
|
2014-09-09 21:25:35 +00:00
|
|
|
|
2015-03-04 22:58:36 +00:00
|
|
|
// Check if the instance is alive (running or pending)
|
|
|
|
// We typically ignore instances that are not alive
|
|
|
|
func isAlive(instance *ec2.Instance) bool {
|
|
|
|
switch instance.State.Name {
|
|
|
|
case "shutting-down", "terminated", "stopping", "stopped":
|
|
|
|
return false
|
|
|
|
case "pending", "running":
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
glog.Errorf("unknown EC2 instance state: %s", instance.State)
|
|
|
|
return false
|
|
|
|
}
|
2015-02-11 22:37:27 +00:00
|
|
|
}
|
|
|
|
|
2014-09-09 21:25:35 +00:00
|
|
|
// Return a list of instances matching regex string.
|
|
|
|
func (aws *AWSCloud) getInstancesByRegex(regex string) ([]string, error) {
|
|
|
|
resp, err := aws.ec2.Instances(nil, nil)
|
|
|
|
if err != nil {
|
|
|
|
return []string{}, err
|
|
|
|
}
|
|
|
|
if resp == nil {
|
2014-11-20 10:00:36 +00:00
|
|
|
return []string{}, fmt.Errorf("no InstanceResp returned")
|
2014-09-09 21:25:35 +00:00
|
|
|
}
|
|
|
|
|
2015-03-04 21:52:49 +00:00
|
|
|
if strings.HasPrefix(regex, "'") && strings.HasSuffix(regex, "'") {
|
|
|
|
glog.Infof("Stripping quotes around regex (%s)", regex)
|
|
|
|
regex = regex[1 : len(regex)-1]
|
|
|
|
}
|
|
|
|
|
2014-09-09 21:25:35 +00:00
|
|
|
re, err := regexp.Compile(regex)
|
|
|
|
if err != nil {
|
|
|
|
return []string{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
instances := []string{}
|
|
|
|
for _, reservation := range resp.Reservations {
|
|
|
|
for _, instance := range reservation.Instances {
|
2015-03-04 22:58:36 +00:00
|
|
|
// TODO: Push filtering down into EC2 API filter?
|
|
|
|
if !isAlive(&instance) {
|
|
|
|
glog.V(2).Infof("skipping EC2 instance (not alive): %s", instance.InstanceId)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-09-09 21:25:35 +00:00
|
|
|
for _, tag := range instance.Tags {
|
|
|
|
if tag.Key == "Name" && re.MatchString(tag.Value) {
|
|
|
|
instances = append(instances, instance.PrivateDNSName)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-03-04 22:58:36 +00:00
|
|
|
glog.V(2).Infof("Matched EC2 instances: %s", instances)
|
2014-09-09 21:25:35 +00:00
|
|
|
return instances, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// List is an implementation of Instances.List.
|
|
|
|
func (aws *AWSCloud) List(filter string) ([]string, error) {
|
|
|
|
// TODO: Should really use tag query. No need to go regexp.
|
|
|
|
return aws.getInstancesByRegex(filter)
|
|
|
|
}
|
2014-09-26 23:28:30 +00:00
|
|
|
|
|
|
|
func (v *AWSCloud) GetNodeResources(name string) (*api.NodeResources, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2015-03-10 04:15:53 +00:00
|
|
|
|
|
|
|
// GetZone implements Zones.GetZone
|
|
|
|
func (self *AWSCloud) GetZone() (cloudprovider.Zone, error) {
|
|
|
|
availabilityZone, err := self.getAvailabilityZone()
|
|
|
|
if err != nil {
|
|
|
|
return cloudprovider.Zone{}, err
|
|
|
|
}
|
|
|
|
return cloudprovider.Zone{
|
|
|
|
FailureDomain: availabilityZone,
|
|
|
|
Region: self.region.Name,
|
|
|
|
}, nil
|
|
|
|
}
|