aboutsummaryrefslogtreecommitdiff
path: root/plugin/kubernetes
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/kubernetes')
-rw-r--r--plugin/kubernetes/README.md13
-rw-r--r--plugin/kubernetes/handler.go2
-rw-r--r--plugin/kubernetes/handler_test.go3
-rw-r--r--plugin/kubernetes/kubernetes.go7
-rw-r--r--plugin/kubernetes/setup.go10
-rw-r--r--plugin/kubernetes/setup_transfer_test.go47
-rw-r--r--plugin/kubernetes/xfr.go252
-rw-r--r--plugin/kubernetes/xfr_test.go255
8 files changed, 188 insertions, 401 deletions
diff --git a/plugin/kubernetes/README.md b/plugin/kubernetes/README.md
index 654e6526f..007e4bd72 100644
--- a/plugin/kubernetes/README.md
+++ b/plugin/kubernetes/README.md
@@ -40,7 +40,6 @@ kubernetes [ZONES...] {
endpoint_pod_names
ttl TTL
noendpoints
- transfer to ADDRESS...
fallthrough [ZONES...]
ignore empty_service
}
@@ -90,11 +89,6 @@ kubernetes [ZONES...] {
0 seconds, and the maximum is capped at 3600 seconds. Setting TTL to 0 will prevent records from being cached.
* `noendpoints` will turn off the serving of endpoint records by disabling the watch on endpoints.
All endpoint queries and headless service queries will result in an NXDOMAIN.
-* `transfer` enables zone transfers. It may be specified multiples times. `To` signals the direction
- (only `to` is allowed). **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as
- plain addresses. The special wildcard `*` means: the entire internet.
- Sending DNS notifies is not supported.
- [Deprecated](https://github.com/kubernetes/dns/blob/master/docs/specification.md#26---deprecated-records) pod records in the subdomain `pod.cluster.local` are not transferred.
* `fallthrough` **[ZONES...]** If a query for a record in the zones for which the plugin is authoritative
results in NXDOMAIN, normally that is what the response will be. However, if you specify this option,
the query will instead be passed on down the plugin chain, which can include another plugin to handle
@@ -105,6 +99,8 @@ kubernetes [ZONES...] {
This allows the querying pod to continue searching for the service in the search path.
The search path could, for example, include another Kubernetes cluster.
+Enabling zone transfer is done by using the *transfer* plugin.
+
## Ready
This plugin reports readiness to the ready plugin. This will happen after it has synced to the
@@ -238,3 +234,8 @@ If monitoring is enabled (via the *prometheus* plugin) then the following metric
## Bugs
The duration metric only supports the "headless\_with\_selector" service currently.
+
+## Also See
+
+See the *autopath* plugin to enable search path optimizations. And use the *transfer* plugin to
+enable outgoing zone transfers.
diff --git a/plugin/kubernetes/handler.go b/plugin/kubernetes/handler.go
index 78761d303..57e9ac528 100644
--- a/plugin/kubernetes/handler.go
+++ b/plugin/kubernetes/handler.go
@@ -28,8 +28,6 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
)
switch state.QType() {
- case dns.TypeAXFR, dns.TypeIXFR:
- k.Transfer(ctx, state)
case dns.TypeA:
records, err = plugin.A(ctx, &k, zone, state, nil, plugin.Options{})
case dns.TypeAAAA:
diff --git a/plugin/kubernetes/handler_test.go b/plugin/kubernetes/handler_test.go
index 302136837..12ebc2765 100644
--- a/plugin/kubernetes/handler_test.go
+++ b/plugin/kubernetes/handler_test.go
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"testing"
- "time"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
@@ -525,7 +524,7 @@ func (APIConnServeTest) Run() {}
func (APIConnServeTest) Stop() error { return nil }
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
-func (APIConnServeTest) Modified() int64 { return time.Now().Unix() }
+func (APIConnServeTest) Modified() int64 { return int64(3) }
func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
if ip != "10.240.0.1" {
diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go
index 7805bff3d..83b4b02d1 100644
--- a/plugin/kubernetes/kubernetes.go
+++ b/plugin/kubernetes/kubernetes.go
@@ -46,7 +46,6 @@ type Kubernetes struct {
primaryZoneIndex int
localIPs []net.IP
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
- TransferTo []string
}
// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
@@ -495,6 +494,12 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
return services, err
}
+// Serial return the SOA serial.
+func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) }
+
+// MinTTL returns the minimal TTL.
+func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl }
+
// match checks if a and b are equal taking wildcards into account.
func match(a, b string) bool {
if wildcard(a) {
diff --git a/plugin/kubernetes/setup.go b/plugin/kubernetes/setup.go
index a412f57f5..03bbc6330 100644
--- a/plugin/kubernetes/setup.go
+++ b/plugin/kubernetes/setup.go
@@ -14,7 +14,6 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
clog "github.com/coredns/coredns/plugin/pkg/log"
- "github.com/coredns/coredns/plugin/pkg/parse"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/miekg/dns"
@@ -235,15 +234,6 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
return nil, c.Errf("ttl must be in range [0, 3600]: %d", t)
}
k8s.ttl = uint32(t)
- case "transfer":
- tos, froms, err := parse.Transfer(c, false)
- if err != nil {
- return nil, err
- }
- if len(froms) != 0 {
- return nil, c.Errf("transfer from is not supported with this plugin")
- }
- k8s.TransferTo = tos
case "noendpoints":
if len(c.RemainingArgs()) != 0 {
return nil, c.ArgErr()
diff --git a/plugin/kubernetes/setup_transfer_test.go b/plugin/kubernetes/setup_transfer_test.go
deleted file mode 100644
index 57d665821..000000000
--- a/plugin/kubernetes/setup_transfer_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package kubernetes
-
-import (
- "testing"
-
- "github.com/coredns/caddy"
-)
-
-func TestKubernetesParseTransfer(t *testing.T) {
- tests := []struct {
- input string // Corefile data as string
- expected string
- shouldErr bool
- }{
- {`kubernetes cluster.local {
- transfer to 1.2.3.4
- }`, "1.2.3.4:53", false},
- {`kubernetes cluster.local {
- transfer to 1.2.3.4:53
- }`, "1.2.3.4:53", false},
- {`kubernetes cluster.local {
- transfer to *
- }`, "*", false},
- {`kubernetes cluster.local {
- transfer
- }`, "", true},
- }
-
- for i, tc := range tests {
- c := caddy.NewTestController("dns", tc.input)
- k, err := kubernetesParse(c)
- if err != nil && !tc.shouldErr {
- t.Fatalf("Test %d: Expected no error, got %q", i, err)
- }
- if err == nil && tc.shouldErr {
- t.Fatalf("Test %d: Expected error, got none", i)
- }
- if err != nil && tc.shouldErr {
- // input should error
- continue
- }
-
- if k.TransferTo[0] != tc.expected {
- t.Errorf("Test %d: Expected Transfer To to be %s, got %s", i, tc.expected, k.TransferTo[0])
- }
- }
-}
diff --git a/plugin/kubernetes/xfr.go b/plugin/kubernetes/xfr.go
index a3a0d4a4a..0392f0252 100644
--- a/plugin/kubernetes/xfr.go
+++ b/plugin/kubernetes/xfr.go
@@ -4,207 +4,151 @@ import (
"context"
"math"
"net"
+ "sort"
"strings"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
+ "github.com/coredns/coredns/plugin/transfer"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
api "k8s.io/api/core/v1"
)
-const transferLength = 2000
-
-// Serial implements the Transferer interface.
-func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) }
-
-// MinTTL implements the Transferer interface.
-func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl }
-
-// Transfer implements the Transferer interface.
-func (k *Kubernetes) Transfer(ctx context.Context, state request.Request) (int, error) {
-
- if !k.transferAllowed(state) {
- return dns.RcodeRefused, nil
- }
-
- // Get all services.
- rrs := make(chan dns.RR)
- go k.transfer(rrs, state.Zone)
-
- records := []dns.RR{}
- for r := range rrs {
- records = append(records, r)
- }
-
- if len(records) == 0 {
- return dns.RcodeServerFailure, nil
- }
-
- ch := make(chan *dns.Envelope)
- tr := new(dns.Transfer)
-
- soa, err := plugin.SOA(ctx, k, state.Zone, state, plugin.Options{})
+// Transfer implements the transfer.Transfer interface.
+func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) {
+ // state is not used here, hence the empty request.Request{]
+ soa, err := plugin.SOA(context.TODO(), k, zone, request.Request{}, plugin.Options{})
if err != nil {
- return dns.RcodeServerFailure, nil
+ return nil, transfer.ErrNotAuthoritative
}
- records = append(soa, records...)
- records = append(records, soa...)
- go func(ch chan *dns.Envelope) {
- j, l := 0, 0
- log.Infof("Outgoing transfer of %d records of zone %s to %s started", len(records), state.Zone, state.IP())
- for i, r := range records {
- l += dns.Len(r)
- if l > transferLength {
- ch <- &dns.Envelope{RR: records[j:i]}
- l = 0
- j = i
- }
- }
- if j < len(records) {
- ch <- &dns.Envelope{RR: records[j:]}
- }
- close(ch)
- }(ch)
+ ch := make(chan []dns.RR)
- tr.Out(state.W, state.Req, ch)
- // Defer closing to the client
- state.W.Hijack()
- return dns.RcodeSuccess, nil
-}
+ zonePath := msg.Path(zone, "coredns")
+ serviceList := k.APIConn.ServiceList()
-// transferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
-// Note: This is copied from zone.transferAllowed, but should eventually be factored into a common transfer pkg.
-func (k *Kubernetes) transferAllowed(state request.Request) bool {
- for _, t := range k.TransferTo {
- if t == "*" {
- return true
- }
- // If remote IP matches we accept.
- remote := state.IP()
- to, _, err := net.SplitHostPort(t)
- if err != nil {
- continue
+ go func() {
+ // ixfr fallback
+ if serial != 0 && soa[0].(*dns.SOA).Serial == serial {
+ ch <- soa
+ close(ch)
+ return
}
- if to == remote {
- return true
- }
- }
- return false
-}
+ ch <- soa
-func (k *Kubernetes) transfer(c chan dns.RR, zone string) {
+ sort.Slice(serviceList, func(i, j int) bool {
+ return serviceList[i].Name < serviceList[j].Name
+ })
- defer close(c)
+ for _, svc := range serviceList {
+ if !k.namespaceExposed(svc.Namespace) {
+ continue
+ }
+ svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name}
+ switch svc.Type {
- zonePath := msg.Path(zone, "coredns")
- serviceList := k.APIConn.ServiceList()
- for _, svc := range serviceList {
- if !k.namespaceExposed(svc.Namespace) {
- continue
- }
- svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name}
- switch svc.Type {
- case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
- clusterIP := net.ParseIP(svc.ClusterIP)
- if clusterIP != nil {
- s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl}
- s.Key = strings.Join(svcBase, "/")
-
- // Change host from IP to Name for SRV records
- host := emitAddressRecord(c, s)
-
- for _, p := range svc.Ports {
- s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
+ case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
+ clusterIP := net.ParseIP(svc.ClusterIP)
+ if clusterIP != nil {
+ s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl}
s.Key = strings.Join(svcBase, "/")
- // Need to generate this to handle use cases for peer-finder
- // ref: https://github.com/coredns/coredns/pull/823
- c <- s.NewSRV(msg.Domain(s.Key), 100)
+ // Change host from IP to Name for SRV records
+ host := emitAddressRecord(ch, s)
- // As per spec unnamed ports do not have a srv record
- // https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
- if p.Name == "" {
- continue
- }
+ for _, p := range svc.Ports {
+ s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
+ s.Key = strings.Join(svcBase, "/")
- s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
+ // Need to generate this to handle use cases for peer-finder
+ // ref: https://github.com/coredns/coredns/pull/823
+ ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), 100)}
- c <- s.NewSRV(msg.Domain(s.Key), 100)
- }
+ // As per spec unnamed ports do not have a srv record
+ // https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
+ if p.Name == "" {
+ continue
+ }
- // Skip endpoint discovery if clusterIP is defined
- continue
- }
+ s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
- endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
+ ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), 100)}
+ }
- for _, ep := range endpointsList {
- if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
+ // Skip endpoint discovery if clusterIP is defined
continue
}
- for _, eps := range ep.Subsets {
- srvWeight := calcSRVWeight(len(eps.Addresses))
- for _, addr := range eps.Addresses {
- s := msg.Service{Host: addr.IP, TTL: k.ttl}
- s.Key = strings.Join(svcBase, "/")
- // We don't need to change the msg.Service host from IP to Name yet
- // so disregard the return value here
- emitAddressRecord(c, s)
-
- s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/")
- // Change host from IP to Name for SRV records
- host := emitAddressRecord(c, s)
- s.Host = host
-
- for _, p := range eps.Ports {
- // As per spec unnamed ports do not have a srv record
- // https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
- if p.Name == "" {
- continue
- }
+ endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
- s.Port = int(p.Port)
+ for _, ep := range endpointsList {
+ if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
+ continue
+ }
- s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
- c <- s.NewSRV(msg.Domain(s.Key), srvWeight)
+ for _, eps := range ep.Subsets {
+ srvWeight := calcSRVWeight(len(eps.Addresses))
+ for _, addr := range eps.Addresses {
+ s := msg.Service{Host: addr.IP, TTL: k.ttl}
+ s.Key = strings.Join(svcBase, "/")
+ // We don't need to change the msg.Service host from IP to Name yet
+ // so disregard the return value here
+ emitAddressRecord(ch, s)
+
+ s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/")
+ // Change host from IP to Name for SRV records
+ host := emitAddressRecord(ch, s)
+ s.Host = host
+
+ for _, p := range eps.Ports {
+ // As per spec unnamed ports do not have a srv record
+ // https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
+ if p.Name == "" {
+ continue
+ }
+
+ s.Port = int(p.Port)
+
+ s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
+ ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), srvWeight)}
+ }
}
}
}
- }
- case api.ServiceTypeExternalName:
+ case api.ServiceTypeExternalName:
- s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl}
- if t, _ := s.HostType(); t == dns.TypeCNAME {
- c <- s.NewCNAME(msg.Domain(s.Key), s.Host)
+ s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl}
+ if t, _ := s.HostType(); t == dns.TypeCNAME {
+ ch <- []dns.RR{s.NewCNAME(msg.Domain(s.Key), s.Host)}
+ }
}
}
- }
+ ch <- soa
+ close(ch)
+ }()
+ return ch, nil
}
-// emitAddressRecord generates a new A or AAAA record based on the msg.Service and writes it to
-// a channel.
+// emitAddressRecord generates a new A or AAAA record based on the msg.Service and writes it to a channel.
// emitAddressRecord returns the host name from the generated record.
-func emitAddressRecord(c chan dns.RR, message msg.Service) string {
- ip := net.ParseIP(message.Host)
- var host string
- dnsType, _ := message.HostType()
+func emitAddressRecord(c chan<- []dns.RR, s msg.Service) string {
+ ip := net.ParseIP(s.Host)
+ dnsType, _ := s.HostType()
switch dnsType {
case dns.TypeA:
- arec := message.NewA(msg.Domain(message.Key), ip)
- host = arec.Hdr.Name
- c <- arec
+ r := s.NewA(msg.Domain(s.Key), ip)
+ c <- []dns.RR{r}
+ return r.Hdr.Name
case dns.TypeAAAA:
- arec := message.NewAAAA(msg.Domain(message.Key), ip)
- host = arec.Hdr.Name
- c <- arec
+ r := s.NewAAAA(msg.Domain(s.Key), ip)
+ c <- []dns.RR{r}
+ return r.Hdr.Name
}
- return host
+ return ""
}
// calcSrvWeight borrows the logic implemented in plugin.SRV for dynamically
diff --git a/plugin/kubernetes/xfr_test.go b/plugin/kubernetes/xfr_test.go
index 1ada4f7aa..b5f13ad6e 100644
--- a/plugin/kubernetes/xfr_test.go
+++ b/plugin/kubernetes/xfr_test.go
@@ -1,229 +1,126 @@
package kubernetes
import (
- "context"
"strings"
"testing"
- "github.com/coredns/coredns/plugin/kubernetes/object"
- "github.com/coredns/coredns/plugin/pkg/dnstest"
- "github.com/coredns/coredns/plugin/test"
-
"github.com/miekg/dns"
)
-func TestKubernetesXFR(t *testing.T) {
+func TestKubernetesAXFR(t *testing.T) {
k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{}
- k.TransferTo = []string{"10.240.0.1:53"}
k.Namespaces = map[string]struct{}{"testns": {}}
- ctx := context.TODO()
- w := dnstest.NewMultiRecorder(&test.ResponseWriter{})
dnsmsg := &dns.Msg{}
dnsmsg.SetAxfr(k.Zones[0])
- _, err := k.ServeDNS(ctx, w, dnsmsg)
+ ch, err := k.Transfer(k.Zones[0], 0)
if err != nil {
t.Error(err)
}
+ validateAXFR(t, ch)
+}
- if len(w.Msgs) == 0 {
- t.Logf("%+v\n", w)
- t.Fatal("Did not get back a zone response")
- }
-
- if len(w.Msgs[0].Answer) == 0 {
- t.Logf("%+v\n", w)
- t.Fatal("Did not get back an answer")
- }
-
- // Ensure xfr starts with SOA
- if w.Msgs[0].Answer[0].Header().Rrtype != dns.TypeSOA {
- t.Error("Invalid XFR, does not start with SOA record")
- }
-
- // Ensure xfr starts with SOA
- // Last message is empty, so we need to go back one further
- if w.Msgs[len(w.Msgs)-2].Answer[len(w.Msgs[len(w.Msgs)-2].Answer)-1].Header().Rrtype != dns.TypeSOA {
- t.Error("Invalid XFR, does not end with SOA record")
- }
-
- testRRs := []dns.RR{}
- for _, tc := range dnsTestCases {
- if tc.Rcode != dns.RcodeSuccess {
- continue
- }
-
- for _, ans := range tc.Answer {
- // Exclude wildcard searches
- if strings.Contains(ans.Header().Name, "*") {
- continue
- }
-
- // Exclude TXT records
- if ans.Header().Rrtype == dns.TypeTXT {
- continue
- }
- testRRs = append(testRRs, ans)
- }
- }
-
- gotRRs := []dns.RR{}
- for _, resp := range w.Msgs {
- for _, ans := range resp.Answer {
- // Skip SOA records since these
- // test cases do not exist
- if ans.Header().Rrtype == dns.TypeSOA {
- continue
- }
-
- gotRRs = append(gotRRs, ans)
- }
-
- }
+func TestKubernetesIXFRFallback(t *testing.T) {
+ k := New([]string{"cluster.local."})
+ k.APIConn = &APIConnServeTest{}
+ k.Namespaces = map[string]struct{}{"testns": {}}
- diff := difference(testRRs, gotRRs)
- if len(diff) != 0 {
- t.Errorf("Got back %d records that do not exist in test cases, should be 0:", len(diff))
- for _, rec := range diff {
- t.Errorf("%+v", rec)
- }
- }
+ dnsmsg := &dns.Msg{}
+ dnsmsg.SetAxfr(k.Zones[0])
- diff = difference(gotRRs, testRRs)
- if len(diff) != 0 {
- t.Errorf("Found %d records we're missing, should be 0:", len(diff))
- for _, rec := range diff {
- t.Errorf("%+v", rec)
- }
+ ch, err := k.Transfer(k.Zones[0], 1)
+ if err != nil {
+ t.Error(err)
}
+ validateAXFR(t, ch)
}
-func TestKubernetesXFRNotAllowed(t *testing.T) {
+func TestKubernetesIXFRCurrent(t *testing.T) {
k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{}
- k.TransferTo = []string{"1.2.3.4:53"}
k.Namespaces = map[string]struct{}{"testns": {}}
- ctx := context.TODO()
- w := dnstest.NewMultiRecorder(&test.ResponseWriter{})
dnsmsg := &dns.Msg{}
dnsmsg.SetAxfr(k.Zones[0])
- _, err := k.ServeDNS(ctx, w, dnsmsg)
+ ch, err := k.Transfer(k.Zones[0], 3)
if err != nil {
t.Error(err)
}
- if len(w.Msgs) == 0 {
- t.Logf("%+v\n", w)
- t.Fatal("Did not get back a zone response")
+ var gotRRs []dns.RR
+ for rrs := range ch {
+ gotRRs = append(gotRRs, rrs...)
}
- if len(w.Msgs[0].Answer) != 0 {
- t.Logf("%+v\n", w)
- t.Fatal("Got an answer, should not have")
+ // ensure only one record is returned
+ if len(gotRRs) > 1 {
+ t.Errorf("Expected only one answer, got %d", len(gotRRs))
}
-}
-// difference shows what we're missing when comparing two RR slices
-func difference(testRRs []dns.RR, gotRRs []dns.RR) []dns.RR {
- expectedRRs := map[string]struct{}{}
- for _, rr := range testRRs {
- expectedRRs[rr.String()] = struct{}{}
+ // Ensure first record is a SOA
+ if gotRRs[0].Header().Rrtype != dns.TypeSOA {
+ t.Error("Invalid transfer response, does not start with SOA record")
}
-
- foundRRs := []dns.RR{}
- for _, rr := range gotRRs {
- if _, ok := expectedRRs[rr.String()]; !ok {
- foundRRs = append(foundRRs, rr)
- }
- }
- return foundRRs
}
-func TestEndpointsEquivalent(t *testing.T) {
- epA := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- }},
- }
- epB := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- }},
+func validateAXFR(t *testing.T, ch <-chan []dns.RR) {
+ xfr := []dns.RR{}
+ for rrs := range ch {
+ xfr = append(xfr, rrs...)
}
- epC := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
- }},
- }
- epD := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
- },
- {
- Addresses: []object.EndpointAddress{{IP: "1.2.2.2", Hostname: "foofoo"}},
- }},
- }
- epE := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}, {IP: "1.1.1.1"}},
- }},
- }
- epF := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foofoo"}},
- }},
- }
- epG := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "TCP"}},
- }},
- }
- epH := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- Ports: []object.EndpointPort{{Name: "newportname", Port: 80, Protocol: "TCP"}},
- }},
- }
- epI := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- Ports: []object.EndpointPort{{Name: "http", Port: 8080, Protocol: "TCP"}},
- }},
- }
- epJ := object.Endpoints{
- Subsets: []object.EndpointSubset{{
- Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
- Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "UDP"}},
- }},
+ if xfr[0].Header().Rrtype != dns.TypeSOA {
+ t.Error("Invalid transfer response, does not start with SOA record")
}
- tests := []struct {
- equiv bool
- a *object.Endpoints
- b *object.Endpoints
- }{
- {true, &epA, &epB},
- {false, &epA, &epC},
- {false, &epA, &epD},
- {false, &epA, &epE},
- {false, &epA, &epF},
- {false, &epF, &epG},
- {false, &epG, &epH},
- {false, &epG, &epI},
- {false, &epG, &epJ},
+ zp := dns.NewZoneParser(strings.NewReader(expectedZone), "", "")
+ i := 0
+ for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
+ if !dns.IsDuplicate(rr, xfr[i]) {
+ t.Fatalf("Record %d, expected\n%v\n, got\n%v", i, rr, xfr[i])
+ }
+ i++
}
- for i, tc := range tests {
- if tc.equiv && !endpointsEquivalent(tc.a, tc.b) {
- t.Errorf("Test %d: expected endpoints to be equivalent and they are not.", i)
- }
- if !tc.equiv && endpointsEquivalent(tc.a, tc.b) {
- t.Errorf("Test %d: expected endpoints to be seen as different but they were not.", i)
- }
+ if err := zp.Err(); err != nil {
+ t.Fatal(err)
}
}
+
+const expectedZone = `
+cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
+external.testns.svc.cluster.local. 5 IN CNAME ext.interwebs.test.
+external-to-service.testns.svc.cluster.local. 5 IN CNAME svc1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.2
+172-0-0-2.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.2
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 172-0-0-2.hdls1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.3
+172-0-0-3.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.3
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 172-0-0-3.hdls1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.4
+dup-name.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.4
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 dup-name.hdls1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5
+dup-name.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 dup-name.hdls1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::1
+5678-abcd--1.hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::1
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 5678-abcd--1.hdls1.testns.svc.cluster.local.
+hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::2
+5678-abcd--2.hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::2
+_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 5678-abcd--2.hdls1.testns.svc.cluster.local.
+hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20
+172-0-0-20.hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20
+svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1
+svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
+_http._tcp.svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
+svc6.testns.svc.cluster.local. 5 IN AAAA 1234:abcd::1
+svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.
+_http._tcp.svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.
+svcempty.testns.svc.cluster.local. 5 IN A 10.0.0.1
+svcempty.testns.svc.cluster.local. 5 IN SRV 0 100 80 svcempty.testns.svc.cluster.local.
+_http._tcp.svcempty.testns.svc.cluster.local. 5 IN SRV 0 100 80 svcempty.testns.svc.cluster.local.
+cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
+`