Skip to content

Commit 76c947d

Browse files
author
Dave McCormick
committed
Update kubernetai plugin to support transfers via the transfers plugin.
Abstract the list of kubernetes plugins to an interface so that we can affectively mock them and control per plugin behaviour and responses.
1 parent ce45312 commit 76c947d

File tree

6 files changed

+272
-54
lines changed

6 files changed

+272
-54
lines changed

plugin/kubernetai/axfr_test.go

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package kubernetai
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/coredns/coredns/plugin/transfer"
8+
9+
"github.com/miekg/dns"
10+
)
11+
12+
func TestKubernetesTransferNonAuthZone(t *testing.T) {
13+
type fields struct {
14+
name string
15+
kubernetes []*mockK8sPlugin
16+
zone string
17+
serial uint32
18+
expectedZone string
19+
expectedError error
20+
}
21+
tests := []fields{
22+
{
23+
name: "TestSingleKubernetesTransferNonAuthZone",
24+
kubernetes: []*mockK8sPlugin{
25+
{
26+
zones: []string{"cluster.local"},
27+
transferErr: transfer.ErrNotAuthoritative,
28+
},
29+
},
30+
zone: "example.com",
31+
expectedError: transfer.ErrNotAuthoritative,
32+
},
33+
{
34+
name: "TestSingleKubernetesTransferAuthZone",
35+
kubernetes: []*mockK8sPlugin{
36+
{
37+
zones: []string{"cluster.local"},
38+
transfer: `
39+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
40+
cluster.local. 5 IN NS ns.dns.cluster.local.
41+
ns.dns.cluster.local. 5 IN A 10.0.0.10
42+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
43+
`,
44+
transferErr: nil,
45+
},
46+
},
47+
zone: "cluster.local",
48+
expectedZone: `
49+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
50+
cluster.local. 5 IN NS ns.dns.cluster.local.
51+
ns.dns.cluster.local. 5 IN A 10.0.0.10
52+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
53+
`,
54+
expectedError: nil,
55+
},
56+
{
57+
name: "TestMultipleNonAuthorititativeSingleAuthoritative",
58+
kubernetes: []*mockK8sPlugin{
59+
{
60+
zones: []string{"fluster.local"},
61+
transfer: `
62+
fluster.local. 5 IN SOA ns.dns.fluster.local. hostmaster.fluster.local. 3 7200 1800 86400 5
63+
fluster.local. 5 IN NS ns.dns.fluster.local.
64+
ns.dns.fluster.local. 5 IN A 10.0.0.10
65+
fluster.local. 5 IN SOA ns.dns.fluster.local. hostmaster.fluster.local. 3 7200 1800 86400 5
66+
`,
67+
transferErr: transfer.ErrNotAuthoritative,
68+
},
69+
{
70+
zones: []string{"bluster.local"},
71+
transferErr: transfer.ErrNotAuthoritative,
72+
},
73+
{
74+
zones: []string{"cluster.local"},
75+
transfer: `
76+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
77+
cluster.local. 5 IN NS ns.dns.cluster.local.
78+
ns.dns.cluster.local. 5 IN A 10.0.0.10
79+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
80+
`,
81+
transferErr: nil,
82+
},
83+
{
84+
zones: []string{"muster.local"},
85+
transferErr: transfer.ErrNotAuthoritative,
86+
},
87+
},
88+
zone: "cluster.local",
89+
expectedZone: `
90+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
91+
cluster.local. 5 IN NS ns.dns.cluster.local.
92+
ns.dns.cluster.local. 5 IN A 10.0.0.10
93+
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
94+
`,
95+
expectedError: nil,
96+
},
97+
}
98+
99+
for _, tt := range tests {
100+
t.Run(tt.name, func(t *testing.T) {
101+
// create kubernetai with mock kubernetes plugins
102+
kai := Kubernetai{}
103+
for _, plug := range tt.kubernetes {
104+
kai.Kubernetes = append(kai.Kubernetes, plug)
105+
}
106+
107+
// create a axfr test message with test zone
108+
dnsmsg := &dns.Msg{}
109+
dnsmsg.SetAxfr(tt.zone)
110+
111+
// perform AXFR
112+
ch, err := kai.Transfer(tt.zone, tt.serial)
113+
if err != nil {
114+
if err != tt.expectedError {
115+
t.Errorf("expected error %+v but received %+v", tt.expectedError, err)
116+
}
117+
return
118+
}
119+
validateAXFR(t, ch, tt.expectedZone)
120+
})
121+
}
122+
}
123+
124+
func validateAXFR(t *testing.T, ch <-chan []dns.RR, expectedZone string) {
125+
xfr := []dns.RR{}
126+
for rrs := range ch {
127+
xfr = append(xfr, rrs...)
128+
}
129+
if xfr[0].Header().Rrtype != dns.TypeSOA {
130+
t.Error("Invalid transfer response, does not start with SOA record")
131+
}
132+
133+
zp := dns.NewZoneParser(strings.NewReader(expectedZone), "", "")
134+
i := 0
135+
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
136+
if !dns.IsDuplicate(rr, xfr[i]) {
137+
t.Fatalf("Record %d, expected\n%v\n, got\n%v", i, rr, xfr[i])
138+
}
139+
i++
140+
}
141+
142+
if err := zp.Err(); err != nil {
143+
t.Fatal(err)
144+
}
145+
}

plugin/kubernetai/kubernetai.go

+64-8
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,76 @@
1+
// Package kubernetai implements a plugin which can embed a number of kubernetes plugins in the same dns server.
12
package kubernetai
23

34
import (
45
"context"
56

67
"github.com/coredns/coredns/plugin"
78
"github.com/coredns/coredns/plugin/kubernetes"
9+
"github.com/coredns/coredns/plugin/kubernetes/object"
810
clog "github.com/coredns/coredns/plugin/pkg/log"
11+
"github.com/coredns/coredns/plugin/transfer"
912
"github.com/coredns/coredns/request"
1013
"github.com/miekg/dns"
1114
)
1215

1316
var log = clog.NewWithPlugin("kubernetai")
1417

18+
// embeddedKubernetesPluginInterface describes the kubernetes plugin interface that kubernetai requires/uses.
19+
type embeddedKubernetesPluginInterface interface {
20+
plugin.Handler
21+
transfer.Transferer
22+
PodWithIP(ip string) (pod *object.Pod)
23+
Zones() (zones plugin.Zones)
24+
}
25+
26+
// embeddedKubernetes wraps a real kubernetes plugin
27+
type embeddedKubernetes struct {
28+
*kubernetes.Kubernetes
29+
}
30+
31+
var _ embeddedKubernetesPluginInterface = &embeddedKubernetes{}
32+
33+
func newEmbeddedKubernetes(k *kubernetes.Kubernetes) *embeddedKubernetes {
34+
return &embeddedKubernetes{
35+
Kubernetes: k,
36+
}
37+
}
38+
39+
// PodWithIP satisfies the embeddedKubernetesPluginInterface by adding this additional method not exported from the kubernetes plugin.
40+
func (ek embeddedKubernetes) PodWithIP(ip string) *object.Pod {
41+
if ek.Kubernetes == nil {
42+
return nil
43+
}
44+
ps := ek.Kubernetes.APIConn.PodIndex(ip)
45+
if len(ps) == 0 {
46+
return nil
47+
}
48+
return ps[0]
49+
}
50+
51+
// Zones satisfies the embeddedKubernetesPluginInterface by providing access to the kubernetes plugin Zones.
52+
func (ek embeddedKubernetes) Zones() plugin.Zones {
53+
if ek.Kubernetes == nil {
54+
return nil
55+
}
56+
return plugin.Zones(ek.Kubernetes.Zones)
57+
}
58+
1559
// Kubernetai handles multiple Kubernetes
1660
type Kubernetai struct {
1761
Zones []string
18-
Kubernetes []*kubernetes.Kubernetes
62+
Kubernetes []embeddedKubernetesPluginInterface
1963
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
20-
p podHandlerItf
2164
}
2265

2366
// New creates a Kubernetai containing one Kubernetes with zones
2467
func New(zones []string) (Kubernetai, *kubernetes.Kubernetes) {
2568
h := Kubernetai{
2669
autoPathSearch: searchFromResolvConf(),
27-
p: &podHandler{},
2870
}
2971
k := kubernetes.New(zones)
30-
h.Kubernetes = append(h.Kubernetes, k)
72+
ek := newEmbeddedKubernetes(k)
73+
h.Kubernetes = append(h.Kubernetes, ek)
3174
return h, k
3275
}
3376

@@ -43,7 +86,7 @@ func (k8i Kubernetai) AutoPath(state request.Request) []string {
4386
// Abort if zone is not in kubernetai stanza.
4487
var zMatch bool
4588
for _, k8s := range k8i.Kubernetes {
46-
zone := plugin.Zones(k8s.Zones).Matches(state.Name())
89+
zone := k8s.Zones().Matches(state.Name())
4790
if zone != "" {
4891
zMatch = true
4992
break
@@ -55,13 +98,13 @@ func (k8i Kubernetai) AutoPath(state request.Request) []string {
5598

5699
// Add autopath result for the handled zones
57100
for _, k := range k8i.Kubernetes {
58-
pod := k8i.p.PodWithIP(*k, state.IP())
101+
pod := k.PodWithIP(state.IP())
59102
if pod == nil {
60103
return nil
61104
}
62105

63106
search := make([]string, 3)
64-
for _, z := range k.Zones {
107+
for _, z := range k.Zones() {
65108
if z == "." {
66109
search[0] = pod.Namespace + ".svc."
67110
search[1] = "svc."
@@ -80,6 +123,19 @@ func (k8i Kubernetai) AutoPath(state request.Request) []string {
80123
return searchPath
81124
}
82125

126+
// Transfer supports the transfer plugin, implementing the Transferer interface, by calling Transfer on each of the embedded plugins.
127+
func (k8i Kubernetai) Transfer(zone string, serial uint32) (retCh <-chan []dns.RR, err error) {
128+
for _, k := range k8i.Kubernetes {
129+
retCh, err = k.Transfer(zone, serial)
130+
if err == transfer.ErrNotAuthoritative {
131+
continue
132+
}
133+
return
134+
}
135+
// none of the embedded plugins were authoritative
136+
return nil, transfer.ErrNotAuthoritative
137+
}
138+
83139
func searchFromResolvConf() []string {
84140
rc, err := dns.ClientConfigFromFile("/etc/resolv.conf")
85141
if err != nil {
@@ -93,7 +149,7 @@ func searchFromResolvConf() []string {
93149
func (k8i Kubernetai) Health() bool {
94150
healthy := true
95151
for _, k := range k8i.Kubernetes {
96-
healthy = healthy && k.APIConn.HasSynced()
152+
healthy = healthy && k.(*embeddedKubernetes).APIConn.HasSynced()
97153
if !healthy {
98154
break
99155
}

0 commit comments

Comments
 (0)