Add k8s external service CNAMEs (#677)
* Add external service cnames * remove cruft * update CI k8s version * change CI k8s version * min k8s ver for ext services * trying k8s 1.5 * k8s 1.5 requires ports spec * remove kruft * update dns schema version
This commit is contained in:
parent
2f2c90f391
commit
d917ff5ac2
11 changed files with 293 additions and 16 deletions
|
@ -12,7 +12,7 @@ go:
|
||||||
go_import_path: github.com/coredns/coredns
|
go_import_path: github.com/coredns/coredns
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- ETCD_VERSION=2.3.1 K8S_VERSION=1.3.7 KUBECTL="docker exec hyperkube /hyperkube kubectl" DNS_ARGUMENTS=""
|
- ETCD_VERSION=2.3.1 K8S_VERSION=1.5.0 KUBECTL="docker exec hyperkube /hyperkube kubectl" DNS_ARGUMENTS=""
|
||||||
|
|
||||||
# In the Travis VM-based build environment, IPv6 networking is not
|
# In the Travis VM-based build environment, IPv6 networking is not
|
||||||
# enabled by default. The sysctl operations below enable IPv6.
|
# enabled by default. The sysctl operations below enable IPv6.
|
||||||
|
|
|
@ -183,3 +183,17 @@ spec:
|
||||||
- name: c-port
|
- name: c-port
|
||||||
port: 1234
|
port: 1234
|
||||||
protocol: UDP
|
protocol: UDP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ext-svc
|
||||||
|
namespace: test-1
|
||||||
|
spec:
|
||||||
|
type: ExternalName
|
||||||
|
externalName: example.net
|
||||||
|
ports:
|
||||||
|
- name: c-port
|
||||||
|
port: 1234
|
||||||
|
protocol: UDP
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ func A(b ServiceBackend, zone string, state request.Request, previousRecords []d
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
|
|
||||||
switch what {
|
switch what {
|
||||||
case dns.TypeANY:
|
case dns.TypeCNAME:
|
||||||
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
|
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
|
||||||
// x CNAME x is a direct loop, don't add those
|
// x CNAME x is a direct loop, don't add those
|
||||||
continue
|
continue
|
||||||
|
@ -92,7 +92,7 @@ func AAAA(b ServiceBackend, zone string, state request.Request, previousRecords
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
|
|
||||||
switch what {
|
switch what {
|
||||||
case dns.TypeANY:
|
case dns.TypeCNAME:
|
||||||
// Try to resolve as CNAME if it's not an IP, but only if we don't create loops.
|
// Try to resolve as CNAME if it's not an IP, but only if we don't create loops.
|
||||||
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
|
if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) {
|
||||||
// x CNAME x is a direct loop, don't add those
|
// x CNAME x is a direct loop, don't add those
|
||||||
|
@ -182,7 +182,7 @@ func SRV(b ServiceBackend, zone string, state request.Request, opt Options) (rec
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
|
|
||||||
switch what {
|
switch what {
|
||||||
case dns.TypeANY:
|
case dns.TypeCNAME:
|
||||||
srv := serv.NewSRV(state.QName(), weight)
|
srv := serv.NewSRV(state.QName(), weight)
|
||||||
records = append(records, srv)
|
records = append(records, srv)
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ func MX(b ServiceBackend, zone string, state request.Request, opt Options) (reco
|
||||||
}
|
}
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
switch what {
|
switch what {
|
||||||
case dns.TypeANY:
|
case dns.TypeCNAME:
|
||||||
mx := serv.NewMX(state.QName())
|
mx := serv.NewMX(state.QName())
|
||||||
records = append(records, mx)
|
records = append(records, mx)
|
||||||
if _, ok := lookup[mx.Mx]; ok {
|
if _, ok := lookup[mx.Mx]; ok {
|
||||||
|
@ -364,7 +364,7 @@ func NS(b ServiceBackend, zone string, state request.Request, opt Options) (reco
|
||||||
for _, serv := range services {
|
for _, serv := range services {
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
switch what {
|
switch what {
|
||||||
case dns.TypeANY:
|
case dns.TypeCNAME:
|
||||||
return nil, nil, debug, fmt.Errorf("NS record must be an IP address: %s", serv.Host)
|
return nil, nil, debug, fmt.Errorf("NS record must be an IP address: %s", serv.Host)
|
||||||
|
|
||||||
case dns.TypeA, dns.TypeAAAA:
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
//
|
//
|
||||||
// dns.TypeA: the service's Host field contains an A record.
|
// dns.TypeA: the service's Host field contains an A record.
|
||||||
// dns.TypeAAAA: the service's Host field contains an AAAA record.
|
// dns.TypeAAAA: the service's Host field contains an AAAA record.
|
||||||
// dns.TypeANY: the service's Host field contains a name.
|
// dns.TypeCNAME: the service's Host field contains a name.
|
||||||
//
|
//
|
||||||
// Note that a service can double/triple as a TXT record or MX record.
|
// Note that a service can double/triple as a TXT record or MX record.
|
||||||
func (s *Service) HostType() (what uint16, normalized net.IP) {
|
func (s *Service) HostType() (what uint16, normalized net.IP) {
|
||||||
|
@ -20,7 +20,7 @@ func (s *Service) HostType() (what uint16, normalized net.IP) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ip == nil:
|
case ip == nil:
|
||||||
return dns.TypeANY, nil
|
return dns.TypeCNAME, nil
|
||||||
|
|
||||||
case ip.To4() != nil:
|
case ip.To4() != nil:
|
||||||
return dns.TypeA, ip.To4()
|
return dns.TypeA, ip.To4()
|
||||||
|
|
|
@ -11,11 +11,11 @@ func TestType(t *testing.T) {
|
||||||
serv Service
|
serv Service
|
||||||
expectedType uint16
|
expectedType uint16
|
||||||
}{
|
}{
|
||||||
{Service{Host: "example.org"}, dns.TypeANY},
|
{Service{Host: "example.org"}, dns.TypeCNAME},
|
||||||
{Service{Host: "127.0.0.1"}, dns.TypeA},
|
{Service{Host: "127.0.0.1"}, dns.TypeA},
|
||||||
{Service{Host: "2000::3"}, dns.TypeAAAA},
|
{Service{Host: "2000::3"}, dns.TypeAAAA},
|
||||||
{Service{Host: "2000..3"}, dns.TypeANY},
|
{Service{Host: "2000..3"}, dns.TypeCNAME},
|
||||||
{Service{Host: "127.0.0.257"}, dns.TypeANY},
|
{Service{Host: "127.0.0.257"}, dns.TypeCNAME},
|
||||||
{Service{Host: "127.0.0.252", Mail: true}, dns.TypeA},
|
{Service{Host: "127.0.0.252", Mail: true}, dns.TypeA},
|
||||||
{Service{Host: "127.0.0.252", Mail: true, Text: "a"}, dns.TypeA},
|
{Service{Host: "127.0.0.252", Mail: true, Text: "a"}, dns.TypeA},
|
||||||
{Service{Host: "127.0.0.254", Mail: false, Text: "a"}, dns.TypeA},
|
{Service{Host: "127.0.0.254", Mail: false, Text: "a"}, dns.TypeA},
|
||||||
|
|
|
@ -108,6 +108,13 @@ kubernetes coredns.local {
|
||||||
#
|
#
|
||||||
cidrs 10.0.0.0/24 10.0.10.0/25
|
cidrs 10.0.0.0/24 10.0.10.0/25
|
||||||
|
|
||||||
|
# upstream <address> [<address>] ...
|
||||||
|
#
|
||||||
|
# Defines upstream resolvers used for resolving services that point to
|
||||||
|
# external hosts (External Services). <address> can be an ip, and ip:port, or
|
||||||
|
# a path to a file structured like resolv.conf.
|
||||||
|
upstream 12.34.56.78:53
|
||||||
|
|
||||||
# fallthrough
|
# fallthrough
|
||||||
#
|
#
|
||||||
# If a query for a record in the cluster zone results in NXDOMAIN,
|
# If a query for a record in the cluster zone results in NXDOMAIN,
|
||||||
|
|
|
@ -55,7 +55,7 @@ const (
|
||||||
// PodModeInsecure is where pod requests are answered without verfying they exist
|
// PodModeInsecure is where pod requests are answered without verfying they exist
|
||||||
PodModeInsecure = "insecure"
|
PodModeInsecure = "insecure"
|
||||||
// DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md
|
// DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md
|
||||||
DNSSchemaVersion = "1.0.0"
|
DNSSchemaVersion = "1.0.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type endpoint struct {
|
type endpoint struct {
|
||||||
|
@ -97,7 +97,7 @@ func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.
|
||||||
}
|
}
|
||||||
|
|
||||||
switch state.Type() {
|
switch state.Type() {
|
||||||
case "A", "SRV":
|
case "A", "CNAME":
|
||||||
if state.Type() == "A" && isDefaultNS(state.Name(), r) {
|
if state.Type() == "A" && isDefaultNS(state.Name(), r) {
|
||||||
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
|
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
|
||||||
// SOA records always use this hardcoded name
|
// SOA records always use this hardcoded name
|
||||||
|
@ -106,6 +106,16 @@ func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.
|
||||||
}
|
}
|
||||||
s, e := k.Records(r)
|
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.
|
||||||
|
case "SRV":
|
||||||
|
s, e := k.Records(r)
|
||||||
|
// SRV for external services is not yet implemented, so remove those records
|
||||||
|
noext := []msg.Service{}
|
||||||
|
for _, svc := range s {
|
||||||
|
if t, _ := svc.HostType(); t != dns.TypeCNAME {
|
||||||
|
noext = append(noext, svc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noext, nil, e
|
||||||
case "TXT":
|
case "TXT":
|
||||||
err := k.recordsForTXT(r, &svcs)
|
err := k.recordsForTXT(r, &svcs)
|
||||||
return svcs, nil, err
|
return svcs, nil, err
|
||||||
|
@ -373,6 +383,14 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone
|
||||||
Port: int(p.Port)}
|
Port: int(p.Port)}
|
||||||
records = append(records, s)
|
records = append(records, s)
|
||||||
}
|
}
|
||||||
|
// If the addr is not an IP (i.e. an external service), add the record ...
|
||||||
|
s := msg.Service{
|
||||||
|
Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/"),
|
||||||
|
Host: svc.addr}
|
||||||
|
if t, _ := s.HostType(); t == dns.TypeCNAME {
|
||||||
|
records = append(records, s)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,8 +484,16 @@ func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
|
||||||
if nsWildcard && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(svc.Namespace, k.Namespaces)) {
|
if nsWildcard && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(svc.Namespace, k.Namespaces)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s := service{name: svc.Name, namespace: svc.Namespace, addr: svc.Spec.ClusterIP}
|
s := service{name: svc.Name, namespace: svc.Namespace}
|
||||||
if s.addr != api.ClusterIPNone {
|
// External Service
|
||||||
|
if svc.Spec.ExternalName != "" {
|
||||||
|
s.addr = svc.Spec.ExternalName
|
||||||
|
resultItems = append(resultItems, s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// ClusterIP service
|
||||||
|
if svc.Spec.ClusterIP != api.ClusterIPNone {
|
||||||
|
s.addr = svc.Spec.ClusterIP
|
||||||
for _, p := range svc.Spec.Ports {
|
for _, p := range svc.Spec.Ports {
|
||||||
if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.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
|
||||||
|
@ -478,6 +504,7 @@ func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Headless service
|
// Headless service
|
||||||
|
s.addr = svc.Spec.ClusterIP
|
||||||
endpointsList := k.APIConn.EndpointsList()
|
endpointsList := k.APIConn.EndpointsList()
|
||||||
|
|
||||||
for _, ep := range endpointsList.Items {
|
for _, ep := range endpointsList.Items {
|
||||||
|
|
|
@ -9,7 +9,9 @@ import (
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"k8s.io/client-go/1.5/pkg/api"
|
"k8s.io/client-go/1.5/pkg/api"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/middleware"
|
||||||
"github.com/coredns/coredns/middleware/etcd/msg"
|
"github.com/coredns/coredns/middleware/etcd/msg"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRecordForTXT(t *testing.T) {
|
func TestRecordForTXT(t *testing.T) {
|
||||||
|
@ -277,3 +279,83 @@ func TestIpFromPodName(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type APIConnServiceTest struct{}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) Run() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) ServiceList() []*api.Service {
|
||||||
|
svcs := []*api.Service{
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "external",
|
||||||
|
Namespace: "testns",
|
||||||
|
},
|
||||||
|
Spec: api.ServiceSpec{
|
||||||
|
ExternalName: "coredns.io",
|
||||||
|
Ports: []api.ServicePort{{
|
||||||
|
Name: "http",
|
||||||
|
Protocol: "tcp",
|
||||||
|
Port: 80,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return svcs
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) PodIndex(string) []interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (APIConnServiceTest) EndpointsList() api.EndpointsList {
|
||||||
|
return api.EndpointsList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServices(t *testing.T) {
|
||||||
|
|
||||||
|
k := Kubernetes{Zones: []string{"interwebs.test"}}
|
||||||
|
k.APIConn = &APIConnServiceTest{}
|
||||||
|
|
||||||
|
type svcAns struct {
|
||||||
|
host string
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
type svcTest struct {
|
||||||
|
qname string
|
||||||
|
qtype uint16
|
||||||
|
answer svcAns
|
||||||
|
}
|
||||||
|
tests := []svcTest{
|
||||||
|
// External Services
|
||||||
|
{qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/coredns/test/interwebs/svc/testns/external"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
state := request.Request{
|
||||||
|
Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
|
||||||
|
}
|
||||||
|
svcs, _, e := k.Services(state, false, middleware.Options{})
|
||||||
|
if e != nil {
|
||||||
|
t.Errorf("Query '%v' got error '%v'", test.qname, e)
|
||||||
|
}
|
||||||
|
if len(svcs) != 1 {
|
||||||
|
t.Errorf("Query %v %v: expected expected 1 answer, got %v", test.qname, dns.TypeToString[test.qtype], len(svcs))
|
||||||
|
} else {
|
||||||
|
if test.answer.host != svcs[0].Host {
|
||||||
|
t.Errorf("Query %v %v: expected host '%v', got '%v'", test.qname, dns.TypeToString[test.qtype], test.answer.host, svcs[0].Host)
|
||||||
|
}
|
||||||
|
if test.answer.key != svcs[0].Key {
|
||||||
|
t.Errorf("Query %v %v: expected key '%v', got '%v'", test.qname, dns.TypeToString[test.qtype], test.answer.key, svcs[0].Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
|
"github.com/coredns/coredns/middleware/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/middleware/proxy"
|
||||||
|
|
||||||
"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"
|
||||||
|
@ -165,6 +167,16 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
|
case "upstream":
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
ups, err := dnsutil.ParseHostPortOrFile(args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k8s.Proxy = proxy.NewLookup(ups)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return k8s, nil
|
return k8s, nil
|
||||||
|
|
|
@ -28,6 +28,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
expectedPodMode string
|
expectedPodMode string
|
||||||
expectedCidrs []net.IPNet
|
expectedCidrs []net.IPNet
|
||||||
expectedFallthrough bool
|
expectedFallthrough bool
|
||||||
|
expectedUpstreams []string
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{
|
{
|
||||||
|
@ -42,6 +43,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with multiple zones",
|
"kubernetes keyword with multiple zones",
|
||||||
|
@ -55,6 +57,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword with zone and empty braces",
|
"kubernetes keyword with zone and empty braces",
|
||||||
|
@ -69,6 +72,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword with url",
|
"endpoint keyword with url",
|
||||||
|
@ -84,6 +88,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with one namespace",
|
"namespaces keyword with one namespace",
|
||||||
|
@ -99,6 +104,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespaces keyword with multiple namespaces",
|
"namespaces keyword with multiple namespaces",
|
||||||
|
@ -114,6 +120,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in seconds",
|
"resync period in seconds",
|
||||||
|
@ -129,6 +136,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period in minutes",
|
"resync period in minutes",
|
||||||
|
@ -144,6 +152,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"basic label selector",
|
"basic label selector",
|
||||||
|
@ -159,6 +168,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"multi-label selector",
|
"multi-label selector",
|
||||||
|
@ -174,6 +184,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fully specified valid config",
|
"fully specified valid config",
|
||||||
|
@ -193,6 +204,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// negative
|
// negative
|
||||||
{
|
{
|
||||||
|
@ -207,6 +219,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kubernetes keyword without a zone",
|
"kubernetes keyword without a zone",
|
||||||
|
@ -220,6 +233,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"endpoint keyword without an endpoint value",
|
"endpoint keyword without an endpoint value",
|
||||||
|
@ -235,6 +249,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"namespace keyword without a namespace value",
|
"namespace keyword without a namespace value",
|
||||||
|
@ -250,6 +265,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resyncperiod keyword without a duration value",
|
"resyncperiod keyword without a duration value",
|
||||||
|
@ -265,6 +281,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period no units",
|
"resync period no units",
|
||||||
|
@ -280,6 +297,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resync period invalid",
|
"resync period invalid",
|
||||||
|
@ -295,6 +313,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with no selector value",
|
"labels with no selector value",
|
||||||
|
@ -310,6 +329,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labels with invalid selector value",
|
"labels with invalid selector value",
|
||||||
|
@ -325,6 +345,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// pods disabled
|
// pods disabled
|
||||||
{
|
{
|
||||||
|
@ -341,6 +362,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
PodModeDisabled,
|
PodModeDisabled,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// pods insecure
|
// pods insecure
|
||||||
{
|
{
|
||||||
|
@ -357,6 +379,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
PodModeInsecure,
|
PodModeInsecure,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// pods verified
|
// pods verified
|
||||||
{
|
{
|
||||||
|
@ -373,6 +396,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
PodModeVerified,
|
PodModeVerified,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// pods invalid
|
// pods invalid
|
||||||
{
|
{
|
||||||
|
@ -389,6 +413,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
PodModeVerified,
|
PodModeVerified,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -405,6 +430,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,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// cidrs ok
|
// cidrs ok
|
||||||
{
|
{
|
||||||
|
@ -421,6 +447,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
// fallthrough invalid
|
// fallthrough invalid
|
||||||
{
|
{
|
||||||
|
@ -437,6 +464,41 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultPodMode,
|
defaultPodMode,
|
||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
// Valid upstream
|
||||||
|
{
|
||||||
|
"valid upstream",
|
||||||
|
`kubernetes coredns.local {
|
||||||
|
upstream 13.14.15.16:53
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
|
"",
|
||||||
|
defaultPodMode,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
[]string{"13.14.15.16:53"},
|
||||||
|
},
|
||||||
|
// Invalid upstream
|
||||||
|
{
|
||||||
|
"valid upstream",
|
||||||
|
`kubernetes coredns.local {
|
||||||
|
upstream 13.14.15.16orange
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
"not an IP address or file: \"13.14.15.16orange\"",
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
defaultResyncPeriod,
|
||||||
|
"",
|
||||||
|
defaultPodMode,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,6 +577,28 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
if foundFallthrough != test.expectedFallthrough {
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
// upstream
|
||||||
|
foundUpstreams := k8sController.Proxy.Upstreams
|
||||||
|
if test.expectedUpstreams == nil {
|
||||||
|
if foundUpstreams != nil {
|
||||||
|
t.Errorf("Test %d: Expected kubernetes controller to not be initialized with upstreams for input '%s'", i, test.input)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if foundUpstreams == nil {
|
||||||
|
t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstreams for input '%s'", i, test.input)
|
||||||
|
} else {
|
||||||
|
if len(*foundUpstreams) != len(test.expectedUpstreams) {
|
||||||
|
t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d upstreams. Instead found %d upstreams for input '%s'", i, len(test.expectedUpstreams), len(*foundUpstreams), test.input)
|
||||||
|
}
|
||||||
|
for j, want := range test.expectedUpstreams {
|
||||||
|
got := (*foundUpstreams)[j].Select().Name
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstream '%s'. Instead found upstream '%s' for input '%s'", i, want, got, test.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,8 @@ var dnsTestCases = []test.Case{
|
||||||
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
||||||
|
test.CNAME("ext-svc.test-1.svc.cluster.local. 0 IN CNAME example.net."),
|
||||||
|
test.A("example.net. 68974 IN A 13.14.15.16"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -73,6 +75,8 @@ var dnsTestCases = []test.Case{
|
||||||
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
||||||
|
test.CNAME("ext-svc.test-1.svc.cluster.local. 0 IN CNAME example.net."),
|
||||||
|
test.A("example.net. 68974 IN A 13.14.15.16"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,6 +98,8 @@ var dnsTestCases = []test.Case{
|
||||||
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
test.A("svc-c.test-1.svc.cluster.local. 303 IN A 10.0.0.115"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.5"),
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
||||||
|
test.CNAME("ext-svc.test-1.svc.cluster.local. 0 IN CNAME example.net."),
|
||||||
|
test.A("example.net. 68974 IN A 13.14.15.16"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -104,7 +110,6 @@ var dnsTestCases = []test.Case{
|
||||||
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
test.A("headless-svc.test-1.svc.cluster.local. 303 IN A 172.17.0.6"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//TODO: Fix below to all use test.SRV not test.A!
|
|
||||||
{
|
{
|
||||||
Qname: "*._TcP.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,
|
||||||
|
@ -240,6 +245,21 @@ var dnsTestCases = []test.Case{
|
||||||
test.NS("cluster.local. 0 IN NS kubernetes.default.svc.cluster.local."),
|
test.NS("cluster.local. 0 IN NS kubernetes.default.svc.cluster.local."),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Qname: "ext-svc.test-1.svc.cluster.local.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.CNAME("ext-svc.test-1.svc.cluster.local. 0 IN CNAME example.net."),
|
||||||
|
test.A("example.net. 72031 IN A 13.14.15.16"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "ext-svc.test-1.svc.cluster.local.", Qtype: dns.TypeCNAME,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.CNAME("ext-svc.test-1.svc.cluster.local. 0 IN CNAME example.net."),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsTestCasesPodsInsecure = []test.Case{
|
var dnsTestCasesPodsInsecure = []test.Case{
|
||||||
|
@ -444,13 +464,34 @@ func doIntegrationTests(t *testing.T, corefile string, testCases []test.Case) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUpstreamServer(t *testing.T) (func(), *caddy.Instance, string) {
|
||||||
|
upfile, rmfile, err := TempFile(os.TempDir(), exampleNet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create file for CNAME upstream lookups: %s", err)
|
||||||
|
}
|
||||||
|
upstreamServerCorefile := `.:0 {
|
||||||
|
file ` + upfile + ` example.net
|
||||||
|
erratic . {
|
||||||
|
drop 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
server, udp := createTestServer(t, upstreamServerCorefile)
|
||||||
|
return rmfile, server, udp
|
||||||
|
}
|
||||||
|
|
||||||
func TestKubernetesIntegration(t *testing.T) {
|
func TestKubernetesIntegration(t *testing.T) {
|
||||||
|
|
||||||
|
removeUpstreamConfig, upstreamServer, udp := createUpstreamServer(t)
|
||||||
|
defer upstreamServer.Stop()
|
||||||
|
defer removeUpstreamConfig()
|
||||||
|
|
||||||
corefile :=
|
corefile :=
|
||||||
`.: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
|
||||||
namespaces test-1
|
namespaces test-1
|
||||||
pods disabled
|
pods disabled
|
||||||
|
upstream ` + udp + `
|
||||||
}
|
}
|
||||||
erratic . {
|
erratic . {
|
||||||
drop 0
|
drop 0
|
||||||
|
@ -530,6 +571,11 @@ func TestKubernetesIntegrationFallthrough(t *testing.T) {
|
||||||
t.Fatalf("Could not create TempFile for fallthrough: %s", err)
|
t.Fatalf("Could not create TempFile for fallthrough: %s", err)
|
||||||
}
|
}
|
||||||
defer rmFunc()
|
defer rmFunc()
|
||||||
|
|
||||||
|
removeUpstreamConfig, upstreamServer, udp := createUpstreamServer(t)
|
||||||
|
defer upstreamServer.Stop()
|
||||||
|
defer removeUpstreamConfig()
|
||||||
|
|
||||||
corefile :=
|
corefile :=
|
||||||
`.:0 {
|
`.:0 {
|
||||||
file ` + dbfile + ` cluster.local
|
file ` + dbfile + ` cluster.local
|
||||||
|
@ -537,6 +583,7 @@ func TestKubernetesIntegrationFallthrough(t *testing.T) {
|
||||||
endpoint http://localhost:8080
|
endpoint http://localhost:8080
|
||||||
cidrs 10.0.0.0/24
|
cidrs 10.0.0.0/24
|
||||||
namespaces test-1
|
namespaces test-1
|
||||||
|
upstream ` + udp + `
|
||||||
fallthrough
|
fallthrough
|
||||||
}
|
}
|
||||||
erratic {
|
erratic {
|
||||||
|
@ -561,3 +608,7 @@ cname.cluster.local. IN CNAME www.example.net.
|
||||||
|
|
||||||
service.namespace.svc.cluster.local. IN SRV 8080 10 10 cluster.local.
|
service.namespace.svc.cluster.local. IN SRV 8080 10 10 cluster.local.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const exampleNet = `; example.net. test file for cname tests
|
||||||
|
example.net. IN A 13.14.15.16
|
||||||
|
`
|
||||||
|
|
Loading…
Add table
Reference in a new issue