aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--middleware/kubernetes/federation.go8
-rw-r--r--middleware/kubernetes/federation_test.go8
-rw-r--r--middleware/kubernetes/handler.go5
-rw-r--r--middleware/kubernetes/kubernetes.go171
-rw-r--r--middleware/kubernetes/kubernetes_test.go180
-rw-r--r--middleware/kubernetes/ns_test.go14
-rw-r--r--middleware/kubernetes/parse.go120
-rw-r--r--middleware/kubernetes/parse_test.go131
-rw-r--r--test/kubernetes_test.go8
9 files changed, 317 insertions, 328 deletions
diff --git a/middleware/kubernetes/federation.go b/middleware/kubernetes/federation.go
index 6f7e5f122..4c8d20cfe 100644
--- a/middleware/kubernetes/federation.go
+++ b/middleware/kubernetes/federation.go
@@ -62,13 +62,13 @@ func (k *Kubernetes) federationCNAMERecord(r recordRequest) msg.Service {
}
if r.endpoint == "" {
return msg.Service{
- Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.typeName, r.federation, r.namespace, r.service}, "/"),
- Host: strings.Join([]string{r.service, r.namespace, r.federation, r.typeName, node.Labels[labelAvailabilityZone], node.Labels[labelRegion], f.zone}, "."),
+ Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.podOrSvc, r.federation, r.namespace, r.service}, "/"),
+ Host: strings.Join([]string{r.service, r.namespace, r.federation, r.podOrSvc, node.Labels[labelAvailabilityZone], node.Labels[labelRegion], f.zone}, "."),
}
}
return msg.Service{
- Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.typeName, r.federation, r.namespace, r.service, r.endpoint}, "/"),
- Host: strings.Join([]string{r.endpoint, r.service, r.namespace, r.federation, r.typeName, node.Labels[labelAvailabilityZone], node.Labels[labelRegion], f.zone}, "."),
+ Key: strings.Join([]string{msg.Path(r.zone, "coredns"), r.podOrSvc, r.federation, r.namespace, r.service, r.endpoint}, "/"),
+ Host: strings.Join([]string{r.endpoint, r.service, r.namespace, r.federation, r.podOrSvc, node.Labels[labelAvailabilityZone], node.Labels[labelRegion], f.zone}, "."),
}
}
diff --git a/middleware/kubernetes/federation_test.go b/middleware/kubernetes/federation_test.go
index eae3d74d4..be5069c26 100644
--- a/middleware/kubernetes/federation_test.go
+++ b/middleware/kubernetes/federation_test.go
@@ -85,17 +85,17 @@ func testFederationCNAMERecord(t *testing.T, k Kubernetes, input recordRequest,
}
func TestFederationCNAMERecord(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs"}}
+ k := Kubernetes{Zones: []string{"inter.webs."}}
k.Federations = []Federation{{name: "fed", zone: "era.tion.com"}}
k.APIConn = apiConnFedTest{}
k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("10.9.8.7") }
- r, _ := k.parseRequest("s1.ns.fed.svc.inter.webs", dns.TypeA)
+ r, _ := k.parseRequest("s1.ns.fed.svc.inter.webs.", dns.TypeA, "inter.webs.")
testFederationCNAMERecord(t, k, r, msg.Service{Key: "/coredns/webs/inter/svc/fed/ns/s1", Host: "s1.ns.fed.svc.fd-az.fd-r.era.tion.com"})
- r, _ = k.parseRequest("ep1.s1.ns.fed.svc.inter.webs", dns.TypeA)
+ r, _ = k.parseRequest("ep1.s1.ns.fed.svc.inter.webs.", dns.TypeA, "inter.webs.")
testFederationCNAMERecord(t, k, r, msg.Service{Key: "/coredns/webs/inter/svc/fed/ns/s1/ep1", Host: "ep1.s1.ns.fed.svc.fd-az.fd-r.era.tion.com"})
- r, _ = k.parseRequest("ep1.s1.ns.foo.svc.inter.webs", dns.TypeA)
+ r, _ = k.parseRequest("ep1.s1.ns.foo.svc.inter.webs.", dns.TypeA, "inter.webs.")
testFederationCNAMERecord(t, k, r, msg.Service{Key: "", Host: ""})
}
diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go
index 520ab0344..868b39d7f 100644
--- a/middleware/kubernetes/handler.go
+++ b/middleware/kubernetes/handler.go
@@ -34,10 +34,13 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
if !k.isRequestInReverseRange(state.Name()) {
return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r)
}
- // Set the zone to this specific request.
+
+ // Set the zone to this specific request, as we want to handle this reverse request.
zone = state.Name()
}
+ state.Zone = zone
+
var (
records []dns.RR
extra []dns.RR
diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go
index 1b07d30d5..417436904 100644
--- a/middleware/kubernetes/kubernetes.go
+++ b/middleware/kubernetes/kubernetes.go
@@ -11,6 +11,7 @@ import (
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/etcd/msg"
+ "github.com/coredns/coredns/middleware/pkg/dnsutil"
dnsstrings "github.com/coredns/coredns/middleware/pkg/strings"
"github.com/coredns/coredns/middleware/proxy"
"github.com/coredns/coredns/request"
@@ -64,7 +65,8 @@ type endpoint struct {
port api.EndpointPort
}
-type service struct {
+// kService is a service as retrieved via the k8s API.
+type kService struct {
name string
namespace string
addr string
@@ -72,23 +74,13 @@ type service struct {
endpoints []endpoint
}
-type pod struct {
+// kPod is a pod as retrieved via the k8s API.
+type kPod struct {
name string
namespace string
addr string
}
-type recordRequest struct {
- port string
- protocol string
- endpoint string
- service string
- namespace string
- typeName string
- zone string
- federation string
-}
-
var (
errNoItems = errors.New("no items found")
errNsNotExposed = errors.New("namespace is not exposed")
@@ -100,12 +92,33 @@ var (
// Services implements the ServiceBackend interface.
func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, debug []msg.Service, err error) {
- r, e := k.parseRequest(state.Name(), state.QType())
+
+ // We're looking again at types, which we've already done in ServeDNS, but there are some types k8s just can't answer.
+ switch state.QType() {
+ case dns.TypeTXT:
+ // 1 label + zone, label must be "dns-version"
+ t, err := dnsutil.TrimZone(state.Name(), state.Zone)
+ if err != nil {
+ return nil, nil, err
+ }
+ segs := dns.SplitDomainName(t)
+ if len(segs) != 1 {
+ return nil, nil, errors.New("servfail")
+ }
+ if segs[0] != "dns-version" {
+ return nil, nil, errInvalidRequest
+ }
+ svc := msg.Service{Text: DNSSchemaVersion, TTL: 28800, Key: msg.Path(state.QName(), "coredns")}
+ return []msg.Service{svc}, nil, nil
+ }
+
+ r, e := k.parseRequest(state.Name(), state.QType(), state.Zone)
if e != nil {
return nil, nil, e
}
- switch state.Type() {
- case "A", "AAAA", "CNAME":
+
+ switch state.QType() {
+ case dns.TypeA, dns.TypeAAAA, dns.TypeCNAME:
if state.Type() == "A" && isDefaultNS(state.Name(), r) {
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
// SOA records always use this hardcoded name
@@ -118,7 +131,7 @@ func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.
return nil, nil, e
}
return s, nil, e // Haven't implemented debug queries yet.
- case "SRV":
+ case dns.TypeSRV:
s, e := k.Entries(r)
// SRV for external services is not yet implemented, so remove those records
noext := []msg.Service{}
@@ -128,13 +141,7 @@ func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.
}
}
return noext, nil, e
- case "TXT":
- if r.typeName == "dns-version" {
- srv := k.recordsForTXT(r)
- svcs = append(svcs, srv)
- }
- return svcs, nil, err
- case "NS":
+ case dns.TypeNS:
srv := k.recordsForNS(r)
svcs = append(svcs, srv)
return svcs, nil, err
@@ -142,11 +149,6 @@ func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.
return nil, nil, nil
}
-func (k *Kubernetes) recordsForTXT(r recordRequest) msg.Service {
- return msg.Service{Text: DNSSchemaVersion, TTL: 28800,
- Key: msg.Path(strings.Join([]string{r.typeName, r.zone}, "."), "coredns")}
-}
-
func (k *Kubernetes) recordsForNS(r recordRequest) msg.Service {
ns := k.coreDNSRecord()
return msg.Service{Host: ns.A.String(),
@@ -234,99 +236,6 @@ func (k *Kubernetes) InitKubeCache() (err error) {
return err
}
-func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16) (r recordRequest, err error) {
- // 3 Possible cases
- // SRV Request: _port._protocol.service.namespace.[federation.]type.zone
- // A Request (endpoint): endpoint.service.namespace.[federation.]type.zone
- // A Request (service): service.namespace.[federation.]type.zone
-
- // separate zone from rest of lowerCasedName
- var segs []string
- for _, z := range k.Zones {
- if dns.IsSubDomain(z, lowerCasedName) {
- r.zone = z
-
- segs = dns.SplitDomainName(lowerCasedName)
- segs = segs[:len(segs)-dns.CountLabel(r.zone)]
- break
- }
- }
- if r.zone == "" {
- return r, errZoneNotFound
- }
-
- r.federation, segs = k.stripFederation(segs)
-
- if qtype == dns.TypeNS {
- return r, nil
- }
-
- if qtype == dns.TypeA && isDefaultNS(lowerCasedName, r) {
- return r, nil
- }
-
- offset := 0
- if qtype == dns.TypeSRV {
- // The kubernetes peer-finder expects queries with empty port and service to resolve
- // If neither is specified, treat it as a wildcard
- if len(segs) == 3 {
- r.port = "*"
- r.service = "*"
- offset = 0
- } else {
- if len(segs) != 5 {
- return r, errInvalidRequest
- }
- // 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 !wildcard(r.port) {
- return r, errInvalidRequest
- }
- }
- if segs[1][0] == '_' {
- r.protocol = segs[1][1:]
- if r.protocol != "tcp" && r.protocol != "udp" {
- return r, errInvalidRequest
- }
- } else {
- r.protocol = segs[1]
- if !wildcard(r.protocol) {
- return r, errInvalidRequest
- }
- }
- if r.port == "" || r.protocol == "" {
- return r, errInvalidRequest
- }
- offset = 2
- }
- }
- if (qtype == dns.TypeA || qtype == dns.TypeAAAA) && len(segs) == 4 {
- // This is an endpoint A/AAAA record request. Get first element as endpoint.
- r.endpoint = segs[0]
- offset = 1
- }
-
- if len(segs) == (offset + 3) {
- r.service = segs[offset]
- r.namespace = segs[offset+1]
- r.typeName = segs[offset+2]
-
- return r, nil
- }
-
- if len(segs) == 1 && qtype == dns.TypeTXT {
- r.typeName = segs[0]
- return r, nil
- }
-
- return r, errInvalidRequest
-
-}
-
// Records not implemented, see Entries().
func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
return nil, fmt.Errorf("NOOP")
@@ -375,7 +284,7 @@ func endpointHostname(addr api.EndpointAddress) string {
return ""
}
-func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, r recordRequest) (records []msg.Service) {
+func (k *Kubernetes) getRecordsForK8sItems(services []kService, pods []kPod, r recordRequest) (records []msg.Service) {
zonePath := msg.Path(r.zone, "coredns")
for _, svc := range services {
@@ -435,7 +344,7 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, r rec
return records
}
-func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) {
+func (k *Kubernetes) findPods(namespace, podname string) (pods []kPod, err error) {
if k.PodMode == PodModeDisabled {
return pods, errPodsDisabled
}
@@ -448,7 +357,7 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error)
}
if k.PodMode == PodModeInsecure {
- s := pod{name: podname, namespace: namespace, addr: ip}
+ s := kPod{name: podname, namespace: namespace, addr: ip}
pods = append(pods, s)
return pods, nil
}
@@ -468,7 +377,7 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error)
}
// check for matching ip and namespace
if ip == p.Status.PodIP && match(namespace, p.Namespace, nsWildcard) {
- s := pod{name: podname, namespace: namespace, addr: ip}
+ s := kPod{name: podname, namespace: namespace, addr: ip}
pods = append(pods, s)
return pods, nil
}
@@ -477,9 +386,9 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error)
}
// get retrieves matching data from the cache.
-func (k *Kubernetes) get(r recordRequest) (services []service, pods []pod, err error) {
+func (k *Kubernetes) get(r recordRequest) (services []kService, pods []kPod, err error) {
switch {
- case r.typeName == Pod:
+ case r.podOrSvc == Pod:
pods, err = k.findPods(r.namespace, r.service)
return nil, pods, err
default:
@@ -488,9 +397,9 @@ func (k *Kubernetes) get(r recordRequest) (services []service, pods []pod, err e
}
}
-func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
+func (k *Kubernetes) findServices(r recordRequest) ([]kService, error) {
serviceList := k.APIConn.ServiceList()
- var resultItems []service
+ var resultItems []kService
nsWildcard := wildcard(r.namespace)
serviceWildcard := wildcard(r.service)
@@ -506,7 +415,7 @@ func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
if nsWildcard && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(svc.Namespace, k.Namespaces)) {
continue
}
- s := service{name: svc.Name, namespace: svc.Namespace}
+ s := kService{name: svc.Name, namespace: svc.Namespace}
// Endpoint query or headless service
if svc.Spec.ClusterIP == api.ClusterIPNone || r.endpoint != "" {
diff --git a/middleware/kubernetes/kubernetes_test.go b/middleware/kubernetes/kubernetes_test.go
index a19cb16f6..8037c0987 100644
--- a/middleware/kubernetes/kubernetes_test.go
+++ b/middleware/kubernetes/kubernetes_test.go
@@ -1,8 +1,6 @@
package kubernetes
import (
- "errors"
- "reflect"
"testing"
"github.com/coredns/coredns/middleware"
@@ -12,17 +10,6 @@ import (
"k8s.io/client-go/1.5/pkg/api"
)
-func TestRecordForTXT(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs.test"}}
- r, _ := k.parseRequest("dns-version.inter.webs.test", dns.TypeTXT)
-
- expected := DNSSchemaVersion
- svc := k.recordsForTXT(r)
- if svc.Text != expected {
- t.Errorf("Expected result '%v'. Instead got result '%v'.", expected, svc.Text)
- }
-}
-
func TestPrimaryZone(t *testing.T) {
k := Kubernetes{Zones: []string{"inter.webs.test", "inter.nets.test"}}
expected := "inter.webs.test"
@@ -32,23 +19,6 @@ func TestPrimaryZone(t *testing.T) {
}
}
-func TestIsNameError(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs.test"}}
- if !k.IsNameError(errNoItems) {
- t.Errorf("Expected 'true' for '%v'", errNoItems)
- }
- if !k.IsNameError(errNsNotExposed) {
- t.Errorf("Expected 'true' for '%v'", errNsNotExposed)
- }
- if !k.IsNameError(errInvalidRequest) {
- t.Errorf("Expected 'true' for '%v'", errInvalidRequest)
- }
- otherErr := errors.New("Some other error occurred")
- if k.IsNameError(otherErr) {
- t.Errorf("Expected 'true' for '%v'", otherErr)
- }
-}
-
func TestWildcard(t *testing.T) {
var tests = []struct {
s string
@@ -70,151 +40,6 @@ func TestWildcard(t *testing.T) {
}
}
-func expectString(t *testing.T, function, qtype, query string, r *recordRequest, field, expected string) {
- ref := reflect.ValueOf(r)
- refField := reflect.Indirect(ref).FieldByName(field)
- got := refField.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, dns.TypeSRV)
- 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, dns.TypeSRV)
- 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, dns.TypeA)
- 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)
- }
-
- // Test NS request
- query = "inter.webs.test."
- r, e = k.parseRequest(query, dns.TypeNS)
- if e != nil {
- t.Errorf("Expected no error from parseRequest(\"%v\", \"NS\"). Instead got '%v'.", query, e)
- }
- tcs = map[string]string{
- "port": "",
- "protocol": "",
- "endpoint": "",
- "service": "",
- "namespace": "",
- "typeName": "",
- "zone": "inter.webs.test",
- }
- for field, expected := range tcs {
- expectString(t, f, "NS", query, &r, field, expected)
- }
-
- // Test TXT request
- query = "dns-version.inter.webs.test."
- r, e = k.parseRequest(query, dns.TypeTXT)
- if e != nil {
- t.Errorf("Expected no error from parseRequest(\"%v\", \"TXT\"). Instead got '%v'.", query, e)
- }
- tcs = map[string]string{
- "port": "",
- "protocol": "",
- "endpoint": "",
- "service": "",
- "namespace": "",
- "typeName": "dns-version",
- "zone": "inter.webs.test",
- }
- for field, expected := range tcs {
- expectString(t, f, "TXT", 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, dns.TypeA)
- if e == nil {
- t.Errorf("Expected error from %v(\"%v\", \"A\").", f, q)
- }
- }
-
- invalidSRVQueries := []string{
- "_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, dns.TypeSRV)
- if e == nil {
- t.Errorf("Expected error from %v(\"%v\", \"SRV\").", f, q)
- }
- }
-}
-
func TestEndpointHostname(t *testing.T) {
var tests = []struct {
ip string
@@ -384,7 +209,7 @@ func (APIConnServiceTest) GetNodeByName(name string) (api.Node, error) {
func TestServices(t *testing.T) {
- k := Kubernetes{Zones: []string{"interwebs.test"}}
+ k := Kubernetes{Zones: []string{"interwebs.test."}}
k.Federations = []Federation{{name: "fed", zone: "era.tion.com"}}
k.interfaceAddrsFunc = localPodIP
k.APIConn = &APIConnServiceTest{}
@@ -414,7 +239,8 @@ func TestServices(t *testing.T) {
for _, test := range tests {
state := request.Request{
- Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
+ Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
+ Zone: "interwebs.test.", // must match from k.Zones[0]
}
svcs, _, e := k.Services(state, false, middleware.Options{})
if e != nil {
diff --git a/middleware/kubernetes/ns_test.go b/middleware/kubernetes/ns_test.go
index 1aa4f910d..b7e2ae513 100644
--- a/middleware/kubernetes/ns_test.go
+++ b/middleware/kubernetes/ns_test.go
@@ -7,10 +7,10 @@ import "k8s.io/client-go/1.5/pkg/api"
import "github.com/miekg/dns"
func TestRecordForNS(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs.test"}}
+ k := Kubernetes{Zones: []string{"inter.webs.test."}}
corednsRecord.Hdr.Name = "coredns.kube-system."
corednsRecord.A = net.IP("1.2.3.4")
- r, _ := k.parseRequest("inter.webs.test", dns.TypeNS)
+ r, _ := k.parseRequest("inter.webs.test.", dns.TypeNS, "inter.webs.test.")
expected := "/coredns/test/webs/inter/kube-system/coredns"
svc := k.recordsForNS(r)
@@ -20,10 +20,10 @@ func TestRecordForNS(t *testing.T) {
}
func TestDefaultNSMsg(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs.test"}}
+ k := Kubernetes{Zones: []string{"inter.webs.test."}}
corednsRecord.Hdr.Name = "coredns.kube-system."
corednsRecord.A = net.IP("1.2.3.4")
- r, _ := k.parseRequest("ns.dns.inter.webs.test", dns.TypeA)
+ r, _ := k.parseRequest("ns.dns.inter.webs.test.", dns.TypeA, "inter.webs.test.")
expected := "/coredns/test/webs/inter/dns/ns"
svc := k.defaultNSMsg(r)
@@ -33,13 +33,13 @@ func TestDefaultNSMsg(t *testing.T) {
}
func TestIsDefaultNS(t *testing.T) {
- k := Kubernetes{Zones: []string{"inter.webs.test"}}
- r, _ := k.parseRequest("ns.dns.inter.webs.test", dns.TypeA)
+ k := Kubernetes{Zones: []string{"inter.webs.test."}}
+ r, _ := k.parseRequest("ns.dns.inter.webs.test", dns.TypeA, "inter.webs.test.")
var name string
var expected bool
- name = "ns.dns.inter.webs.test"
+ name = "ns.dns.inter.webs.test."
expected = true
if isDefaultNS(name, r) != expected {
t.Errorf("Expected IsDefaultNS('%v') to be '%v'.", name, expected)
diff --git a/middleware/kubernetes/parse.go b/middleware/kubernetes/parse.go
new file mode 100644
index 000000000..790060896
--- /dev/null
+++ b/middleware/kubernetes/parse.go
@@ -0,0 +1,120 @@
+package kubernetes
+
+import (
+ "github.com/coredns/coredns/middleware/pkg/dnsutil"
+
+ "github.com/miekg/dns"
+)
+
+type recordRequest struct {
+ // The named port from the kubernetes DNS spec, this is the service part (think _https) from a well formed
+ // SRV record.
+ port string
+ // The protocol is usually _udp or _tcp (if set), and comes from the protocol part of a well formed
+ // SRV record.
+ protocol string
+ endpoint string
+ service string
+ namespace string
+ // A each name can be for a pod or a service, here we track what we've seen. This value is true for
+ // pods and false for services. If we ever need to extend this well use a typed value.
+ podOrSvc string
+ zone string
+ federation string
+}
+
+// TODO(miek): make it use request.Request.
+func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16, zone ...string) (r recordRequest, err error) {
+ // 3 Possible cases
+ // SRV Request: _port._protocol.service.namespace.[federation.]type.zone
+ // A Request (endpoint): endpoint.service.namespace.[federation.]type.zone
+ // A Request (service): service.namespace.[federation.]type.zone
+
+ if len(zone) == 0 {
+ panic("parseRequest must be called with a zone")
+ }
+
+ base, _ := dnsutil.TrimZone(lowerCasedName, zone[0])
+ segs := dns.SplitDomainName(base)
+
+ r.zone = zone[0]
+ r.federation, segs = k.stripFederation(segs)
+
+ if qtype == dns.TypeNS {
+ return r, nil
+ }
+
+ if qtype == dns.TypeA && isDefaultNS(lowerCasedName, r) {
+ return r, nil
+ }
+
+ offset := 0
+ if qtype == dns.TypeSRV {
+ // The kubernetes peer-finder expects queries with empty port and service to resolve
+ // If neither is specified, treat it as a wildcard
+ if len(segs) == 3 {
+ r.port = "*"
+ r.service = "*"
+ offset = 0
+ } else {
+ if len(segs) != 5 {
+ return r, errInvalidRequest
+ }
+ // 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 !wildcard(r.port) {
+ return r, errInvalidRequest
+ }
+ }
+ if segs[1][0] == '_' {
+ r.protocol = segs[1][1:]
+ if r.protocol != "tcp" && r.protocol != "udp" {
+ return r, errInvalidRequest
+ }
+ } else {
+ r.protocol = segs[1]
+ if !wildcard(r.protocol) {
+ return r, errInvalidRequest
+ }
+ }
+ if r.port == "" || r.protocol == "" {
+ return r, errInvalidRequest
+ }
+ offset = 2
+ }
+ }
+ if (qtype == dns.TypeA || qtype == dns.TypeAAAA) && len(segs) == 4 {
+ // This is an endpoint A/AAAA record request. Get first element as endpoint.
+ r.endpoint = segs[0]
+ offset = 1
+ }
+
+ if len(segs) == (offset + 3) {
+ r.service = segs[offset]
+ r.namespace = segs[offset+1]
+ r.podOrSvc = segs[offset+2]
+
+ return r, nil
+ }
+
+ return r, errInvalidRequest
+}
+
+// String return a string representation of r, it just returns all
+// fields concatenated with dots.
+// This is mostly used in tests.
+func (r recordRequest) String() string {
+ s := r.port
+ s += "." + r.protocol
+ s += "." + r.endpoint
+ s += "." + r.service
+ s += "." + r.namespace
+ s += "." + r.podOrSvc
+ s += "." + r.zone
+ s += "." + r.federation
+ return s
+}
diff --git a/middleware/kubernetes/parse_test.go b/middleware/kubernetes/parse_test.go
new file mode 100644
index 000000000..06c3711cd
--- /dev/null
+++ b/middleware/kubernetes/parse_test.go
@@ -0,0 +1,131 @@
+package kubernetes
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/miekg/dns"
+)
+
+func expectString(t *testing.T, function, qtype, query string, r *recordRequest, field, expected string) {
+ ref := reflect.ValueOf(r)
+ refField := reflect.Indirect(ref).FieldByName(field)
+ got := refField.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
+
+ zone := "intern.webs.tests."
+ k := Kubernetes{Zones: []string{zone}}
+ f := "parseRequest"
+
+ // Test a valid SRV request
+ //
+ query := "_http._tcp.webs.mynamespace.svc.inter.webs.test."
+ r, e := k.parseRequest(query, dns.TypeSRV, zone)
+ 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",
+ "podOrSvc": Svc,
+ "zone": zone,
+ }
+ 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, dns.TypeSRV, zone)
+ 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",
+ "podOrSvc": Svc,
+ "zone": zone,
+ }
+ 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, dns.TypeA, zone)
+ 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",
+ "podOrSvc": Svc,
+ "zone": zone,
+ }
+ for field, expected := range tcs {
+ expectString(t, f, "A", query, &r, field, expected)
+ }
+
+ // Test NS request
+ query = "inter.webs.test."
+ r, e = k.parseRequest(query, dns.TypeNS, zone)
+ if e != nil {
+ t.Errorf("Expected no error from parseRequest(\"%v\", \"NS\"). Instead got '%v'.", query, e)
+ }
+ tcs = map[string]string{
+ "port": "",
+ "protocol": "",
+ "endpoint": "",
+ "service": "",
+ "namespace": "",
+ "podOrSvc": "",
+ "zone": zone,
+ }
+ for field, expected := range tcs {
+ expectString(t, f, "NS", query, &r, field, expected)
+ }
+
+ // Invalid query tests
+ invalidAQueries := []string{
+ "_http._tcp.webs.mynamespace.svc.inter.webs.test.", // A requests cannot have port or protocol TODO(miek): this must return NODATA
+
+ }
+ for _, q := range invalidAQueries {
+ _, e = k.parseRequest(q, dns.TypeA, zone)
+ if e == nil {
+ t.Errorf("Expected error from %v(\"%v\", \"A\").", f, q)
+ }
+ }
+
+ invalidSRVQueries := []string{
+ "_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
+ }
+
+ for _, q := range invalidSRVQueries {
+ _, e = k.parseRequest(q, dns.TypeSRV, zone)
+ if e == nil {
+ t.Errorf("Expected error from %v(\"%v\", \"SRV\").", f, q)
+ }
+ }
+}
diff --git a/test/kubernetes_test.go b/test/kubernetes_test.go
index ac5414743..1d00be3b8 100644
--- a/test/kubernetes_test.go
+++ b/test/kubernetes_test.go
@@ -543,7 +543,7 @@ func TestKubernetesIntegrationCidrReverseZone(t *testing.T) {
kubernetes cluster.local {
endpoint http://localhost:8080
namespaces test-1
- cidrs 10.0.0.0/24
+ cidrs 10.0.0.0/24
}
erratic . {
drop 0
@@ -555,10 +555,10 @@ func TestKubernetesIntegrationCidrReverseZone(t *testing.T) {
func TestKubernetesIntegrationPartialCidrReverseZone(t *testing.T) {
corefile :=
`.:0 {
- kubernetes cluster.local {
+ kubernetes cluster.local {
endpoint http://localhost:8080
namespaces test-1
- cidrs 10.0.0.96/28 10.0.0.120/32
+ cidrs 10.0.0.96/28 10.0.0.120/32
}
erratic . {
drop 0
@@ -572,7 +572,7 @@ func TestKubernetesIntegrationAllNSExposed(t *testing.T) {
`.:0 {
kubernetes cluster.local {
endpoint http://localhost:8080
- cidrs 10.0.0.0/24
+ cidrs 10.0.0.0/24
}
`
doIntegrationTests(t, corefile, dnsTestCasesAllNSExposed)