Skip to content

Commit 13b4a22

Browse files
davidmccormickDave McCormick
authored and
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. Signed-off-by: Dave McCormick <[email protected]>
1 parent ce45312 commit 13b4a22

File tree

6 files changed

+273
-54
lines changed

6 files changed

+273
-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

+65-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,20 @@ 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+
// It will return a channel to the FIRST kubernetai stanza that reports that it is authoritative for the requested zone.
128+
func (k8i Kubernetai) Transfer(zone string, serial uint32) (retCh <-chan []dns.RR, err error) {
129+
for _, k := range k8i.Kubernetes {
130+
retCh, err = k.Transfer(zone, serial)
131+
if err == transfer.ErrNotAuthoritative {
132+
continue
133+
}
134+
return
135+
}
136+
// none of the embedded plugins were authoritative
137+
return nil, transfer.ErrNotAuthoritative
138+
}
139+
83140
func searchFromResolvConf() []string {
84141
rc, err := dns.ClientConfigFromFile("/etc/resolv.conf")
85142
if err != nil {
@@ -93,7 +150,7 @@ func searchFromResolvConf() []string {
93150
func (k8i Kubernetai) Health() bool {
94151
healthy := true
95152
for _, k := range k8i.Kubernetes {
96-
healthy = healthy && k.APIConn.HasSynced()
153+
healthy = healthy && k.(*embeddedKubernetes).APIConn.HasSynced()
97154
if !healthy {
98155
break
99156
}

0 commit comments

Comments
 (0)