From 1c53d4130e83aecdbd06d40ed3daaf90e7e26a03 Mon Sep 17 00:00:00 2001 From: John Belamaric Date: Wed, 19 Apr 2017 16:08:30 -0400 Subject: [PATCH] Add fallthrough support for Kubernetes (#626) * Add fallthrough support for Kubernetes This enables registering other services in the same zone as Kubernetes services. This also re-orders the middleware chain so that Kubernetes comes before other types, in order to make this work out-of-the-box. * Remove extra line --- core/dnsserver/zdirectives.go | 2 +- middleware.cfg | 10 ++--- middleware/kubernetes/handler.go | 3 ++ middleware/kubernetes/kubernetes.go | 1 + middleware/kubernetes/setup.go | 7 ++++ middleware/kubernetes/setup_test.go | 49 ++++++++++++++++++++++++ test/kubernetes_test.go | 59 +++++++++++++++++++++++++++-- 7 files changed, 122 insertions(+), 9 deletions(-) diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go index 7be013e17..d23862a82 100644 --- a/core/dnsserver/zdirectives.go +++ b/core/dnsserver/zdirectives.go @@ -26,11 +26,11 @@ var directives = []string{ "loadbalance", "dnssec", "reverse", + "kubernetes", "file", "auto", "secondary", "etcd", - "kubernetes", "proxy", "whoami", "erratic", diff --git a/middleware.cfg b/middleware.cfg index 6695718a2..909ba104f 100644 --- a/middleware.cfg +++ b/middleware.cfg @@ -34,11 +34,11 @@ 120:loadbalance:loadbalance 130:dnssec:dnssec 140:reverse:reverse -150:file:file -160:auto:auto -170:secondary:secondary -180:etcd:etcd -190:kubernetes:kubernetes +150:kubernetes:kubernetes +160:file:file +170:auto:auto +180:secondary:secondary +190:etcd:etcd 200:proxy:proxy 210:whoami:whoami 220:erratic:erratic diff --git a/middleware/kubernetes/handler.go b/middleware/kubernetes/handler.go index 9dfc5c5a0..6eb637506 100644 --- a/middleware/kubernetes/handler.go +++ b/middleware/kubernetes/handler.go @@ -71,6 +71,9 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M _, _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) } if k.IsNameError(err) { + if k.Fallthrough { + return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r) + } // Make err nil when returning here, so we don't log spam for NXDOMAIN. return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /*debug*/, nil /* err */, middleware.Options{}) } diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go index ca2d69824..82bbcbce3 100644 --- a/middleware/kubernetes/kubernetes.go +++ b/middleware/kubernetes/kubernetes.go @@ -43,6 +43,7 @@ type Kubernetes struct { Selector *labels.Selector PodMode string ReverseCidrs []net.IPNet + Fallthrough bool } const ( diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go index be045a522..9467d1d28 100644 --- a/middleware/kubernetes/setup.go +++ b/middleware/kubernetes/setup.go @@ -155,6 +155,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { continue } return nil, c.ArgErr() + case "fallthrough": + args := c.RemainingArgs() + if len(args) == 0 { + k8s.Fallthrough = true + continue + } + return nil, c.ArgErr() } } return k8s, nil diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go index bc6418c0d..f9a87a805 100644 --- a/middleware/kubernetes/setup_test.go +++ b/middleware/kubernetes/setup_test.go @@ -27,6 +27,7 @@ func TestKubernetesParse(t *testing.T) { expectedLabelSelector string // expected label selector value expectedPodMode string expectedCidrs []net.IPNet + expectedFallthrough bool }{ // positive { @@ -40,6 +41,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "kubernetes keyword with multiple zones", @@ -52,6 +54,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "kubernetes keyword with zone and empty braces", @@ -65,6 +68,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "endpoint keyword with url", @@ -79,6 +83,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "namespaces keyword with one namespace", @@ -93,6 +98,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "namespaces keyword with multiple namespaces", @@ -107,6 +113,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "resync period in seconds", @@ -121,6 +128,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "resync period in minutes", @@ -135,6 +143,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "basic label selector", @@ -149,6 +158,7 @@ func TestKubernetesParse(t *testing.T) { "environment=prod", defaultPodMode, nil, + false, }, { "multi-label selector", @@ -163,6 +173,7 @@ func TestKubernetesParse(t *testing.T) { "application=nginx,environment in (production,qa,staging)", defaultPodMode, nil, + false, }, { "fully specified valid config", @@ -171,6 +182,7 @@ func TestKubernetesParse(t *testing.T) { endpoint http://localhost:8080 namespaces demo test labels environment in (production, staging, qa),application=nginx + fallthrough }`, false, "", @@ -180,6 +192,7 @@ func TestKubernetesParse(t *testing.T) { "application=nginx,environment in (production,qa,staging)", defaultPodMode, nil, + true, }, // negative { @@ -193,6 +206,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "kubernetes keyword without a zone", @@ -205,6 +219,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "endpoint keyword without an endpoint value", @@ -219,6 +234,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "namespace keyword without a namespace value", @@ -233,6 +249,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "resyncperiod keyword without a duration value", @@ -247,6 +264,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "resync period no units", @@ -261,6 +279,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "resync period invalid", @@ -275,6 +294,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "labels with no selector value", @@ -289,6 +309,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, { "labels with invalid selector value", @@ -303,6 +324,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, }, // pods disabled { @@ -318,6 +340,7 @@ func TestKubernetesParse(t *testing.T) { "", PodModeDisabled, nil, + false, }, // pods insecure { @@ -333,6 +356,7 @@ func TestKubernetesParse(t *testing.T) { "", PodModeInsecure, nil, + false, }, // pods verified { @@ -348,6 +372,7 @@ func TestKubernetesParse(t *testing.T) { "", PodModeVerified, nil, + false, }, // pods invalid { @@ -363,6 +388,7 @@ func TestKubernetesParse(t *testing.T) { "", PodModeVerified, nil, + false, }, // cidrs ok { @@ -378,6 +404,7 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, []net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")}, + false, }, // cidrs ok { @@ -393,6 +420,23 @@ func TestKubernetesParse(t *testing.T) { "", defaultPodMode, nil, + false, + }, + // fallthrough invalid + { + "Extra params for fallthrough", + `kubernetes coredns.local { + fallthrough junk +}`, + true, + "Wrong argument count", + -1, + 0, + defaultResyncPeriod, + "", + defaultPodMode, + nil, + false, }, } @@ -466,6 +510,11 @@ func TestKubernetesParse(t *testing.T) { t.Errorf("Test %d: Expected kubernetes controller to be initialized with cidr '%s'. Instead found cidr '%s' for input '%s'", i, test.expectedCidrs[j].String(), foundCidrs[j].String(), test.input) } } + // fallthrough + foundFallthrough := k8sController.Fallthrough + if foundFallthrough != test.expectedFallthrough { + t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input) + } } } diff --git a/test/kubernetes_test.go b/test/kubernetes_test.go index 54d346ad5..4ef4e18ec 100644 --- a/test/kubernetes_test.go +++ b/test/kubernetes_test.go @@ -3,6 +3,7 @@ package test import ( + "os" "testing" "time" @@ -372,6 +373,23 @@ var dnsTestCasesAllNSExposed = []test.Case{ }, } +var dnsTestCasesFallthrough = []test.Case{ + { + Qname: "f.b.svc.cluster.local.", Qtype: dns.TypeA, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.A("f.b.svc.cluster.local. 303 IN A 10.10.10.11"), + }, + }, + { + Qname: "foo.cluster.local.", Qtype: dns.TypeA, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.A("foo.cluster.local. 303 IN A 10.10.10.10"), + }, + }, +} + func createTestServer(t *testing.T, corefile string) (*caddy.Instance, string) { server, err := CoreDNSServer(corefile) if err != nil { @@ -424,9 +442,6 @@ func TestKubernetesIntegration(t *testing.T) { `.:0 { kubernetes cluster.local 0.0.10.in-addr.arpa { endpoint http://localhost:8080 - #endpoint https://kubernetes/ - #tls admin.pem admin-key.pem ca.pem - #tls k8s_auth/client2.crt k8s_auth/client2.key k8s_auth/ca2.crt namespaces test-1 pods disabled } @@ -501,3 +516,41 @@ func TestKubernetesIntegrationAllNSExposed(t *testing.T) { ` doIntegrationTests(t, corefile, dnsTestCasesAllNSExposed) } + +func TestKubernetesIntegrationFallthrough(t *testing.T) { + dbfile, rmFunc, err := TempFile(os.TempDir(), clusterLocal) + if err != nil { + t.Fatalf("Could not create TempFile for fallthrough: %s", err) + } + defer rmFunc() + corefile := + `.:0 { + file ` + dbfile + ` cluster.local + kubernetes cluster.local { + endpoint http://localhost:8080 + cidrs 10.0.0.0/24 + namespaces test-1 + fallthrough + } + erratic { + drop 0 + } +` + cases := append(dnsTestCases, dnsTestCasesFallthrough...) + doIntegrationTests(t, corefile, cases) +} + +const clusterLocal = `; cluster.local test file for fallthrough +cluster.local. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600 +cluster.local. IN NS b.iana-servers.net. +cluster.local. IN NS a.iana-servers.net. +cluster.local. IN A 127.0.0.1 +cluster.local. IN A 127.0.0.2 +foo.cluster.local. IN A 10.10.10.10 +f.b.svc.cluster.local. IN A 10.10.10.11 +*.w.cluster.local. IN TXT "Wildcard" +a.b.svc.cluster.local. IN TXT "Not a wildcard" +cname.cluster.local. IN CNAME www.example.net. + +service.namespace.svc.cluster.local. IN SRV 8080 10 10 cluster.local. +`