From e3fa49f750b7054f3086d00a9fa893914778836e Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Sun, 2 Jul 2017 22:46:04 -0700 Subject: [PATCH] Support for multi-target domains This commit adds ability to have several endpoints for single domain name with CoreDNS by adding random to the DNS name and setting TargetStrip to 1 so that it will be stripped upon read. When TXT record is encountered, it is merged with the first available A record, if any available. Otherwise dedicated record is created. --- provider/coredns.go | 70 +++++++++++++++++++++++++++++----------- provider/coredns_test.go | 26 ++++++++++++--- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/provider/coredns.go b/provider/coredns.go index 9835da9f9c..ccbf536449 100644 --- a/provider/coredns.go +++ b/provider/coredns.go @@ -19,8 +19,11 @@ package provider import ( "container/list" "encoding/json" + "fmt" + "math/rand" "net" "strings" + "time" log "github.com/Sirupsen/logrus" etcd "github.com/coreos/etcd/client" @@ -30,6 +33,10 @@ import ( "github.com/kubernetes-incubator/external-dns/plan" ) +func init() { + rand.Seed(time.Now().UnixNano()) +} + // skyDNSClient is an interface to work with SkyDNS service records in etcd type skyDNSClient interface { GetServices(prefix string) ([]*Service, error) @@ -163,10 +170,11 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { for _, service := range services { domains := strings.Split(strings.TrimPrefix(service.Key, "/skydns/"), "/") reverse(domains) - dnsName := strings.Join(domains, ".") + dnsName := strings.Join(domains[service.TargetStrip:], ".") if !p.domainFilter.Match(dnsName) { continue } + prefix := strings.Join(domains[:service.TargetStrip], ".") if service.Host != "" { ep := endpoint.NewEndpoint( dnsName, @@ -174,6 +182,7 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { guessRecordType(service.Host), ) ep.Labels["originalText"] = service.Text + ep.Labels["prefix"] = prefix result = append(result, ep) } if service.Text != "" { @@ -182,6 +191,7 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) { service.Text, endpoint.RecordTypeTXT, ) + ep.Labels["prefix"] = prefix result = append(result, ep) } } @@ -202,27 +212,47 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", dnsName) continue } - service := Service{} + var services []Service for _, ep := range group { - switch ep.RecordType { - case endpoint.RecordTypeA: - service.Host = ep.Target - case endpoint.RecordTypeCNAME: - if service.Host == "" { - service.Host = ep.Target - } - case endpoint.RecordTypeTXT: - service.Text = ep.Target - default: - log.Error("Unsupported record type", ep.RecordType) + if ep.RecordType == endpoint.RecordTypeTXT { + continue + } + prefix := ep.Labels["prefix"] + if prefix == "" { + prefix = fmt.Sprintf("%08x", rand.Int31()) + } + service := Service{ + Host: ep.Target, + Text: ep.Labels["originalText"], + Key: etcdKeyFor(prefix + "." + dnsName), + TargetStrip: strings.Count(prefix, ".") + 1, + } + services = append(services, service) + } + index := 0 + for _, ep := range group { + if ep.RecordType != "TXT" { continue } - if service.Text == "" { - service.Text = ep.Labels["originalText"] + if index >= len(services) { + prefix := ep.Labels["prefix"] + if prefix == "" { + prefix = fmt.Sprintf("%08x", rand.Int31()) + } + services = append(services, Service{ + Key: etcdKeyFor(prefix + "." + dnsName), + TargetStrip: strings.Count(prefix, ".") + 1, + }) } + services[index].Text = ep.Target + index++ } - if service.Host != "" || service.Text != "" { - service.Key = etcdKeyFor(dnsName) + + for i := index; index > 0 && i < len(services); i++ { + services[i].Text = "" + } + + for _, service := range services { log.Infof("Add/set key %s to Host=%s, Text=%s", service.Key, service.Host, service.Text) if !p.dryRun { err := p.client.SaveService(&service) @@ -234,7 +264,11 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error { } for _, ep := range changes.Delete { - key := etcdKeyFor(ep.DNSName) + dnsName := ep.DNSName + if ep.Labels["prefix"] != "" { + dnsName = ep.Labels["prefix"] + "." + dnsName + } + key := etcdKeyFor(dnsName) log.Infof("Delete key %s", key) if !p.dryRun { err := p.client.DeleteService(key) diff --git a/provider/coredns_test.go b/provider/coredns_test.go index 108f65504c..9ee6b07b50 100644 --- a/provider/coredns_test.go +++ b/provider/coredns_test.go @@ -241,9 +241,11 @@ func TestCoreDNSApplyChanges(t *testing.T) { Create: []*endpoint.Endpoint{ endpoint.NewEndpoint("domain3.local", "7.7.7.7", endpoint.RecordTypeA), }, - UpdateNew: []*endpoint.Endpoint{updatedEp}, + UpdateNew: []*endpoint.Endpoint{ + endpoint.NewEndpoint("domain1.local", "6.6.6.6", "A"), + }, } - coredns.ApplyChanges(changes2) + applyServiceChanges(coredns, changes2) expectedServices2 := map[string]*Service{ "/skydns/local/domain1": {Host: "6.6.6.6", Text: "string1"}, @@ -260,7 +262,7 @@ func TestCoreDNSApplyChanges(t *testing.T) { }, } - coredns.ApplyChanges(changes3) + applyServiceChanges(coredns, changes3) expectedServices3 := map[string]*Service{ "/skydns/local/domain2": {Host: "site.local"}, @@ -268,12 +270,28 @@ func TestCoreDNSApplyChanges(t *testing.T) { validateServices(client.services, expectedServices3, t, 3) } +func applyServiceChanges(provider coreDNSProvider, changes *plan.Changes) { + records, _ := provider.Records() + for _, col := range [][]*endpoint.Endpoint{changes.Create, changes.UpdateNew, changes.Delete} { + for _, record := range col { + for _, existingRecord := range records { + if existingRecord.DNSName == record.DNSName && existingRecord.RecordType == record.RecordType { + record.MergeLabels(existingRecord.Labels) + } + } + } + } + provider.ApplyChanges(changes) +} + func validateServices(services, expectedServices map[string]*Service, t *testing.T, step int) { if len(services) != len(expectedServices) { t.Errorf("wrong number of records on step %d: %d != %d", step, len(services), len(expectedServices)) } for key, value := range services { - expectedService := expectedServices[key] + keyParts := strings.Split(key, "/") + expectedKey := strings.Join(keyParts[:len(keyParts)-value.TargetStrip], "/") + expectedService := expectedServices[expectedKey] if expectedService == nil { t.Errorf("unexpected service %s", key) continue