middleware/kubernetes: Server side path lookups (#750)

* initial commit

* add config options

* add readme

* rewording

* revert unlreated change

* normalize host domain path

* add ndots opt, allow > 1 host domains, pull host domains from resolv.conf

* implementing review feedback

* update readme

* use dns lib, config format, defaults

* Correct autopath example.
This commit is contained in:
Chris O'Haver 2017-06-28 18:44:30 -04:00 committed by John Belamaric
parent 817f3960b8
commit edf71fb168
6 changed files with 407 additions and 51 deletions

View file

@ -121,6 +121,58 @@ kubernetes coredns.local {
# Each line consists of the name of the federation, and the domain. # Each line consists of the name of the federation, and the domain.
federation myfed foo.example.com federation myfed foo.example.com
# autopath [NDOTS [RESPONSE [RESOLV-CONF]]
#
# Enables server side search path lookups for pods. When enabled, coredns
# will identify search path queries from pods and perform the remaining
# lookups in the path on the pod's behalf. The search path used mimics the
# resolv.conf search path deployed to pods. E.g.
#
# search ns1.svc.cluster.local svc.cluster.local cluster.local foo.com
#
# If no domains in the path produce an answer, a lookup on the bare question
# will be attempted.
#
# A successful response will contain a question section with the original
# question, and an answer section containing the record for the question that
# actually had an answer. This means that the question and answer will not
# match. For example:
#
# # host -v -t a google.com
# Trying "google.com.default.svc.cluster.local"
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50957
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
#
# ;; QUESTION SECTION:
# ;google.com.default.svc.cluster.local. IN A
#
# ;; ANSWER SECTION:
# google.com. 175 IN A 216.58.194.206
#
#
# NDOTS (default: 0) This provides an adjustable threshold to
# prevent server side lookups from triggering. If the number of dots before
# the first search domain is less than this number, then the search path will
# not executed on the server side.
#
# RESPONSE (default: SERVFAIL) RESPONSE can be either NXDOMAIN, SERVFAIL or
# NOERROR. This option causes coredns to return the given response instead of
# NXDOMAIN when the all searches in the path produce no results. Setting this
# to SERVFAIL or NOERROR should prevent the client from fruitlessly continuing
# the client side searches in the path after the server already checked them.
#
# RESOLV-CONF (default: /etc/resolv.conf) If specified, coredns uses this
# file to get the host's search domains. CoreDNS performs a lookup on these
# domains if the in-cluster search domains in the path fail to produce an
# answer. If not specified, the values will be read from the local resolv.conf
# file (i.e the resolv.conf file in the pod containing coredns).
#
# Enabling autopath causes coredns to use more memory since it needs to
# maintain a watch on all pods. If autopath and "pods verified" mode are
# both enabled, they will share the same watch. I.e. enabling both options
# should have an equivalent memory impact of just one.
autopath 0 SERVFAIL /etc/resolv.conf
# fallthrough # fallthrough
# #
# If a query for a record in the cluster zone results in NXDOMAIN, # If a query for a record in the cluster zone results in NXDOMAIN,

View file

@ -2,9 +2,11 @@ package kubernetes
import ( import (
"errors" "errors"
"strings"
"github.com/coredns/coredns/middleware" "github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/dnsutil" "github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/middleware/rewrite"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -39,37 +41,55 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
zone = state.Name() zone = state.Name()
} }
var ( records, extra, _, err := k.routeRequest(zone, state)
records, extra []dns.RR
err error if k.AutoPath.Enabled && k.IsNameError(err) {
) p := k.findPodWithIP(state.IP())
switch state.Type() { for p != nil {
case "A": name, path, ok := splitSearch(zone, state.QName(), p.Namespace)
records, _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) if !ok {
case "AAAA": break
records, _, err = middleware.AAAA(&k, zone, state, nil, middleware.Options{}) }
case "TXT": if (dns.CountLabel(name) - 1) < k.AutoPath.NDots {
records, _, err = middleware.TXT(&k, zone, state, middleware.Options{}) break
case "CNAME": }
records, _, err = middleware.CNAME(&k, zone, state, middleware.Options{}) // Search "svc.cluster.local" and "cluster.local"
case "PTR": for i := 0; i < 2; i++ {
records, _, err = middleware.PTR(&k, zone, state, middleware.Options{}) path = strings.Join(dns.SplitDomainName(path)[1:], ".")
case "MX": state = state.NewWithQuestion(strings.Join([]string{name, path}, "."), state.QType())
records, extra, _, err = middleware.MX(&k, zone, state, middleware.Options{}) records, extra, _, err = k.routeRequest(zone, state)
case "SRV": if !k.IsNameError(err) {
records, extra, _, err = middleware.SRV(&k, zone, state, middleware.Options{}) break
case "SOA": }
records, _, err = middleware.SOA(&k, zone, state, middleware.Options{}) }
case "NS": if !k.IsNameError(err) {
if state.Name() == zone { break
records, extra, _, err = middleware.NS(&k, zone, state, middleware.Options{}) }
break // Fallthrough with the host search path (if set)
wr := rewrite.NewResponseReverter(w, r)
for _, hostsearch := range k.AutoPath.HostSearchPath {
r = state.NewWithQuestion(strings.Join([]string{name, hostsearch}, "."), state.QType()).Req
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, wr, r)
if rcode == dns.RcodeSuccess {
return rcode, nextErr
}
}
// Search . in this middleware
state = state.NewWithQuestion(strings.Join([]string{name, "."}, ""), state.QType())
records, extra, _, err = k.routeRequest(zone, state)
if !k.IsNameError(err) {
break
}
// Search . in the next middleware
r = state.Req
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, wr, r)
if rcode == dns.RcodeNameError {
rcode = k.AutoPath.OnNXDOMAIN
}
return rcode, nextErr
} }
fallthrough
default:
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, _, err = middleware.A(&k, zone, state, nil, middleware.Options{})
} }
if k.IsNameError(err) { if k.IsNameError(err) {
if k.Fallthrough { if k.Fallthrough {
return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r)
@ -95,5 +115,36 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
return dns.RcodeSuccess, nil return dns.RcodeSuccess, nil
} }
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":
records, _, err = middleware.A(k, zone, state, nil, middleware.Options{})
case "AAAA":
records, _, err = middleware.AAAA(k, zone, state, nil, middleware.Options{})
case "TXT":
records, _, err = middleware.TXT(k, zone, state, middleware.Options{})
case "CNAME":
records, _, err = middleware.CNAME(k, zone, state, middleware.Options{})
case "PTR":
records, _, err = middleware.PTR(k, zone, state, middleware.Options{})
case "MX":
records, extra, _, err = middleware.MX(k, zone, state, middleware.Options{})
case "SRV":
records, extra, _, err = middleware.SRV(k, zone, state, middleware.Options{})
case "SOA":
records, _, err = middleware.SOA(k, zone, state, middleware.Options{})
case "NS":
if state.Name() == zone {
records, extra, _, err = middleware.NS(k, zone, state, middleware.Options{})
break
}
fallthrough
default:
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, _, err = middleware.A(k, zone, state, nil, middleware.Options{})
}
return records, extra, nil, err
}
// Name implements the Handler interface. // Name implements the Handler interface.
func (k Kubernetes) Name() string { return "kubernetes" } func (k Kubernetes) Name() string { return "kubernetes" }

View file

@ -28,26 +28,35 @@ import (
// Kubernetes implements a middleware that connects to a Kubernetes cluster. // Kubernetes implements a middleware that connects to a Kubernetes cluster.
type Kubernetes struct { type Kubernetes struct {
Next middleware.Handler Next middleware.Handler
Zones []string Zones []string
primaryZone int primaryZone int
Proxy proxy.Proxy // Proxy for looking up names during the resolution process Proxy proxy.Proxy // Proxy for looking up names during the resolution process
APIEndpoint string APIEndpoint string
APICertAuth string APICertAuth string
APIClientCert string APIClientCert string
APIClientKey string APIClientKey string
APIConn dnsController APIConn dnsController
ResyncPeriod time.Duration ResyncPeriod time.Duration
Namespaces []string Namespaces []string
Federations []Federation Federations []Federation
LabelSelector *unversionedapi.LabelSelector LabelSelector *unversionedapi.LabelSelector
Selector *labels.Selector Selector *labels.Selector
PodMode string PodMode string
ReverseCidrs []net.IPNet ReverseCidrs []net.IPNet
Fallthrough bool Fallthrough bool
AutoPath
interfaceAddrs interfaceAddrser interfaceAddrs interfaceAddrser
} }
type AutoPath struct {
Enabled bool
NDots int
ResolvConfFile string
HostSearchPath []string
OnNXDOMAIN int
}
const ( const (
// PodModeDisabled is the default value where pod requests are ignored // PodModeDisabled is the default value where pod requests are ignored
PodModeDisabled = "disabled" PodModeDisabled = "disabled"
@ -97,6 +106,7 @@ var errInvalidRequest = errors.New("invalid query name")
var errZoneNotFound = errors.New("zone not found") var errZoneNotFound = errors.New("zone not found")
var errAPIBadPodType = errors.New("expected type *api.Pod") var errAPIBadPodType = errors.New("expected type *api.Pod")
var errPodsDisabled = errors.New("pod records disabled") var errPodsDisabled = errors.New("pod records disabled")
var errResolvConfReadErr = errors.New("resolv.conf read error")
// Services implements the ServiceBackend interface. // Services implements the ServiceBackend interface.
func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, debug []msg.Service, err error) { func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, debug []msg.Service, err error) {
@ -183,7 +193,7 @@ func (k *Kubernetes) Lookup(state request.Request, name string, typ uint16) (*dn
// IsNameError implements the ServiceBackend interface. // IsNameError implements the ServiceBackend interface.
func (k *Kubernetes) IsNameError(err error) bool { func (k *Kubernetes) IsNameError(err error) bool {
return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest || err == errZoneNotFound
} }
// Debug implements the ServiceBackend interface. // Debug implements the ServiceBackend interface.
@ -245,7 +255,7 @@ func (k *Kubernetes) InitKubeCache() (err error) {
} }
opts := dnsControlOpts{ opts := dnsControlOpts{
initPodCache: k.PodMode == PodModeVerified, initPodCache: (k.PodMode == PodModeVerified || k.AutoPath.Enabled),
} }
k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, opts) k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, opts)
@ -448,6 +458,21 @@ func ipFromPodName(podname string) string {
return strings.Replace(podname, "-", ":", -1) return strings.Replace(podname, "-", ":", -1)
} }
func (k *Kubernetes) findPodWithIP(ip string) (p *api.Pod) {
if k.PodMode != PodModeVerified {
return nil
}
objList := k.APIConn.PodIndex(ip)
for _, o := range objList {
p, ok := o.(*api.Pod)
if !ok {
return nil
}
return p
}
return nil
}
func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) { func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) {
if k.PodMode == PodModeDisabled { if k.PodMode == PodModeDisabled {
return pods, errPodsDisabled return pods, errPodsDisabled
@ -634,3 +659,11 @@ func (k *Kubernetes) localPodIP() net.IP {
} }
return nil return nil
} }
func splitSearch(zone, question, namespace string) (name, search string, ok bool) {
search = strings.Join([]string{namespace, "svc", zone}, ".")
if dns.IsSubDomain(search, question) {
return question[:len(question)-len(search)-1], search, true
}
return "", "", false
}

View file

@ -480,3 +480,27 @@ func TestServices(t *testing.T) {
} }
} }
func TestSplitSearchPath(t *testing.T) {
type testCase struct {
question string
namespace string
expectedName string
expectedSearch string
expectedOk bool
}
tests := []testCase{
{question: "test.blah.com", namespace: "ns1", expectedName: "", expectedSearch: "", expectedOk: false},
{question: "foo.com.ns2.svc.interwebs.nets", namespace: "ns1", expectedName: "", expectedSearch: "", expectedOk: false},
{question: "foo.com.svc.interwebs.nets", namespace: "ns1", expectedName: "", expectedSearch: "", expectedOk: false},
{question: "foo.com.ns1.svc.interwebs.nets", namespace: "ns1", expectedName: "foo.com", expectedSearch: "ns1.svc.interwebs.nets", expectedOk: true},
}
zone := "interwebs.nets"
for _, c := range tests {
name, search, ok := splitSearch(zone, c.question, c.namespace)
if c.expectedName != name || c.expectedSearch != search || c.expectedOk != ok {
t.Errorf("Case %v: Expected name'%v', search:'%v', ok:'%v'. Got name:'%v', search:'%v', ok:'%v'.", c.question, c.expectedName, c.expectedSearch, c.expectedOk, name, search, ok)
}
}
}

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"strconv"
"strings" "strings"
"time" "time"
@ -11,6 +12,7 @@ import (
"github.com/coredns/coredns/middleware" "github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/pkg/dnsutil" "github.com/coredns/coredns/middleware/pkg/dnsutil"
"github.com/coredns/coredns/middleware/proxy" "github.com/coredns/coredns/middleware/proxy"
"github.com/miekg/dns"
"github.com/mholt/caddy" "github.com/mholt/caddy"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
@ -187,7 +189,48 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
continue continue
} }
return nil, fmt.Errorf("incorrect number of arguments for federation, got %v, expected 2", len(args)) 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{
NDots: defautNdots,
HostSearchPath: []string{},
ResolvConfFile: defaultResolvConfFile,
OnNXDOMAIN: defaultOnNXDOMAIN,
}
if len(args) > 3 {
return nil, fmt.Errorf("incorrect number of arguments for autopath, got %v, expected at most 3", len(args))
}
if len(args) > 0 {
ndots, err := strconv.Atoi(args[0])
if err != nil {
return nil, fmt.Errorf("invalid NDOTS argument for autopath, got '%v', expected an integer", ndots)
}
k8s.AutoPath.NDots = ndots
}
if len(args) > 1 {
switch args[1] {
case dns.RcodeToString[dns.RcodeNameError]:
k8s.AutoPath.OnNXDOMAIN = dns.RcodeNameError
case dns.RcodeToString[dns.RcodeSuccess]:
k8s.AutoPath.OnNXDOMAIN = dns.RcodeSuccess
case dns.RcodeToString[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]
}
rc, err := dns.ClientConfigFromFile(k8s.AutoPath.ResolvConfFile)
if err != nil {
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
continue
} }
} }
return k8s, nil return k8s, nil
@ -197,6 +240,9 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
} }
const ( const (
defaultResyncPeriod = 5 * time.Minute defaultResyncPeriod = 5 * time.Minute
defaultPodMode = PodModeDisabled defaultPodMode = PodModeDisabled
defautNdots = 0
defaultResolvConfFile = "/etc/resolv.conf"
defaultOnNXDOMAIN = dns.RcodeServerFailure
) )

View file

@ -2,11 +2,16 @@ package kubernetes
import ( import (
"net" "net"
"os"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/coredns/coredns/middleware/test"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/miekg/dns"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
) )
@ -16,6 +21,13 @@ func parseCidr(cidr string) net.IPNet {
} }
func TestKubernetesParse(t *testing.T) { func TestKubernetesParse(t *testing.T) {
f, rm, err := test.TempFile(os.TempDir(), testResolveConf)
autoPathResolvConfFile := f
if err != nil {
t.Fatalf("Could not create resolv.conf TempFile: %s", err)
}
defer rm()
tests := []struct { tests := []struct {
description string // Human-facing description of test case description string // Human-facing description of test case
input string // Corefile data as string input string // Corefile data as string
@ -30,6 +42,7 @@ func TestKubernetesParse(t *testing.T) {
expectedFallthrough bool expectedFallthrough bool
expectedUpstreams []string expectedUpstreams []string
expectedFederations []Federation expectedFederations []Federation
expectedAutoPath AutoPath
}{ }{
// positive // positive
{ {
@ -46,6 +59,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"kubernetes keyword with multiple zones", "kubernetes keyword with multiple zones",
@ -61,6 +75,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"kubernetes keyword with zone and empty braces", "kubernetes keyword with zone and empty braces",
@ -77,6 +92,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"endpoint keyword with url", "endpoint keyword with url",
@ -94,6 +110,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"namespaces keyword with one namespace", "namespaces keyword with one namespace",
@ -111,6 +128,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
nil, nil,
AutoPath{},
}, },
{ {
"namespaces keyword with multiple namespaces", "namespaces keyword with multiple namespaces",
@ -128,6 +146,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"resync period in seconds", "resync period in seconds",
@ -145,6 +164,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"resync period in minutes", "resync period in minutes",
@ -162,6 +182,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"basic label selector", "basic label selector",
@ -179,6 +200,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"multi-label selector", "multi-label selector",
@ -196,6 +218,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"fully specified valid config", "fully specified valid config",
@ -217,6 +240,7 @@ func TestKubernetesParse(t *testing.T) {
true, true,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// negative // negative
{ {
@ -233,6 +257,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"kubernetes keyword without a zone", "kubernetes keyword without a zone",
@ -248,6 +273,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"endpoint keyword without an endpoint value", "endpoint keyword without an endpoint value",
@ -265,6 +291,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"namespace keyword without a namespace value", "namespace keyword without a namespace value",
@ -282,6 +309,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"resyncperiod keyword without a duration value", "resyncperiod keyword without a duration value",
@ -299,6 +327,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"resync period no units", "resync period no units",
@ -316,6 +345,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"resync period invalid", "resync period invalid",
@ -333,6 +363,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"labels with no selector value", "labels with no selector value",
@ -350,6 +381,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
{ {
"labels with invalid selector value", "labels with invalid selector value",
@ -367,6 +399,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// pods disabled // pods disabled
{ {
@ -385,6 +418,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// pods insecure // pods insecure
{ {
@ -403,6 +437,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// pods verified // pods verified
{ {
@ -421,6 +456,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// pods invalid // pods invalid
{ {
@ -439,6 +475,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// cidrs ok // cidrs ok
{ {
@ -457,6 +494,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// cidrs ok // cidrs ok
{ {
@ -475,6 +513,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// fallthrough invalid // fallthrough invalid
{ {
@ -493,6 +532,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// Valid upstream // Valid upstream
{ {
@ -511,6 +551,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
[]string{"13.14.15.16:53"}, []string{"13.14.15.16:53"},
[]Federation{}, []Federation{},
AutoPath{},
}, },
// Invalid upstream // Invalid upstream
{ {
@ -529,6 +570,7 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
}, },
// Valid federations // Valid federations
{ {
@ -551,6 +593,7 @@ func TestKubernetesParse(t *testing.T) {
{name: "foo", zone: "bar.crawl.com"}, {name: "foo", zone: "bar.crawl.com"},
{name: "fed", zone: "era.tion.com"}, {name: "fed", zone: "era.tion.com"},
}, },
AutoPath{},
}, },
// Invalid federations // Invalid federations
{ {
@ -569,6 +612,104 @@ func TestKubernetesParse(t *testing.T) {
false, false,
nil, nil,
[]Federation{}, []Federation{},
AutoPath{},
},
// autopath
{
"valid autopath",
`kubernetes coredns.local {
autopath 1 NXDOMAIN ` + autoPathResolvConfFile + `
}`,
false,
"",
1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
nil,
nil,
AutoPath{
Enabled: true,
NDots: 1,
HostSearchPath: []string{"bar.com.", "baz.com."},
ResolvConfFile: autoPathResolvConfFile,
OnNXDOMAIN: dns.RcodeNameError,
},
},
{
"invalid autopath RESPONSE",
`kubernetes coredns.local {
autopath 0 CRY
}`,
true,
"invalid RESPONSE argument for autopath",
-1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
nil,
nil,
AutoPath{},
},
{
"invalid autopath NDOTS",
`kubernetes coredns.local {
autopath polka
}`,
true,
"invalid NDOTS argument for autopath",
-1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
nil,
nil,
AutoPath{},
},
{
"invalid autopath RESOLV-CONF",
`kubernetes coredns.local {
autopath 1 NOERROR /wrong/path/to/resolv.conf
}`,
true,
"error when parsing",
-1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
nil,
nil,
AutoPath{},
},
{
"invalid autopath invalid option",
`kubernetes coredns.local {
autopath 1 SERVFAIL ` + autoPathResolvConfFile + ` foo
}`,
true,
"incorrect number of arguments",
-1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
nil,
nil,
AutoPath{},
}, },
} }
@ -669,6 +810,15 @@ 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)
}
} }
} }
const testResolveConf = `nameserver 1.2.3.4
domain foo.com
search bar.com baz.com
options ndots:5
`