mirror of https://github.com/k3s-io/k3s
Initial impl. of cloud provider interface for AWS
parent
f085f4d5d4
commit
b548465adf
|
@ -20,6 +20,7 @@ package main
|
|||
// This should probably be part of some configuration fed into the build for a
|
||||
// given binary target.
|
||||
import (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"code.google.com/p/gcfg"
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
type EC2 interface {
|
||||
Instances(instIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error)
|
||||
}
|
||||
|
||||
// AWSCloud is an implementation of Interface, TCPLoadBalancer and Instances for Amazon Web Services.
|
||||
type AWSCloud struct {
|
||||
ec2 EC2
|
||||
cfg *AWSCloudConfig
|
||||
}
|
||||
|
||||
type AWSCloudConfig struct {
|
||||
Global struct {
|
||||
Region string
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("No AWS cloud provider config file given")
|
||||
}
|
||||
|
||||
var cfg AWSCloudConfig
|
||||
err := gcfg.ReadInto(&cfg, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.Global.Region == "" {
|
||||
return nil, fmt.Errorf("No region specified in configuration file")
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, fmt.Errorf("Unable to read AWS cloud provider config file: %s", err)
|
||||
}
|
||||
|
||||
auth, err := authFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
region, ok := aws.Regions[cfg.Global.Region]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Not a valid AWS region: %s", cfg.Global.Region)
|
||||
}
|
||||
|
||||
ec2 := ec2.New(auth, region)
|
||||
return &AWSCloud{
|
||||
ec2: ec2,
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// IPAddress is an implementation of Instances.IPAddress.
|
||||
func (aws *AWSCloud) IPAddress(name string) (net.IP, error) {
|
||||
f := ec2.NewFilter()
|
||||
f.Add("private-dns-name", name)
|
||||
|
||||
resp, err := aws.ec2.Instances(nil, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Reservations) == 0 {
|
||||
return nil, fmt.Errorf("No reservations found for host: %s", name)
|
||||
}
|
||||
if len(resp.Reservations) > 1 {
|
||||
return nil, fmt.Errorf("Multiple reservations found for host: %s", name)
|
||||
}
|
||||
if len(resp.Reservations[0].Instances) == 0 {
|
||||
return nil, fmt.Errorf("No instances found for host: %s", name)
|
||||
}
|
||||
if len(resp.Reservations[0].Instances) > 1 {
|
||||
return nil, fmt.Errorf("Multiple instances found for host: %s", name)
|
||||
}
|
||||
|
||||
ipAddress := resp.Reservations[0].Instances[0].PrivateIpAddress
|
||||
ip := net.ParseIP(ipAddress)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("Invalid network IP: %s", ipAddress)
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return []string{}, fmt.Errorf("No InstanceResp returned")
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
instances := []string{}
|
||||
for _, reservation := range resp.Reservations {
|
||||
for _, instance := range reservation.Instances {
|
||||
for _, tag := range instance.Tags {
|
||||
if tag.Key == "Name" && re.MatchString(tag.Value) {
|
||||
instances = append(instances, instance.PrivateDNSName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
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 (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/goamz/aws"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
)
|
||||
|
||||
func TestReadAWSCloudConfig(t *testing.T) {
|
||||
_, err1 := readAWSCloudConfig(nil)
|
||||
if err1 == nil {
|
||||
t.Errorf("Should error when no config reader is given")
|
||||
}
|
||||
|
||||
_, err2 := readAWSCloudConfig(strings.NewReader(""))
|
||||
if err2 == nil {
|
||||
t.Errorf("Should error when config is empty")
|
||||
}
|
||||
|
||||
_, err3 := readAWSCloudConfig(strings.NewReader("[global]\n"))
|
||||
if err3 == nil {
|
||||
t.Errorf("Should error when no region is specified")
|
||||
}
|
||||
|
||||
cfg, err4 := readAWSCloudConfig(strings.NewReader("[global]\nregion = eu-west-1"))
|
||||
if err4 != nil {
|
||||
t.Errorf("Should succeed when a region is specified: %s", err4)
|
||||
}
|
||||
if cfg.Global.Region != "eu-west-1" {
|
||||
t.Errorf("Should read region from config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAWSCloud(t *testing.T) {
|
||||
fakeAuthFunc := func() (auth aws.Auth, err error) {
|
||||
return aws.Auth{"", "", ""}, nil
|
||||
}
|
||||
|
||||
_, err1 := newAWSCloud(nil, fakeAuthFunc)
|
||||
if err1 == nil {
|
||||
t.Errorf("Should error when no config reader is given")
|
||||
}
|
||||
|
||||
_, err2 := newAWSCloud(strings.NewReader(
|
||||
"[global]\nregion = blahonga"),
|
||||
fakeAuthFunc)
|
||||
if err2 == nil {
|
||||
t.Errorf("Should error when config specifies invalid region")
|
||||
}
|
||||
|
||||
_, err3 := newAWSCloud(
|
||||
strings.NewReader("[global]\nregion = eu-west-1"),
|
||||
fakeAuthFunc)
|
||||
if err3 != nil {
|
||||
t.Errorf("Should succeed when a valid region is specified: %s", err3)
|
||||
}
|
||||
}
|
||||
|
||||
type FakeEC2 struct {
|
||||
instances func(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error)
|
||||
}
|
||||
|
||||
func (ec2 *FakeEC2) Instances(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error) {
|
||||
return ec2.instances(instanceIds, filter)
|
||||
}
|
||||
|
||||
func mockInstancesResp(instances []ec2.Instance) (aws *AWSCloud) {
|
||||
return &AWSCloud{
|
||||
&FakeEC2{
|
||||
func(instanceIds []string, filter *ec2.Filter) (resp *ec2.InstancesResp, err error) {
|
||||
return &ec2.InstancesResp{"",
|
||||
[]ec2.Reservation{
|
||||
ec2.Reservation{"", "", "", nil, instances}}}, nil
|
||||
}},
|
||||
nil}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
instances := make([]ec2.Instance, 4)
|
||||
instances[0].Tags = []ec2.Tag{ec2.Tag{"Name", "foo"}}
|
||||
instances[0].PrivateDNSName = "instance1"
|
||||
instances[1].Tags = []ec2.Tag{ec2.Tag{"Name", "bar"}}
|
||||
instances[1].PrivateDNSName = "instance2"
|
||||
instances[2].Tags = []ec2.Tag{ec2.Tag{"Name", "baz"}}
|
||||
instances[2].PrivateDNSName = "instance3"
|
||||
instances[3].Tags = []ec2.Tag{ec2.Tag{"Name", "quux"}}
|
||||
instances[3].PrivateDNSName = "instance4"
|
||||
|
||||
aws := mockInstancesResp(instances)
|
||||
|
||||
table := []struct {
|
||||
input string
|
||||
expect []string
|
||||
}{
|
||||
{"blahonga", []string{}},
|
||||
{"quux", []string{"instance4"}},
|
||||
{"a", []string{"instance2", "instance3"}},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
result, err := aws.List(item.input)
|
||||
if err != nil {
|
||||
t.Errorf("Expected call with %v to succeed, failed with %s", item.input, err)
|
||||
}
|
||||
if e, a := item.expect, result; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddress(t *testing.T) {
|
||||
instances := make([]ec2.Instance, 2)
|
||||
instances[0].PrivateDNSName = "instance1"
|
||||
instances[0].PrivateIpAddress = "192.168.0.1"
|
||||
instances[1].PrivateDNSName = "instance2"
|
||||
instances[1].PrivateIpAddress = "192.168.0.2"
|
||||
|
||||
aws1 := mockInstancesResp([]ec2.Instance{})
|
||||
_, err1 := aws1.IPAddress("instance")
|
||||
if err1 == nil {
|
||||
t.Errorf("Should error when no instance found")
|
||||
}
|
||||
|
||||
aws2 := mockInstancesResp(instances)
|
||||
_, err2 := aws2.IPAddress("instance1")
|
||||
if err2 == nil {
|
||||
t.Errorf("Should error when multiple instances found")
|
||||
}
|
||||
|
||||
aws3 := mockInstancesResp(instances[0:1])
|
||||
ip3, err3 := aws3.IPAddress("instance1")
|
||||
if err3 != nil {
|
||||
t.Errorf("Should not error when instance found")
|
||||
}
|
||||
if e, a := instances[0].PrivateIpAddress, ip3.String(); e != a {
|
||||
t.Errorf("Expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue