mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	dont require/allow "_" prefix for srv wildcard fields (#472)
* dont require/allow "_" prefix for srv wildcard fields * streamline parse/validation of req name * removing nametemplate * error when zone not found, loopify unit tests
This commit is contained in:
		
				
					committed by
					
						 Miek Gieben
						Miek Gieben
					
				
			
			
				
	
			
			
			
						parent
						
							b6a2a5aeaa
						
					
				
				
					commit
					a6d232a622
				
			| @@ -1,15 +1,14 @@ | ||||
| # kubernetes | ||||
|  | ||||
| *kubernetes* enables reading zone data from a kubernetes cluster. Record names | ||||
| are constructed as "myservice.mynamespace.coredns.local" where: | ||||
| are constructed as "myservice.mynamespace.type.coredns.local" where: | ||||
|  | ||||
| * "myservice" is the name of the k8s service (this may include multiple DNS labels, | ||||
|   such as "c1.myservice"), | ||||
| * "mynamespace" is the k8s namespace for the service, and | ||||
| * "type" is svc or pod | ||||
| * "coredns.local" is the zone configured for `kubernetes`. | ||||
|  | ||||
| The record name format can be changed by specifying a name template in the Corefile. | ||||
|  | ||||
| ## Syntax | ||||
|  | ||||
| ~~~ | ||||
| @@ -50,9 +49,6 @@ This is the default kubernetes setup, with everything specified in full: | ||||
|         # The tls cert, key and the CA cert filenames | ||||
|         tls cert key cacert | ||||
| 		 | ||||
|         # Assemble k8s record names with the template | ||||
|         template {service}.{namespace}.{type}.{zone} | ||||
| 		 | ||||
|         # Only expose the k8s namespace "demo" | ||||
|         namespaces demo | ||||
| 		 | ||||
| @@ -64,15 +60,15 @@ This is the default kubernetes setup, with everything specified in full: | ||||
|         # "application=nginx" in the staging or qa environments. | ||||
|         #labels environment in (staging, qa),application=nginx | ||||
| 		 | ||||
| 		# The mode of responding to pod A record requests.  | ||||
| 		# e.g 1-2-3-4.ns.pod.zone.  This option is provided to allow use of | ||||
| 		# SSL certs when connecting directly to pods. | ||||
| 		# Valid values: disabled, verified, insecure | ||||
| 		#  disabled: default. ignore pod requests, always returning NXDOMAIN | ||||
| 		#  insecure: Always return an A record with IP from request (without  | ||||
| 		#            checking k8s).  This option is is vulnerable to abuse if | ||||
| 		# 	         used maliciously in conjuction with wildcard SSL certs. | ||||
| 		pods disabled | ||||
|         # The mode of responding to pod A record requests.  | ||||
|         # e.g 1-2-3-4.ns.pod.zone.  This option is provided to allow use of | ||||
|         # SSL certs when connecting directly to pods. | ||||
|         # Valid values: disabled, verified, insecure | ||||
|         #  disabled: default. ignore pod requests, always returning NXDOMAIN | ||||
|         #  insecure: Always return an A record with IP from request (without  | ||||
|         #            checking k8s).  This option is is vulnerable to abuse if | ||||
|         #            used maliciously in conjuction with wildcard SSL certs. | ||||
| 	pods disabled | ||||
|     } | ||||
|     # Perform DNS response caching for the coredns.local zone | ||||
|     # Cache timeout is specified by an integer in seconds | ||||
| @@ -82,22 +78,12 @@ This is the default kubernetes setup, with everything specified in full: | ||||
|  | ||||
| Defaults: | ||||
| * If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed. | ||||
| * If the `template` keyword is omitted, the default template of "{service}.{namespace}.{type}.{zone}" is used. | ||||
| * If the `resyncperiod` keyword is omitted, the default resync period is 5 minutes. | ||||
| * The `labels` keyword is only used when filtering results based on kubernetes label selector syntax | ||||
|   is required. The label selector syntax is described in the kubernetes API documentation at: | ||||
|   http://kubernetes.io/docs/user-guide/labels/ | ||||
| * If the `pods` keyword is omitted, all pod type requests will result in NXDOMAIN | ||||
|  | ||||
| ### Template Syntax | ||||
| Record name templates can be constructed using the symbolic elements: | ||||
|  | ||||
| | template symbol | description                                                         | | ||||
| | `{service}`     | Kubernetes object/service name.                                     | | ||||
| | `{namespace}`   | The kubernetes namespace.                                           | | ||||
| | `{type}`        | The type of the kubernetes object. Supports values 'svc' and 'pod'. | | ||||
| | `{zone}`        | The zone configured for the kubernetes middleware.                  | | ||||
|  | ||||
| ### Basic Setup | ||||
|  | ||||
| #### Launch Kubernetes | ||||
| @@ -146,7 +132,6 @@ Build CoreDNS and launch using this configuration file: | ||||
|     kubernetes coredns.local { | ||||
|         resyncperiod 5m | ||||
|         endpoint http://localhost:8080 | ||||
|         template {service}.{namespace}.{type}.{zone} | ||||
|         namespaces demo | ||||
|         # Only expose the records for kubernetes objects | ||||
|         # that matches this label selector.  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import ( | ||||
|  | ||||
| 	"github.com/miekg/coredns/middleware" | ||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||
| 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" | ||||
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | ||||
| 	dnsstrings "github.com/miekg/coredns/middleware/pkg/strings" | ||||
| 	"github.com/miekg/coredns/middleware/proxy" | ||||
| @@ -38,7 +37,6 @@ type Kubernetes struct { | ||||
| 	APIClientKey  string | ||||
| 	APIConn       *dnsController | ||||
| 	ResyncPeriod  time.Duration | ||||
| 	NameTemplate  *nametemplate.Template | ||||
| 	Namespaces    []string | ||||
| 	LabelSelector *unversionedapi.LabelSelector | ||||
| 	Selector      *labels.Selector | ||||
| @@ -69,16 +67,22 @@ type pod struct { | ||||
| 	addr      string | ||||
| } | ||||
|  | ||||
| type recordRequest struct { | ||||
| 	port, protocol, endpoint, service, namespace, typeName, zone string | ||||
| } | ||||
|  | ||||
| var errNoItems = errors.New("no items found") | ||||
| var errNsNotExposed = errors.New("namespace is not exposed") | ||||
| var errInvalidRequest = errors.New("invalid query name") | ||||
|  | ||||
| // Services implements the ServiceBackend interface. | ||||
| func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) { | ||||
| 	if state.Type() == "SRV" && !ValidSRV(state.Name()) { | ||||
| 		return nil, nil, errInvalidRequest | ||||
|  | ||||
| 	r, e := k.parseRequest(state.Name(), state.Type()) | ||||
| 	if e != nil { | ||||
| 		return nil, nil, e | ||||
| 	} | ||||
| 	s, e := k.Records(state.Name(), exact) | ||||
| 	s, e := k.Records(r) | ||||
| 	return s, nil, e // Haven't implemented debug queries yet. | ||||
| } | ||||
|  | ||||
| @@ -177,85 +181,94 @@ func (k *Kubernetes) InitKubeCache() error { | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // getZoneForName returns the zone string that matches the name and a | ||||
| // list of the DNS labels from name that are within the zone. | ||||
| // For example, if "coredns.local" is a zone configured for the | ||||
| // Kubernetes middleware, then getZoneForName("a.b.coredns.local") | ||||
| // will return ("coredns.local", ["a", "b"]). | ||||
| func (k *Kubernetes) getZoneForName(name string) (string, []string) { | ||||
| 	var zone string | ||||
| 	var serviceSegments []string | ||||
| func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest, err error) { | ||||
| 	// 3 Possible cases | ||||
| 	//   SRV Request: _port._protocol.service.namespace.type.zone | ||||
| 	//   A Request (endpoint): endpoint.service.namespace.type.zone | ||||
| 	//   A Request (service): service.namespace.type.zone | ||||
|  | ||||
| 	// separate zone from rest of lowerCasedName | ||||
| 	var segs []string | ||||
| 	for _, z := range k.Zones { | ||||
| 		if dns.IsSubDomain(z, name) { | ||||
| 			zone = z | ||||
| 		if dns.IsSubDomain(z, lowerCasedName) { | ||||
| 			r.zone = z | ||||
|  | ||||
| 			serviceSegments = dns.SplitDomainName(name) | ||||
| 			serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)] | ||||
| 			segs = dns.SplitDomainName(lowerCasedName) | ||||
| 			segs = segs[:len(segs)-dns.CountLabel(r.zone)] | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return zone, serviceSegments | ||||
| } | ||||
|  | ||||
| // stripSRVPrefix separates out the port and protocol segments, if present | ||||
| // If not present, assume all ports/protocols (e.g. wildcard) | ||||
| func stripSRVPrefix(name []string) (string, string, []string) { | ||||
| 	if name[0][0] == '_' && name[1][0] == '_' { | ||||
| 		return name[0][1:], name[1][1:], name[2:] | ||||
| 	if r.zone == "" { | ||||
| 		return r, errors.New("zone not found") | ||||
| 	} | ||||
| 	// no srv prefix present | ||||
| 	return "*", "*", name | ||||
| } | ||||
|  | ||||
| func stripEndpointName(name []string) (endpoint string, nameOut []string) { | ||||
| 	if len(name) == 4 { | ||||
| 		return strings.ToLower(name[0]), name[1:] | ||||
| 	offset := 0 | ||||
| 	if len(segs) == 5 { | ||||
| 		// This is a SRV style request, get first two elements as port and | ||||
| 		// protocol, stripping leading underscores if present. | ||||
| 		if segs[0][0] == '_' { | ||||
| 			r.port = segs[0][1:] | ||||
| 		} else { | ||||
| 			r.port = segs[0] | ||||
| 			if !symbolContainsWildcard(r.port) { | ||||
| 				return r, errors.New("srv port must start with an underscore or be a wildcard") | ||||
| 			} | ||||
| 		} | ||||
| 		if segs[1][0] == '_' { | ||||
| 			r.protocol = segs[1][1:] | ||||
| 			if r.protocol != "tcp" && r.protocol != "udp" { | ||||
| 				return r, errors.New("invalid srv protocol: " + r.protocol) | ||||
| 			} | ||||
| 		} else { | ||||
| 			r.protocol = segs[1] | ||||
| 			if !symbolContainsWildcard(r.protocol) { | ||||
| 				return r, errors.New("srv protocol must start with an underscore or be a wildcard") | ||||
| 			} | ||||
| 		} | ||||
| 		offset = 2 | ||||
| 	} else if len(segs) == 4 { | ||||
| 		// This is an endpoint A style request. Get first element as endpoint. | ||||
| 		r.endpoint = segs[0] | ||||
| 		offset = 1 | ||||
| 	} | ||||
| 	return "", name | ||||
|  | ||||
| 	// SRV requests require a port and protocol | ||||
| 	if qtype == "SRV" { | ||||
| 		if r.port == "" || r.protocol == "" { | ||||
| 			return r, errors.New("invalid srv request") | ||||
| 		} | ||||
| 	} | ||||
| 	// A requests cannot have port/protocol | ||||
| 	if qtype == "A" { | ||||
| 		if r.port != "" && r.protocol != "" { | ||||
| 			return r, errors.New("invalid a request") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(segs) == (offset + 3) { | ||||
| 		r.service = segs[offset] | ||||
| 		r.namespace = segs[offset+1] | ||||
| 		r.typeName = segs[offset+2] | ||||
|  | ||||
| 		return r, nil | ||||
| 	} | ||||
|  | ||||
| 	return r, errors.New("invalid request") | ||||
|  | ||||
| } | ||||
|  | ||||
| // Records looks up services in kubernetes. If exact is true, it will lookup | ||||
| // just this name. This is used when find matches when completing SRV lookups | ||||
| // for instance. | ||||
| func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | ||||
| 	var ( | ||||
| 		serviceName string | ||||
| 		namespace   string | ||||
| 		typeName    string | ||||
| 	) | ||||
|  | ||||
| 	zone, serviceSegments := k.getZoneForName(name) | ||||
| 	port, protocol, serviceSegments := stripSRVPrefix(serviceSegments) | ||||
| 	endpointname, serviceSegments := stripEndpointName(serviceSegments) | ||||
| 	if len(serviceSegments) < 3 { | ||||
| 		return nil, errNoItems | ||||
| 	} | ||||
|  | ||||
| 	serviceName = serviceSegments[0] | ||||
| 	namespace = serviceSegments[1] | ||||
| 	typeName = serviceSegments[2] | ||||
|  | ||||
| 	if namespace == "" { | ||||
| 		err := errors.New("Parsing query string did not produce a namespace value. Assuming wildcard namespace.") | ||||
| 		log.Printf("[WARN] %v\n", err) | ||||
| 		namespace = "*" | ||||
| 	} | ||||
|  | ||||
| 	if serviceName == "" { | ||||
| 		err := errors.New("Parsing query string did not produce a serviceName value. Assuming wildcard serviceName.") | ||||
| 		log.Printf("[WARN] %v\n", err) | ||||
| 		serviceName = "*" | ||||
| 	} | ||||
| func (k *Kubernetes) Records(r recordRequest) ([]msg.Service, error) { | ||||
|  | ||||
| 	// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile | ||||
| 	// Case where namespace contains a wildcard is handled in Get(...) method. | ||||
| 	if (!symbolContainsWildcard(namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(namespace, k.Namespaces)) { | ||||
| 	if (!symbolContainsWildcard(r.namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(r.namespace, k.Namespaces)) { | ||||
| 		return nil, errNsNotExposed | ||||
| 	} | ||||
|  | ||||
| 	services, pods, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName) | ||||
| 	services, pods, err := k.Get(r) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -264,7 +277,7 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | ||||
| 		return nil, errNoItems | ||||
| 	} | ||||
|  | ||||
| 	records := k.getRecordsForK8sItems(services, pods, zone) | ||||
| 	records := k.getRecordsForK8sItems(services, pods, r.zone) | ||||
| 	return records, nil | ||||
| } | ||||
|  | ||||
| @@ -320,18 +333,6 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone | ||||
| 	return records | ||||
| } | ||||
|  | ||||
| // Get retrieves matching data from the cache. | ||||
| func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, pods []pod, err error) { | ||||
| 	switch { | ||||
| 	case typeName == "pod": | ||||
| 		pods, err = k.findPods(namespace, servicename) | ||||
| 		return nil, pods, err | ||||
| 	default: | ||||
| 		services, err = k.findServices(namespace, servicename, endpointname, port, protocol) | ||||
| 		return services, nil, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ipFromPodName(podname string) string { | ||||
| 	if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { | ||||
| 		return strings.Replace(podname, "-", ".", -1) | ||||
| @@ -362,18 +363,30 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) | ||||
|  | ||||
| } | ||||
|  | ||||
| func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, protocol string) ([]service, error) { | ||||
| // Get retrieves matching data from the cache. | ||||
| func (k *Kubernetes) Get(r recordRequest) (services []service, pods []pod, err error) { | ||||
| 	switch { | ||||
| 	case r.typeName == "pod": | ||||
| 		pods, err = k.findPods(r.namespace, r.service) | ||||
| 		return nil, pods, err | ||||
| 	default: | ||||
| 		services, err = k.findServices(r) | ||||
| 		return services, nil, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (k *Kubernetes) findServices(r recordRequest) ([]service, error) { | ||||
| 	serviceList := k.APIConn.ServiceList() | ||||
|  | ||||
| 	var resultItems []service | ||||
|  | ||||
| 	nsWildcard := symbolContainsWildcard(namespace) | ||||
| 	serviceWildcard := symbolContainsWildcard(servicename) | ||||
| 	portWildcard := symbolContainsWildcard(port) | ||||
| 	protocolWildcard := symbolContainsWildcard(protocol) | ||||
| 	nsWildcard := symbolContainsWildcard(r.namespace) | ||||
| 	serviceWildcard := symbolContainsWildcard(r.service) | ||||
| 	portWildcard := symbolContainsWildcard(r.port) || r.port == "" | ||||
| 	protocolWildcard := symbolContainsWildcard(r.protocol) || r.protocol == "" | ||||
|  | ||||
| 	for _, svc := range serviceList { | ||||
| 		if !(symbolMatches(namespace, svc.Namespace, nsWildcard) && symbolMatches(servicename, svc.Name, serviceWildcard)) { | ||||
| 		if !(symbolMatches(r.namespace, svc.Namespace, nsWildcard) && symbolMatches(r.service, svc.Name, serviceWildcard)) { | ||||
| 			continue | ||||
| 		} | ||||
| 		// If namespace has a wildcard, filter results against Corefile namespace list. | ||||
| @@ -384,7 +397,7 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr | ||||
| 		s := service{name: svc.Name, namespace: svc.Namespace, addr: svc.Spec.ClusterIP} | ||||
| 		if s.addr != api.ClusterIPNone { | ||||
| 			for _, p := range svc.Spec.Ports { | ||||
| 				if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { | ||||
| 				if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { | ||||
| 					continue | ||||
| 				} | ||||
| 				s.ports = append(s.ports, p) | ||||
| @@ -405,10 +418,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr | ||||
| 				for _, addr := range eps.Addresses { | ||||
| 					for _, p := range eps.Ports { | ||||
| 						ephostname := endpointHostname(addr) | ||||
| 						if endpointname != "" && endpointname != ephostname { | ||||
| 						if r.endpoint != "" && r.endpoint != ephostname { | ||||
| 							continue | ||||
| 						} | ||||
| 						if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { | ||||
| 						if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { | ||||
| 							continue | ||||
| 						} | ||||
| 						s.endpoints = append(s.endpoints, endpoint{addr: addr, port: p}) | ||||
| @@ -422,16 +435,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr | ||||
| } | ||||
|  | ||||
| func symbolMatches(queryString, candidateString string, wildcard bool) bool { | ||||
| 	result := false | ||||
| 	switch { | ||||
| 	case !wildcard: | ||||
| 		result = (queryString == candidateString) | ||||
| 	case queryString == "*": | ||||
| 		result = true | ||||
| 	case queryString == "any": | ||||
| 		result = true | ||||
| 	if wildcard { | ||||
| 		return true | ||||
| 	} | ||||
| 	return result | ||||
| 	return queryString == candidateString | ||||
| } | ||||
|  | ||||
| // getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument | ||||
| @@ -476,57 +483,3 @@ func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service { | ||||
| func symbolContainsWildcard(symbol string) bool { | ||||
| 	return (strings.Contains(symbol, "*") || (symbol == "any")) | ||||
| } | ||||
|  | ||||
| // ValidSRV parses a server record validating _port._proto. prefix labels. | ||||
| // The valid schema is: | ||||
| //   * Fist two segments must start with an "_", | ||||
| //   * Second segment must be one of _tcp|_udp|_*|_any | ||||
| func ValidSRV(name string) bool { | ||||
|  | ||||
| 	// Does it start with a "_" ? | ||||
| 	if len(name) > 0 && name[0] != '_' { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// First label | ||||
| 	first, end := dns.NextLabel(name, 0) | ||||
| 	if end { | ||||
| 		return false | ||||
| 	} | ||||
| 	// Second label | ||||
| 	off, end := dns.NextLabel(name, first) | ||||
| 	if end { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// first:off has captured _tcp. or _udp. (if present) | ||||
| 	second := name[first:off] | ||||
| 	if len(second) > 0 && second[0] != '_' { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// A bit convoluted to avoid strings.ToLower | ||||
| 	if len(second) == 5 { | ||||
| 		// matches _tcp | ||||
| 		if (second[1] == 't' || second[1] == 'T') && (second[2] == 'c' || second[2] == 'C') && | ||||
| 			(second[3] == 'p' || second[3] == 'P') { | ||||
| 			return true | ||||
| 		} | ||||
| 		// matches _udp | ||||
| 		if (second[1] == 'u' || second[1] == 'U') && (second[2] == 'd' || second[2] == 'D') && | ||||
| 			(second[3] == 'p' || second[3] == 'P') { | ||||
| 			return true | ||||
| 		} | ||||
| 		// matches _any | ||||
| 		if (second[1] == 'a' || second[1] == 'A') && (second[2] == 'n' || second[2] == 'N') && | ||||
| 			(second[3] == 'y' || second[3] == 'Y') { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	// matches _* | ||||
| 	if len(second) == 3 && second[1] == '*' { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package kubernetes | ||||
|  | ||||
| import "testing" | ||||
| import "reflect" | ||||
|  | ||||
| // Test data for TestSymbolContainsWildcard cases. | ||||
| var testdataSymbolContainsWildcard = []struct { | ||||
| @@ -23,3 +24,114 @@ func TestSymbolContainsWildcard(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func expectString(t *testing.T, function, qtype, query string, r *recordRequest, field, expected string) { | ||||
| 	ref := reflect.ValueOf(r) | ||||
| 	ref_f := reflect.Indirect(ref).FieldByName(field) | ||||
| 	got := ref_f.String() | ||||
| 	if got != expected { | ||||
| 		t.Errorf("Expected %v(%v, \"%v\") to get %v == \"%v\". Instead got \"%v\".", function, query, qtype, field, expected, got) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParseRequest(t *testing.T) { | ||||
|  | ||||
| 	var tcs map[string]string | ||||
|  | ||||
| 	k := Kubernetes{Zones: []string{"inter.webs.test"}} | ||||
| 	f := "parseRequest" | ||||
|  | ||||
| 	// Test a valid SRV request | ||||
| 	// | ||||
| 	query := "_http._tcp.webs.mynamespace.svc.inter.webs.test." | ||||
| 	r, e := k.parseRequest(query, "SRV") | ||||
| 	if e != nil { | ||||
| 		t.Errorf("Expected no error from parseRequest(%v, \"SRV\"). Instead got '%v'.", query, e) | ||||
| 	} | ||||
|  | ||||
| 	tcs = map[string]string{ | ||||
| 		"port":      "http", | ||||
| 		"protocol":  "tcp", | ||||
| 		"endpoint":  "", | ||||
| 		"service":   "webs", | ||||
| 		"namespace": "mynamespace", | ||||
| 		"typeName":  "svc", | ||||
| 		"zone":      "inter.webs.test", | ||||
| 	} | ||||
| 	for field, expected := range tcs { | ||||
| 		expectString(t, f, "SRV", query, &r, field, expected) | ||||
| 	} | ||||
|  | ||||
| 	// Test wildcard acceptance | ||||
| 	// | ||||
| 	query = "*.any.*.any.svc.inter.webs.test." | ||||
| 	r, e = k.parseRequest(query, "SRV") | ||||
| 	if e != nil { | ||||
| 		t.Errorf("Expected no error from parseRequest(\"%v\", \"SRV\"). Instead got '%v'.", query, e) | ||||
| 	} | ||||
|  | ||||
| 	tcs = map[string]string{ | ||||
| 		"port":      "*", | ||||
| 		"protocol":  "any", | ||||
| 		"endpoint":  "", | ||||
| 		"service":   "*", | ||||
| 		"namespace": "any", | ||||
| 		"typeName":  "svc", | ||||
| 		"zone":      "inter.webs.test", | ||||
| 	} | ||||
| 	for field, expected := range tcs { | ||||
| 		expectString(t, f, "SRV", query, &r, field, expected) | ||||
| 	} | ||||
|  | ||||
| 	// Test A request of endpoint | ||||
| 	// | ||||
| 	query = "1-2-3-4.webs.mynamespace.svc.inter.webs.test." | ||||
| 	r, e = k.parseRequest(query, "A") | ||||
| 	if e != nil { | ||||
| 		t.Errorf("Expected no error from parseRequest(\"%v\", \"A\"). Instead got '%v'.", query, e) | ||||
| 	} | ||||
| 	tcs = map[string]string{ | ||||
| 		"port":      "", | ||||
| 		"protocol":  "", | ||||
| 		"endpoint":  "1-2-3-4", | ||||
| 		"service":   "webs", | ||||
| 		"namespace": "mynamespace", | ||||
| 		"typeName":  "svc", | ||||
| 		"zone":      "inter.webs.test", | ||||
| 	} | ||||
| 	for field, expected := range tcs { | ||||
| 		expectString(t, f, "A", query, &r, field, expected) | ||||
| 	} | ||||
|  | ||||
| 	// Invalid query tests | ||||
| 	// | ||||
|  | ||||
| 	invalidAQueries := []string{ | ||||
| 		"_http._tcp.webs.mynamespace.svc.inter.webs.test.", // A requests cannot have port or protocol | ||||
| 		"servname.ns1.srv.inter.nets.test.",                // A requests must have zone that matches corefile | ||||
|  | ||||
| 	} | ||||
| 	for _, q := range invalidAQueries { | ||||
| 		_, e = k.parseRequest(q, "A") | ||||
| 		if e == nil { | ||||
| 			t.Errorf("Expected error from %v(\"%v\", \"A\").", f, q) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	invalidSRVQueries := []string{ | ||||
| 		"webs.mynamespace.svc.inter.webs.test.",            // SRV requests must have port and protocol | ||||
| 		"_http._pcp.webs.mynamespace.svc.inter.webs.test.", // SRV protocol must be tcp or udp | ||||
| 		"_http._tcp.ep.webs.ns.svc.inter.webs.test.",       // SRV requests cannot have an endpoint | ||||
| 		"_*._*.webs.mynamespace.svc.inter.webs.test.",      // SRV request with invalid wildcards | ||||
| 		"_http._tcp", | ||||
| 		"_tcp.test.", | ||||
| 		".", | ||||
| 	} | ||||
|  | ||||
| 	for _, q := range invalidSRVQueries { | ||||
| 		_, e = k.parseRequest(q, "SRV") | ||||
| 		if e == nil { | ||||
| 			t.Errorf("Expected error from %v(\"%v\", \"SRV\").", f, q) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -12,7 +12,11 @@ import ( | ||||
| ) | ||||
|  | ||||
| func (k Kubernetes) records(state request.Request, exact bool) ([]msg.Service, error) { | ||||
| 	services, err := k.Records(state.Name(), exact) | ||||
| 	r, err := k.parseRequest(state.Name(), state.Type()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	services, err := k.Records(r) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|   | ||||
| @@ -1,195 +0,0 @@ | ||||
| package nametemplate | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strings" | ||||
|  | ||||
| 	dns_strings "github.com/miekg/coredns/middleware/pkg/strings" | ||||
| ) | ||||
|  | ||||
| // Likely symbols that require support: | ||||
| // {id} | ||||
| // {ip} | ||||
| // {portname} | ||||
| // {protocolname} | ||||
| // {servicename} | ||||
| // {namespace} | ||||
| // {type}              "svc" or "pod" | ||||
| // {zone} | ||||
|  | ||||
| // SkyDNS normal services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}" | ||||
| // This resolves to the cluster IP of the service. | ||||
|  | ||||
| // SkyDNS headless services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}" | ||||
| // This resolves to the set of IPs of the pods selected by the Service. Clients are expected to | ||||
| // consume the set or else use round-robin selection from the set. | ||||
|  | ||||
| var symbols = map[string]string{ | ||||
| 	"service":   "{service}", | ||||
| 	"namespace": "{namespace}", | ||||
| 	"type":      "{type}", | ||||
| 	"zone":      "{zone}", | ||||
| } | ||||
|  | ||||
| var types = []string{ | ||||
| 	"svc", | ||||
| 	"pod", | ||||
| } | ||||
|  | ||||
| var requiredSymbols = []string{ | ||||
| 	"namespace", | ||||
| 	"service", | ||||
| } | ||||
|  | ||||
| // TODO: Validate that provided NameTemplate string only contains: | ||||
| //			* valid, known symbols, or | ||||
| //			* static strings | ||||
|  | ||||
| // TODO: Support collapsing multiple segments into a symbol. Either: | ||||
| //			* all left-over segments are used as the "service" name, or | ||||
| //			* some scheme like "{namespace}.{namespace}" means use | ||||
| //			  segments concatenated with a "." for the namespace, or | ||||
| //			* {namespace2:4} means use segements 2->4 for the namespace. | ||||
|  | ||||
| // TODO: possibly need to store length of segmented format to handle cases | ||||
| //       where query string segments to a shorter or longer list than the template. | ||||
| //		 When query string segments to shorter than template: | ||||
| //			* either wildcards are being used, or | ||||
| //			* we are not looking up an A, AAAA, or SRV record (eg NS), or | ||||
| //			* we can just short-circuit failure before hitting the k8s API. | ||||
| //		 Where the query string is longer than the template, need to define which | ||||
| //		 symbol consumes the other segments. Most likely this would be the servicename. | ||||
| //		 Also consider how to handle static strings in the format template. | ||||
|  | ||||
| // Template holds the kubernetes template. | ||||
| type Template struct { | ||||
| 	formatString string | ||||
| 	splitFormat  []string | ||||
| 	// Element is a map of element name :: index in the segmented record name for the named element | ||||
| 	Element map[string]int | ||||
| } | ||||
|  | ||||
| // SetTemplate use the string s the set the template. | ||||
| func (t *Template) SetTemplate(s string) error { | ||||
| 	var err error | ||||
|  | ||||
| 	t.Element = map[string]int{} | ||||
|  | ||||
| 	t.formatString = s | ||||
| 	t.splitFormat = strings.Split(t.formatString, ".") | ||||
| 	for templateIndex, v := range t.splitFormat { | ||||
| 		elementPositionSet := false | ||||
| 		for name, symbol := range symbols { | ||||
| 			if v == symbol { | ||||
| 				t.Element[name] = templateIndex | ||||
| 				elementPositionSet = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !elementPositionSet { | ||||
| 			if strings.Contains(v, "{") { | ||||
| 				err = errors.New("Record name template contains the unknown symbol '" + v + "'") | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err == nil && !t.IsValid() { | ||||
| 		err = errors.New("Record name template does not pass NameTemplate validation") | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // TODO: Find a better way to pull the data segments out of the | ||||
| //       query string based on the template. Perhaps it is better | ||||
| //		 to treat the query string segments as a reverse stack and | ||||
| //       step down the stack to find the right element. | ||||
|  | ||||
| // ZoneFromSegmentArray returns the zone string from the segments. | ||||
| func (t *Template) ZoneFromSegmentArray(segments []string) string { | ||||
| 	index, ok := t.Element["zone"] | ||||
| 	if !ok { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return strings.Join(segments[index:], ".") | ||||
| } | ||||
|  | ||||
| // NamespaceFromSegmentArray returns the namespace string from the segments. | ||||
| func (t *Template) NamespaceFromSegmentArray(segments []string) string { | ||||
| 	return t.symbolFromSegmentArray("namespace", segments) | ||||
| } | ||||
|  | ||||
| // ServiceFromSegmentArray returns the service string from the segments. | ||||
| func (t *Template) ServiceFromSegmentArray(segments []string) string { | ||||
| 	return t.symbolFromSegmentArray("service", segments) | ||||
| } | ||||
|  | ||||
| // TypeFromSegmentArray returns the type string from the segments. | ||||
| func (t *Template) TypeFromSegmentArray(segments []string) string { | ||||
| 	typeSegment := t.symbolFromSegmentArray("type", segments) | ||||
|  | ||||
| 	// Limit type to known types symbols | ||||
| 	if dns_strings.StringInSlice(typeSegment, types) { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return typeSegment | ||||
| } | ||||
|  | ||||
| func (t *Template) symbolFromSegmentArray(symbol string, segments []string) string { | ||||
| 	index, ok := t.Element[symbol] | ||||
| 	if !ok { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return segments[index] | ||||
| } | ||||
|  | ||||
| // RecordNameFromNameValues returns the string produced by applying the | ||||
| // values to the NameTemplate format string. | ||||
| func (t *Template) RecordNameFromNameValues(values NameValues) string { | ||||
| 	recordName := make([]string, len(t.splitFormat)) | ||||
| 	copy(recordName[:], t.splitFormat) | ||||
|  | ||||
| 	for name, index := range t.Element { | ||||
| 		if index == -1 { | ||||
| 			continue | ||||
| 		} | ||||
| 		switch name { | ||||
| 		case "type": | ||||
| 			recordName[index] = values.TypeName | ||||
| 		case "service": | ||||
| 			recordName[index] = values.ServiceName | ||||
| 		case "namespace": | ||||
| 			recordName[index] = values.Namespace | ||||
| 		case "zone": | ||||
| 			recordName[index] = values.Zone | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.Join(recordName, ".") | ||||
| } | ||||
|  | ||||
| // IsValid returns true if the template has all the required symbols, false otherwise. | ||||
| func (t *Template) IsValid() bool { | ||||
| 	result := true | ||||
|  | ||||
| 	// Ensure that all requiredSymbols are found in NameTemplate | ||||
| 	for _, symbol := range requiredSymbols { | ||||
| 		if _, ok := t.Element[symbol]; !ok { | ||||
| 			result = false | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // NameValues contains a number of values. | ||||
| // TODO(...): better docs. | ||||
| type NameValues struct { | ||||
| 	ServiceName string | ||||
| 	Namespace   string | ||||
| 	TypeName    string | ||||
| 	Zone        string | ||||
| } | ||||
| @@ -1,127 +0,0 @@ | ||||
| package nametemplate | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	zone      = 0 | ||||
| 	namespace = 1 | ||||
| 	service   = 2 | ||||
| ) | ||||
|  | ||||
| // Map of format string :: expected locations of name symbols in the format. | ||||
| // -1 value indicates that symbol does not exist in format. | ||||
| var exampleTemplates = map[string][]int{ | ||||
| 	"{service}.{namespace}.{type}.{zone}": {3, 1, 0}, // service symbol expected @ position 0, namespace @ 1, zone @ 3 | ||||
| 	"{namespace}.{type}.{zone}":           {2, 0, -1}, | ||||
| 	"": {-1, -1, -1}, | ||||
| } | ||||
|  | ||||
| func TestSetTemplate(t *testing.T) { | ||||
| 	for s, expectedValue := range exampleTemplates { | ||||
|  | ||||
| 		n := new(Template) | ||||
| 		n.SetTemplate(s) | ||||
|  | ||||
| 		// check the indexes resulting from calling SetTemplate() against expectedValues | ||||
| 		if expectedValue[zone] != -1 { | ||||
| 			if n.Element["zone"] != expectedValue[zone] { | ||||
| 				t.Errorf("Expected zone at index '%v', instead found at index '%v' for format string '%v'", expectedValue[zone], n.Element["zone"], s) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestServiceFromSegmentArray(t *testing.T) { | ||||
| 	var ( | ||||
| 		n               *Template | ||||
| 		formatString    string | ||||
| 		queryString     string | ||||
| 		splitQuery      []string | ||||
| 		expectedService string | ||||
| 		actualService   string | ||||
| 	) | ||||
|  | ||||
| 	// Case where template contains {service} | ||||
| 	n = new(Template) | ||||
| 	formatString = "{service}.{namespace}.{type}.{zone}" | ||||
| 	n.SetTemplate(formatString) | ||||
|  | ||||
| 	queryString = "myservice.mynamespace.svc.coredns" | ||||
| 	splitQuery = strings.Split(queryString, ".") | ||||
| 	expectedService = "myservice" | ||||
| 	actualService = n.ServiceFromSegmentArray(splitQuery) | ||||
|  | ||||
| 	if actualService != expectedService { | ||||
| 		t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString) | ||||
| 	} | ||||
|  | ||||
| 	// Case where template does not contain {service} | ||||
| 	n = new(Template) | ||||
| 	formatString = "{namespace}.{type}.{zone}" | ||||
| 	n.SetTemplate(formatString) | ||||
|  | ||||
| 	queryString = "mynamespace.svc.coredns" | ||||
| 	splitQuery = strings.Split(queryString, ".") | ||||
| 	expectedService = "" | ||||
| 	actualService = n.ServiceFromSegmentArray(splitQuery) | ||||
|  | ||||
| 	if actualService != expectedService { | ||||
| 		t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestZoneFromSegmentArray(t *testing.T) { | ||||
| 	var ( | ||||
| 		n            *Template | ||||
| 		formatString string | ||||
| 		queryString  string | ||||
| 		splitQuery   []string | ||||
| 		expectedZone string | ||||
| 		actualZone   string | ||||
| 	) | ||||
|  | ||||
| 	// Case where template contains {zone} | ||||
| 	n = new(Template) | ||||
| 	formatString = "{service}.{namespace}.{type}.{zone}" | ||||
| 	n.SetTemplate(formatString) | ||||
|  | ||||
| 	queryString = "myservice.mynamespace.svc.coredns" | ||||
| 	splitQuery = strings.Split(queryString, ".") | ||||
| 	expectedZone = "coredns" | ||||
| 	actualZone = n.ZoneFromSegmentArray(splitQuery) | ||||
|  | ||||
| 	if actualZone != expectedZone { | ||||
| 		t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) | ||||
| 	} | ||||
|  | ||||
| 	// Case where template does not contain {zone} | ||||
| 	n = new(Template) | ||||
| 	formatString = "{service}.{namespace}.{type}" | ||||
| 	n.SetTemplate(formatString) | ||||
|  | ||||
| 	queryString = "mynamespace.coredns.svc" | ||||
| 	splitQuery = strings.Split(queryString, ".") | ||||
| 	expectedZone = "" | ||||
| 	actualZone = n.ZoneFromSegmentArray(splitQuery) | ||||
|  | ||||
| 	if actualZone != expectedZone { | ||||
| 		t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) | ||||
| 	} | ||||
|  | ||||
| 	// Case where zone is multiple segments | ||||
| 	n = new(Template) | ||||
| 	formatString = "{service}.{namespace}.{type}.{zone}" | ||||
| 	n.SetTemplate(formatString) | ||||
|  | ||||
| 	queryString = "myservice.mynamespace.svc.coredns.cluster.local" | ||||
| 	splitQuery = strings.Split(queryString, ".") | ||||
| 	expectedZone = "coredns.cluster.local" | ||||
| 	actualZone = n.ZoneFromSegmentArray(splitQuery) | ||||
|  | ||||
| 	if actualZone != expectedZone { | ||||
| 		t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) | ||||
| 	} | ||||
| } | ||||
| @@ -8,7 +8,6 @@ import ( | ||||
|  | ||||
| 	"github.com/miekg/coredns/core/dnsserver" | ||||
| 	"github.com/miekg/coredns/middleware" | ||||
| 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" | ||||
|  | ||||
| 	"github.com/mholt/caddy" | ||||
| 	unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" | ||||
| @@ -52,8 +51,6 @@ func setup(c *caddy.Controller) error { | ||||
|  | ||||
| func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| 	k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod} | ||||
| 	k8s.NameTemplate = new(nametemplate.Template) | ||||
| 	k8s.NameTemplate.SetTemplate(defaultNameTemplate) | ||||
| 	k8s.PodMode = PodModeDisabled | ||||
|  | ||||
| 	for c.Next() { | ||||
| @@ -99,18 +96,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| 						continue | ||||
| 					} | ||||
| 					return nil, c.ArgErr() | ||||
|  | ||||
| 				case "template": | ||||
| 					args := c.RemainingArgs() | ||||
| 					if len(args) > 0 { | ||||
| 						template := strings.Join(args, "") | ||||
| 						err := k8s.NameTemplate.SetTemplate(template) | ||||
| 						if err != nil { | ||||
| 							return nil, err | ||||
| 						} | ||||
| 						continue | ||||
| 					} | ||||
| 					return nil, c.ArgErr() | ||||
| 				case "namespaces": | ||||
| 					args := c.RemainingArgs() | ||||
| 					if len(args) > 0 { | ||||
| @@ -164,7 +149,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	defaultNameTemplate = "{service}.{namespace}.{type}.{zone}" | ||||
| 	defaultResyncPeriod = 5 * time.Minute | ||||
| 	defaultPodMode      = PodModeDisabled | ||||
| ) | ||||
|   | ||||
| @@ -16,7 +16,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 		shouldErr             bool          // true if test case is exected to produce an error. | ||||
| 		expectedErrContent    string        // substring from the expected error. Empty for positive cases. | ||||
| 		expectedZoneCount     int           // expected count of defined zones. | ||||
| 		expectedNTValid       bool          // NameTemplate to be initialized and valid | ||||
| 		expectedNSCount       int           // expected count of namespaces. | ||||
| 		expectedResyncPeriod  time.Duration // expected resync period value | ||||
| 		expectedLabelSelector string        // expected label selector value | ||||
| @@ -28,7 +27,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -39,7 +37,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			2, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -51,7 +48,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -64,20 +60,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"template keyword with valid template", | ||||
| 			`kubernetes coredns.local { | ||||
| 	template {service}.{namespace}.{zone} | ||||
| }`, | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -90,7 +72,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			1, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -103,7 +84,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			2, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -116,7 +96,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			30 * time.Second, | ||||
| 			"", | ||||
| @@ -129,7 +108,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			15 * time.Minute, | ||||
| 			"", | ||||
| @@ -142,7 +120,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"environment=prod", | ||||
| @@ -155,7 +132,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			false, | ||||
| 			"", | ||||
| 			1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"application=nginx,environment in (production,qa,staging)", | ||||
| @@ -165,14 +141,12 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			`kubernetes coredns.local test.local { | ||||
|     resyncperiod 15m | ||||
| 	endpoint http://localhost:8080 | ||||
| 	template {service}.{namespace}.{zone} | ||||
| 	namespaces demo test | ||||
|     labels environment in (production, staging, qa),application=nginx | ||||
| }`, | ||||
| 			false, | ||||
| 			"", | ||||
| 			2, | ||||
| 			true, | ||||
| 			2, | ||||
| 			15 * time.Minute, | ||||
| 			"application=nginx,environment in (production,qa,staging)", | ||||
| @@ -184,7 +158,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Kubernetes setup called without keyword 'kubernetes' in Corefile", | ||||
| 			-1, | ||||
| 			false, | ||||
| 			-1, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -195,7 +168,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Zone name must be provided for kubernetes middleware", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -208,37 +180,10 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Wrong argument count or unexpected line ending after 'endpoint'", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			-1, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"template keyword without a template value", | ||||
| 			`kubernetes coredns.local { | ||||
|     template | ||||
| }`, | ||||
| 			true, | ||||
| 			"Wrong argument count or unexpected line ending after 'template'", | ||||
| 			-1, | ||||
| 			false, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"template keyword with an invalid template value", | ||||
| 			`kubernetes coredns.local { | ||||
|     template {namespace}.{zone} | ||||
| }`, | ||||
| 			true, | ||||
| 			"Record name template does not pass NameTemplate validation", | ||||
| 			-1, | ||||
| 			false, | ||||
| 			0, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"namespace keyword without a namespace value", | ||||
| 			`kubernetes coredns.local { | ||||
| @@ -247,7 +192,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Parse error: Wrong argument count or unexpected line ending after 'namespaces'", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			-1, | ||||
| 			defaultResyncPeriod, | ||||
| 			"", | ||||
| @@ -260,7 +204,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Wrong argument count or unexpected line ending after 'resyncperiod'", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			0 * time.Minute, | ||||
| 			"", | ||||
| @@ -273,7 +216,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Unable to parse resync duration value. Value provided was ", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			0 * time.Second, | ||||
| 			"", | ||||
| @@ -286,7 +228,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Unable to parse resync duration value. Value provided was ", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			0 * time.Second, | ||||
| 			"", | ||||
| @@ -299,7 +240,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Wrong argument count or unexpected line ending after 'labels'", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			0 * time.Second, | ||||
| 			"", | ||||
| @@ -312,7 +252,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			true, | ||||
| 			"Unable to parse label selector. Value provided was", | ||||
| 			-1, | ||||
| 			true, | ||||
| 			0, | ||||
| 			0 * time.Second, | ||||
| 			"", | ||||
| @@ -354,16 +293,6 @@ func TestKubernetesParse(t *testing.T) { | ||||
| 			t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input) | ||||
| 		} | ||||
|  | ||||
| 		//    NameTemplate | ||||
| 		if k8sController.NameTemplate == nil { | ||||
| 			t.Errorf("Test %d: Expected kubernetes controller to be initialized with a NameTemplate. Instead found '%v' for input '%s'", i, k8sController.NameTemplate, test.input) | ||||
| 		} else { | ||||
| 			foundNTValid := k8sController.NameTemplate.IsValid() | ||||
| 			if foundNTValid != test.expectedNTValid { | ||||
| 				t.Errorf("Test %d: Expected NameTemplate validity to be '%v', instead found '%v' for input '%s'", i, test.expectedNTValid, foundNTValid, test.input) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//    Namespaces | ||||
| 		foundNSCount := len(k8sController.Namespaces) | ||||
| 		if foundNSCount != test.expectedNSCount { | ||||
|   | ||||
| @@ -105,7 +105,7 @@ var dnsTestCases = []test.Case{ | ||||
| 	}, | ||||
| 	//TODO: Fix below to all use test.SRV not test.A! | ||||
| 	{ | ||||
| 		Qname: "_*._*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*._TcP.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode: dns.RcodeSuccess, | ||||
| 		Answer: []dns.RR{ | ||||
| 			test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local.      303    IN    SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), | ||||
| @@ -113,12 +113,12 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.any.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode: dns.RcodeSuccess, | ||||
| 		Answer: []dns.RR{ | ||||
| 			test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local.      303    IN    SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), | ||||
| @@ -126,7 +126,7 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "ANY.*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode: dns.RcodeSuccess, | ||||
| 		Answer: []dns.RR{ | ||||
| 			test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local.      303    IN    SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), | ||||
| @@ -134,17 +134,17 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_c-port._*.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "_c-port._UDP.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode: dns.RcodeSuccess, | ||||
| 		Answer: []dns.RR{ | ||||
| 			test.SRV("_c-port._udp.svc-c.test-1.svc.cluster.local.      303    IN    SRV 10 100 1234 svc-c.test-1.svc.cluster.local."), | ||||
| @@ -153,7 +153,7 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode: dns.RcodeSuccess, | ||||
| 		Answer: []dns.RR{ | ||||
| 			test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local.      303    IN    SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), | ||||
| @@ -162,12 +162,12 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Qname: "*.*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| @@ -180,18 +180,18 @@ var dnsTestCases = []test.Case{ | ||||
| 		}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Qname: "*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeServerFailure, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "_*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Qname: "*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeServerFailure, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		Qname: "svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		Rcode:  dns.RcodeServerFailure, | ||||
| 		Answer: []dns.RR{}, | ||||
| 	}, | ||||
| 	{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user