-
Notifications
You must be signed in to change notification settings - Fork 40k
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
AWS Route53 dnsprovider #26049
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
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 { | ||
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 | ||
} |
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 | ||
} |
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) | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very optional: RRDatas() or just Data() or Records() might be better? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optional: TTL() is more readable method name, I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.