mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 05:36:12 +00:00
Merge pull request #8297 from iterion/switch-to-aws-sdk-go
AWS: Switch to aws-sdk-go
This commit is contained in:
@@ -20,7 +20,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -28,8 +30,9 @@ import (
|
||||
"time"
|
||||
|
||||
"code.google.com/p/gcfg"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/aws/credentials"
|
||||
"github.com/awslabs/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
@@ -41,19 +44,19 @@ import (
|
||||
// Abstraction over EC2, to allow mocking/other implementations
|
||||
type EC2 interface {
|
||||
// Query EC2 for instances matching the filter
|
||||
Instances(instIds []string, filter *ec2InstanceFilter) (resp *ec2.InstancesResp, err error)
|
||||
Instances(instanceIds []string, filter *ec2InstanceFilter) (instances []*ec2.Instance, err error)
|
||||
|
||||
// Attach a volume to an instance
|
||||
AttachVolume(volumeID string, instanceId string, mountDevice string) (resp *ec2.AttachVolumeResp, err error)
|
||||
AttachVolume(volumeID, instanceId, mountDevice string) (resp *ec2.VolumeAttachment, err error)
|
||||
// Detach a volume from whatever instance it is attached to
|
||||
// TODO: We should specify the InstanceID and the Device, for safety
|
||||
DetachVolume(volumeID string) (resp *ec2.SimpleResp, err error)
|
||||
DetachVolume(volumeID, instanceId, mountDevice string) (resp *ec2.VolumeAttachment, err error)
|
||||
// Lists volumes
|
||||
Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.VolumesResp, err error)
|
||||
Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.DescribeVolumesOutput, err error)
|
||||
// Create an EBS volume
|
||||
CreateVolume(request *ec2.CreateVolume) (resp *ec2.CreateVolumeResp, err error)
|
||||
CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error)
|
||||
// Delete an EBS volume
|
||||
DeleteVolume(volumeID string) (resp *ec2.SimpleResp, err error)
|
||||
DeleteVolume(volumeID string) (resp *ec2.DeleteVolumeOutput, err error)
|
||||
}
|
||||
|
||||
// Abstraction over the AWS metadata service
|
||||
@@ -87,7 +90,7 @@ type AWSCloud struct {
|
||||
metadata AWSMetadata
|
||||
cfg *AWSCloudConfig
|
||||
availabilityZone string
|
||||
region aws.Region
|
||||
region string
|
||||
|
||||
// The AWS instance that we are running on
|
||||
selfAWSInstance *awsInstance
|
||||
@@ -109,73 +112,156 @@ type ec2InstanceFilter struct {
|
||||
}
|
||||
|
||||
// True if the passed instance matches the filter
|
||||
func (f *ec2InstanceFilter) Matches(instance ec2.Instance) bool {
|
||||
if f.PrivateDNSName != "" && instance.PrivateDNSName != f.PrivateDNSName {
|
||||
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 {
|
||||
// awsSdkEC2 is an implementation of the EC2 interface, backed by aws-sdk-go
|
||||
type awsSdkEC2 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)
|
||||
}
|
||||
func stringPointerArray(orig []string) []*string {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
return self.ec2.Instances(instanceIds, goamzFilter)
|
||||
|
||||
n := make([]*string, len(orig))
|
||||
for i := range orig {
|
||||
n[i] = &orig[i]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type goamzMetadata struct {
|
||||
func isNilOrEmpty(s *string) bool {
|
||||
return s == nil || *s == ""
|
||||
}
|
||||
|
||||
// Implementation of EC2.Instances
|
||||
func (self *awsSdkEC2) Instances(instanceIds []string, filter *ec2InstanceFilter) (resp []*ec2.Instance, err error) {
|
||||
var filters []*ec2.Filter
|
||||
if filter != nil && filter.PrivateDNSName != "" {
|
||||
filters = []*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("private-dns-name"),
|
||||
Values: []*string{
|
||||
aws.String(filter.PrivateDNSName),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fetchedInstances := []*ec2.Instance{}
|
||||
var nextToken *string
|
||||
|
||||
for {
|
||||
res, err := self.ec2.DescribeInstances(&ec2.DescribeInstancesInput{
|
||||
InstanceIDs: stringPointerArray(instanceIds),
|
||||
Filters: filters,
|
||||
NextToken: nextToken,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, reservation := range res.Reservations {
|
||||
fetchedInstances = append(fetchedInstances, reservation.Instances...)
|
||||
}
|
||||
|
||||
nextToken = res.NextToken
|
||||
if isNilOrEmpty(nextToken) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return fetchedInstances, nil
|
||||
}
|
||||
|
||||
type awsSdkMetadata struct {
|
||||
}
|
||||
|
||||
var metadataClient = http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
|
||||
// Implements AWSMetadata.GetMetaData
|
||||
func (self *goamzMetadata) GetMetaData(key string) ([]byte, error) {
|
||||
v, err := aws.GetMetaData(key)
|
||||
func (self *awsSdkMetadata) GetMetaData(key string) ([]byte, error) {
|
||||
// TODO Get an implementation of this merged into aws-sdk-go
|
||||
url := "http://169.254.169.254/latest/meta-data/" + key
|
||||
|
||||
res, err := metadataClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
err = fmt.Errorf("Code %d returned for url %s", res.StatusCode, url)
|
||||
return nil, fmt.Errorf("Error querying AWS metadata for key %s: %v", key, err)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error querying AWS metadata for key %s: %v", key, err)
|
||||
}
|
||||
return v, nil
|
||||
|
||||
return []byte(body), nil
|
||||
}
|
||||
|
||||
type AuthFunc func() (auth aws.Auth, err error)
|
||||
type AuthFunc func() (creds *credentials.Credentials)
|
||||
|
||||
func (s *goamzEC2) AttachVolume(volumeID string, instanceId string, device string) (resp *ec2.AttachVolumeResp, err error) {
|
||||
return s.ec2.AttachVolume(volumeID, instanceId, device)
|
||||
func (s *awsSdkEC2) AttachVolume(volumeID, instanceId, device string) (resp *ec2.VolumeAttachment, err error) {
|
||||
|
||||
request := ec2.AttachVolumeInput{
|
||||
Device: &device,
|
||||
InstanceID: &instanceId,
|
||||
VolumeID: &volumeID,
|
||||
}
|
||||
return s.ec2.AttachVolume(&request)
|
||||
}
|
||||
|
||||
func (s *goamzEC2) DetachVolume(volumeID string) (resp *ec2.SimpleResp, err error) {
|
||||
return s.ec2.DetachVolume(volumeID)
|
||||
func (s *awsSdkEC2) DetachVolume(volumeID, instanceId, device string) (resp *ec2.VolumeAttachment, err error) {
|
||||
request := ec2.DetachVolumeInput{
|
||||
Device: &device,
|
||||
InstanceID: &instanceId,
|
||||
VolumeID: &volumeID,
|
||||
}
|
||||
return s.ec2.DetachVolume(&request)
|
||||
}
|
||||
|
||||
func (s *goamzEC2) Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.VolumesResp, err error) {
|
||||
return s.ec2.Volumes(volumeIDs, filter)
|
||||
func (s *awsSdkEC2) Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.DescribeVolumesOutput, err error) {
|
||||
request := ec2.DescribeVolumesInput{
|
||||
VolumeIDs: stringPointerArray(volumeIDs),
|
||||
}
|
||||
return s.ec2.DescribeVolumes(&request)
|
||||
}
|
||||
|
||||
func (s *goamzEC2) CreateVolume(request *ec2.CreateVolume) (resp *ec2.CreateVolumeResp, err error) {
|
||||
func (s *awsSdkEC2) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) {
|
||||
return s.ec2.CreateVolume(request)
|
||||
}
|
||||
|
||||
func (s *goamzEC2) DeleteVolume(volumeID string) (resp *ec2.SimpleResp, err error) {
|
||||
return s.ec2.DeleteVolume(volumeID)
|
||||
func (s *awsSdkEC2) DeleteVolume(volumeID string) (resp *ec2.DeleteVolumeOutput, err error) {
|
||||
request := ec2.DeleteVolumeInput{VolumeID: &volumeID}
|
||||
return s.ec2.DeleteVolume(&request)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider("aws", func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
metadata := &goamzMetadata{}
|
||||
metadata := &awsSdkMetadata{}
|
||||
return newAWSCloud(config, getAuth, metadata)
|
||||
})
|
||||
}
|
||||
|
||||
func getAuth() (auth aws.Auth, err error) {
|
||||
return aws.GetAuth("", "")
|
||||
func getAuth() (creds *credentials.Credentials) {
|
||||
return credentials.NewChainCredentials(
|
||||
[]credentials.Provider{
|
||||
&credentials.EnvProvider{},
|
||||
&credentials.EC2RoleProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
// readAWSCloudConfig reads an instance of AWSCloudConfig from config reader.
|
||||
@@ -217,6 +303,26 @@ func getAvailabilityZone(metadata AWSMetadata) (string, error) {
|
||||
return string(availabilityZoneBytes), nil
|
||||
}
|
||||
|
||||
func isRegionValid(region string) bool {
|
||||
regions := [...]string{
|
||||
"us-east-1",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
"eu-west-1",
|
||||
"eu-central-1",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ap-northeast-1",
|
||||
"sa-east-1",
|
||||
}
|
||||
for _, r := range regions {
|
||||
if r == region {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newAWSCloud creates a new instance of AWSCloud.
|
||||
// authFunc and instanceId are primarily for tests
|
||||
func newAWSCloud(config io.Reader, authFunc AuthFunc, metadata AWSMetadata) (*AWSCloud, error) {
|
||||
@@ -225,10 +331,7 @@ func newAWSCloud(config io.Reader, authFunc AuthFunc, metadata AWSMetadata) (*AW
|
||||
return nil, fmt.Errorf("unable to read AWS cloud provider config file: %v", err)
|
||||
}
|
||||
|
||||
auth, err := authFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds := authFunc()
|
||||
|
||||
zone := cfg.Global.Zone
|
||||
if len(zone) <= 1 {
|
||||
@@ -236,17 +339,22 @@ func newAWSCloud(config io.Reader, authFunc AuthFunc, metadata AWSMetadata) (*AW
|
||||
}
|
||||
regionName := zone[:len(zone)-1]
|
||||
|
||||
region, ok := aws.Regions[regionName]
|
||||
if !ok {
|
||||
valid := isRegionValid(regionName)
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("not a valid AWS zone (unknown region): %s", zone)
|
||||
}
|
||||
|
||||
ec2 := &goamzEC2{ec2: ec2.New(auth, region)}
|
||||
ec2 := &awsSdkEC2{
|
||||
ec2: ec2.New(&aws.Config{
|
||||
Region: regionName,
|
||||
Credentials: creds,
|
||||
}),
|
||||
}
|
||||
|
||||
awsCloud := &AWSCloud{
|
||||
ec2: ec2,
|
||||
cfg: cfg,
|
||||
region: region,
|
||||
region: regionName,
|
||||
availabilityZone: zone,
|
||||
metadata: metadata,
|
||||
}
|
||||
@@ -287,11 +395,11 @@ func (aws *AWSCloud) NodeAddresses(name string) ([]api.NodeAddress, error) {
|
||||
|
||||
addresses := []api.NodeAddress{}
|
||||
|
||||
if instance.PrivateIpAddress != "" {
|
||||
ipAddress := instance.PrivateIpAddress
|
||||
if *instance.PrivateIPAddress != "" {
|
||||
ipAddress := *instance.PrivateIPAddress
|
||||
ip := net.ParseIP(ipAddress)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("EC2 instance had invalid private address: %s (%s)", instance.InstanceId, ipAddress)
|
||||
return nil, fmt.Errorf("EC2 instance had invalid private address: %s (%s)", *instance.InstanceID, ipAddress)
|
||||
}
|
||||
addresses = append(addresses, api.NodeAddress{Type: api.NodeInternalIP, Address: ip.String()})
|
||||
|
||||
@@ -300,11 +408,11 @@ func (aws *AWSCloud) NodeAddresses(name string) ([]api.NodeAddress, error) {
|
||||
}
|
||||
|
||||
// TODO: Other IP addresses (multiple ips)?
|
||||
if instance.PublicIpAddress != "" {
|
||||
ipAddress := instance.PublicIpAddress
|
||||
if *instance.PublicIPAddress != "" {
|
||||
ipAddress := *instance.PublicIPAddress
|
||||
ip := net.ParseIP(ipAddress)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("EC2 instance had invalid public address: %s (%s)", instance.InstanceId, ipAddress)
|
||||
return nil, fmt.Errorf("EC2 instance had invalid public address: %s (%s)", *instance.InstanceID, ipAddress)
|
||||
}
|
||||
addresses = append(addresses, api.NodeAddress{Type: api.NodeExternalIP, Address: ip.String()})
|
||||
}
|
||||
@@ -318,7 +426,7 @@ func (aws *AWSCloud) ExternalID(name string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return inst.InstanceId, nil
|
||||
return *inst.InstanceID, nil
|
||||
}
|
||||
|
||||
// Return the instances matching the relevant private dns name.
|
||||
@@ -326,60 +434,59 @@ func (aws *AWSCloud) getInstancesByDnsName(name string) (*ec2.Instance, error) {
|
||||
f := &ec2InstanceFilter{}
|
||||
f.PrivateDNSName = name
|
||||
|
||||
resp, err := aws.ec2.Instances(nil, f)
|
||||
instances, err := aws.ec2.Instances(nil, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
matchingInstances := []*ec2.Instance{}
|
||||
for _, instance := range 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
|
||||
}
|
||||
|
||||
matchingInstances = append(matchingInstances, instance)
|
||||
}
|
||||
|
||||
if len(instances) == 0 {
|
||||
if len(matchingInstances) == 0 {
|
||||
return nil, fmt.Errorf("no instances found for host: %s", name)
|
||||
}
|
||||
if len(instances) > 1 {
|
||||
if len(matchingInstances) > 1 {
|
||||
return nil, fmt.Errorf("multiple instances found for host: %s", name)
|
||||
}
|
||||
return instances[0], nil
|
||||
return matchingInstances[0], nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
state := *instance.State
|
||||
switch *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)
|
||||
glog.Errorf("unknown EC2 instance state: %s", *instance.State)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Return a list of instances matching regex string.
|
||||
func (aws *AWSCloud) getInstancesByRegex(regex string) ([]string, error) {
|
||||
resp, err := aws.ec2.Instances(nil, nil)
|
||||
instances, err := aws.ec2.Instances(nil, nil)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
if resp == nil {
|
||||
return []string{}, fmt.Errorf("no InstanceResp returned")
|
||||
if len(instances) == 0 {
|
||||
return []string{}, fmt.Errorf("no instances returned")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(regex, "'") && strings.HasSuffix(regex, "'") {
|
||||
@@ -392,38 +499,36 @@ func (aws *AWSCloud) getInstancesByRegex(regex string) ([]string, error) {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
instances := []string{}
|
||||
for _, reservation := range resp.Reservations {
|
||||
for _, instance := range reservation.Instances {
|
||||
// TODO: Push filtering down into EC2 API filter?
|
||||
if !isAlive(&instance) {
|
||||
glog.V(2).Infof("skipping EC2 instance (%s): %s",
|
||||
instance.State.Name, instance.InstanceId)
|
||||
continue
|
||||
}
|
||||
matchingInstances := []string{}
|
||||
for _, instance := range instances {
|
||||
// TODO: Push filtering down into EC2 API filter?
|
||||
if !isAlive(instance) {
|
||||
glog.V(2).Infof("skipping EC2 instance (%s): %s",
|
||||
*instance.State.Name, *instance.InstanceID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Only return fully-ready instances when listing instances
|
||||
// (vs a query by name, where we will return it if we find it)
|
||||
if instance.State.Name == "pending" {
|
||||
glog.V(2).Infof("skipping EC2 instance (pending): %s", instance.InstanceId)
|
||||
continue
|
||||
}
|
||||
if instance.PrivateDNSName == "" {
|
||||
glog.V(2).Infof("skipping EC2 instance (no PrivateDNSName): %s",
|
||||
instance.InstanceId)
|
||||
continue
|
||||
}
|
||||
// Only return fully-ready instances when listing instances
|
||||
// (vs a query by name, where we will return it if we find it)
|
||||
if *instance.State.Name == "pending" {
|
||||
glog.V(2).Infof("skipping EC2 instance (pending): %s", *instance.InstanceID)
|
||||
continue
|
||||
}
|
||||
if *instance.PrivateDNSName == "" {
|
||||
glog.V(2).Infof("skipping EC2 instance (no PrivateDNSName): %s",
|
||||
*instance.InstanceID)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tag := range instance.Tags {
|
||||
if tag.Key == "Name" && re.MatchString(tag.Value) {
|
||||
instances = append(instances, instance.PrivateDNSName)
|
||||
break
|
||||
}
|
||||
for _, tag := range instance.Tags {
|
||||
if *tag.Key == "Name" && re.MatchString(*tag.Value) {
|
||||
matchingInstances = append(matchingInstances, *instance.PrivateDNSName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
glog.V(2).Infof("Matched EC2 instances: %s", instances)
|
||||
return instances, nil
|
||||
glog.V(2).Infof("Matched EC2 instances: %s", matchingInstances)
|
||||
return matchingInstances, nil
|
||||
}
|
||||
|
||||
// List is an implementation of Instances.List.
|
||||
@@ -439,7 +544,7 @@ func (aws *AWSCloud) GetNodeResources(name string) (*api.NodeResources, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := getResourcesByInstanceType(instance.InstanceType)
|
||||
resources, err := getResourcesByInstanceType(*instance.InstanceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -595,7 +700,7 @@ func (self *AWSCloud) GetZone() (cloudprovider.Zone, error) {
|
||||
}
|
||||
return cloudprovider.Zone{
|
||||
FailureDomain: self.availabilityZone,
|
||||
Region: self.region.Name,
|
||||
Region: self.region,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -645,23 +750,17 @@ func (self *awsInstance) getInstanceType() *awsInstanceType {
|
||||
|
||||
// Gets the full information about this instance from the EC2 API
|
||||
func (self *awsInstance) getInfo() (*ec2.Instance, error) {
|
||||
resp, err := self.ec2.Instances([]string{self.awsID}, nil)
|
||||
instances, err := self.ec2.Instances([]string{self.awsID}, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying ec2 for instance info: %v", err)
|
||||
}
|
||||
if len(resp.Reservations) == 0 {
|
||||
return nil, fmt.Errorf("no reservations found for instance: %s", self.awsID)
|
||||
}
|
||||
if len(resp.Reservations) > 1 {
|
||||
return nil, fmt.Errorf("multiple reservations found for instance: %s", self.awsID)
|
||||
}
|
||||
if len(resp.Reservations[0].Instances) == 0 {
|
||||
if len(instances) == 0 {
|
||||
return nil, fmt.Errorf("no instances found for instance: %s", self.awsID)
|
||||
}
|
||||
if len(resp.Reservations[0].Instances) > 1 {
|
||||
if len(instances) > 1 {
|
||||
return nil, fmt.Errorf("multiple instances found for instance: %s", self.awsID)
|
||||
}
|
||||
return &resp.Reservations[0].Instances[0], nil
|
||||
return instances[0], nil
|
||||
}
|
||||
|
||||
// Assigns an unused mount device for the specified volume.
|
||||
@@ -685,8 +784,8 @@ func (self *awsInstance) assignMountDevice(volumeID string) (mountDevice string,
|
||||
return "", false, err
|
||||
}
|
||||
deviceMappings := map[string]string{}
|
||||
for _, blockDevice := range info.BlockDevices {
|
||||
deviceMappings[blockDevice.DeviceName] = blockDevice.VolumeId
|
||||
for _, blockDevice := range info.BlockDeviceMappings {
|
||||
deviceMappings[*blockDevice.DeviceName] = *blockDevice.EBS.VolumeID
|
||||
}
|
||||
self.deviceMappings = deviceMappings
|
||||
}
|
||||
@@ -792,7 +891,7 @@ func (self *awsDisk) getInfo() (*ec2.Volume, error) {
|
||||
if len(resp.Volumes) > 1 {
|
||||
return nil, fmt.Errorf("multiple volumes found for volume: %s", self.awsID)
|
||||
}
|
||||
return &resp.Volumes[0], nil
|
||||
return resp.Volumes[0], nil
|
||||
}
|
||||
|
||||
func (self *awsDisk) waitForAttachmentStatus(status string) error {
|
||||
@@ -813,7 +912,7 @@ func (self *awsDisk) waitForAttachmentStatus(status string) error {
|
||||
if attachmentStatus != "" {
|
||||
glog.Warning("Found multiple attachments: ", info)
|
||||
}
|
||||
attachmentStatus = attachment.Status
|
||||
attachmentStatus = *attachment.State
|
||||
}
|
||||
if attachmentStatus == "" {
|
||||
attachmentStatus = "detached"
|
||||
@@ -883,7 +982,7 @@ func (aws *AWSCloud) AttachDisk(instanceName string, diskName string, readOnly b
|
||||
return "", fmt.Errorf("Error finding instance: %v", err)
|
||||
}
|
||||
|
||||
awsInstance = newAWSInstance(aws.ec2, instance.InstanceId)
|
||||
awsInstance = newAWSInstance(aws.ec2, *instance.InstanceID)
|
||||
}
|
||||
|
||||
if readOnly {
|
||||
@@ -937,7 +1036,7 @@ func (aws *AWSCloud) DetachDisk(instanceName string, diskName string) error {
|
||||
}
|
||||
|
||||
// TODO: We should specify the InstanceID and the Device, for safety
|
||||
response, err := aws.ec2.DetachVolume(disk.awsID)
|
||||
response, err := aws.ec2.DetachVolume(disk.awsID, instanceName, diskName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error detaching EBS volume: %v", err)
|
||||
}
|
||||
@@ -954,18 +1053,19 @@ func (aws *AWSCloud) DetachDisk(instanceName string, diskName string) error {
|
||||
|
||||
// Implements Volumes.CreateVolume
|
||||
func (aws *AWSCloud) CreateVolume(volumeOptions *VolumeOptions) (string, error) {
|
||||
request := &ec2.CreateVolume{}
|
||||
request.AvailZone = aws.availabilityZone
|
||||
request.Size = (int64(volumeOptions.CapacityMB) + 1023) / 1024
|
||||
request := &ec2.CreateVolumeInput{}
|
||||
request.AvailabilityZone = &aws.availabilityZone
|
||||
volSize := (int64(volumeOptions.CapacityMB) + 1023) / 1024
|
||||
request.Size = &volSize
|
||||
response, err := aws.ec2.CreateVolume(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
az := response.AvailZone
|
||||
awsID := response.VolumeId
|
||||
az := response.AvailabilityZone
|
||||
awsID := response.VolumeID
|
||||
|
||||
volumeName := "aws://" + az + "/" + awsID
|
||||
volumeName := "aws://" + *az + "/" + *awsID
|
||||
|
||||
return volumeName, nil
|
||||
}
|
||||
|
@@ -22,8 +22,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/aws/credentials"
|
||||
"github.com/awslabs/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||
@@ -96,8 +97,8 @@ func TestReadAWSCloudConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewAWSCloud(t *testing.T) {
|
||||
fakeAuthFunc := func() (auth aws.Auth, err error) {
|
||||
return aws.Auth{"", "", ""}, nil
|
||||
fakeAuthFunc := func() (creds *credentials.Credentials) {
|
||||
return credentials.NewStaticCredentials("", "", "")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -161,7 +162,7 @@ func TestNewAWSCloud(t *testing.T) {
|
||||
}
|
||||
|
||||
type FakeEC2 struct {
|
||||
instances []ec2.Instance
|
||||
instances []*ec2.Instance
|
||||
}
|
||||
|
||||
func contains(haystack []string, needle string) bool {
|
||||
@@ -173,20 +174,19 @@ func contains(haystack []string, needle string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *FakeEC2) Instances(instanceIds []string, filter *ec2InstanceFilter) (resp *ec2.InstancesResp, err error) {
|
||||
matches := []ec2.Instance{}
|
||||
func (self *FakeEC2) Instances(instanceIds []string, filter *ec2InstanceFilter) (instances []*ec2.Instance, err error) {
|
||||
matches := []*ec2.Instance{}
|
||||
for _, instance := range self.instances {
|
||||
if filter != nil && !filter.Matches(instance) {
|
||||
continue
|
||||
}
|
||||
if instanceIds != nil && !contains(instanceIds, instance.InstanceId) {
|
||||
if instanceIds != nil && !contains(instanceIds, *instance.InstanceID) {
|
||||
continue
|
||||
}
|
||||
matches = append(matches, instance)
|
||||
}
|
||||
return &ec2.InstancesResp{"",
|
||||
[]ec2.Reservation{
|
||||
{"", "", "", nil, matches}}}, nil
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
type FakeMetadata struct {
|
||||
@@ -204,27 +204,27 @@ func (self *FakeMetadata) GetMetaData(key string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) AttachVolume(volumeID string, instanceId string, mountDevice string) (resp *ec2.AttachVolumeResp, err error) {
|
||||
func (ec2 *FakeEC2) AttachVolume(volumeID, instanceId, mountDevice string) (resp *ec2.VolumeAttachment, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) DetachVolume(volumeID string) (resp *ec2.SimpleResp, err error) {
|
||||
func (ec2 *FakeEC2) DetachVolume(volumeID, instanceId, mountDevice string) (resp *ec2.VolumeAttachment, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.VolumesResp, err error) {
|
||||
func (ec2 *FakeEC2) Volumes(volumeIDs []string, filter *ec2.Filter) (resp *ec2.DescribeVolumesOutput, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) CreateVolume(request *ec2.CreateVolume) (resp *ec2.CreateVolumeResp, err error) {
|
||||
func (ec2 *FakeEC2) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) DeleteVolume(volumeID string) (resp *ec2.SimpleResp, err error) {
|
||||
func (ec2 *FakeEC2) DeleteVolume(volumeID string) (resp *ec2.DeleteVolumeOutput, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func mockInstancesResp(instances []ec2.Instance) (aws *AWSCloud) {
|
||||
func mockInstancesResp(instances []*ec2.Instance) (aws *AWSCloud) {
|
||||
availabilityZone := "us-west-2d"
|
||||
return &AWSCloud{
|
||||
ec2: &FakeEC2{
|
||||
@@ -238,26 +238,67 @@ func mockAvailabilityZone(region string, availabilityZone string) *AWSCloud {
|
||||
return &AWSCloud{
|
||||
ec2: &FakeEC2{},
|
||||
availabilityZone: availabilityZone,
|
||||
region: aws.Regions[region],
|
||||
region: region,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
instances := make([]ec2.Instance, 4)
|
||||
instances[0].Tags = []ec2.Tag{{"Name", "foo"}}
|
||||
instances[0].PrivateDNSName = "instance1"
|
||||
instances[0].State.Name = "running"
|
||||
instances[1].Tags = []ec2.Tag{{"Name", "bar"}}
|
||||
instances[1].PrivateDNSName = "instance2"
|
||||
instances[1].State.Name = "running"
|
||||
instances[2].Tags = []ec2.Tag{{"Name", "baz"}}
|
||||
instances[2].PrivateDNSName = "instance3"
|
||||
instances[2].State.Name = "running"
|
||||
instances[3].Tags = []ec2.Tag{{"Name", "quux"}}
|
||||
instances[3].PrivateDNSName = "instance4"
|
||||
instances[3].State.Name = "running"
|
||||
// TODO this setup is not very clean and could probably be improved
|
||||
var instance0 ec2.Instance
|
||||
var instance1 ec2.Instance
|
||||
var instance2 ec2.Instance
|
||||
var instance3 ec2.Instance
|
||||
|
||||
//0
|
||||
tag0 := ec2.Tag{
|
||||
Key: aws.String("Name"),
|
||||
Value: aws.String("foo"),
|
||||
}
|
||||
instance0.Tags = []*ec2.Tag{&tag0}
|
||||
instance0.PrivateDNSName = aws.String("instance1")
|
||||
state0 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance0.State = &state0
|
||||
|
||||
//1
|
||||
tag1 := ec2.Tag{
|
||||
Key: aws.String("Name"),
|
||||
Value: aws.String("bar"),
|
||||
}
|
||||
instance1.Tags = []*ec2.Tag{&tag1}
|
||||
instance1.PrivateDNSName = aws.String("instance2")
|
||||
state1 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance1.State = &state1
|
||||
|
||||
//2
|
||||
tag2 := ec2.Tag{
|
||||
Key: aws.String("Name"),
|
||||
Value: aws.String("baz"),
|
||||
}
|
||||
instance2.Tags = []*ec2.Tag{&tag2}
|
||||
instance2.PrivateDNSName = aws.String("instance3")
|
||||
state2 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance2.State = &state2
|
||||
|
||||
//3
|
||||
tag3 := ec2.Tag{
|
||||
Key: aws.String("Name"),
|
||||
Value: aws.String("quux"),
|
||||
}
|
||||
instance3.Tags = []*ec2.Tag{&tag3}
|
||||
instance3.PrivateDNSName = aws.String("instance4")
|
||||
state3 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance3.State = &state3
|
||||
|
||||
instances := []*ec2.Instance{&instance0, &instance1, &instance2, &instance3}
|
||||
aws := mockInstancesResp(instances)
|
||||
|
||||
table := []struct {
|
||||
@@ -292,16 +333,31 @@ func testHasNodeAddress(t *testing.T, addrs []api.NodeAddress, addressType api.N
|
||||
func TestNodeAddresses(t *testing.T) {
|
||||
// Note these instances have the same name
|
||||
// (we test that this produces an error)
|
||||
instances := make([]ec2.Instance, 2)
|
||||
instances[0].PrivateDNSName = "instance1"
|
||||
instances[0].PrivateIpAddress = "192.168.0.1"
|
||||
instances[0].PublicIpAddress = "1.2.3.4"
|
||||
instances[0].State.Name = "running"
|
||||
instances[1].PrivateDNSName = "instance1"
|
||||
instances[1].PrivateIpAddress = "192.168.0.2"
|
||||
instances[1].State.Name = "running"
|
||||
var instance0 ec2.Instance
|
||||
var instance1 ec2.Instance
|
||||
|
||||
aws1 := mockInstancesResp([]ec2.Instance{})
|
||||
//0
|
||||
instance0.PrivateDNSName = aws.String("instance1")
|
||||
instance0.PrivateIPAddress = aws.String("192.168.0.1")
|
||||
instance0.PublicIPAddress = aws.String("1.2.3.4")
|
||||
instance0.InstanceType = aws.String("c3.large")
|
||||
state0 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance0.State = &state0
|
||||
|
||||
//1
|
||||
instance1.PrivateDNSName = aws.String("instance1")
|
||||
instance1.PrivateIPAddress = aws.String("192.168.0.2")
|
||||
instance1.InstanceType = aws.String("c3.large")
|
||||
state1 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance1.State = &state1
|
||||
|
||||
instances := []*ec2.Instance{&instance0, &instance1}
|
||||
|
||||
aws1 := mockInstancesResp([]*ec2.Instance{})
|
||||
_, err1 := aws1.NodeAddresses("instance")
|
||||
if err1 == nil {
|
||||
t.Errorf("Should error when no instance found")
|
||||
@@ -345,16 +401,35 @@ func TestGetRegion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetResources(t *testing.T) {
|
||||
instances := make([]ec2.Instance, 3)
|
||||
instances[0].PrivateDNSName = "m3.medium"
|
||||
instances[0].InstanceType = "m3.medium"
|
||||
instances[0].State.Name = "running"
|
||||
instances[1].PrivateDNSName = "r3.8xlarge"
|
||||
instances[1].InstanceType = "r3.8xlarge"
|
||||
instances[1].State.Name = "running"
|
||||
instances[2].PrivateDNSName = "unknown.type"
|
||||
instances[2].InstanceType = "unknown.type"
|
||||
instances[2].State.Name = "running"
|
||||
var instance0 ec2.Instance
|
||||
var instance1 ec2.Instance
|
||||
var instance2 ec2.Instance
|
||||
|
||||
//0
|
||||
instance0.PrivateDNSName = aws.String("m3.medium")
|
||||
instance0.InstanceType = aws.String("m3.medium")
|
||||
state0 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance0.State = &state0
|
||||
|
||||
//1
|
||||
instance1.PrivateDNSName = aws.String("r3.8xlarge")
|
||||
instance1.InstanceType = aws.String("r3.8xlarge")
|
||||
state1 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance1.State = &state1
|
||||
|
||||
//2
|
||||
instance2.PrivateDNSName = aws.String("unknown.type")
|
||||
instance2.InstanceType = aws.String("unknown.type")
|
||||
state2 := ec2.InstanceState{
|
||||
Name: aws.String("running"),
|
||||
}
|
||||
instance2.State = &state2
|
||||
|
||||
instances := []*ec2.Instance{&instance0, &instance1, &instance2}
|
||||
|
||||
aws1 := mockInstancesResp(instances)
|
||||
|
||||
|
Reference in New Issue
Block a user