Revert "Implement notifies for transfer plugin (#3972)" (#3995)

This reverts commit 68f1dd5ddf.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang
2020-07-08 09:00:26 -07:00
committed by GitHub
parent 68f1dd5ddf
commit 614d08cba2
42 changed files with 988 additions and 707 deletions

View File

@@ -40,6 +40,7 @@ kubernetes [ZONES...] {
endpoint_pod_names
ttl TTL
noendpoints
transfer to ADDRESS...
fallthrough [ZONES...]
ignore empty_service
}
@@ -89,6 +90,11 @@ 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
@@ -99,8 +105,6 @@ 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
@@ -234,8 +238,3 @@ 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.

View File

@@ -28,6 +28,8 @@ 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:

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"testing"
"time"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
@@ -524,7 +525,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 int64(3) }
func (APIConnServeTest) Modified() int64 { return time.Now().Unix() }
func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
if ip != "10.240.0.1" {

View File

@@ -46,6 +46,7 @@ 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
@@ -494,12 +495,6 @@ 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) {

View File

@@ -14,6 +14,7 @@ import (
"github.com/coredns/coredns/plugin/metrics"
"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/caddyserver/caddy"
@@ -240,6 +241,15 @@ 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()

View File

@@ -0,0 +1,47 @@
package kubernetes
import (
"testing"
"github.com/caddyserver/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])
}
}
}

View File

@@ -4,151 +4,207 @@ 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"
)
// 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 nil, transfer.ErrNotAuthoritative
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
}
ch := make(chan []dns.RR)
// 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{})
if err != nil {
return dns.RcodeServerFailure, nil
}
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)
tr.Out(state.W, state.Req, ch)
// Defer closing to the client
state.W.Hijack()
return dns.RcodeSuccess, nil
}
// 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
}
if to == remote {
return true
}
}
return false
}
func (k *Kubernetes) transfer(c chan dns.RR, zone string) {
defer close(c)
zonePath := msg.Path(zone, "coredns")
serviceList := k.APIConn.ServiceList()
go func() {
// ixfr fallback
if serial != 0 && soa[0].(*dns.SOA).Serial == serial {
ch <- soa
close(ch)
return
for _, svc := range serviceList {
if !k.namespaceExposed(svc.Namespace) {
continue
}
ch <- soa
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, "/")
sort.Slice(serviceList, func(i, j int) bool {
return serviceList[i].Name < serviceList[j].Name
})
// Change host from IP to Name for SRV records
host := emitAddressRecord(c, s)
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}
for _, p := range svc.Ports {
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
s.Key = strings.Join(svcBase, "/")
// Change host from IP to Name for SRV records
host := emitAddressRecord(ch, s)
// 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)
for _, p := range svc.Ports {
s := msg.Service{Host: host, Port: int(p.Port), 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
ch <- []dns.RR{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
}
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), 100)}
}
// Skip endpoint discovery if clusterIP is defined
continue
}
endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
for _, ep := range endpointsList {
if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
// 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 _, 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, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
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
c <- s.NewSRV(msg.Domain(s.Key), 100)
}
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
}
// Skip endpoint discovery if clusterIP is defined
continue
}
s.Port = int(p.Port)
endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
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)}
for _, ep := range endpointsList {
if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
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
}
s.Port = int(p.Port)
s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
c <- 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 {
ch <- []dns.RR{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 {
c <- 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, s msg.Service) string {
ip := net.ParseIP(s.Host)
dnsType, _ := s.HostType()
func emitAddressRecord(c chan dns.RR, message msg.Service) string {
ip := net.ParseIP(message.Host)
var host string
dnsType, _ := message.HostType()
switch dnsType {
case dns.TypeA:
r := s.NewA(msg.Domain(s.Key), ip)
c <- []dns.RR{r}
return r.Hdr.Name
arec := message.NewA(msg.Domain(message.Key), ip)
host = arec.Hdr.Name
c <- arec
case dns.TypeAAAA:
r := s.NewAAAA(msg.Domain(s.Key), ip)
c <- []dns.RR{r}
return r.Hdr.Name
arec := message.NewAAAA(msg.Domain(message.Key), ip)
host = arec.Hdr.Name
c <- arec
}
return ""
return host
}
// calcSrvWeight borrows the logic implemented in plugin.SRV for dynamically

View File

@@ -1,126 +1,229 @@
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 TestKubernetesAXFR(t *testing.T) {
func TestKubernetesXFR(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])
ch, err := k.Transfer(k.Zones[0], 0)
if err != nil {
t.Error(err)
}
validateAXFR(t, ch)
}
func TestKubernetesIXFRFallback(t *testing.T) {
k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{}
k.Namespaces = map[string]struct{}{"testns": {}}
dnsmsg := &dns.Msg{}
dnsmsg.SetAxfr(k.Zones[0])
ch, err := k.Transfer(k.Zones[0], 1)
if err != nil {
t.Error(err)
}
validateAXFR(t, ch)
}
func TestKubernetesIXFRCurrent(t *testing.T) {
k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{}
k.Namespaces = map[string]struct{}{"testns": {}}
dnsmsg := &dns.Msg{}
dnsmsg.SetAxfr(k.Zones[0])
ch, err := k.Transfer(k.Zones[0], 3)
_, err := k.ServeDNS(ctx, w, dnsmsg)
if err != nil {
t.Error(err)
}
var gotRRs []dns.RR
for rrs := range ch {
gotRRs = append(gotRRs, rrs...)
if len(w.Msgs) == 0 {
t.Logf("%+v\n", w)
t.Fatal("Did not get back a zone response")
}
// ensure only one record is returned
if len(gotRRs) > 1 {
t.Errorf("Expected only one answer, got %d", len(gotRRs))
if len(w.Msgs[0].Answer) == 0 {
t.Logf("%+v\n", w)
t.Fatal("Did not get back an answer")
}
// Ensure first record is a SOA
if gotRRs[0].Header().Rrtype != dns.TypeSOA {
t.Error("Invalid transfer response, does not start with SOA record")
}
}
func validateAXFR(t *testing.T, ch <-chan []dns.RR) {
xfr := []dns.RR{}
for rrs := range ch {
xfr = append(xfr, rrs...)
}
if xfr[0].Header().Rrtype != dns.TypeSOA {
t.Error("Invalid transfer response, does not start with SOA record")
// 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")
}
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])
// 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)
}
i++
}
if err := zp.Err(); err != nil {
t.Fatal(err)
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)
}
}
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)
}
}
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)
}
}
}
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
`
func TestKubernetesXFRNotAllowed(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)
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")
}
if len(w.Msgs[0].Answer) != 0 {
t.Logf("%+v\n", w)
t.Fatal("Got an answer, should not have")
}
}
// 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{}{}
}
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"}},
}},
}
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"}},
}},
}
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},
}
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)
}
}
}