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
This commit is contained in:
parent
5a60090933
commit
1c53d4130e
7 changed files with 122 additions and 9 deletions
|
@ -26,11 +26,11 @@ var directives = []string{
|
||||||
"loadbalance",
|
"loadbalance",
|
||||||
"dnssec",
|
"dnssec",
|
||||||
"reverse",
|
"reverse",
|
||||||
|
"kubernetes",
|
||||||
"file",
|
"file",
|
||||||
"auto",
|
"auto",
|
||||||
"secondary",
|
"secondary",
|
||||||
"etcd",
|
"etcd",
|
||||||
"kubernetes",
|
|
||||||
"proxy",
|
"proxy",
|
||||||
"whoami",
|
"whoami",
|
||||||
"erratic",
|
"erratic",
|
||||||
|
|
|
@ -34,11 +34,11 @@
|
||||||
120:loadbalance:loadbalance
|
120:loadbalance:loadbalance
|
||||||
130:dnssec:dnssec
|
130:dnssec:dnssec
|
||||||
140:reverse:reverse
|
140:reverse:reverse
|
||||||
150:file:file
|
150:kubernetes:kubernetes
|
||||||
160:auto:auto
|
160:file:file
|
||||||
170:secondary:secondary
|
170:auto:auto
|
||||||
180:etcd:etcd
|
180:secondary:secondary
|
||||||
190:kubernetes:kubernetes
|
190:etcd:etcd
|
||||||
200:proxy:proxy
|
200:proxy:proxy
|
||||||
210:whoami:whoami
|
210:whoami:whoami
|
||||||
220:erratic:erratic
|
220:erratic:erratic
|
||||||
|
|
|
@ -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{})
|
_, _, err = middleware.A(&k, zone, state, nil, middleware.Options{})
|
||||||
}
|
}
|
||||||
if k.IsNameError(err) {
|
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.
|
// 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{})
|
return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /*debug*/, nil /* err */, middleware.Options{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ type Kubernetes struct {
|
||||||
Selector *labels.Selector
|
Selector *labels.Selector
|
||||||
PodMode string
|
PodMode string
|
||||||
ReverseCidrs []net.IPNet
|
ReverseCidrs []net.IPNet
|
||||||
|
Fallthrough bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -155,6 +155,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
|
case "fallthrough":
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
|
k8s.Fallthrough = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k8s, nil
|
return k8s, nil
|
||||||
|
|
|
@ -27,6 +27,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
expectedLabelSelector string // expected label selector value
|
expectedLabelSelector string // expected label selector value
|
||||||
expectedPodMode string
|
expectedPodMode string
|
||||||
expectedCidrs []net.IPNet
|
expectedCidrs []net.IPNet
|
||||||
|
expectedFallthrough bool
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{
|
{
|
||||||
|
@ -40,6 +41,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with multiple zones",
|
"kubernetes keyword with multiple zones",
|
||||||
|
@ -52,6 +54,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with zone and empty braces",
|
"kubernetes keyword with zone and empty braces",
|
||||||
|
@ -65,6 +68,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword with url",
|
"endpoint keyword with url",
|
||||||
|
@ -79,6 +83,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with one namespace",
|
"namespaces keyword with one namespace",
|
||||||
|
@ -93,6 +98,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with multiple namespaces",
|
"namespaces keyword with multiple namespaces",
|
||||||
|
@ -107,6 +113,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in seconds",
|
"resync period in seconds",
|
||||||
|
@ -121,6 +128,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in minutes",
|
"resync period in minutes",
|
||||||
|
@ -135,6 +143,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"basic label selector",
|
"basic label selector",
|
||||||
|
@ -149,6 +158,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"environment=prod",
|
"environment=prod",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"multi-label selector",
|
"multi-label selector",
|
||||||
|
@ -163,6 +173,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"application=nginx,environment in (production,qa,staging)",
|
"application=nginx,environment in (production,qa,staging)",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fully specified valid config",
|
"fully specified valid config",
|
||||||
|
@ -171,6 +182,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
endpoint http://localhost:8080
|
endpoint http://localhost:8080
|
||||||
namespaces demo test
|
namespaces demo test
|
||||||
labels environment in (production, staging, qa),application=nginx
|
labels environment in (production, staging, qa),application=nginx
|
||||||
|
fallthrough
|
||||||
}`,
|
}`,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
@ -180,6 +192,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"application=nginx,environment in (production,qa,staging)",
|
"application=nginx,environment in (production,qa,staging)",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
// negative
|
// negative
|
||||||
{
|
{
|
||||||
|
@ -193,6 +206,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword without a zone",
|
"kubernetes keyword without a zone",
|
||||||
|
@ -205,6 +219,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword without an endpoint value",
|
"endpoint keyword without an endpoint value",
|
||||||
|
@ -219,6 +234,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespace keyword without a namespace value",
|
"namespace keyword without a namespace value",
|
||||||
|
@ -233,6 +249,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resyncperiod keyword without a duration value",
|
"resyncperiod keyword without a duration value",
|
||||||
|
@ -247,6 +264,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period no units",
|
"resync period no units",
|
||||||
|
@ -261,6 +279,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period invalid",
|
"resync period invalid",
|
||||||
|
@ -275,6 +294,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with no selector value",
|
"labels with no selector value",
|
||||||
|
@ -289,6 +309,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with invalid selector value",
|
"labels with invalid selector value",
|
||||||
|
@ -303,6 +324,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// pods disabled
|
// pods disabled
|
||||||
{
|
{
|
||||||
|
@ -318,6 +340,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
PodModeDisabled,
|
PodModeDisabled,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// pods insecure
|
// pods insecure
|
||||||
{
|
{
|
||||||
|
@ -333,6 +356,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
PodModeInsecure,
|
PodModeInsecure,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// pods verified
|
// pods verified
|
||||||
{
|
{
|
||||||
|
@ -348,6 +372,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
PodModeVerified,
|
PodModeVerified,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// pods invalid
|
// pods invalid
|
||||||
{
|
{
|
||||||
|
@ -363,6 +388,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
PodModeVerified,
|
PodModeVerified,
|
||||||
nil,
|
nil,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -378,6 +404,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
[]net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")},
|
[]net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -393,6 +420,23 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func createTestServer(t *testing.T, corefile string) (*caddy.Instance, string) {
|
||||||
server, err := CoreDNSServer(corefile)
|
server, err := CoreDNSServer(corefile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -424,9 +442,6 @@ func TestKubernetesIntegration(t *testing.T) {
|
||||||
`.:0 {
|
`.:0 {
|
||||||
kubernetes cluster.local 0.0.10.in-addr.arpa {
|
kubernetes cluster.local 0.0.10.in-addr.arpa {
|
||||||
endpoint http://localhost:8080
|
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
|
namespaces test-1
|
||||||
pods disabled
|
pods disabled
|
||||||
}
|
}
|
||||||
|
@ -501,3 +516,41 @@ func TestKubernetesIntegrationAllNSExposed(t *testing.T) {
|
||||||
`
|
`
|
||||||
doIntegrationTests(t, corefile, dnsTestCasesAllNSExposed)
|
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.
|
||||||
|
`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue