Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS Route53 dnsprovider #26049

Merged
merged 2 commits into from Jun 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

420 changes: 420 additions & 0 deletions Godeps/LICENSES

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions federation/pkg/dnsprovider/providers/aws/route53/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2016 The Kubernetes Authors 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 route53

import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/testing"
)

// Compile time check for interface adeherence
var _ dnsprovider.Interface = Interface{}

type Interface struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a test only interface? Move it to testing package?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's the interface to Route53. I think we should leave it where it is.

service testing.Route53API
}

// newInterfaceWithStub facilitates stubbing out the underlying AWS Route53
// library for testing purposes. It returns an provider-independent interface.
func newInterfaceWithStub(service testing.Route53API) *Interface {
return &Interface{service}
}

func (i Interface) Zones() (zones dnsprovider.Zones, supported bool) {
return Zones{&i}, true
}
44 changes: 44 additions & 0 deletions federation/pkg/dnsprovider/providers/aws/route53/route53.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2016 The Kubernetes Authors 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.
*/

// route53 is the implementation of pkg/dnsprovider interface for AWS Route53
package route53

import (
"io"

"k8s.io/kubernetes/federation/pkg/dnsprovider"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
)

const (
ProviderName = "aws-route53"
)

func init() {
dnsprovider.RegisterDnsProvider(ProviderName, func(config io.Reader) (dnsprovider.Interface, error) {
return newRoute53(config)
})
}

// newRoute53 creates a new instance of an AWS Route53 DNS Interface.
func newRoute53(config io.Reader) (*Interface, error) {
// Connect to AWS Route53 - TODO: Do more sophisticated auth
svc := route53.New(session.New())
return newInterfaceWithStub(svc), nil
}
236 changes: 236 additions & 0 deletions federation/pkg/dnsprovider/providers/aws/route53/route53_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
Copyright 2016 The Kubernetes Authors 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 route53

import (
"flag"
"fmt"
"os"
"testing"

"k8s.io/kubernetes/federation/pkg/dnsprovider"
route53testing "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/aws/route53/testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53"
)

func newTestInterface() (dnsprovider.Interface, error) {
// Use this to test the real cloud service.
// i, err := dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00"))
return newFakeInterface() // Use this to stub out the entire cloud service
}

func newFakeInterface() (dnsprovider.Interface, error) {
var service route53testing.Route53API
service = route53testing.NewRoute53APIStub()
iface := newInterfaceWithStub(service)
// Add a fake zone to test against.
params := &route53.CreateHostedZoneInput{
CallerReference: aws.String("Nonce"), // Required
Name: aws.String("example.com"), // Required
}
_, err := iface.service.CreateHostedZone(params)
if err != nil {
return nil, err
}
return iface, nil
}

var interface_ dnsprovider.Interface

func TestMain(m *testing.M) {
fmt.Printf("Parsing flags.\n")
flag.Parse()
var err error
fmt.Printf("Getting new test interface.\n")
interface_, err = newTestInterface()
if err != nil {
fmt.Printf("Error creating interface: %v", err)
os.Exit(1)
}
fmt.Printf("Running tests...\n")
os.Exit(m.Run())
}

// firstZone returns the first zone for the configured dns provider account/project,
// or fails if it can't be found
func firstZone(t *testing.T) dnsprovider.Zone {
t.Logf("Getting zones")
z, supported := interface_.Zones()
if supported {
t.Logf("Got zones %v\n", z)
} else {
t.Fatalf("Zones interface not supported by interface %v", interface_)
}
zones, err := z.List()
if err != nil {
t.Fatalf("Failed to list zones: %v", err)
} else {
t.Logf("Got zone list: %v\n", zones)
}
if len(zones) < 1 {
t.Fatalf("Zone listing returned %d, expected >= %d", len(zones), 1)
} else {
t.Logf("Got at least 1 zone in list:%v\n", zones[0])
}
return zones[0]
}

/* rrs returns the ResourceRecordSets interface for a given zone */
func rrs(t *testing.T, zone dnsprovider.Zone) (r dnsprovider.ResourceRecordSets) {
rrsets, supported := zone.ResourceRecordSets()
if !supported {
t.Fatalf("ResourceRecordSets interface not supported by zone %v", zone)
return r
}
return rrsets
}

func listRrsOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets) []dnsprovider.ResourceRecordSet {
rrset, err := rrsets.List()
if err != nil {
t.Fatalf("Failed to list recordsets: %v", err)
} else {
if len(rrset) < 0 {
t.Fatalf("Record set length=%d, expected >=0", len(rrset))
} else {
t.Logf("Got %d recordsets: %v", len(rrset), rrset)
}
}
return rrset
}

func getExampleRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www11."+zone.Name(), []string{"10.10.10.10", "169.20.20.20"}, 180, rrstype.A)
}

func getInvalidRrs(zone dnsprovider.Zone) dnsprovider.ResourceRecordSet {
rrsets, _ := zone.ResourceRecordSets()
return rrsets.New("www12."+zone.Name(), []string{"rubbish", "rubbish"}, 180, rrstype.A)
}

func addRrsetOrFail(t *testing.T, rrsets dnsprovider.ResourceRecordSets, rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordSet {
result, err := rrsets.Add(rrset)
if err != nil {
t.Fatalf("Failed to add recordsets: %v", err)
}
return result
}

/* TestResourceRecordSetsList verifies that listing of zones succeeds */
func TestZonesList(t *testing.T) {
firstZone(t)
}

/* TestResourceRecordSetsList verifies that listing of RRS's succeeds */
func TestResourceRecordSetsList(t *testing.T) {
listRrsOrFail(t, rrs(t, firstZone(t)))
}

/* TestResourceRecordSetsAddSuccess verifies that addition of a valid RRS succeeds */
func TestResourceRecordSetsAddSuccess(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
set := addRrsetOrFail(t, sets, getExampleRrs(zone))
defer sets.Remove(set)
t.Logf("Successfully added resource record set: %v", set)
}

/* TestResourceRecordSetsAdditionVisible verifies that added RRS is visible after addition */
func TestResourceRecordSetsAdditionVisible(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
set := addRrsetOrFail(t, sets, rrset)
defer sets.Remove(set)
t.Logf("Successfully added resource record set: %v", set)
found := false
for _, record := range listRrsOrFail(t, sets) {
if record.Name() == rrset.Name() {
found = true
break
}
}
if !found {
t.Errorf("Failed to find added resource record set %s", rrset.Name())
}
}

/* TestResourceRecordSetsAddDuplicateFail verifies that addition of a duplicate RRS fails */
func TestResourceRecordSetsAddDuplicateFail(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
set := addRrsetOrFail(t, sets, rrset)
defer sets.Remove(set)
t.Logf("Successfully added resource record set: %v", set)
// Try to add it again, and verify that the call fails.
rrs, err := sets.Add(rrset)
if err == nil {
defer sets.Remove(rrs)
t.Errorf("Should have failed to add duplicate resource record %v, but succeeded instead.", set)
} else {
t.Logf("Correctly failed to add duplicate resource record %v: %v", set, err)
}
}

/* TestResourceRecordSetsRemove verifies that the removal of an existing RRS succeeds */
func TestResourceRecordSetsRemove(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
set := addRrsetOrFail(t, sets, rrset)
err := sets.Remove(set)
if err != nil {
// Try again to clean up.
defer sets.Remove(rrset)
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", set)
}
}

/* TestResourceRecordSetsRemoveGone verifies that a removed RRS no longer exists */
func TestResourceRecordSetsRemoveGone(t *testing.T) {
zone := firstZone(t)
sets := rrs(t, zone)
rrset := getExampleRrs(zone)
set := addRrsetOrFail(t, sets, rrset)
err := sets.Remove(set)
if err != nil {
// Try again to clean up.
defer sets.Remove(rrset)
t.Errorf("Failed to remove resource record set %v after adding", rrset)
} else {
t.Logf("Successfully removed resource set %v after adding", set)
}
// Check that it's gone
list := listRrsOrFail(t, sets)
found := false
for _, set := range list {
if set.Name() == rrset.Name() {
found = true
break
}
}
if found {
t.Errorf("Deleted resource record set %v is still present", rrset)
}
}
53 changes: 53 additions & 0 deletions federation/pkg/dnsprovider/providers/aws/route53/rrset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright 2016 The Kubernetes Authors 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 route53

import (
"k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"

"github.com/aws/aws-sdk-go/service/route53"
)

// Compile time check for interface adeherence
var _ dnsprovider.ResourceRecordSet = ResourceRecordSet{}

type ResourceRecordSet struct {
impl *route53.ResourceRecordSet
rrsets *ResourceRecordSets
}

func (rrset ResourceRecordSet) Name() string {
return *rrset.impl.Name
}

func (rrset ResourceRecordSet) Rrdatas() []string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very optional: RRDatas() or just Data() or Records() might be better?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would absolutely agree, only rrdatas is a pretty well-known DNS term, used by the DNS RFC, Google Cloud DNS, AWS Route 53, bind, and most other DNS things. So I suggest that we leave it the way it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg

// Sigh - need to unpack the strings out of the route53 ResourceRecords
result := make([]string, len(rrset.impl.ResourceRecords))
for i, record := range rrset.impl.ResourceRecords {
result[i] = *record.Value
}
return result
}

func (rrset ResourceRecordSet) Ttl() int64 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: TTL() is more readable method name, I think.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I thought that was the Go way of doing acronyms, but I was wrong. Will fix.

https://github.com/golang/go/wiki/CodeReviewComments#initialisms

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a separate PR, as it affects the shared interface, and other implementations of it.

return *rrset.impl.TTL
}

func (rrset ResourceRecordSet) Type() rrstype.RrsType {
return rrstype.RrsType(*rrset.impl.Type)
}