diff --git a/middleware/kubernetes/autopath.go b/middleware/kubernetes/autopath/autopath.go similarity index 68% rename from middleware/kubernetes/autopath.go rename to middleware/kubernetes/autopath/autopath.go index fa79212a9..abcb360ae 100644 --- a/middleware/kubernetes/autopath.go +++ b/middleware/kubernetes/autopath/autopath.go @@ -1,8 +1,8 @@ -package kubernetes +package autopath import "github.com/miekg/dns" -// AutoPathWriter implements a ResponseWriter that also does the following: +// Writer implements a ResponseWriter that also does the following: // * reverts question section of a packet to its original state. // This is done to avoid the 'Question section mismatch:' error in client. // * Defers write to the client if the response code is NXDOMAIN. This is needed @@ -10,40 +10,48 @@ import "github.com/miekg/dns" // * Allow forced write to client regardless of response code. This is needed to // write the packet to the client if the final search in the path fails to // produce results. -// * Overwrites response code with AutoPathWriter.Rcode if the response code +// * Overwrites response code with Writer.Rcode if the response code // is NXDOMAIN (NameError). This is needed to support the AutoPath.OnNXDOMAIN // function, which returns a NOERROR to client instead of NXDOMAIN if the final // search in the path fails to produce results. -type AutoPathWriter struct { +type Writer struct { dns.ResponseWriter original dns.Question Rcode int Sent bool } -// NewAutoPathWriter returns a pointer to a new AutoPathWriter -func NewAutoPathWriter(w dns.ResponseWriter, r *dns.Msg) *AutoPathWriter { - return &AutoPathWriter{ +// AutoPath enables server side search path lookups for pods. +type AutoPath struct { + NDots int + ResolvConfFile string + HostSearchPath []string + OnNXDOMAIN int +} + +// NewWriter returns a pointer to a new Writer +func NewWriter(w dns.ResponseWriter, r *dns.Msg) *Writer { + return &Writer{ ResponseWriter: w, original: r.Question[0], Rcode: dns.RcodeSuccess, } } -// WriteMsg writes to client, unless response will be NXDOMAIN -func (apw *AutoPathWriter) WriteMsg(res *dns.Msg) error { +// WriteMsg writes to client, unless response will be NXDOMAIN. +func (apw *Writer) WriteMsg(res *dns.Msg) error { return apw.overrideMsg(res, false) } -// ForceWriteMsg forces the write to client regardless of response code -func (apw *AutoPathWriter) ForceWriteMsg(res *dns.Msg) error { +// ForceWriteMsg forces the write to client regardless of response code. +func (apw *Writer) ForceWriteMsg(res *dns.Msg) error { return apw.overrideMsg(res, true) } // overrideMsg overrides rcode, reverts question, adds CNAME, and calls the // underlying ResponseWriter's WriteMsg method unless the write is deferred, // or force = true. -func (apw *AutoPathWriter) overrideMsg(res *dns.Msg, force bool) error { +func (apw *Writer) overrideMsg(res *dns.Msg, force bool) error { if res.Rcode == dns.RcodeNameError { res.Rcode = apw.Rcode } @@ -56,7 +64,7 @@ func (apw *AutoPathWriter) overrideMsg(res *dns.Msg, force bool) error { } res.Answer = append(res.Answer, nil) copy(res.Answer[1:], res.Answer) - res.Answer[0] = newCNAME(apw.original.Name, dns.Fqdn(a.Header().Name), a.Header().Ttl) + res.Answer[0] = CNAME(apw.original.Name, a.Header().Name, a.Header().Ttl) } res.Question[0] = apw.original apw.Sent = true @@ -64,14 +72,14 @@ func (apw *AutoPathWriter) overrideMsg(res *dns.Msg, force bool) error { } // Write is a wrapper that records the size of the message that gets written. -func (apw *AutoPathWriter) Write(buf []byte) (int, error) { +func (apw *Writer) Write(buf []byte) (int, error) { n, err := apw.ResponseWriter.Write(buf) return n, err } // Hijack implements dns.Hijacker. It simply wraps the underlying // ResponseWriter's Hijack method if there is one, or returns an error. -func (apw *AutoPathWriter) Hijack() { +func (apw *Writer) Hijack() { apw.ResponseWriter.Hijack() return } diff --git a/middleware/kubernetes/autopath/cname.go b/middleware/kubernetes/autopath/cname.go new file mode 100644 index 000000000..471266747 --- /dev/null +++ b/middleware/kubernetes/autopath/cname.go @@ -0,0 +1,10 @@ +package autopath + +import "github.com/miekg/dns" + +// CNAME returns a new CNAME formed from name target and ttl. +func CNAME(name string, target string, ttl uint32) *dns.CNAME { + return &dns.CNAME{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}, + Target: dns.Fqdn(target)} +} diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go index e7660c3a9..3ef4f7a37 100644 --- a/middleware/kubernetes/handler.go +++ b/middleware/kubernetes/handler.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/coredns/coredns/middleware" + "github.com/coredns/coredns/middleware/kubernetes/autopath" "github.com/coredns/coredns/middleware/pkg/dnsutil" "github.com/coredns/coredns/request" @@ -41,14 +42,14 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M records, extra, _, err := k.routeRequest(zone, state) // Check for Autopath search eligibility - if k.AutoPath.Enabled && k.IsNameError(err) && (state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA) { + if (k.autoPath != nil) && k.IsNameError(err) && (state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA) { p := k.findPodWithIP(state.IP()) for p != nil { name, path, ok := splitSearch(zone, state.QName(), p.Namespace) if !ok { break } - if (dns.CountLabel(name) - 1) < k.AutoPath.NDots { + if (dns.CountLabel(name) - 1) < k.autoPath.NDots { break } origQName := state.QName() @@ -60,7 +61,7 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M if !k.IsNameError(err) && len(records) > 0 { records = append(records, nil) copy(records[1:], records) - records[0] = newCNAME(origQName, records[0].Header().Name, records[0].Header().Ttl) + records[0] = autopath.CNAME(origQName, records[0].Header().Name, records[0].Header().Ttl) break } } @@ -68,8 +69,8 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M break } // Try host search path (if set) in the next middleware - apw := NewAutoPathWriter(w, r) - for _, hostsearch := range k.AutoPath.HostSearchPath { + apw := autopath.NewWriter(w, r) + for _, hostsearch := range k.autoPath.HostSearchPath { newstate := state.NewWithQuestion(strings.Join([]string{name, hostsearch}, "."), state.QType()) rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, apw, newstate.Req) if apw.Sent { @@ -82,11 +83,11 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M if !k.IsNameError(err) && len(records) > 0 { records = append(records, nil) copy(records[1:], records) - records[0] = newCNAME(origQName, records[0].Header().Name, records[0].Header().Ttl) + records[0] = autopath.CNAME(origQName, records[0].Header().Name, records[0].Header().Ttl) break } // Search . in the next middleware - apw.Rcode = k.AutoPath.OnNXDOMAIN + apw.Rcode = k.autoPath.OnNXDOMAIN newstate = state.NewWithQuestion(strings.Join([]string{name, "."}, ""), state.QType()) r = newstate.Req rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, apw, r) @@ -125,11 +126,6 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M return dns.RcodeSuccess, nil } -func newCNAME(name string, target string, ttl uint32) *dns.CNAME { - // TODO factor this out and put in dnsutil - return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}, Target: dns.Fqdn(target)} -} - func (k *Kubernetes) routeRequest(zone string, state request.Request) (records []dns.RR, extra []dns.RR, debug []dns.RR, err error) { switch state.Type() { case "A": diff --git a/middleware/kubernetes/handler_test.go b/middleware/kubernetes/handler_test.go index 04d64722f..dc6b62923 100644 --- a/middleware/kubernetes/handler_test.go +++ b/middleware/kubernetes/handler_test.go @@ -5,6 +5,7 @@ import ( "sort" "testing" + "github.com/coredns/coredns/middleware/kubernetes/autopath" "github.com/coredns/coredns/middleware/pkg/dnsrecorder" "github.com/coredns/coredns/middleware/test" @@ -267,8 +268,8 @@ func TestServeDNS(t *testing.T) { k.ReverseCidrs = []net.IPNet{*cidr} k.Federations = []Federation{{name: "fed", zone: "federal.test."}} k.APIConn = &APIConnServeTest{} - k.AutoPath.Enabled = true - k.AutoPath.HostSearchPath = []string{"hostdom.test"} + k.autoPath = new(autopath.AutoPath) + k.autoPath.HostSearchPath = []string{"hostdom.test"} k.interfaceAddrsFunc = localPodIP k.Next = testHandler(nextMWMap) @@ -288,11 +289,11 @@ func TestServeDNS(t *testing.T) { runServeDNSTests(ctx, t, podModeVerifiedCases, k) // Set ndots to 2 for the ndots test cases - k.AutoPath.NDots = 2 + k.autoPath.NDots = 2 runServeDNSTests(ctx, t, autopath2NDotsCases, k) - k.AutoPath.NDots = defautNdots + k.autoPath.NDots = defautNdots // Disable the NXDOMAIN override (enabled by default) - k.OnNXDOMAIN = dns.RcodeNameError + k.autoPath.OnNXDOMAIN = dns.RcodeNameError runServeDNSTests(ctx, t, autopathCases, k) runServeDNSTests(ctx, t, autopathBareSearchExpectNameErr, k) diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go index 3649eb02c..7c5f59f4f 100644 --- a/middleware/kubernetes/kubernetes.go +++ b/middleware/kubernetes/kubernetes.go @@ -11,6 +11,7 @@ import ( "github.com/coredns/coredns/middleware" "github.com/coredns/coredns/middleware/etcd/msg" + "github.com/coredns/coredns/middleware/kubernetes/autopath" "github.com/coredns/coredns/middleware/pkg/dnsutil" dnsstrings "github.com/coredns/coredns/middleware/pkg/strings" "github.com/coredns/coredns/middleware/proxy" @@ -45,17 +46,9 @@ type Kubernetes struct { PodMode string ReverseCidrs []net.IPNet Fallthrough bool - AutoPath - interfaceAddrsFunc func() net.IP -} -// AutoPath enables server side search path lookups for pods -type AutoPath struct { - Enabled bool - NDots int - ResolvConfFile string - HostSearchPath []string - OnNXDOMAIN int + autoPath *autopath.AutoPath + interfaceAddrsFunc func() net.IP } const ( @@ -259,7 +252,7 @@ func (k *Kubernetes) InitKubeCache() (err error) { } opts := dnsControlOpts{ - initPodCache: (k.PodMode == PodModeVerified || k.AutoPath.Enabled), + initPodCache: (k.PodMode == PodModeVerified || (k.autoPath != nil)), } k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, opts) @@ -468,7 +461,7 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, r rec } func (k *Kubernetes) findPodWithIP(ip string) (p *api.Pod) { - if !k.AutoPath.Enabled { + if k.autoPath == nil { return nil } objList := k.APIConn.PodIndex(ip) diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go index d56b788f9..d892ef94a 100644 --- a/middleware/kubernetes/setup.go +++ b/middleware/kubernetes/setup.go @@ -10,6 +10,7 @@ import ( "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/middleware" + "github.com/coredns/coredns/middleware/kubernetes/autopath" "github.com/coredns/coredns/middleware/pkg/dnsutil" "github.com/coredns/coredns/middleware/proxy" "github.com/miekg/dns" @@ -192,7 +193,7 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { return nil, fmt.Errorf("incorrect number of arguments for federation, got %v, expected 2", len(args)) case "autopath": // name zone args := c.RemainingArgs() - k8s.AutoPath = AutoPath{ + k8s.autoPath = &autopath.AutoPath{ NDots: defautNdots, HostSearchPath: []string{}, ResolvConfFile: defaultResolvConfFile, @@ -207,30 +208,29 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { if err != nil { return nil, fmt.Errorf("invalid NDOTS argument for autopath, got '%v', expected an integer", ndots) } - k8s.AutoPath.NDots = ndots + k8s.autoPath.NDots = ndots } if len(args) > 1 { switch args[1] { case dns.RcodeToString[dns.RcodeNameError]: - k8s.AutoPath.OnNXDOMAIN = dns.RcodeNameError + k8s.autoPath.OnNXDOMAIN = dns.RcodeNameError case dns.RcodeToString[dns.RcodeSuccess]: - k8s.AutoPath.OnNXDOMAIN = dns.RcodeSuccess + k8s.autoPath.OnNXDOMAIN = dns.RcodeSuccess case dns.RcodeToString[dns.RcodeServerFailure]: - k8s.AutoPath.OnNXDOMAIN = dns.RcodeServerFailure + k8s.autoPath.OnNXDOMAIN = dns.RcodeServerFailure default: return nil, fmt.Errorf("invalid RESPONSE argument for autopath, got '%v', expected SERVFAIL, NOERROR, or NXDOMAIN", args[1]) } } if len(args) > 2 { - k8s.AutoPath.ResolvConfFile = args[2] + k8s.autoPath.ResolvConfFile = args[2] } - rc, err := dns.ClientConfigFromFile(k8s.AutoPath.ResolvConfFile) + rc, err := dns.ClientConfigFromFile(k8s.autoPath.ResolvConfFile) if err != nil { - return nil, fmt.Errorf("error when parsing %v: %v", k8s.AutoPath.ResolvConfFile, err) + return nil, fmt.Errorf("error when parsing %v: %v", k8s.autoPath.ResolvConfFile, err) } - k8s.AutoPath.HostSearchPath = rc.Search - middleware.Zones(k8s.AutoPath.HostSearchPath).Normalize() - k8s.AutoPath.Enabled = true + k8s.autoPath.HostSearchPath = rc.Search + middleware.Zones(k8s.autoPath.HostSearchPath).Normalize() continue } } diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go index 9512c8fbf..3cfc94bf1 100644 --- a/middleware/kubernetes/setup_test.go +++ b/middleware/kubernetes/setup_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/coredns/coredns/middleware/kubernetes/autopath" "github.com/coredns/coredns/middleware/test" "github.com/mholt/caddy" @@ -42,7 +43,7 @@ func TestKubernetesParse(t *testing.T) { expectedFallthrough bool expectedUpstreams []string expectedFederations []Federation - expectedAutoPath AutoPath + expectedAutoPath *autopath.AutoPath }{ // positive { @@ -59,7 +60,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "kubernetes keyword with multiple zones", @@ -75,7 +76,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "kubernetes keyword with zone and empty braces", @@ -92,7 +93,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "endpoint keyword with url", @@ -110,7 +111,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "namespaces keyword with one namespace", @@ -128,7 +129,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{}, + nil, }, { "namespaces keyword with multiple namespaces", @@ -146,7 +147,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "resync period in seconds", @@ -164,7 +165,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "resync period in minutes", @@ -182,7 +183,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "basic label selector", @@ -200,7 +201,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "multi-label selector", @@ -218,7 +219,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "fully specified valid config", @@ -240,7 +241,7 @@ func TestKubernetesParse(t *testing.T) { true, nil, []Federation{}, - AutoPath{}, + nil, }, // negative { @@ -257,7 +258,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "kubernetes keyword without a zone", @@ -273,7 +274,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "endpoint keyword without an endpoint value", @@ -291,7 +292,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "namespace keyword without a namespace value", @@ -309,7 +310,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "resyncperiod keyword without a duration value", @@ -327,7 +328,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "resync period no units", @@ -345,7 +346,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "resync period invalid", @@ -363,7 +364,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "labels with no selector value", @@ -381,7 +382,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, { "labels with invalid selector value", @@ -399,7 +400,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // pods disabled { @@ -418,7 +419,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // pods insecure { @@ -437,7 +438,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // pods verified { @@ -456,7 +457,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // pods invalid { @@ -475,7 +476,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // cidrs ok { @@ -494,7 +495,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // cidrs ok { @@ -513,7 +514,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // fallthrough invalid { @@ -532,7 +533,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // Valid upstream { @@ -551,7 +552,7 @@ func TestKubernetesParse(t *testing.T) { false, []string{"13.14.15.16:53"}, []Federation{}, - AutoPath{}, + nil, }, // Invalid upstream { @@ -570,7 +571,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // Valid federations { @@ -593,7 +594,7 @@ func TestKubernetesParse(t *testing.T) { {name: "foo", zone: "bar.crawl.com"}, {name: "fed", zone: "era.tion.com"}, }, - AutoPath{}, + nil, }, // Invalid federations { @@ -612,7 +613,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, []Federation{}, - AutoPath{}, + nil, }, // autopath { @@ -631,8 +632,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{ - Enabled: true, + &autopath.AutoPath{ NDots: 1, HostSearchPath: []string{"bar.com.", "baz.com."}, ResolvConfFile: autoPathResolvConfFile, @@ -655,7 +655,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{}, + nil, }, { "invalid autopath NDOTS", @@ -673,7 +673,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{}, + nil, }, { "invalid autopath RESOLV-CONF", @@ -691,7 +691,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{}, + nil, }, { "invalid autopath invalid option", @@ -709,7 +709,7 @@ func TestKubernetesParse(t *testing.T) { false, nil, nil, - AutoPath{}, + nil, }, } @@ -811,8 +811,8 @@ func TestKubernetesParse(t *testing.T) { } } // autopath - if !reflect.DeepEqual(test.expectedAutoPath, k8sController.AutoPath) { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with autopath '%v'. Instead found autopath '%v' for input '%s'", i, test.expectedAutoPath, k8sController.AutoPath, test.input) + if !reflect.DeepEqual(test.expectedAutoPath, k8sController.autoPath) { + t.Errorf("Test %d: Expected kubernetes controller to be initialized with autopath '%v'. Instead found autopath '%v' for input '%s'", i, test.expectedAutoPath, k8sController.autoPath, test.input) } } }