mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -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 | ||||||
|  |  | ||||||
| *kubernetes* enables reading zone data from a kubernetes cluster. Record names | *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, | * "myservice" is the name of the k8s service (this may include multiple DNS labels, | ||||||
|   such as "c1.myservice"), |   such as "c1.myservice"), | ||||||
| * "mynamespace" is the k8s namespace for the service, and | * "mynamespace" is the k8s namespace for the service, and | ||||||
|  | * "type" is svc or pod | ||||||
| * "coredns.local" is the zone configured for `kubernetes`. | * "coredns.local" is the zone configured for `kubernetes`. | ||||||
|  |  | ||||||
| The record name format can be changed by specifying a name template in the Corefile. |  | ||||||
|  |  | ||||||
| ## Syntax | ## 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 |         # The tls cert, key and the CA cert filenames | ||||||
|         tls cert key cacert |         tls cert key cacert | ||||||
| 		 | 		 | ||||||
|         # Assemble k8s record names with the template |  | ||||||
|         template {service}.{namespace}.{type}.{zone} |  | ||||||
| 		 |  | ||||||
|         # Only expose the k8s namespace "demo" |         # Only expose the k8s namespace "demo" | ||||||
|         namespaces demo |         namespaces demo | ||||||
| 		 | 		 | ||||||
| @@ -82,22 +78,12 @@ This is the default kubernetes setup, with everything specified in full: | |||||||
|  |  | ||||||
| Defaults: | Defaults: | ||||||
| * If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed. | * 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. | * 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 | * 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: |   is required. The label selector syntax is described in the kubernetes API documentation at: | ||||||
|   http://kubernetes.io/docs/user-guide/labels/ |   http://kubernetes.io/docs/user-guide/labels/ | ||||||
| * If the `pods` keyword is omitted, all pod type requests will result in NXDOMAIN | * 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 | ### Basic Setup | ||||||
|  |  | ||||||
| #### Launch Kubernetes | #### Launch Kubernetes | ||||||
| @@ -146,7 +132,6 @@ Build CoreDNS and launch using this configuration file: | |||||||
|     kubernetes coredns.local { |     kubernetes coredns.local { | ||||||
|         resyncperiod 5m |         resyncperiod 5m | ||||||
|         endpoint http://localhost:8080 |         endpoint http://localhost:8080 | ||||||
|         template {service}.{namespace}.{type}.{zone} |  | ||||||
|         namespaces demo |         namespaces demo | ||||||
|         # Only expose the records for kubernetes objects |         # Only expose the records for kubernetes objects | ||||||
|         # that matches this label selector.  |         # that matches this label selector.  | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/miekg/coredns/middleware" | 	"github.com/miekg/coredns/middleware" | ||||||
| 	"github.com/miekg/coredns/middleware/etcd/msg" | 	"github.com/miekg/coredns/middleware/etcd/msg" | ||||||
| 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" |  | ||||||
| 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | 	"github.com/miekg/coredns/middleware/pkg/dnsutil" | ||||||
| 	dnsstrings "github.com/miekg/coredns/middleware/pkg/strings" | 	dnsstrings "github.com/miekg/coredns/middleware/pkg/strings" | ||||||
| 	"github.com/miekg/coredns/middleware/proxy" | 	"github.com/miekg/coredns/middleware/proxy" | ||||||
| @@ -38,7 +37,6 @@ type Kubernetes struct { | |||||||
| 	APIClientKey  string | 	APIClientKey  string | ||||||
| 	APIConn       *dnsController | 	APIConn       *dnsController | ||||||
| 	ResyncPeriod  time.Duration | 	ResyncPeriod  time.Duration | ||||||
| 	NameTemplate  *nametemplate.Template |  | ||||||
| 	Namespaces    []string | 	Namespaces    []string | ||||||
| 	LabelSelector *unversionedapi.LabelSelector | 	LabelSelector *unversionedapi.LabelSelector | ||||||
| 	Selector      *labels.Selector | 	Selector      *labels.Selector | ||||||
| @@ -69,16 +67,22 @@ type pod struct { | |||||||
| 	addr      string | 	addr      string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type recordRequest struct { | ||||||
|  | 	port, protocol, endpoint, service, namespace, typeName, zone string | ||||||
|  | } | ||||||
|  |  | ||||||
| var errNoItems = errors.New("no items found") | var errNoItems = errors.New("no items found") | ||||||
| var errNsNotExposed = errors.New("namespace is not exposed") | var errNsNotExposed = errors.New("namespace is not exposed") | ||||||
| var errInvalidRequest = errors.New("invalid query name") | var errInvalidRequest = errors.New("invalid query name") | ||||||
|  |  | ||||||
| // Services implements the ServiceBackend interface. | // Services implements the ServiceBackend interface. | ||||||
| func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) { | 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. | 	return s, nil, e // Haven't implemented debug queries yet. | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -177,85 +181,94 @@ func (k *Kubernetes) InitKubeCache() error { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| // getZoneForName returns the zone string that matches the name and a | func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest, err error) { | ||||||
| // list of the DNS labels from name that are within the zone. | 	// 3 Possible cases | ||||||
| // For example, if "coredns.local" is a zone configured for the | 	//   SRV Request: _port._protocol.service.namespace.type.zone | ||||||
| // Kubernetes middleware, then getZoneForName("a.b.coredns.local") | 	//   A Request (endpoint): endpoint.service.namespace.type.zone | ||||||
| // will return ("coredns.local", ["a", "b"]). | 	//   A Request (service): service.namespace.type.zone | ||||||
| func (k *Kubernetes) getZoneForName(name string) (string, []string) { |  | ||||||
| 	var zone string |  | ||||||
| 	var serviceSegments []string |  | ||||||
|  |  | ||||||
|  | 	// separate zone from rest of lowerCasedName | ||||||
|  | 	var segs []string | ||||||
| 	for _, z := range k.Zones { | 	for _, z := range k.Zones { | ||||||
| 		if dns.IsSubDomain(z, name) { | 		if dns.IsSubDomain(z, lowerCasedName) { | ||||||
| 			zone = z | 			r.zone = z | ||||||
|  |  | ||||||
| 			serviceSegments = dns.SplitDomainName(name) | 			segs = dns.SplitDomainName(lowerCasedName) | ||||||
| 			serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)] | 			segs = segs[:len(segs)-dns.CountLabel(r.zone)] | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	if r.zone == "" { | ||||||
| 	return zone, serviceSegments | 		return r, errors.New("zone not found") | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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:] |  | ||||||
| 	} | 	} | ||||||
| 	// no srv prefix present |  | ||||||
| 	return "*", "*", name |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func stripEndpointName(name []string) (endpoint string, nameOut []string) { | 	offset := 0 | ||||||
| 	if len(name) == 4 { | 	if len(segs) == 5 { | ||||||
| 		return strings.ToLower(name[0]), name[1:] | 		// 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") | ||||||
| 			} | 			} | ||||||
| 	return "", name | 		} | ||||||
|  | 		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 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 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 | // 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 | // just this name. This is used when find matches when completing SRV lookups | ||||||
| // for instance. | // for instance. | ||||||
| func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | func (k *Kubernetes) Records(r recordRequest) ([]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 = "*" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile | 	// 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. | 	// 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 | 		return nil, errNsNotExposed | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	services, pods, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName) | 	services, pods, err := k.Get(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -264,7 +277,7 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | |||||||
| 		return nil, errNoItems | 		return nil, errNoItems | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	records := k.getRecordsForK8sItems(services, pods, zone) | 	records := k.getRecordsForK8sItems(services, pods, r.zone) | ||||||
| 	return records, nil | 	return records, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -320,18 +333,6 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone | |||||||
| 	return records | 	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 { | func ipFromPodName(podname string) string { | ||||||
| 	if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { | 	if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { | ||||||
| 		return strings.Replace(podname, "-", ".", -1) | 		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() | 	serviceList := k.APIConn.ServiceList() | ||||||
|  |  | ||||||
| 	var resultItems []service | 	var resultItems []service | ||||||
|  |  | ||||||
| 	nsWildcard := symbolContainsWildcard(namespace) | 	nsWildcard := symbolContainsWildcard(r.namespace) | ||||||
| 	serviceWildcard := symbolContainsWildcard(servicename) | 	serviceWildcard := symbolContainsWildcard(r.service) | ||||||
| 	portWildcard := symbolContainsWildcard(port) | 	portWildcard := symbolContainsWildcard(r.port) || r.port == "" | ||||||
| 	protocolWildcard := symbolContainsWildcard(protocol) | 	protocolWildcard := symbolContainsWildcard(r.protocol) || r.protocol == "" | ||||||
|  |  | ||||||
| 	for _, svc := range serviceList { | 	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 | 			continue | ||||||
| 		} | 		} | ||||||
| 		// If namespace has a wildcard, filter results against Corefile namespace list. | 		// 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} | 		s := service{name: svc.Name, namespace: svc.Namespace, addr: svc.Spec.ClusterIP} | ||||||
| 		if s.addr != api.ClusterIPNone { | 		if s.addr != api.ClusterIPNone { | ||||||
| 			for _, p := range svc.Spec.Ports { | 			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 | 					continue | ||||||
| 				} | 				} | ||||||
| 				s.ports = append(s.ports, p) | 				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 _, addr := range eps.Addresses { | ||||||
| 					for _, p := range eps.Ports { | 					for _, p := range eps.Ports { | ||||||
| 						ephostname := endpointHostname(addr) | 						ephostname := endpointHostname(addr) | ||||||
| 						if endpointname != "" && endpointname != ephostname { | 						if r.endpoint != "" && r.endpoint != ephostname { | ||||||
| 							continue | 							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 | 							continue | ||||||
| 						} | 						} | ||||||
| 						s.endpoints = append(s.endpoints, endpoint{addr: addr, port: p}) | 						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 { | func symbolMatches(queryString, candidateString string, wildcard bool) bool { | ||||||
| 	result := false | 	if wildcard { | ||||||
| 	switch { | 		return true | ||||||
| 	case !wildcard: |  | ||||||
| 		result = (queryString == candidateString) |  | ||||||
| 	case queryString == "*": |  | ||||||
| 		result = true |  | ||||||
| 	case queryString == "any": |  | ||||||
| 		result = true |  | ||||||
| 	} | 	} | ||||||
| 	return result | 	return queryString == candidateString | ||||||
| } | } | ||||||
|  |  | ||||||
| // getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument | // 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 { | func symbolContainsWildcard(symbol string) bool { | ||||||
| 	return (strings.Contains(symbol, "*") || (symbol == "any")) | 	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 | package kubernetes | ||||||
|  |  | ||||||
| import "testing" | import "testing" | ||||||
|  | import "reflect" | ||||||
|  |  | ||||||
| // Test data for TestSymbolContainsWildcard cases. | // Test data for TestSymbolContainsWildcard cases. | ||||||
| var testdataSymbolContainsWildcard = []struct { | 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) { | 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 { | 	if err != nil { | ||||||
| 		return nil, err | 		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/core/dnsserver" | ||||||
| 	"github.com/miekg/coredns/middleware" | 	"github.com/miekg/coredns/middleware" | ||||||
| 	"github.com/miekg/coredns/middleware/kubernetes/nametemplate" |  | ||||||
|  |  | ||||||
| 	"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" | ||||||
| @@ -52,8 +51,6 @@ func setup(c *caddy.Controller) error { | |||||||
|  |  | ||||||
| func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||||
| 	k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod} | 	k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod} | ||||||
| 	k8s.NameTemplate = new(nametemplate.Template) |  | ||||||
| 	k8s.NameTemplate.SetTemplate(defaultNameTemplate) |  | ||||||
| 	k8s.PodMode = PodModeDisabled | 	k8s.PodMode = PodModeDisabled | ||||||
|  |  | ||||||
| 	for c.Next() { | 	for c.Next() { | ||||||
| @@ -99,18 +96,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | |||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 					return nil, c.ArgErr() | 					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": | 				case "namespaces": | ||||||
| 					args := c.RemainingArgs() | 					args := c.RemainingArgs() | ||||||
| 					if len(args) > 0 { | 					if len(args) > 0 { | ||||||
| @@ -164,7 +149,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	defaultNameTemplate = "{service}.{namespace}.{type}.{zone}" |  | ||||||
| 	defaultResyncPeriod = 5 * time.Minute | 	defaultResyncPeriod = 5 * time.Minute | ||||||
| 	defaultPodMode      = PodModeDisabled | 	defaultPodMode      = PodModeDisabled | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 		shouldErr             bool          // true if test case is exected to produce an error. | 		shouldErr             bool          // true if test case is exected to produce an error. | ||||||
| 		expectedErrContent    string        // substring from the expected error. Empty for positive cases. | 		expectedErrContent    string        // substring from the expected error. Empty for positive cases. | ||||||
| 		expectedZoneCount     int           // expected count of defined zones. | 		expectedZoneCount     int           // expected count of defined zones. | ||||||
| 		expectedNTValid       bool          // NameTemplate to be initialized and valid |  | ||||||
| 		expectedNSCount       int           // expected count of namespaces. | 		expectedNSCount       int           // expected count of namespaces. | ||||||
| 		expectedResyncPeriod  time.Duration // expected resync period value | 		expectedResyncPeriod  time.Duration // expected resync period value | ||||||
| 		expectedLabelSelector string        // expected label selector value | 		expectedLabelSelector string        // expected label selector value | ||||||
| @@ -28,7 +27,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -39,7 +37,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			2, | 			2, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -51,7 +48,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -64,20 +60,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, |  | ||||||
| 			defaultResyncPeriod, |  | ||||||
| 			"", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"template keyword with valid template", |  | ||||||
| 			`kubernetes coredns.local { |  | ||||||
| 	template {service}.{namespace}.{zone} |  | ||||||
| }`, |  | ||||||
| 			false, |  | ||||||
| 			"", |  | ||||||
| 			1, |  | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -90,7 +72,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			1, | 			1, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -103,7 +84,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			2, | 			2, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -116,7 +96,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			30 * time.Second, | 			30 * time.Second, | ||||||
| 			"", | 			"", | ||||||
| @@ -129,7 +108,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			15 * time.Minute, | 			15 * time.Minute, | ||||||
| 			"", | 			"", | ||||||
| @@ -142,7 +120,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"environment=prod", | 			"environment=prod", | ||||||
| @@ -155,7 +132,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			1, | 			1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"application=nginx,environment in (production,qa,staging)", | 			"application=nginx,environment in (production,qa,staging)", | ||||||
| @@ -165,14 +141,12 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			`kubernetes coredns.local test.local { | 			`kubernetes coredns.local test.local { | ||||||
|     resyncperiod 15m |     resyncperiod 15m | ||||||
| 	endpoint http://localhost:8080 | 	endpoint http://localhost:8080 | ||||||
| 	template {service}.{namespace}.{zone} |  | ||||||
| 	namespaces demo test | 	namespaces demo test | ||||||
|     labels environment in (production, staging, qa),application=nginx |     labels environment in (production, staging, qa),application=nginx | ||||||
| }`, | }`, | ||||||
| 			false, | 			false, | ||||||
| 			"", | 			"", | ||||||
| 			2, | 			2, | ||||||
| 			true, |  | ||||||
| 			2, | 			2, | ||||||
| 			15 * time.Minute, | 			15 * time.Minute, | ||||||
| 			"application=nginx,environment in (production,qa,staging)", | 			"application=nginx,environment in (production,qa,staging)", | ||||||
| @@ -184,7 +158,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Kubernetes setup called without keyword 'kubernetes' in Corefile", | 			"Kubernetes setup called without keyword 'kubernetes' in Corefile", | ||||||
| 			-1, | 			-1, | ||||||
| 			false, |  | ||||||
| 			-1, | 			-1, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -195,7 +168,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Zone name must be provided for kubernetes middleware", | 			"Zone name must be provided for kubernetes middleware", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -208,37 +180,10 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Wrong argument count or unexpected line ending after 'endpoint'", | 			"Wrong argument count or unexpected line ending after 'endpoint'", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			-1, | 			-1, | ||||||
| 			defaultResyncPeriod, | 			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", | 			"namespace keyword without a namespace value", | ||||||
| 			`kubernetes coredns.local { | 			`kubernetes coredns.local { | ||||||
| @@ -247,7 +192,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Parse error: Wrong argument count or unexpected line ending after 'namespaces'", | 			"Parse error: Wrong argument count or unexpected line ending after 'namespaces'", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			-1, | 			-1, | ||||||
| 			defaultResyncPeriod, | 			defaultResyncPeriod, | ||||||
| 			"", | 			"", | ||||||
| @@ -260,7 +204,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Wrong argument count or unexpected line ending after 'resyncperiod'", | 			"Wrong argument count or unexpected line ending after 'resyncperiod'", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			0 * time.Minute, | 			0 * time.Minute, | ||||||
| 			"", | 			"", | ||||||
| @@ -273,7 +216,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Unable to parse resync duration value. Value provided was ", | 			"Unable to parse resync duration value. Value provided was ", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			0 * time.Second, | 			0 * time.Second, | ||||||
| 			"", | 			"", | ||||||
| @@ -286,7 +228,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Unable to parse resync duration value. Value provided was ", | 			"Unable to parse resync duration value. Value provided was ", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			0 * time.Second, | 			0 * time.Second, | ||||||
| 			"", | 			"", | ||||||
| @@ -299,7 +240,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Wrong argument count or unexpected line ending after 'labels'", | 			"Wrong argument count or unexpected line ending after 'labels'", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			0 * time.Second, | 			0 * time.Second, | ||||||
| 			"", | 			"", | ||||||
| @@ -312,7 +252,6 @@ func TestKubernetesParse(t *testing.T) { | |||||||
| 			true, | 			true, | ||||||
| 			"Unable to parse label selector. Value provided was", | 			"Unable to parse label selector. Value provided was", | ||||||
| 			-1, | 			-1, | ||||||
| 			true, |  | ||||||
| 			0, | 			0, | ||||||
| 			0 * time.Second, | 			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) | 			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 | 		//    Namespaces | ||||||
| 		foundNSCount := len(k8sController.Namespaces) | 		foundNSCount := len(k8sController.Namespaces) | ||||||
| 		if foundNSCount != test.expectedNSCount { | 		if foundNSCount != test.expectedNSCount { | ||||||
|   | |||||||
| @@ -105,7 +105,7 @@ var dnsTestCases = []test.Case{ | |||||||
| 	}, | 	}, | ||||||
| 	//TODO: Fix below to all use test.SRV not test.A! | 	//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, | 		Rcode: dns.RcodeSuccess, | ||||||
| 		Answer: []dns.RR{ | 		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."), | 			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, | 		Rcode:  dns.RcodeNameError, | ||||||
| 		Answer: []dns.RR{}, | 		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, | 		Rcode: dns.RcodeSuccess, | ||||||
| 		Answer: []dns.RR{ | 		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."), | 			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, | 		Rcode: dns.RcodeSuccess, | ||||||
| 		Answer: []dns.RR{ | 		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."), | 			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, | 		Rcode:  dns.RcodeNameError, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Qname: "_*._*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, | 		Qname: "*.*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
| 		Rcode:  dns.RcodeNameError, | 		Rcode:  dns.RcodeNameError, | ||||||
| 		Answer: []dns.RR{}, | 		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, | 		Rcode: dns.RcodeSuccess, | ||||||
| 		Answer: []dns.RR{ | 		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."), | 			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, | 		Rcode: dns.RcodeSuccess, | ||||||
| 		Answer: []dns.RR{ | 		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."), | 			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, | 		Rcode:  dns.RcodeNameError, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Qname: "_*._*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | 		Qname: "*.*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
| 		Rcode:  dns.RcodeNameError, | 		Rcode:  dns.RcodeNameError, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| @@ -180,18 +180,18 @@ var dnsTestCases = []test.Case{ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Qname: "_*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | 		Qname: "*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
| 		Rcode:  dns.RcodeNameError, | 		Rcode:  dns.RcodeServerFailure, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Qname: "_*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | 		Qname: "*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
| 		Rcode:  dns.RcodeNameError, | 		Rcode:  dns.RcodeServerFailure, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		Qname: "svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | 		Qname: "svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
| 		Rcode:  dns.RcodeNameError, | 		Rcode:  dns.RcodeServerFailure, | ||||||
| 		Answer: []dns.RR{}, | 		Answer: []dns.RR{}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user