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
|
// This should probably be part of some configuration fed into the build for a
|
||||||
// given binary target.
|
// given binary target.
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
_ "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