mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
264 lines
9.7 KiB
Go
264 lines
9.7 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/coredns/coredns/plugin/kubernetes/object"
|
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
|
"github.com/coredns/coredns/plugin/test"
|
|
|
|
"github.com/miekg/dns"
|
|
api "k8s.io/api/core/v1"
|
|
)
|
|
|
|
type APIConnZonalTest struct{}
|
|
|
|
func (APIConnZonalTest) HasSynced() bool { return true }
|
|
func (APIConnZonalTest) Run() {}
|
|
func (APIConnZonalTest) Stop() error { return nil }
|
|
func (APIConnZonalTest) PodIndex(string) []*object.Pod { return nil }
|
|
func (APIConnZonalTest) SvcIndexReverse(string) []*object.Service { return nil }
|
|
func (APIConnZonalTest) SvcExtIndexReverse(string) []*object.Service { return nil }
|
|
func (APIConnZonalTest) ServiceImportList() []*object.ServiceImport { return nil }
|
|
func (APIConnZonalTest) SvcImportIndex(string) []*object.ServiceImport { return nil }
|
|
func (APIConnZonalTest) EpIndexReverse(string) []*object.Endpoints { return nil }
|
|
func (APIConnZonalTest) McEpIndex(string) []*object.MultiClusterEndpoints { return nil }
|
|
func (APIConnZonalTest) Modified(ModifiedMode) int64 { return int64(1499347823) }
|
|
|
|
func (a APIConnZonalTest) ServiceList() []*object.Service {
|
|
return []*object.Service{
|
|
{
|
|
Name: "hdls",
|
|
Namespace: "testns",
|
|
ClusterIPs: []string{api.ClusterIPNone},
|
|
},
|
|
{
|
|
Name: "clstr",
|
|
Namespace: "testns",
|
|
ClusterIPs: []string{"10.0.0.10"},
|
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a APIConnZonalTest) SvcIndex(idx string) []*object.Service {
|
|
switch idx {
|
|
case "hdls.testns":
|
|
return a.ServiceList()[:1]
|
|
case "clstr.testns":
|
|
return a.ServiceList()[1:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a APIConnZonalTest) EndpointsList() []*object.Endpoints {
|
|
return []*object.Endpoints{
|
|
{
|
|
Subsets: []object.EndpointSubset{
|
|
{
|
|
Addresses: []object.EndpointAddress{
|
|
{IP: "172.0.0.1"},
|
|
{IP: "172.0.0.2"},
|
|
{IP: "172.0.0.3"},
|
|
},
|
|
Ports: []object.EndpointPort{{Port: 80, Name: "http", Protocol: "tcp"}},
|
|
},
|
|
},
|
|
Zones: map[string]string{
|
|
"172.0.0.1": "us-west-2a",
|
|
"172.0.0.2": "us-west-2b",
|
|
"172.0.0.3": "us-west-2b",
|
|
},
|
|
Name: "hdls-slice",
|
|
Namespace: "testns",
|
|
Index: object.EndpointsKey("hdls", "testns"),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a APIConnZonalTest) EpIndex(idx string) []*object.Endpoints {
|
|
if idx == "hdls.testns" {
|
|
return a.EndpointsList()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (APIConnZonalTest) GetNodeByName(_ context.Context, _ string) (*api.Node, error) {
|
|
return &api.Node{}, nil
|
|
}
|
|
|
|
func (APIConnZonalTest) GetNamespaceByName(name string) (*object.Namespace, error) {
|
|
return &object.Namespace{Name: name}, nil
|
|
}
|
|
|
|
var zonalTestCases = []test.Case{
|
|
{ // pin: endpoints narrowed to the requested zone
|
|
Qname: "us-west-2a.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.A("us-west-2a.pin._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.1"),
|
|
},
|
|
},
|
|
{
|
|
Qname: "us-west-2b.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.A("us-west-2b.pin._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.2"),
|
|
test.A("us-west-2b.pin._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.3"),
|
|
},
|
|
},
|
|
{ // pin: any zone label without matching endpoints — drained zones and
|
|
// typos alike — is the same determinate empty answer
|
|
Qname: "us-west-2c.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeSuccess,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // prefer: narrows exactly like pin when the zone is populated
|
|
Qname: "us-west-2a.prefer._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.A("us-west-2a.prefer._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.1"),
|
|
},
|
|
},
|
|
{ // prefer: an empty zone falls back to every endpoint — the fallback
|
|
// is chosen in the name, so it is not a silent widening of a pin
|
|
Qname: "us-west-2c.prefer._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.A("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.1"),
|
|
test.A("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.2"),
|
|
test.A("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.3"),
|
|
},
|
|
},
|
|
{ // a nonexistent service stays NXDOMAIN under any directive
|
|
Qname: "us-west-2a.pin._zone.ghost.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeNameError,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // an unknown directive keeps the stock too-long NXDOMAIN
|
|
Qname: "us-west-2a.florp._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeNameError,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // zone-scoped names are defined for headless services only
|
|
Qname: "us-west-2a.pin._zone.clstr.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeNameError,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // pin: exists-but-empty stays NODATA for non-address qtypes too (TXT
|
|
// has its own lookup branch; NXDOMAIN would be negative-cached per name)
|
|
Qname: "us-west-2c.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeTXT,
|
|
Rcode: dns.RcodeSuccess,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // prefer: SRV narrows when populated and falls back when not, same as A
|
|
Qname: "us-west-2c.prefer._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.SRV("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN SRV 0 33 80 172-0-0-1.hdls.testns.svc.cluster.local."),
|
|
test.SRV("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN SRV 0 33 80 172-0-0-2.hdls.testns.svc.cluster.local."),
|
|
test.SRV("us-west-2c.prefer._zone.hdls.testns.svc.cluster.local. 5 IN SRV 0 33 80 172-0-0-3.hdls.testns.svc.cluster.local."),
|
|
},
|
|
Extra: []dns.RR{
|
|
test.A("172-0-0-1.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.1"),
|
|
test.A("172-0-0-2.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.2"),
|
|
test.A("172-0-0-3.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.3"),
|
|
},
|
|
},
|
|
{ // prefer: TXT on a zonal name is NODATA like every non-address type
|
|
Qname: "us-west-2c.prefer._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeTXT,
|
|
Rcode: dns.RcodeSuccess,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
{ // pin: SRV comes out zone-filtered, since filtering happens at endpoint selection
|
|
Qname: "us-west-2b.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
|
|
Rcode: dns.RcodeSuccess,
|
|
Answer: []dns.RR{
|
|
test.SRV("us-west-2b.pin._zone.hdls.testns.svc.cluster.local. 5 IN SRV 0 50 80 172-0-0-2.hdls.testns.svc.cluster.local."),
|
|
test.SRV("us-west-2b.pin._zone.hdls.testns.svc.cluster.local. 5 IN SRV 0 50 80 172-0-0-3.hdls.testns.svc.cluster.local."),
|
|
},
|
|
Extra: []dns.RR{
|
|
test.A("172-0-0-2.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.2"),
|
|
test.A("172-0-0-3.hdls.testns.svc.cluster.local. 5 IN A 172.0.0.3"),
|
|
},
|
|
},
|
|
}
|
|
|
|
// Without the option the shape is three-labels-too-long, exactly as it has
|
|
// always been: behavior identical to before the feature existed.
|
|
var zonalDisabledTestCases = []test.Case{
|
|
{
|
|
Qname: "us-west-2a.pin._zone.hdls.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
|
Rcode: dns.RcodeNameError,
|
|
Ns: []dns.RR{
|
|
test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"),
|
|
},
|
|
},
|
|
}
|
|
|
|
// A zone-only endpoint change alters zonal answers, so it must not be
|
|
// classified as equivalent (which would skip the serial bump). Zone is
|
|
// empty in default configurations, so this costs nothing when the option
|
|
// is off.
|
|
func TestEndpointsEquivalentZoneChange(t *testing.T) {
|
|
eps := func(zone string) *object.Endpoints {
|
|
return &object.Endpoints{
|
|
Subsets: []object.EndpointSubset{{
|
|
Addresses: []object.EndpointAddress{{IP: "172.0.0.1"}},
|
|
}},
|
|
Zones: map[string]string{"172.0.0.1": zone},
|
|
}
|
|
}
|
|
if !endpointsEquivalent(eps("us-west-2a"), eps("us-west-2a")) {
|
|
t.Fatal("identical endpoints must be equivalent")
|
|
}
|
|
if endpointsEquivalent(eps("us-west-2a"), eps("us-west-2b")) {
|
|
t.Fatal("a zone-only change alters zonal answers and must not be equivalent")
|
|
}
|
|
}
|
|
|
|
func TestServeDNSZonal(t *testing.T) {
|
|
k := New([]string{"cluster.local."})
|
|
k.APIConn = &APIConnZonalTest{}
|
|
k.Next = test.NextHandler(dns.RcodeSuccess, nil)
|
|
k.Namespaces = map[string]struct{}{"testns": {}}
|
|
k.opts.zonal = true
|
|
ctx := context.TODO()
|
|
|
|
runZonalCases(ctx, t, k, zonalTestCases)
|
|
|
|
k.opts.zonal = false
|
|
runZonalCases(ctx, t, k, zonalDisabledTestCases)
|
|
}
|
|
|
|
func runZonalCases(ctx context.Context, t *testing.T, k *Kubernetes, cases []test.Case) {
|
|
t.Helper()
|
|
for i, tc := range cases {
|
|
r := tc.Msg()
|
|
w := dnstest.NewRecorder(&test.ResponseWriter{})
|
|
if _, err := k.ServeDNS(ctx, w, r); err != nil {
|
|
t.Errorf("Test %d expected no error, got %v", i, err)
|
|
continue
|
|
}
|
|
if w.Msg == nil {
|
|
t.Fatalf("Test %d, got nil message for %q", i, r.Question[0].Name)
|
|
}
|
|
if err := test.SortAndCheck(w.Msg, tc); err != nil {
|
|
t.Errorf("Test %d (%s), %v", i, tc.Qname, err)
|
|
}
|
|
}
|
|
}
|