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:
parent
817f3960b8
commit
edf71fb168
6 changed files with 407 additions and 51 deletions
|
@ -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,
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue