Implementation of multiple targets based on PR #404 and #396 (#418)

* Endpoint.Target is now Endpoint.Targets. This is its own type representing mutliple targets for a single DNS name while adding some convenience for sorting and comparing

* Made everything compile and tests run through with the new Endpoint.Targets

* The ingress source can now properly handle multiple target ips per host

* Added custom conflict resolver, to better understand how conflict resolution has to work for me

* My custom conflict resolver behaves a bit different than the PerResource resolver, therefore I needed to modify the expected test result

Removed unnecessary FIXME

* The ingress source now creates CNAME endpoints with multiple targets to let the DNS provider decide how to handle multiple CNAME targets. This could be interesting for weighted targets etc.

* Adopted the expected results to the new way we create endpoints for CNAMEs

* Removed Add method from Targets since manipulating the slice through here is unnecessary complicated and doesn't deliver enough convenience

* Reverted ConflictResolver to the original one. There is some discussing to do what the best way is to handle conflicts

* Added missing documenting comment to IsLess of Targets

* Added documenting comments to Targets,Targets.Same and NewTargets to clarify their intention and usage

* Service source now also generates endpoints with multiple targets

* Service and Ingress source now sort all Targets for every Endpoint to make order of Targets predictable

* Endpoints generated by the Google Cloud DNS provider now also have sorted Targets to make order of Targets predictable

* Modified provider dyn to be able to compile with multi target changes

* Fixed small nitpicks, so my code is acceptable

* Fixed merge method after updating to new Targets. Replacing '!=' with .Same of course needs a boolean negation

* Tests for dyn provider now also use the new Targets instead of Target

* Simplified extractServiceIps as implied by linki to make it more readable

* ref: change service ClusterIP retrieval again

* Added entry to CHANGELOG.md describing the new features contained in this PR
This commit is contained in:
Till Klocke 2018-02-21 18:11:45 +01:00 committed by Martin Linkhorst
parent f5b0d93e91
commit 5d54849699
38 changed files with 473 additions and 329 deletions

View File

@ -1,5 +1,6 @@
- DigitalOcean: DigitalOcean creates entries with host in them twice (#459) @njuettner
- Bugfix: Retrive all DNSimple response pages (#468) @jbowes
- external-dns does now provide support for multiple targets for A records. This is currently only supported by the Google Cloud DNS provider (#418) @dereulenspiegel
- Graceful handling of misconfigure password for dyn provider (#470) @jvassev
- Don't log sensitive data on start (#463) @jvassev
- Google: Improve logging to help trace misconfigurations (#388) @stealthybox

View File

@ -48,25 +48,25 @@ func (p *mockProvider) ApplyChanges(changes *plan.Changes) error {
}
for i := range changes.Create {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || changes.Create[i].Target != p.ExpectChanges.Create[i].Target {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
return errors.New("created record is wrong")
}
}
for i := range changes.UpdateNew {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || changes.UpdateNew[i].Target != p.ExpectChanges.UpdateNew[i].Target {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
return errors.New("delete record is wrong")
}
}
for i := range changes.UpdateOld {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || changes.UpdateOld[i].Target != p.ExpectChanges.UpdateOld[i].Target {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
return errors.New("delete record is wrong")
}
}
for i := range changes.Delete {
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || changes.Delete[i].Target != p.ExpectChanges.Delete[i].Target {
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
return errors.New("delete record is wrong")
}
}
@ -91,11 +91,11 @@ func TestRunOnce(t *testing.T) {
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
Target: "1.2.3.4",
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "update-record",
Target: "8.8.4.4",
Targets: endpoint.Targets{"8.8.4.4"},
},
}, nil)
@ -104,25 +104,25 @@ func TestRunOnce(t *testing.T) {
[]*endpoint.Endpoint{
{
DNSName: "update-record",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "delete-record",
Target: "4.3.2.1",
Targets: endpoint.Targets{"4.3.2.1"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-record", Target: "1.2.3.4"},
{DNSName: "create-record", Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-record", Target: "8.8.4.4"},
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-record", Target: "8.8.8.8"},
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-record", Target: "4.3.2.1"},
{DNSName: "delete-record", Targets: endpoint.Targets{"4.3.2.1"}},
},
},
)

View File

@ -18,6 +18,7 @@ package endpoint
import (
"fmt"
"sort"
"strings"
)
@ -38,12 +39,78 @@ func (ttl TTL) IsConfigured() bool {
return ttl > 0
}
// Targets is a representation of a list of targets for an endpoint.
type Targets []string
// NewTargets is a convenience method to create a new Targets object from a vararg of strings
func NewTargets(target ...string) Targets {
t := make(Targets, 0, len(target))
t = append(t, target...)
return t
}
func (t Targets) String() string {
return strings.Join(t, ";")
}
func (t Targets) Len() int {
return len(t)
}
func (t Targets) Less(i, j int) bool {
return t[i] < t[j]
}
func (t Targets) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
// Same compares to Targets and returns true if they are completely identical
func (t Targets) Same(o Targets) bool {
if len(t) != len(o) {
return false
}
sort.Stable(t)
sort.Stable(o)
for i, e := range t {
if e != o[i] {
return false
}
}
return true
}
// IsLess should fulfill the requirement to compare two targets and chosse the 'lesser' one.
// In the past target was a simple string so simple string comparison could be used. Now we define 'less'
// as either being the shorter list of targets or where the first entry is less.
// FIXME We really need to define under which circumstances a list Targets is considered 'less'
// than another.
func (t Targets) IsLess(o Targets) bool {
if len(t) < len(o) {
return true
}
if len(t) > len(o) {
return false
}
sort.Sort(t)
sort.Sort(o)
for i, e := range t {
if e != o[i] {
return e < o[i]
}
}
return false
}
// Endpoint is a high-level way of a connection between a service and an IP
type Endpoint struct {
// The hostname of the DNS record
DNSName string
// The target the DNS record points to
Target string
// The targets the DNS record points to
Targets Targets
// RecordType type of record, e.g. CNAME, A, TXT etc
RecordType string
// TTL for the record
@ -61,7 +128,7 @@ func NewEndpoint(dnsName, target, recordType string) *Endpoint {
func NewEndpointWithTTL(dnsName, target, recordType string, ttl TTL) *Endpoint {
return &Endpoint{
DNSName: strings.TrimSuffix(dnsName, "."),
Target: strings.TrimSuffix(target, "."),
Targets: Targets{strings.TrimSuffix(target, ".")},
RecordType: recordType,
Labels: NewLabels(),
RecordTTL: ttl,
@ -69,5 +136,5 @@ func NewEndpointWithTTL(dnsName, target, recordType string, ttl TTL) *Endpoint {
}
func (e *Endpoint) String() string {
return fmt.Sprintf("%s %d IN %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.Target)
return fmt.Sprintf("%s %d IN %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.Targets)
}

View File

@ -22,7 +22,7 @@ import (
func TestNewEndpoint(t *testing.T) {
e := NewEndpoint("example.org", "foo.com", "CNAME")
if e.DNSName != "example.org" || e.Target != "foo.com" || e.RecordType != "CNAME" {
if e.DNSName != "example.org" || e.Targets[0] != "foo.com" || e.RecordType != "CNAME" {
t.Error("endpoint is not initialized correctly")
}
if e.Labels == nil {
@ -30,7 +30,7 @@ func TestNewEndpoint(t *testing.T) {
}
w := NewEndpoint("example.org.", "load-balancer.com.", "")
if w.DNSName != "example.org" || w.Target != "load-balancer.com" || w.RecordType != "" {
if w.DNSName != "example.org" || w.Targets[0] != "load-balancer.com" || w.RecordType != "" {
t.Error("endpoint is not initialized correctly")
}
}

View File

@ -33,13 +33,12 @@ func (b byAllFields) Less(i, j int) bool {
return true
}
if b[i].DNSName == b[j].DNSName {
if b[i].Target < b[j].Target {
return true
}
if b[i].Target == b[j].Target {
// This rather bad, we need a more complex comparison for Targets, which considers all elements
if b[i].Targets.Same(b[j].Targets) {
return b[i].RecordType <= b[j].RecordType
}
return false
return b[i].Targets.String() <= b[j].Targets.String()
}
return false
}
@ -47,7 +46,7 @@ func (b byAllFields) Less(i, j int) bool {
// SameEndpoint returns true if two endpoints are same
// considers example.org. and example.org DNSName/Target as different endpoints
func SameEndpoint(a, b *endpoint.Endpoint) bool {
return a.DNSName == b.DNSName && a.Target == b.Target && a.RecordType == b.RecordType &&
return a.DNSName == b.DNSName && a.Targets.Same(b.Targets) && a.RecordType == b.RecordType &&
a.Labels[endpoint.OwnerLabelKey] == b.Labels[endpoint.OwnerLabelKey] && a.RecordTTL == b.RecordTTL &&
a.Labels[endpoint.ResourceLabelKey] == b.Labels[endpoint.ResourceLabelKey]
}

View File

@ -27,31 +27,31 @@ func ExampleSameEndpoints() {
eps := []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "load-balancer.org",
Targets: endpoint.Targets{"load-balancer.org"},
},
{
DNSName: "example.org",
Target: "load-balancer.org",
Targets: endpoint.Targets{"load-balancer.org"},
RecordType: endpoint.RecordTypeTXT,
},
{
DNSName: "abc.com",
Target: "something",
Targets: endpoint.Targets{"something"},
RecordType: endpoint.RecordTypeTXT,
},
{
DNSName: "abc.com",
Target: "1.2.3.4",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "bbc.com",
Target: "foo.com",
Targets: endpoint.Targets{"foo.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "cbc.com",
Target: "foo.com",
Targets: endpoint.Targets{"foo.com"},
RecordType: "CNAME",
RecordTTL: endpoint.TTL(60),
},

View File

@ -64,7 +64,7 @@ func (s PerResource) ResolveUpdate(current *endpoint.Endpoint, candidates []*end
// less returns true if endpoint x is less than y
func (s PerResource) less(x, y *endpoint.Endpoint) bool {
return x.Target < y.Target
return x.Targets.IsLess(y.Targets)
}
// TODO: with cross-resource/cross-cluster setup alternative variations of ConflictResolver can be used

View File

@ -45,7 +45,7 @@ func (suite *ResolverSuite) SetupTest() {
// initialize endpoints used in tests
suite.fooV1Cname = &endpoint.Endpoint{
DNSName: "foo",
Target: "v1",
Targets: endpoint.Targets{"v1"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
@ -53,7 +53,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.fooV2Cname = &endpoint.Endpoint{
DNSName: "foo",
Target: "v2",
Targets: endpoint.Targets{"v2"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v2",
@ -61,7 +61,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.fooV2CnameDuplicate = &endpoint.Endpoint{
DNSName: "foo",
Target: "v2",
Targets: endpoint.Targets{"v2"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v2-duplicate",
@ -69,7 +69,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.fooA5 = &endpoint.Endpoint{
DNSName: "foo",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-5",
@ -77,7 +77,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.bar127A = &endpoint.Endpoint{
DNSName: "bar",
Target: "127.0.0.1",
Targets: endpoint.Targets{"127.0.0.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/bar-127",
@ -85,7 +85,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.bar127AAnother = &endpoint.Endpoint{ //TODO: remove this once we move to multiple targets under same endpoint
DNSName: "bar",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/bar-127",
@ -93,7 +93,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.bar192A = &endpoint.Endpoint{
DNSName: "bar",
Target: "192.168.0.1",
Targets: endpoint.Targets{"192.168.0.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/bar-192",
@ -101,7 +101,7 @@ func (suite *ResolverSuite) SetupTest() {
}
suite.legacyBar192A = &endpoint.Endpoint{
DNSName: "bar",
Target: "192.168.0.1",
Targets: endpoint.Targets{"192.168.0.1"},
RecordType: "A",
}
}
@ -120,7 +120,7 @@ func (suite *ResolverSuite) TestStrictResolver() {
// should actually get the updated record (note ttl is different)
newFooV1Cname := &endpoint.Endpoint{
DNSName: suite.fooV1Cname.DNSName,
Target: suite.fooV1Cname.Target,
Targets: suite.fooV1Cname.Targets,
Labels: suite.fooV1Cname.Labels,
RecordType: suite.fooV1Cname.RecordType,
RecordTTL: suite.fooV1Cname.RecordTTL + 1, // ttl is different

View File

@ -166,7 +166,7 @@ func inheritOwner(from, to *endpoint.Endpoint) {
}
func targetChanged(desired, current *endpoint.Endpoint) bool {
return desired.Target != current.Target
return !desired.Targets.Same(current.Targets)
}
func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {

View File

@ -39,7 +39,7 @@ type PlanTestSuite struct {
func (suite *PlanTestSuite) SetupTest() {
suite.fooV1Cname = &endpoint.Endpoint{
DNSName: "foo",
Target: "v1",
Targets: endpoint.Targets{"v1"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
@ -49,7 +49,7 @@ func (suite *PlanTestSuite) SetupTest() {
// same resource as fooV1Cname, but target is different. It will never be picked because its target lexicographically bigger than "v1"
suite.fooV3CnameSameResource = &endpoint.Endpoint{ // TODO: remove this once endpoint can support multiple targets
DNSName: "foo",
Target: "v3",
Targets: endpoint.Targets{"v3"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v1",
@ -58,7 +58,7 @@ func (suite *PlanTestSuite) SetupTest() {
}
suite.fooV2Cname = &endpoint.Endpoint{
DNSName: "foo",
Target: "v2",
Targets: endpoint.Targets{"v2"},
RecordType: "CNAME",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-v2",
@ -66,12 +66,12 @@ func (suite *PlanTestSuite) SetupTest() {
}
suite.fooV2CnameNoLabel = &endpoint.Endpoint{
DNSName: "foo",
Target: "v2",
Targets: endpoint.Targets{"v2"},
RecordType: "CNAME",
}
suite.fooA5 = &endpoint.Endpoint{
DNSName: "foo",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-5",
@ -79,7 +79,7 @@ func (suite *PlanTestSuite) SetupTest() {
}
suite.bar127A = &endpoint.Endpoint{
DNSName: "bar",
Target: "127.0.0.1",
Targets: endpoint.Targets{"127.0.0.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/bar-127",
@ -87,7 +87,7 @@ func (suite *PlanTestSuite) SetupTest() {
}
suite.bar127AWithTTL = &endpoint.Endpoint{
DNSName: "bar",
Target: "127.0.0.1",
Targets: endpoint.Targets{"127.0.0.1"},
RecordType: "A",
RecordTTL: 300,
Labels: map[string]string{
@ -96,7 +96,7 @@ func (suite *PlanTestSuite) SetupTest() {
}
suite.bar192A = &endpoint.Endpoint{
DNSName: "bar",
Target: "192.168.0.1",
Targets: endpoint.Targets{"192.168.0.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/bar-192",
@ -196,7 +196,7 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
expectedUpdateNew := []*endpoint.Endpoint{{
DNSName: suite.fooV2Cname.DNSName,
Target: suite.fooV2Cname.Target,
Targets: suite.fooV2Cname.Targets,
RecordType: suite.fooV2Cname.RecordType,
RecordTTL: suite.fooV2Cname.RecordTTL,
Labels: map[string]string{

View File

@ -28,12 +28,12 @@ func TestApply(t *testing.T) {
// empty list of records
empty := []*endpoint.Endpoint{}
// a simple entry
fooV1 := []*endpoint.Endpoint{{DNSName: "foo", Target: "v1"}}
fooV1 := []*endpoint.Endpoint{{DNSName: "foo", Targets: endpoint.Targets{"v1"}}}
// the same entry but with different target
fooV2 := []*endpoint.Endpoint{{DNSName: "foo", Target: "v2"}}
fooV2 := []*endpoint.Endpoint{{DNSName: "foo", Targets: endpoint.Targets{"v2"}}}
// another two simple entries
bar := []*endpoint.Endpoint{{DNSName: "bar", Target: "v1"}}
baz := []*endpoint.Endpoint{{DNSName: "baz", Target: "v1"}}
bar := []*endpoint.Endpoint{{DNSName: "bar", Targets: endpoint.Targets{"v1"}}}
baz := []*endpoint.Endpoint{{DNSName: "baz", Targets: endpoint.Targets{"v1"}}}
for _, tc := range []struct {
policy Policy

View File

@ -378,8 +378,8 @@ func newChange(action string, endpoint *endpoint.Endpoint) *route53.Change {
if isAWSLoadBalancer(endpoint) {
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{
DNSName: aws.String(endpoint.Target),
HostedZoneId: aws.String(canonicalHostedZone(endpoint.Target)),
DNSName: aws.String(endpoint.Targets[0]),
HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])),
EvaluateTargetHealth: aws.Bool(evaluateTargetHealth),
}
} else {
@ -391,7 +391,7 @@ func newChange(action string, endpoint *endpoint.Endpoint) *route53.Change {
}
change.ResourceRecordSet.ResourceRecords = []*route53.ResourceRecord{
{
Value: aws.String(endpoint.Target),
Value: aws.String(endpoint.Targets[0]),
},
}
}
@ -429,7 +429,7 @@ func suitableZones(hostname string, zones map[string]*route53.HostedZone) []*rou
// isAWSLoadBalancer determines if a given hostname belongs to an AWS load balancer.
func isAWSLoadBalancer(ep *endpoint.Endpoint) bool {
if ep.RecordType == endpoint.RecordTypeCNAME {
return canonicalHostedZone(ep.Target) != ""
return canonicalHostedZone(ep.Targets[0]) != ""
}
return false

View File

@ -630,7 +630,7 @@ func TestAWSCreateRecordsWithCNAME(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
records := []*endpoint.Endpoint{
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.example.org", RecordType: endpoint.RecordTypeCNAME},
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME},
}
require.NoError(t, provider.CreateRecords(records))
@ -655,7 +655,7 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
records := []*endpoint.Endpoint{
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.eu-central-1.elb.amazonaws.com", RecordType: endpoint.RecordTypeCNAME},
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.eu-central-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME},
}
require.NoError(t, provider.CreateRecords(records))
@ -685,7 +685,7 @@ func TestAWSisLoadBalancer(t *testing.T) {
{"foo.example.org", endpoint.RecordTypeCNAME, false},
} {
ep := &endpoint.Endpoint{
Target: tc.target,
Targets: endpoint.Targets{tc.target},
RecordType: tc.recordType,
}
assert.Equal(t, tc.expected, isAWSLoadBalancer(ep))

View File

@ -163,7 +163,7 @@ func (p *AzureProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
"Found %s record for '%s' with target '%s'.",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
)
endpoints = append(endpoints, ep)
return true
@ -323,7 +323,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
"Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.",
endpoint.RecordType,
name,
endpoint.Target,
endpoint.Targets,
zone,
)
continue
@ -333,7 +333,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
"Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.",
endpoint.RecordType,
name,
endpoint.Target,
endpoint.Targets,
zone,
)
@ -354,7 +354,7 @@ func (p *AzureProvider) updateRecords(updated azureChangeMap) {
"Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v",
endpoint.RecordType,
name,
endpoint.Target,
endpoint.Targets,
zone,
err,
)
@ -388,7 +388,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
TTL: to.Int64Ptr(ttl),
ARecords: &[]dns.ARecord{
{
Ipv4Address: to.StringPtr(endpoint.Target),
Ipv4Address: to.StringPtr(endpoint.Targets[0]),
},
},
},
@ -398,7 +398,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
RecordSetProperties: &dns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
CnameRecord: &dns.CnameRecord{
Cname: to.StringPtr(endpoint.Target),
Cname: to.StringPtr(endpoint.Targets[0]),
},
},
}, nil
@ -409,7 +409,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
TxtRecords: &[]dns.TxtRecord{
{
Value: &[]string{
endpoint.Target,
endpoint.Targets[0],
},
},
},

View File

@ -290,7 +290,7 @@ func newCloudFlareChange(action string, endpoint *endpoint.Endpoint, proxied boo
TTL: 1,
Proxied: proxied,
Type: endpoint.RecordType,
Content: endpoint.Target,
Content: endpoint.Targets[0],
},
}
}

View File

@ -21,11 +21,11 @@ import (
"os"
"testing"
"github.com/cloudflare/cloudflare-go"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -336,12 +336,12 @@ func (m *mockCloudFlareUpdateRecordsFail) ListZones(zoneID ...string) ([]cloudfl
}
func TestNewCloudFlareChanges(t *testing.T) {
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
endpoints := []*endpoint.Endpoint{{DNSName: "new", Targets: endpoint.Targets{"target"}}}
newCloudFlareChanges(cloudFlareCreate, endpoints, true)
}
func TestNewCloudFlareChangeNoProxied(t *testing.T) {
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Target: "target"}, false)
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}}, false)
assert.False(t, change.ResourceRecordSet.Proxied)
}
@ -361,7 +361,7 @@ func TestNewCloudFlareChangeProxiable(t *testing.T) {
}
for _, cloudFlareType := range cloudFlareTypes {
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: cloudFlareType.recordType, Target: "target"}, true)
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: cloudFlareType.recordType, Targets: endpoint.Targets{"target"}}, true)
if cloudFlareType.proxiable {
assert.True(t, change.ResourceRecordSet.Proxied)
@ -370,7 +370,7 @@ func TestNewCloudFlareChangeProxiable(t *testing.T) {
}
}
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "*.foo", RecordType: "A", Target: "target"}, true)
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "*.foo", RecordType: "A", Targets: endpoint.Targets{"target"}}, true)
assert.False(t, change.ResourceRecordSet.Proxied)
}
@ -433,10 +433,10 @@ func TestApplyChanges(t *testing.T) {
provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{},
}
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Target: "target"}, {DNSName: "new.ext-dns-test.unrelated.to.", Target: "target"}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target"}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-old"}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Target: "target-new"}}
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}, {DNSName: "new.ext-dns-test.unrelated.to.", Targets: endpoint.Targets{"target"}}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-old"}}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-new"}}}
err := provider.ApplyChanges(changes)
if err != nil {
t.Errorf("should not fail, %s", err)

View File

@ -260,7 +260,7 @@ func newDigitalOceanChange(action string, endpoint *endpoint.Endpoint) *DigitalO
ResourceRecordSet: godo.DomainRecord{
Name: endpoint.DNSName,
Type: endpoint.RecordType,
Data: endpoint.Target,
Data: endpoint.Targets[0],
},
}
return change

View File

@ -400,7 +400,7 @@ func (m *mockDigitalOceanCreateRecordsFail) Records(ctx context.Context, domain
func TestNewDigitalOceanChanges(t *testing.T) {
action := DigitalOceanCreate
endpoints := []*endpoint.Endpoint{{DNSName: "new", Target: "target"}}
endpoints := []*endpoint.Endpoint{{DNSName: "new", Targets: endpoint.Targets{"target"}}}
_ = newDigitalOceanChanges(action, endpoints)
}
@ -425,10 +425,10 @@ func TestDigitalOceanApplyChanges(t *testing.T) {
provider := &DigitalOceanProvider{
Client: &mockDigitalOceanClient{},
}
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.bar.com", Target: "target"}, {DNSName: "new.ext-dns-test.unexpected.com", Target: "target"}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.com", Target: "target"}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.de", Target: "target-old"}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.foo.com", Target: "target-new"}}
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.bar.com", Targets: endpoint.Targets{"target"}}, {DNSName: "new.ext-dns-test.unexpected.com", Targets: endpoint.Targets{"target"}}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.com", Targets: endpoint.Targets{"target"}}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.bar.de", Targets: endpoint.Targets{"target-old"}}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.foo.com", Targets: endpoint.Targets{"target-new"}}}
err := provider.ApplyChanges(changes)
if err != nil {
t.Errorf("should not fail, %s", err)

View File

@ -192,7 +192,7 @@ func newDnsimpleChange(action string, e *endpoint.Endpoint) *dnsimpleChange {
ResourceRecordSet: dnsimple.ZoneRecord{
Name: e.DNSName,
Type: e.RecordType,
Content: e.Target,
Content: e.Targets[0],
},
}
return change

View File

@ -138,9 +138,9 @@ func testDnsimpleProviderRecords(t *testing.T) {
}
func testDnsimpleProviderApplyChanges(t *testing.T) {
changes := &plan.Changes{}
changes.Create = []*endpoint.Endpoint{{DNSName: "example.example.com", Target: "target", RecordType: endpoint.RecordTypeCNAME}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "example-beta.example.com", Target: "127.0.0.1", RecordType: endpoint.RecordTypeA}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "example.example.com", Target: "target", RecordType: endpoint.RecordTypeCNAME}}
changes.Create = []*endpoint.Endpoint{{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME}}
changes.Delete = []*endpoint.Endpoint{{DNSName: "example-beta.example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME}}
mockProvider.accountID = "1"
err := mockProvider.ApplyChanges(changes)

View File

@ -214,7 +214,7 @@ func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoint {
continue
}
if matchingNew.Target != old.Target {
if !matchingNew.Targets.Same(old.Targets) {
// new target: always update, TTL will be overwritten too if necessary
result = append(result, matchingNew)
continue
@ -275,7 +275,7 @@ func (d *dynProviderState) recordLinkToEndpoint(client *dynect.Client, recordLin
DNSName: rec.Data.FQDN,
RecordTTL: endpoint.TTL(rec.Data.TTL),
RecordType: rec.Data.RecordType,
Target: target,
Targets: endpoint.Targets{target},
}
log.Debugf("Fetched new endpoint for %s: %+v", recordLink, result)
@ -297,11 +297,11 @@ func endpointToRecord(ep *endpoint.Endpoint) *dynect.DataBlock {
result := dynect.DataBlock{}
if ep.RecordType == endpoint.RecordTypeA {
result.Address = ep.Target
result.Address = ep.Targets[0]
} else if ep.RecordType == endpoint.RecordTypeCNAME {
result.CName = ep.Target
result.CName = ep.Targets[0]
} else if ep.RecordType == endpoint.RecordTypeTXT {
result.TxtData = ep.Target
result.TxtData = ep.Targets[0]
}
return &result

View File

@ -32,13 +32,13 @@ func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeA,
},
@ -47,13 +47,13 @@ func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
@ -66,13 +66,13 @@ func TestDynMerge_UpdateOnTTLChanges(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
@ -81,13 +81,13 @@ func TestDynMerge_UpdateOnTTLChanges(t *testing.T) {
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(77),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(10),
RecordType: endpoint.RecordTypeCNAME,
},
@ -102,13 +102,13 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
@ -117,13 +117,13 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1-changed",
Targets: endpoint.Targets{"target1-changed"},
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
@ -131,20 +131,20 @@ func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
merged := merge(updateOld, updateNew)
assert.Equal(t, 1, len(merged))
assert.Equal(t, "target1-changed", merged[0].Target)
assert.Equal(t, "target1-changed", merged[0].Targets[0])
}
func TestDynMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
@ -153,13 +153,13 @@ func TestDynMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
Targets: endpoint.Targets{"target1"},
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
Targets: endpoint.Targets{"target2"},
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
@ -271,7 +271,7 @@ func TestDyn_cachePut(t *testing.T) {
c.Put("link", &endpoint.Endpoint{
DNSName: "name",
Target: "target",
Targets: endpoint.Targets{"target"},
RecordTTL: endpoint.TTL(10000),
RecordType: "A",
})
@ -287,7 +287,7 @@ func TestDyn_cachePutExpired(t *testing.T) {
c.Put("link", &endpoint.Endpoint{
DNSName: "name",
Target: "target",
Targets: endpoint.Targets{"target"},
RecordTTL: endpoint.TTL(0),
RecordType: "A",
})

View File

@ -18,6 +18,7 @@ package provider
import (
"fmt"
"sort"
"strings"
"github.com/linki/instrumented_http"
@ -191,13 +192,20 @@ func (p *GoogleProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
f := func(resp *dns.ResourceRecordSetsListResponse) error {
for _, r := range resp.Rrsets {
if !supportedRecordType(r.Type) {
continue
}
ep := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(r.Name, "."),
RecordType: r.Type,
Targets: make(endpoint.Targets, 0, len(r.Rrdatas)),
}
for _, rr := range r.Rrdatas {
// each page is processed sequentially, no need for a mutex here.
if supportedRecordType(r.Type) {
endpoints = append(endpoints, endpoint.NewEndpoint(r.Name, rr, r.Type))
}
ep.Targets = append(ep.Targets, strings.TrimSuffix(rr, "."))
}
sort.Sort(ep.Targets)
endpoints = append(endpoints, ep)
}
return nil
@ -347,9 +355,10 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
// TODO(linki): works around appending a trailing dot to TXT records. I think
// we should go back to storing DNS names with a trailing dot internally. This
// way we can use it has is here and trim it off if it exists when necessary.
target := ep.Target
targets := make([]string, len(ep.Targets))
copy(targets, []string(ep.Targets))
if ep.RecordType == endpoint.RecordTypeCNAME {
target = ensureTrailingDot(target)
targets[0] = ensureTrailingDot(targets[0])
}
// no annotation results in a Ttl of 0, default to 300 for backwards-compatability
@ -360,7 +369,7 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
return &dns.ResourceRecordSet{
Name: ensureTrailingDot(ep.DNSName),
Rrdatas: []string{target},
Rrdatas: targets,
Ttl: ttl,
Type: ep.RecordType,
}

View File

@ -257,7 +257,7 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
obj := ibclient.NewRecordA(
ibclient.RecordA{
Name: ep.DNSName,
Ipv4Addr: ep.Target,
Ipv4Addr: ep.Targets[0],
},
)
if getObject {
@ -275,7 +275,7 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
obj := ibclient.NewRecordCNAME(
ibclient.RecordCNAME{
Name: ep.DNSName,
Canonical: ep.Target,
Canonical: ep.Targets[0],
},
)
if getObject {
@ -292,13 +292,13 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool) (rec
var res []ibclient.RecordTXT
// The Infoblox API strips enclosing double quotes from TXT records lacking whitespace.
// Here we reconcile that fact by making this state match that reality.
if target, err2 := strconv.Unquote(ep.Target); err2 == nil && !strings.Contains(ep.Target, " ") {
ep.Target = target
if target, err2 := strconv.Unquote(ep.Targets[0]); err2 == nil && !strings.Contains(ep.Targets[0], " ") {
ep.Targets = endpoint.Targets{target}
}
obj := ibclient.NewRecordTXT(
ibclient.RecordTXT{
Name: ep.DNSName,
Text: ep.Target,
Text: ep.Targets[0],
},
)
if getObject {
@ -323,7 +323,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
"Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
zone,
)
continue
@ -333,7 +333,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
"Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
zone,
)
@ -343,7 +343,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
zone,
err,
)
@ -355,7 +355,7 @@ func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
"Failed to create %s record named '%s' to '%s' for DNS zone '%s': %v",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
zone,
err,
)
@ -378,7 +378,7 @@ func (p *InfobloxProvider) deleteRecords(deleted infobloxChangeMap) {
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
ep.RecordType,
ep.DNSName,
ep.Target,
ep.Targets,
zone,
err,
)

View File

@ -203,7 +203,7 @@ func convertToInMemoryRecord(endpoints []*endpoint.Endpoint) []*inMemoryRecord {
records = append(records, &inMemoryRecord{
Type: ep.RecordType,
Name: ep.DNSName,
Target: ep.Target,
Target: ep.Targets[0],
})
}
return records

View File

@ -186,16 +186,17 @@ func testInMemoryRecords(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "example.org",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{""},
},
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeCNAME,
},
},
@ -214,7 +215,7 @@ func testInMemoryRecords(t *testing.T) {
assert.EqualError(t, err, ErrZoneNotFound.Error())
} else {
require.NoError(t, err)
assert.True(t, testutils.SameEndpoints(ti.expected, records))
assert.True(t, testutils.SameEndpoints(ti.expected, records), "Endpoints not the same: Expected: %+v Records: %+v", ti.expected, records)
}
})
}
@ -314,7 +315,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -333,14 +334,14 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
},
@ -358,14 +359,14 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
},
@ -383,12 +384,12 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "foo.org",
Target: "4.4.4.4",
Targets: endpoint.Targets{"4.4.4.4"},
RecordType: endpoint.RecordTypeA,
},
},
@ -408,7 +409,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -416,7 +417,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Delete: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -433,12 +434,12 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -458,7 +459,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "new.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -478,7 +479,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Delete: []*endpoint.Endpoint{
{
DNSName: "new.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -497,7 +498,7 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Delete: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: endpoint.RecordTypeA,
},
},
@ -512,21 +513,21 @@ func testInMemoryValidateChangeBatch(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "foo.bar.new.org",
Target: "4.8.8.9",
Targets: endpoint.Targets{"4.8.8.9"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "4.8.8.4",
Targets: endpoint.Targets{"4.8.8.4"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: endpoint.RecordTypeA,
},
},
@ -608,7 +609,7 @@ func testInMemoryApplyChanges(t *testing.T) {
changes: &plan.Changes{
Create: []*endpoint.Endpoint{{
DNSName: "example.de",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
}},
UpdateNew: []*endpoint.Endpoint{},
@ -625,7 +626,7 @@ func testInMemoryApplyChanges(t *testing.T) {
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -633,7 +634,7 @@ func testInMemoryApplyChanges(t *testing.T) {
Delete: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},
@ -649,7 +650,7 @@ func testInMemoryApplyChanges(t *testing.T) {
Delete: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: endpoint.RecordTypeA,
},
},
@ -697,28 +698,28 @@ func testInMemoryApplyChanges(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "foo.bar.new.org",
Target: "4.8.8.9",
Targets: endpoint.Targets{"4.8.8.9"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "4.8.8.4",
Targets: endpoint.Targets{"4.8.8.4"},
RecordType: endpoint.RecordTypeA,
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "foo.bar.org",
Target: "5.5.5.5",
Targets: endpoint.Targets{"5.5.5.5"},
RecordType: endpoint.RecordTypeA,
},
},
Delete: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeA,
},
},

View File

@ -49,7 +49,7 @@ func testNoopRecords(t *testing.T) {
providerRecords := []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "example-lb.com",
Targets: endpoint.Targets{"example-lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
}
@ -71,19 +71,19 @@ func testNoopApplyChanges(t *testing.T) {
providerRecords := []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "old-lb.com",
Targets: endpoint.Targets{"old-lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
}
expectedUpdate := []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "new-example-lb.com",
Targets: endpoint.Targets{"new-example-lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "new-record.org",
Target: "new-lb.org",
Targets: endpoint.Targets{"new-lb.org"},
RecordType: endpoint.RecordTypeCNAME,
},
}
@ -98,7 +98,7 @@ func testNoopApplyChanges(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "lb.com",
Targets: endpoint.Targets{"lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
},
@ -110,21 +110,21 @@ func testNoopApplyChanges(t *testing.T) {
Create: []*endpoint.Endpoint{
{
DNSName: "new-record.org",
Target: "new-lb.org",
Targets: endpoint.Targets{"new-lb.org"},
RecordType: endpoint.RecordTypeCNAME,
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "new-example-lb.com",
Targets: endpoint.Targets{"new-example-lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "old-lb.com",
Targets: endpoint.Targets{"old-lb.com"},
RecordType: endpoint.RecordTypeCNAME,
},
},

View File

@ -66,7 +66,8 @@ func (im *TXTRegistry) Records() ([]*endpoint.Endpoint, error) {
endpoints = append(endpoints, record)
continue
}
labels, err := endpoint.NewLabelsFromString(record.Target)
// We simply assume that TXT records for the registry will always have only one target.
labels, err := endpoint.NewLabelsFromString(record.Targets[0])
if err == endpoint.ErrInvalidHeritage {
//if no heritage is found or it is invalid
//case when value of txt record cannot be identified

View File

@ -82,7 +82,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
expectedRecords := []*endpoint.Endpoint{
{
DNSName: "foo.test-zone.example.org",
Target: "foo.loadbalancer.com",
Targets: endpoint.Targets{"foo.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -90,7 +90,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
},
{
DNSName: "bar.test-zone.example.org",
Target: "my-domain.com",
Targets: endpoint.Targets{"my-domain.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "owner",
@ -98,7 +98,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
},
{
DNSName: "txt.bar.test-zone.example.org",
Target: "baz.test-zone.example.org",
Targets: endpoint.Targets{"baz.test-zone.example.org"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -106,7 +106,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
},
{
DNSName: "qux.test-zone.example.org",
Target: "random",
Targets: endpoint.Targets{"random"},
RecordType: endpoint.RecordTypeTXT,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -114,7 +114,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
},
{
DNSName: "tar.test-zone.example.org",
Target: "tar.loadbalancer.com",
Targets: endpoint.Targets{"tar.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "owner-2",
@ -122,7 +122,7 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
},
{
DNSName: "foobar.test-zone.example.org",
Target: "foobar.loadbalancer.com",
Targets: endpoint.Targets{"foobar.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -155,7 +155,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
expectedRecords := []*endpoint.Endpoint{
{
DNSName: "foo.test-zone.example.org",
Target: "foo.loadbalancer.com",
Targets: endpoint.Targets{"foo.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -163,7 +163,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
},
{
DNSName: "bar.test-zone.example.org",
Target: "my-domain.com",
Targets: endpoint.Targets{"my-domain.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -171,7 +171,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
},
{
DNSName: "txt.bar.test-zone.example.org",
Target: "baz.test-zone.example.org",
Targets: endpoint.Targets{"baz.test-zone.example.org"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "owner",
@ -180,7 +180,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
},
{
DNSName: "qux.test-zone.example.org",
Target: "random",
Targets: endpoint.Targets{"random"},
RecordType: endpoint.RecordTypeTXT,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -188,7 +188,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
},
{
DNSName: "tar.test-zone.example.org",
Target: "tar.loadbalancer.com",
Targets: endpoint.Targets{"tar.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "",
@ -196,7 +196,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
},
{
DNSName: "foobar.test-zone.example.org",
Target: "foobar.loadbalancer.com",
Targets: endpoint.Targets{"foobar.loadbalancer.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "owner",

View File

@ -43,7 +43,7 @@ func (ms *dedupSource) Endpoints() ([]*endpoint.Endpoint, error) {
}
for _, ep := range endpoints {
identifier := ep.DNSName + " / " + ep.Target
identifier := ep.DNSName + " / " + ep.Targets.String()
if _, ok := collected[identifier]; ok {
log.Debugf("Removing duplicate endpoint %s", ep)

View File

@ -40,53 +40,53 @@ func testDedupEndpoints(t *testing.T) {
{
"one endpoint returns one endpoint",
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
},
{
"two different endpoints return two endpoints",
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "4.5.6.7"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "4.5.6.7"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
},
},
{
"two endpoints with same dnsname and different targets return two endpoints",
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "4.5.6.7"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "4.5.6.7"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"4.5.6.7"}},
},
},
{
"two endpoints with different dnsname and same target return two endpoints",
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
},
{
"two endpoints with same dnsname and same target return one endpoint",
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
},
} {

View File

@ -60,7 +60,7 @@ func TestFakeEndpointsResolveToIPAddresses(t *testing.T) {
endpoints := generateTestEndpoints()
for _, e := range endpoints {
ip := net.ParseIP(e.Target)
ip := net.ParseIP(e.Targets[0])
if ip == nil {
t.Error(e)

View File

@ -19,6 +19,7 @@ package source
import (
"bytes"
"fmt"
"sort"
"strings"
"text/template"
@ -109,33 +110,32 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) {
endpoints = append(endpoints, ingEndpoints...)
}
for _, ep := range endpoints {
sort.Sort(ep.Targets)
}
return endpoints, nil
}
// get endpoints from optional "target" annotation
// Returns empty endpoints array if none are found.
func getEndpointsFromTargetAnnotation(ing *v1beta1.Ingress, hostname string) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
func getTargetsFromTargetAnnotation(ing *v1beta1.Ingress) endpoint.Targets {
var targets endpoint.Targets
// Get the desired hostname of the ingress from the annotation.
targetAnnotation, exists := ing.Annotations[targetAnnotationKey]
if exists {
ttl, err := getTTLFromAnnotations(ing.Annotations)
if err != nil {
log.Warn(err)
}
// splits the hostname annotation and removes the trailing periods
targetsList := strings.Split(strings.Replace(targetAnnotation, " ", "", -1), ",")
for _, targetHostname := range targetsList {
targetHostname = strings.TrimSuffix(targetHostname, ".")
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, targetHostname, suitableType(targetHostname), ttl))
targets = append(targets, targetHostname)
}
}
return endpoints
return targets
}
func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint
var buf bytes.Buffer
err := sc.fqdnTemplate.Execute(&buf, ing)
@ -145,26 +145,18 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin
hostname := buf.String()
endpoints = getEndpointsFromTargetAnnotation(ing, hostname)
if len(endpoints) != 0 {
return endpoints, nil
}
ttl, err := getTTLFromAnnotations(ing.Annotations)
if err != nil {
log.Warn(err)
}
for _, lb := range ing.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.IP, endpoint.RecordTypeA, ttl))
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
}
targets := getTargetsFromTargetAnnotation(ing)
if len(targets) == 0 {
targets = targetsFromIngressStatus(ing.Status)
}
return endpoints, nil
return endpointsForHostname(hostname, targets, ttl), nil
}
// filterByAnnotations filters a list of ingresses by a given annotation selector.
@ -208,32 +200,77 @@ func (sc *ingressSource) setResourceLabel(ingress v1beta1.Ingress, endpoints []*
func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
for _, rule := range ing.Spec.Rules {
if rule.Host == "" {
continue
}
annotationEndpoints := getEndpointsFromTargetAnnotation(ing, rule.Host)
if len(annotationEndpoints) != 0 {
endpoints = append(endpoints, annotationEndpoints...)
continue
}
ttl, err := getTTLFromAnnotations(ing.Annotations)
if err != nil {
log.Warn(err)
}
for _, lb := range ing.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rule.Host, lb.IP, endpoint.RecordTypeA, ttl))
targets := getTargetsFromTargetAnnotation(ing)
if len(targets) == 0 {
targets = targetsFromIngressStatus(ing.Status)
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rule.Host, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
for _, rule := range ing.Spec.Rules {
if rule.Host == "" {
continue
}
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...)
}
return endpoints
}
func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
var aTargets endpoint.Targets
var cnameTargets endpoint.Targets
for _, t := range targets {
switch suitableType(t) {
case endpoint.RecordTypeA:
aTargets = append(aTargets, t)
default:
cnameTargets = append(cnameTargets, t)
}
}
if len(aTargets) > 0 {
epA := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."),
Targets: aTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeA,
Labels: endpoint.NewLabels(),
}
endpoints = append(endpoints, epA)
}
if len(cnameTargets) > 0 {
epCNAME := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."),
Targets: cnameTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME,
Labels: endpoint.NewLabels(),
}
endpoints = append(endpoints, epCNAME)
}
return endpoints
}
func targetsFromIngressStatus(status v1beta1.IngressStatus) endpoint.Targets {
var targets endpoint.Targets
for _, lb := range status.LoadBalancer.Ingress {
if lb.IP != "" {
targets = append(targets, lb.IP)
}
if lb.Hostname != "" {
targets = append(targets, lb.Hostname)
}
}
return targets
}

View File

@ -134,7 +134,7 @@ func testEndpointsFromIngress(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Target: "lb.com",
Targets: endpoint.Targets{"lb.com"},
},
},
},
@ -147,7 +147,7 @@ func testEndpointsFromIngress(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
@ -161,19 +161,11 @@ func testEndpointsFromIngress(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "foo.bar",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8", "127.0.0.1"},
},
{
DNSName: "foo.bar",
Target: "127.0.0.1",
},
{
DNSName: "foo.bar",
Target: "elb.com",
},
{
DNSName: "foo.bar",
Target: "alb.com",
Targets: endpoint.Targets{"elb.com", "alb.com"},
},
},
},
@ -244,11 +236,11 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "new.org",
Target: "lb.com",
Targets: endpoint.Targets{"lb.com"},
},
},
},
@ -272,11 +264,11 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "new.org",
Target: "lb.com",
Targets: endpoint.Targets{"lb.com"},
},
},
},
@ -300,7 +292,7 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
@ -322,7 +314,7 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
@ -379,7 +371,7 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
@ -417,7 +409,7 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
@ -455,11 +447,11 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "fake1.ext-dns.test.com",
Target: "8.8.8.8",
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "fake1.ext-dns.test.com",
Target: "elb.com",
Targets: endpoint.Targets{"elb.com"},
},
},
fqdnTemplate: "{{.Name}}.ext-dns.test.com",
@ -516,17 +508,17 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "example2.org",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "example3.org",
Target: "1.2.3.4",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeA,
},
},
@ -559,12 +551,12 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordTTL: endpoint.TTL(6),
},
{
DNSName: "example2.org",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordTTL: endpoint.TTL(1),
},
},
@ -606,17 +598,17 @@ func testIngressEndpoints(t *testing.T) {
expected: []*endpoint.Endpoint{
{
DNSName: "fake1.ext-dns.test.com",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "fake2.ext-dns.test.com",
Target: "ingress-target.com",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "fake3.ext-dns.test.com",
Target: "1.2.3.4",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeA,
},
},

View File

@ -40,8 +40,8 @@ func testMultiSourceImplementsSource(t *testing.T) {
// testMultiSourceEndpoints tests merged endpoints from children are returned.
func testMultiSourceEndpoints(t *testing.T) {
foo := &endpoint.Endpoint{DNSName: "foo", Target: "8.8.8.8"}
bar := &endpoint.Endpoint{DNSName: "bar", Target: "8.8.4.4"}
foo := &endpoint.Endpoint{DNSName: "foo", Targets: endpoint.Targets{"8.8.8.8"}}
bar := &endpoint.Endpoint{DNSName: "bar", Targets: endpoint.Targets{"8.8.4.4"}}
for _, tc := range []struct {
title string

View File

@ -19,6 +19,7 @@ package source
import (
"bytes"
"fmt"
"sort"
"strings"
"text/template"
@ -32,6 +33,10 @@ import (
"github.com/kubernetes-incubator/external-dns/endpoint"
)
const (
defaultTargetsCapacity = 10
)
// serviceSource is an implementation of Source for Kubernetes service objects.
// It will find all services that are under our jurisdiction, i.e. annotated
// desired hostname and matching or no controller annotation. For each of the
@ -119,10 +124,14 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) {
endpoints = append(endpoints, svcEndpoints...)
}
for _, ep := range endpoints {
sort.Sort(ep.Targets)
}
return endpoints, nil
}
func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname string) []*endpoint.Endpoint {
func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname string) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
@ -138,6 +147,7 @@ func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname strin
if v.Spec.Hostname != "" {
headlessDomain = v.Spec.Hostname + "." + headlessDomain
}
log.Debugf("Generating matching endpoint %s with HostIP %s", headlessDomain, v.Status.HostIP)
// To reduce traffice on the DNS API only add record for running Pods. Good Idea?
if v.Status.Phase == v1.PodRunning {
@ -221,54 +231,82 @@ func (sc *serviceSource) setResourceLabel(service v1.Service, endpoints []*endpo
}
func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
hostname = strings.TrimSuffix(hostname, ".")
ttl, err := getTTLFromAnnotations(svc.Annotations)
if err != nil {
log.Warn(err)
}
epA := &endpoint.Endpoint{
RecordTTL: ttl,
RecordType: endpoint.RecordTypeA,
Labels: endpoint.NewLabels(),
Targets: make(endpoint.Targets, 0, defaultTargetsCapacity),
DNSName: hostname,
}
epCNAME := &endpoint.Endpoint{
RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME,
Labels: endpoint.NewLabels(),
Targets: make(endpoint.Targets, 0, defaultTargetsCapacity),
DNSName: hostname,
}
var endpoints []*endpoint.Endpoint
var targets endpoint.Targets
switch svc.Spec.Type {
case v1.ServiceTypeLoadBalancer:
endpoints = append(endpoints, extractLoadBalancerEndpoints(svc, hostname)...)
targets = append(targets, extractLoadBalancerTargets(svc)...)
case v1.ServiceTypeClusterIP:
if sc.publishInternal {
endpoints = append(endpoints, extractServiceIps(svc, hostname)...)
targets = append(targets, extractServiceIps(svc)...)
}
if svc.Spec.ClusterIP == v1.ClusterIPNone {
endpoints = append(endpoints, sc.extractHeadlessEndpoint(svc, hostname)...)
endpoints = append(endpoints, sc.extractHeadlessEndpoints(svc, hostname)...)
}
}
for _, t := range targets {
if suitableType(t) == endpoint.RecordTypeA {
epA.Targets = append(epA.Targets, t)
}
if suitableType(t) == endpoint.RecordTypeCNAME {
epCNAME.Targets = append(epCNAME.Targets, t)
}
}
if len(epA.Targets) > 0 {
endpoints = append(endpoints, epA)
}
if len(epCNAME.Targets) > 0 {
endpoints = append(endpoints, epCNAME)
}
return endpoints
}
func extractServiceIps(svc *v1.Service, hostname string) []*endpoint.Endpoint {
ttl, err := getTTLFromAnnotations(svc.Annotations)
if err != nil {
log.Warn(err)
}
func extractServiceIps(svc *v1.Service) endpoint.Targets {
if svc.Spec.ClusterIP == v1.ClusterIPNone {
log.Debugf("Unable to associate %s headless service with a Cluster IP", svc.Name)
return []*endpoint.Endpoint{}
return endpoint.Targets{}
}
return []*endpoint.Endpoint{endpoint.NewEndpointWithTTL(hostname, svc.Spec.ClusterIP, endpoint.RecordTypeA, ttl)}
return endpoint.Targets{svc.Spec.ClusterIP}
}
func extractLoadBalancerEndpoints(svc *v1.Service, hostname string) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
func extractLoadBalancerTargets(svc *v1.Service) endpoint.Targets {
var targets endpoint.Targets
ttl, err := getTTLFromAnnotations(svc.Annotations)
if err != nil {
log.Warn(err)
}
// Create a corresponding endpoint for each configured external entrypoint.
for _, lb := range svc.Status.LoadBalancer.Ingress {
if lb.IP != "" {
//TODO(ideahitme): consider retrieving record type from resource annotation instead of empty
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.IP, endpoint.RecordTypeA, ttl))
targets = append(targets, lb.IP)
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(hostname, lb.Hostname, endpoint.RecordTypeCNAME, ttl))
targets = append(targets, lb.Hostname)
}
}
return endpoints
return targets
}

View File

@ -191,7 +191,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -229,8 +229,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -250,8 +250,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -271,7 +271,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"lb.example.com"}, // Kubernetes omits the trailing dot
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "lb.example.com"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
},
false,
},
@ -291,8 +291,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "lb.example.com"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
},
false,
},
@ -313,7 +313,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -352,7 +352,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -390,7 +390,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -411,7 +411,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -470,7 +470,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -512,7 +512,7 @@ func testServiceSourceEndpoints(t *testing.T) {
false,
},
{
"multiple external entrypoints return multiple endpoints",
"multiple external entrypoints return a single endpoint with multiple targets",
"",
"",
"testing",
@ -527,8 +527,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4", "8.8.8.8"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "8.8.8.8"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}},
},
false,
},
@ -566,7 +565,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -588,8 +587,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "bar.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -607,8 +606,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4", "elb.com"},
[]*endpoint.Endpoint{
{DNSName: "foo.bar.example.com", Target: "1.2.3.4"},
{DNSName: "foo.bar.example.com", Target: "elb.com"},
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}},
},
false,
},
@ -628,8 +627,8 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4", "elb.com"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Target: "elb.com"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}},
},
false,
},
@ -649,7 +648,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "mate.example.org", Target: "1.2.3.4"},
{DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -685,7 +684,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
},
false,
},
@ -706,7 +705,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
},
false,
},
@ -727,7 +726,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(10)},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)},
},
false,
},
@ -748,7 +747,7 @@ func testServiceSourceEndpoints(t *testing.T) {
"",
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4", RecordTTL: endpoint.TTL(0)},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
},
false,
},
@ -846,7 +845,7 @@ func TestClusterIpServices(t *testing.T) {
"1.2.3.4",
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
},
false,
},
@ -864,7 +863,7 @@ func TestClusterIpServices(t *testing.T) {
"4.5.6.7",
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo.bar.example.com", Target: "4.5.6.7"},
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}},
},
false,
},
@ -988,8 +987,8 @@ func TestHeadlessServices(t *testing.T) {
[]string{"foo-0", "foo-1"},
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", Target: "1.1.1.1"},
{DNSName: "foo-1.service.example.org", Target: "1.1.1.1"},
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
},
false,
},
@ -1015,7 +1014,7 @@ func TestHeadlessServices(t *testing.T) {
[]string{"foo-0", "foo-1"},
[]v1.PodPhase{v1.PodRunning, v1.PodFailed},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", Target: "1.1.1.1"},
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
},
false,
},
@ -1041,8 +1040,8 @@ func TestHeadlessServices(t *testing.T) {
[]string{"", ""},
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", Target: "1.1.1.1"},
{DNSName: "service.example.org", Target: "1.1.1.1"},
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
},
false,
},

View File

@ -39,8 +39,8 @@ func validateEndpoint(t *testing.T, endpoint, expected *endpoint.Endpoint) {
t.Errorf("expected %s, got %s", expected.DNSName, endpoint.DNSName)
}
if endpoint.Target != expected.Target {
t.Errorf("expected %s, got %s", expected.Target, endpoint.Target)
if !endpoint.Targets.Same(expected.Targets) {
t.Errorf("expected %s, got %s", expected.Targets, endpoint.Targets)
}
if endpoint.RecordTTL != expected.RecordTTL {