mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	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:
		
				
					committed by
					
						 John Belamaric
						John Belamaric
					
				
			
			
				
	
			
			
			
						parent
						
							817f3960b8
						
					
				
				
					commit
					edf71fb168
				
			| @@ -121,6 +121,58 @@ kubernetes coredns.local { | ||||
| 	# Each line consists of the name of the federation, and the domain. | ||||
| 	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 | ||||
| 	# | ||||
| 	# If a query for a record in the cluster zone results in NXDOMAIN, | ||||
|   | ||||
| @@ -2,9 +2,11 @@ package kubernetes | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/coredns/coredns/middleware" | ||||
| 	"github.com/coredns/coredns/middleware/pkg/dnsutil" | ||||
| 	"github.com/coredns/coredns/middleware/rewrite" | ||||
| 	"github.com/coredns/coredns/request" | ||||
|  | ||||
| 	"github.com/miekg/dns" | ||||
| @@ -39,37 +41,55 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M | ||||
| 		zone = state.Name() | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		records, extra []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 | ||||
| 	records, extra, _, err := k.routeRequest(zone, state) | ||||
|  | ||||
| 	if k.AutoPath.Enabled && k.IsNameError(err) { | ||||
| 		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 { | ||||
| 				break | ||||
| 			} | ||||
| 			// Search "svc.cluster.local" and "cluster.local" | ||||
| 			for i := 0; i < 2; i++ { | ||||
| 				path = strings.Join(dns.SplitDomainName(path)[1:], ".") | ||||
| 				state = state.NewWithQuestion(strings.Join([]string{name, path}, "."), state.QType()) | ||||
| 				records, extra, _, err = k.routeRequest(zone, state) | ||||
| 				if !k.IsNameError(err) { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if !k.IsNameError(err) { | ||||
| 				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.Fallthrough { | ||||
| 			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 | ||||
| } | ||||
|  | ||||
| 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. | ||||
| func (k Kubernetes) Name() string { return "kubernetes" } | ||||
|   | ||||
| @@ -28,26 +28,35 @@ import ( | ||||
|  | ||||
| // Kubernetes implements a middleware that connects to a Kubernetes cluster. | ||||
| type Kubernetes struct { | ||||
| 	Next           middleware.Handler | ||||
| 	Zones          []string | ||||
| 	primaryZone    int | ||||
| 	Proxy          proxy.Proxy // Proxy for looking up names during the resolution process | ||||
| 	APIEndpoint    string | ||||
| 	APICertAuth    string | ||||
| 	APIClientCert  string | ||||
| 	APIClientKey   string | ||||
| 	APIConn        dnsController | ||||
| 	ResyncPeriod   time.Duration | ||||
| 	Namespaces     []string | ||||
| 	Federations    []Federation | ||||
| 	LabelSelector  *unversionedapi.LabelSelector | ||||
| 	Selector       *labels.Selector | ||||
| 	PodMode        string | ||||
| 	ReverseCidrs   []net.IPNet | ||||
| 	Fallthrough    bool | ||||
| 	Next          middleware.Handler | ||||
| 	Zones         []string | ||||
| 	primaryZone   int | ||||
| 	Proxy         proxy.Proxy // Proxy for looking up names during the resolution process | ||||
| 	APIEndpoint   string | ||||
| 	APICertAuth   string | ||||
| 	APIClientCert string | ||||
| 	APIClientKey  string | ||||
| 	APIConn       dnsController | ||||
| 	ResyncPeriod  time.Duration | ||||
| 	Namespaces    []string | ||||
| 	Federations   []Federation | ||||
| 	LabelSelector *unversionedapi.LabelSelector | ||||
| 	Selector      *labels.Selector | ||||
| 	PodMode       string | ||||
| 	ReverseCidrs  []net.IPNet | ||||
| 	Fallthrough   bool | ||||
| 	AutoPath | ||||
| 	interfaceAddrs interfaceAddrser | ||||
| } | ||||
|  | ||||
| type AutoPath struct { | ||||
| 	Enabled        bool | ||||
| 	NDots          int | ||||
| 	ResolvConfFile string | ||||
| 	HostSearchPath []string | ||||
| 	OnNXDOMAIN     int | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	// PodModeDisabled is the default value where pod requests are ignored | ||||
| 	PodModeDisabled = "disabled" | ||||
| @@ -97,6 +106,7 @@ var errInvalidRequest = errors.New("invalid query name") | ||||
| var errZoneNotFound = errors.New("zone not found") | ||||
| var errAPIBadPodType = errors.New("expected type *api.Pod") | ||||
| var errPodsDisabled = errors.New("pod records disabled") | ||||
| var errResolvConfReadErr = errors.New("resolv.conf read error") | ||||
|  | ||||
| // 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) { | ||||
| @@ -183,7 +193,7 @@ func (k *Kubernetes) Lookup(state request.Request, name string, typ uint16) (*dn | ||||
|  | ||||
| // IsNameError implements the ServiceBackend interface. | ||||
| 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. | ||||
| @@ -245,7 +255,7 @@ func (k *Kubernetes) InitKubeCache() (err error) { | ||||
| 	} | ||||
|  | ||||
| 	opts := dnsControlOpts{ | ||||
| 		initPodCache: k.PodMode == PodModeVerified, | ||||
| 		initPodCache: (k.PodMode == PodModeVerified || k.AutoPath.Enabled), | ||||
| 	} | ||||
| 	k.APIConn = newdnsController(kubeClient, k.ResyncPeriod, k.Selector, opts) | ||||
|  | ||||
| @@ -448,6 +458,21 @@ func ipFromPodName(podname string) string { | ||||
| 	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) { | ||||
| 	if k.PodMode == PodModeDisabled { | ||||
| 		return pods, errPodsDisabled | ||||
| @@ -634,3 +659,11 @@ func (k *Kubernetes) localPodIP() net.IP { | ||||
| 	} | ||||
| 	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" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @@ -11,6 +12,7 @@ import ( | ||||
| 	"github.com/coredns/coredns/middleware" | ||||
| 	"github.com/coredns/coredns/middleware/pkg/dnsutil" | ||||
| 	"github.com/coredns/coredns/middleware/proxy" | ||||
| 	"github.com/miekg/dns" | ||||
|  | ||||
| 	"github.com/mholt/caddy" | ||||
| 	unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" | ||||
| @@ -187,7 +189,48 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| 						continue | ||||
| 					} | ||||
| 					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 | ||||
| @@ -197,6 +240,9 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	defaultResyncPeriod = 5 * time.Minute | ||||
| 	defaultPodMode      = PodModeDisabled | ||||
| 	defaultResyncPeriod   = 5 * time.Minute | ||||
| 	defaultPodMode        = PodModeDisabled | ||||
| 	defautNdots           = 0 | ||||
| 	defaultResolvConfFile = "/etc/resolv.conf" | ||||
| 	defaultOnNXDOMAIN     = dns.RcodeServerFailure | ||||
| ) | ||||
|   | ||||
| @@ -2,11 +2,16 @@ package kubernetes | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coredns/coredns/middleware/test" | ||||
|  | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/miekg/dns" | ||||
| 	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) { | ||||
| 	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 { | ||||
| 		description           string        // Human-facing description of test case | ||||
| 		input                 string        // Corefile data as string | ||||
| @@ -30,6 +42,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 		expectedFallthrough   bool | ||||
| 		expectedUpstreams     []string | ||||
| 		expectedFederations   []Federation | ||||
| 		expectedAutoPath      AutoPath | ||||
| 	}{ | ||||
| 		// positive | ||||
| 		{ | ||||
| @@ -46,6 +59,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"kubernetes keyword with multiple zones", | ||||
| @@ -61,6 +75,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"kubernetes keyword with zone and empty braces", | ||||
| @@ -77,6 +92,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"endpoint keyword with url", | ||||
| @@ -94,6 +110,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"namespaces keyword with one namespace", | ||||
| @@ -111,6 +128,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			nil, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"namespaces keyword with multiple namespaces", | ||||
| @@ -128,6 +146,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"resync period in seconds", | ||||
| @@ -145,6 +164,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"resync period in minutes", | ||||
| @@ -162,6 +182,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"basic label selector", | ||||
| @@ -179,6 +200,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"multi-label selector", | ||||
| @@ -196,6 +218,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"fully specified valid config", | ||||
| @@ -217,6 +240,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// negative | ||||
| 		{ | ||||
| @@ -233,6 +257,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"kubernetes keyword without a zone", | ||||
| @@ -248,6 +273,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"endpoint keyword without an endpoint value", | ||||
| @@ -265,6 +291,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"namespace keyword without a namespace value", | ||||
| @@ -282,6 +309,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"resyncperiod keyword without a duration value", | ||||
| @@ -299,6 +327,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"resync period no units", | ||||
| @@ -316,6 +345,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"resync period invalid", | ||||
| @@ -333,6 +363,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"labels with no selector value", | ||||
| @@ -350,6 +381,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"labels with invalid selector value", | ||||
| @@ -367,6 +399,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// pods disabled | ||||
| 		{ | ||||
| @@ -385,6 +418,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// pods insecure | ||||
| 		{ | ||||
| @@ -403,6 +437,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// pods verified | ||||
| 		{ | ||||
| @@ -421,6 +456,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// pods invalid | ||||
| 		{ | ||||
| @@ -439,6 +475,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// cidrs ok | ||||
| 		{ | ||||
| @@ -457,6 +494,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// cidrs ok | ||||
| 		{ | ||||
| @@ -475,6 +513,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// fallthrough invalid | ||||
| 		{ | ||||
| @@ -493,6 +532,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// Valid upstream | ||||
| 		{ | ||||
| @@ -511,6 +551,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			[]string{"13.14.15.16:53"}, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// Invalid upstream | ||||
| 		{ | ||||
| @@ -529,6 +570,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]Federation{}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// Valid federations | ||||
| 		{ | ||||
| @@ -551,6 +593,7 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 				{name: "foo", zone: "bar.crawl.com"}, | ||||
| 				{name: "fed", zone: "era.tion.com"}, | ||||
| 			}, | ||||
| 			AutoPath{}, | ||||
| 		}, | ||||
| 		// Invalid federations | ||||
| 		{ | ||||
| @@ -569,6 +612,104 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			nil, | ||||
| 			[]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 | ||||
| ` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user