mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
842 lines
22 KiB
842 lines
22 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package dns |
|
|
|
import ( |
|
"fmt" |
|
"net" |
|
"reflect" |
|
"testing" |
|
"time" |
|
|
|
"github.com/armon/go-radix" |
|
|
|
"github.com/hashicorp/consul/internal/dnsutil" |
|
|
|
"github.com/miekg/dns" |
|
"github.com/stretchr/testify/mock" |
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/go-hclog" |
|
|
|
"github.com/hashicorp/consul/acl" |
|
"github.com/hashicorp/consul/agent/config" |
|
"github.com/hashicorp/consul/agent/discovery" |
|
"github.com/hashicorp/consul/agent/structs" |
|
) |
|
|
|
// HandleTestCase is a test case for the HandleRequest function. |
|
// Tests for HandleRequest are split into multiple files to make it easier to |
|
// manage and understand the tests. Other test files are: |
|
// - router_addr_test.go |
|
// - router_ns_test.go |
|
// - router_prepared_query_test.go |
|
// - router_ptr_test.go |
|
// - router_recursor_test.go |
|
// - router_service_test.go |
|
// - router_soa_test.go |
|
// - router_virtual_test.go |
|
// - router_v2_services_test.go |
|
// - router_workload_test.go |
|
type HandleTestCase struct { |
|
name string |
|
agentConfig *config.RuntimeConfig // This will override the default test Router Config |
|
configureDataFetcher func(fetcher discovery.CatalogDataFetcher) |
|
validateAndNormalizeExpected bool |
|
configureRecursor func(recursor dnsRecursor) |
|
mockProcessorError error |
|
request *dns.Msg |
|
requestContext *Context |
|
remoteAddress net.Addr |
|
response *dns.Msg |
|
} |
|
|
|
func Test_HandleRequest_Validation(t *testing.T) { |
|
testCases := []HandleTestCase{ |
|
{ |
|
name: "request with empty message", |
|
request: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{}, |
|
}, |
|
validateAndNormalizeExpected: false, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
Response: true, |
|
Authoritative: false, |
|
Rcode: dns.RcodeRefused, |
|
}, |
|
Compress: false, |
|
Question: nil, |
|
Answer: nil, |
|
Ns: nil, |
|
Extra: nil, |
|
}, |
|
}, |
|
// Context Tests |
|
{ |
|
name: "When a request context is provided, use those field in the query", |
|
request: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.service.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
}, |
|
requestContext: &Context{ |
|
Token: "test-token", |
|
DefaultNamespace: "test-namespace", |
|
DefaultPartition: "test-partition", |
|
}, |
|
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { |
|
result := []*discovery.Result{ |
|
{ |
|
Type: discovery.ResultTypeNode, |
|
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, |
|
Tenancy: discovery.ResultTenancy{ |
|
Namespace: "test-namespace", |
|
Partition: "test-partition", |
|
}, |
|
}, |
|
} |
|
|
|
fetcher.(*discovery.MockCatalogDataFetcher). |
|
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). |
|
Return(result, nil). |
|
Run(func(args mock.Arguments) { |
|
ctx := args.Get(0).(discovery.Context) |
|
req := args.Get(1).(*discovery.QueryPayload) |
|
reqType := args.Get(2).(discovery.LookupType) |
|
|
|
require.Equal(t, "test-token", ctx.Token) |
|
|
|
require.Equal(t, "foo", req.Name) |
|
require.Equal(t, "test-namespace", req.Tenancy.Namespace) |
|
require.Equal(t, "test-partition", req.Tenancy.Partition) |
|
|
|
require.Equal(t, discovery.LookupTypeService, reqType) |
|
}) |
|
}, |
|
validateAndNormalizeExpected: true, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Response: true, |
|
Authoritative: true, |
|
}, |
|
Compress: true, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.service.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
Answer: []dns.RR{ |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "foo.service.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
Ttl: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
{ |
|
name: "When a request context is provided, values do not override explicit tenancy", |
|
request: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Opcode: dns.OpcodeQuery, |
|
}, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.service.bar.ns.baz.ap.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
}, |
|
requestContext: &Context{ |
|
Token: "test-token", |
|
DefaultNamespace: "test-namespace", |
|
DefaultPartition: "test-partition", |
|
}, |
|
configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { |
|
result := []*discovery.Result{ |
|
{ |
|
Type: discovery.ResultTypeNode, |
|
Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, |
|
Tenancy: discovery.ResultTenancy{ |
|
Namespace: "bar", |
|
Partition: "baz", |
|
}, |
|
}, |
|
} |
|
|
|
fetcher.(*discovery.MockCatalogDataFetcher). |
|
On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). |
|
Return(result, nil). |
|
Run(func(args mock.Arguments) { |
|
ctx := args.Get(0).(discovery.Context) |
|
req := args.Get(1).(*discovery.QueryPayload) |
|
reqType := args.Get(2).(discovery.LookupType) |
|
|
|
require.Equal(t, "test-token", ctx.Token) |
|
|
|
require.Equal(t, "foo", req.Name) |
|
require.Equal(t, "bar", req.Tenancy.Namespace) |
|
require.Equal(t, "baz", req.Tenancy.Partition) |
|
|
|
require.Equal(t, discovery.LookupTypeService, reqType) |
|
}) |
|
}, |
|
validateAndNormalizeExpected: true, |
|
response: &dns.Msg{ |
|
MsgHdr: dns.MsgHdr{ |
|
Response: true, |
|
Authoritative: true, |
|
}, |
|
Compress: true, |
|
Question: []dns.Question{ |
|
{ |
|
Name: "foo.service.bar.ns.baz.ap.consul.", |
|
Qtype: dns.TypeA, |
|
Qclass: dns.ClassINET, |
|
}, |
|
}, |
|
Answer: []dns.RR{ |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "foo.service.bar.ns.baz.ap.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
Ttl: 123, |
|
}, |
|
A: net.ParseIP("1.2.3.4"), |
|
}, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
runHandleTestCases(t, tc) |
|
}) |
|
} |
|
} |
|
|
|
// runHandleTestCases runs the test cases for the HandleRequest function. |
|
func runHandleTestCases(t *testing.T, tc HandleTestCase) { |
|
cdf := discovery.NewMockCatalogDataFetcher(t) |
|
if tc.validateAndNormalizeExpected { |
|
cdf.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) |
|
cdf.On("NormalizeRequest", mock.Anything).Return() |
|
} |
|
|
|
if tc.configureDataFetcher != nil { |
|
tc.configureDataFetcher(cdf) |
|
} |
|
cfg := buildDNSConfig(tc.agentConfig, cdf, tc.mockProcessorError) |
|
|
|
router, err := NewRouter(cfg) |
|
require.NoError(t, err) |
|
|
|
// Replace the recursor with a mock and configure |
|
router.recursor = newMockDnsRecursor(t) |
|
if tc.configureRecursor != nil { |
|
tc.configureRecursor(router.recursor) |
|
} |
|
|
|
ctx := tc.requestContext |
|
if ctx == nil { |
|
ctx = &Context{} |
|
} |
|
|
|
var remoteAddress net.Addr |
|
if tc.remoteAddress != nil { |
|
remoteAddress = tc.remoteAddress |
|
} else { |
|
remoteAddress = &net.UDPAddr{} |
|
} |
|
|
|
actual := router.HandleRequest(tc.request, *ctx, remoteAddress) |
|
require.Equal(t, tc.response, actual) |
|
} |
|
|
|
func TestRouterDynamicConfig_GetTTLForService(t *testing.T) { |
|
type testCase struct { |
|
name string |
|
inputKey string |
|
shouldMatch bool |
|
expectedDuration time.Duration |
|
} |
|
|
|
testCases := []testCase{ |
|
{ |
|
name: "strict match", |
|
inputKey: "foo", |
|
shouldMatch: true, |
|
expectedDuration: 1 * time.Second, |
|
}, |
|
{ |
|
name: "wildcard match", |
|
inputKey: "bar", |
|
shouldMatch: true, |
|
expectedDuration: 2 * time.Second, |
|
}, |
|
{ |
|
name: "wildcard match 2", |
|
inputKey: "bart", |
|
shouldMatch: true, |
|
expectedDuration: 2 * time.Second, |
|
}, |
|
{ |
|
name: "no match", |
|
inputKey: "homer", |
|
shouldMatch: false, |
|
expectedDuration: 0 * time.Second, |
|
}, |
|
} |
|
|
|
rtCfg := &config.RuntimeConfig{ |
|
DNSServiceTTL: map[string]time.Duration{ |
|
"foo": 1 * time.Second, |
|
"bar*": 2 * time.Second, |
|
}, |
|
} |
|
cfg, err := getDynamicRouterConfig(rtCfg) |
|
require.NoError(t, err) |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
actual, ok := cfg.GetTTLForService(tc.inputKey) |
|
require.Equal(t, tc.shouldMatch, ok) |
|
require.Equal(t, tc.expectedDuration, actual) |
|
}) |
|
} |
|
} |
|
func buildDNSConfig(agentConfig *config.RuntimeConfig, cdf discovery.CatalogDataFetcher, _ error) Config { |
|
cfg := Config{ |
|
AgentConfig: &config.RuntimeConfig{ |
|
DNSDomain: "consul", |
|
DNSNodeTTL: 123 * time.Second, |
|
DNSSOA: config.RuntimeSOAConfig{ |
|
Refresh: 1, |
|
Retry: 2, |
|
Expire: 3, |
|
Minttl: 4, |
|
}, |
|
DNSUDPAnswerLimit: maxUDPAnswerLimit, |
|
}, |
|
EntMeta: acl.EnterpriseMeta{}, |
|
Logger: hclog.NewNullLogger(), |
|
Processor: discovery.NewQueryProcessor(cdf), |
|
TokenFunc: func() string { return "" }, |
|
TranslateServiceAddressFunc: func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string { |
|
return address |
|
}, |
|
TranslateAddressFunc: func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string { |
|
return addr |
|
}, |
|
} |
|
|
|
if agentConfig != nil { |
|
cfg.AgentConfig = agentConfig |
|
} |
|
|
|
return cfg |
|
} |
|
|
|
// TestDNS_BinaryTruncate tests the dnsBinaryTruncate function. |
|
func TestDNS_BinaryTruncate(t *testing.T) { |
|
msgSrc := new(dns.Msg) |
|
msgSrc.Compress = true |
|
msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) |
|
|
|
for i := 0; i < 5000; i++ { |
|
target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) |
|
msgSrc.Answer = append(msgSrc.Answer, &dns.SRV{Hdr: dns.RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: dns.TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) |
|
msgSrc.Extra = append(msgSrc.Extra, &dns.CNAME{Hdr: dns.RR_Header{Name: target, Class: 1, Rrtype: dns.TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) |
|
} |
|
for _, compress := range []bool{true, false} { |
|
for idx, maxSize := range []int{12, 256, 512, 8192, 65535} { |
|
t.Run(fmt.Sprintf("binarySearch %d", maxSize), func(t *testing.T) { |
|
msg := new(dns.Msg) |
|
msgSrc.Compress = compress |
|
msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) |
|
msg.Answer = msgSrc.Answer |
|
msg.Extra = msgSrc.Extra |
|
msg.Ns = msgSrc.Ns |
|
index := make(map[string]dns.RR, len(msg.Extra)) |
|
indexRRs(msg.Extra, index) |
|
blen := dnsBinaryTruncate(msg, maxSize, index, true) |
|
msg.Answer = msg.Answer[:blen] |
|
syncExtra(index, msg) |
|
predicted := msg.Len() |
|
buf, err := msg.Pack() |
|
if err != nil { |
|
t.Error(err) |
|
} |
|
if predicted < len(buf) { |
|
t.Fatalf("Bug in DNS library: %d != %d", predicted, len(buf)) |
|
} |
|
if len(buf) > maxSize || (idx != 0 && len(buf) < 16) { |
|
t.Fatalf("bad[%d]: %d > %d", idx, len(buf), maxSize) |
|
} |
|
}) |
|
} |
|
} |
|
} |
|
|
|
// TestDNS_syncExtra tests the syncExtra function. |
|
func TestDNS_syncExtra(t *testing.T) { |
|
resp := &dns.Msg{ |
|
Answer: []dns.RR{ |
|
// These two are on the same host so the redundant extra |
|
// records should get deduplicated. |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1001, |
|
Target: "ip-10-0-1-185.node.dc1.consul.", |
|
}, |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1002, |
|
Target: "ip-10-0-1-185.node.dc1.consul.", |
|
}, |
|
// This one isn't in the Consul domain so it will get a |
|
// CNAME and then an A record from the recursor. |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1003, |
|
Target: "demo.consul.io.", |
|
}, |
|
// This one isn't in the Consul domain and it will get |
|
// a CNAME and A record from a recursor that alters the |
|
// case of the name. This proves we look up in the index |
|
// in a case-insensitive way. |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1001, |
|
Target: "insensitive.consul.io.", |
|
}, |
|
// This is also a CNAME, but it'll be set up to loop to |
|
// make sure we don't crash. |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1001, |
|
Target: "deadly.consul.io.", |
|
}, |
|
// This is also a CNAME, but it won't have another record. |
|
&dns.SRV{ |
|
Hdr: dns.RR_Header{ |
|
Name: "redis-cache-redis.service.consul.", |
|
Rrtype: dns.TypeSRV, |
|
Class: dns.ClassINET, |
|
}, |
|
Port: 1001, |
|
Target: "nope.consul.io.", |
|
}, |
|
}, |
|
Extra: []dns.RR{ |
|
// These should get deduplicated. |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "ip-10-0-1-185.node.dc1.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("10.0.1.185"), |
|
}, |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "ip-10-0-1-185.node.dc1.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("10.0.1.185"), |
|
}, |
|
// This is a normal CNAME followed by an A record but we |
|
// have flipped the order. The algorithm should emit them |
|
// in the opposite order. |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "fakeserver.consul.io.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("127.0.0.1"), |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "demo.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "fakeserver.consul.io.", |
|
}, |
|
// These differ in case to test case insensitivity. |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "INSENSITIVE.CONSUL.IO.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "Another.Server.Com.", |
|
}, |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "another.server.com.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("127.0.0.1"), |
|
}, |
|
// This doesn't appear in the answer, so should get |
|
// dropped. |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "ip-10-0-1-186.node.dc1.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("10.0.1.186"), |
|
}, |
|
// These two test edge cases with CNAME handling. |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "deadly.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "deadly.consul.io.", |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "nope.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "notthere.consul.io.", |
|
}, |
|
}, |
|
} |
|
|
|
index := make(map[string]dns.RR) |
|
indexRRs(resp.Extra, index) |
|
syncExtra(index, resp) |
|
|
|
expected := &dns.Msg{ |
|
Answer: resp.Answer, |
|
Extra: []dns.RR{ |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "ip-10-0-1-185.node.dc1.consul.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("10.0.1.185"), |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "demo.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "fakeserver.consul.io.", |
|
}, |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "fakeserver.consul.io.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("127.0.0.1"), |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "INSENSITIVE.CONSUL.IO.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "Another.Server.Com.", |
|
}, |
|
&dns.A{ |
|
Hdr: dns.RR_Header{ |
|
Name: "another.server.com.", |
|
Rrtype: dns.TypeA, |
|
Class: dns.ClassINET, |
|
}, |
|
A: net.ParseIP("127.0.0.1"), |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "deadly.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "deadly.consul.io.", |
|
}, |
|
&dns.CNAME{ |
|
Hdr: dns.RR_Header{ |
|
Name: "nope.consul.io.", |
|
Rrtype: dns.TypeCNAME, |
|
Class: dns.ClassINET, |
|
}, |
|
Target: "notthere.consul.io.", |
|
}, |
|
}, |
|
} |
|
if !reflect.DeepEqual(resp, expected) { |
|
t.Fatalf("Bad %#v vs. %#v", *resp, *expected) |
|
} |
|
} |
|
|
|
// getUint32Ptr return the pointer of an uint32 literal |
|
func getUint32Ptr(i uint32) *uint32 { |
|
return &i |
|
} |
|
|
|
func TestRouter_ReloadConfig(t *testing.T) { |
|
cdf := discovery.NewMockCatalogDataFetcher(t) |
|
cfg := buildDNSConfig(nil, cdf, nil) |
|
router, err := NewRouter(cfg) |
|
require.NoError(t, err) |
|
|
|
router.recursor = newMockDnsRecursor(t) |
|
|
|
// Reload the config |
|
newAgentConfig := &config.RuntimeConfig{ |
|
DNSARecordLimit: 123, |
|
DNSEnableTruncate: true, |
|
DNSNodeTTL: 234, |
|
DNSRecursorStrategy: "strategy-123", |
|
DNSRecursorTimeout: 345, |
|
DNSUDPAnswerLimit: 456, |
|
DNSNodeMetaTXT: true, |
|
DNSDisableCompression: true, |
|
DNSSOA: config.RuntimeSOAConfig{ |
|
Expire: 123, |
|
Minttl: 234, |
|
Refresh: 345, |
|
Retry: 456, |
|
}, |
|
DNSServiceTTL: map[string]time.Duration{ |
|
"wildcard-config-*": 123, |
|
"strict-config": 234, |
|
}, |
|
DNSRecursors: []string{ |
|
"8.8.8.8", |
|
"2001:4860:4860::8888", |
|
}, |
|
} |
|
|
|
expectTTLRadix := radix.New() |
|
expectTTLRadix.Insert("wildcard-config-", time.Duration(123)) |
|
|
|
expectedCfg := &RouterDynamicConfig{ |
|
ARecordLimit: 123, |
|
EnableTruncate: true, |
|
NodeTTL: 234, |
|
RecursorStrategy: "strategy-123", |
|
RecursorTimeout: 345, |
|
UDPAnswerLimit: 456, |
|
NodeMetaTXT: true, |
|
DisableCompression: true, |
|
SOAConfig: SOAConfig{ |
|
Expire: 123, |
|
Minttl: 234, |
|
Refresh: 345, |
|
Retry: 456, |
|
}, |
|
TTLRadix: expectTTLRadix, |
|
TTLStrict: map[string]time.Duration{ |
|
"strict-config": 234, |
|
}, |
|
Recursors: []string{ |
|
"8.8.8.8:53", |
|
"[2001:4860:4860::8888]:53", |
|
}, |
|
} |
|
err = router.ReloadConfig(newAgentConfig) |
|
require.NoError(t, err) |
|
savedCfg := router.dynamicConfig.Load().(*RouterDynamicConfig) |
|
|
|
// Ensure the new config is used |
|
require.Equal(t, expectedCfg, savedCfg) |
|
} |
|
|
|
func Test_isPTRSubdomain(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
domain string |
|
expected bool |
|
}{ |
|
{ |
|
name: "empty domain returns false", |
|
domain: "", |
|
expected: false, |
|
}, |
|
{ |
|
name: "last label is 'arpa' returns true", |
|
domain: "my-addr.arpa.", |
|
expected: true, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
actual := isPTRSubdomain(tc.domain) |
|
require.Equal(t, tc.expected, actual) |
|
}) |
|
} |
|
} |
|
|
|
func Test_isAddrSubdomain(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
domain string |
|
expected bool |
|
}{ |
|
{ |
|
name: "empty domain returns false", |
|
domain: "", |
|
expected: false, |
|
}, |
|
{ |
|
name: "'c000020a.addr.dc1.consul.' returns true", |
|
domain: "c000020a.addr.dc1.consul.", |
|
expected: true, |
|
}, |
|
{ |
|
name: "'c000020a.addr.consul.' returns true", |
|
domain: "c000020a.addr.consul.", |
|
expected: true, |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
actual := isAddrSubdomain(tc.domain) |
|
require.Equal(t, tc.expected, actual) |
|
}) |
|
} |
|
} |
|
|
|
func Test_stripAnyFailoverSuffix(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
target string |
|
expectedEnableFailover bool |
|
expectedResult string |
|
}{ |
|
{ |
|
name: "my-addr.service.dc1.consul.failover. returns 'my-addr.service.dc1.consul' and true", |
|
target: "my-addr.service.dc1.consul.failover.", |
|
expectedEnableFailover: true, |
|
expectedResult: "my-addr.service.dc1.consul.", |
|
}, |
|
{ |
|
name: "my-addr.service.dc1.consul.no-failover. returns 'my-addr.service.dc1.consul' and false", |
|
target: "my-addr.service.dc1.consul.no-failover.", |
|
expectedEnableFailover: false, |
|
expectedResult: "my-addr.service.dc1.consul.", |
|
}, |
|
{ |
|
name: "my-addr.service.dc1.consul. returns 'my-addr.service.dc1.consul' and false", |
|
target: "my-addr.service.dc1.consul.", |
|
expectedEnableFailover: false, |
|
expectedResult: "my-addr.service.dc1.consul.", |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
actual, actualEnableFailover := stripAnyFailoverSuffix(tc.target) |
|
require.Equal(t, tc.expectedEnableFailover, actualEnableFailover) |
|
require.Equal(t, tc.expectedResult, actual) |
|
}) |
|
} |
|
} |
|
|
|
func Test_trimDomain(t *testing.T) { |
|
testCases := []struct { |
|
name string |
|
domain string |
|
altDomain string |
|
questionName string |
|
expectedResult string |
|
}{ |
|
{ |
|
name: "given domain is 'consul.' and altDomain is 'my.consul.', when calling trimDomain with 'my-service.my.consul.', it returns 'my-service.'", |
|
questionName: "my-service.my.consul.", |
|
domain: "consul.", |
|
altDomain: "my.consul.", |
|
expectedResult: "my-service.", |
|
}, |
|
{ |
|
name: "given domain is 'consul.' and altDomain is 'my.consul.', when calling trimDomain with 'my-service.consul.', it returns 'my-service.'", |
|
questionName: "my-service.consul.", |
|
domain: "consul.", |
|
altDomain: "my.consul.", |
|
expectedResult: "my-service.", |
|
}, |
|
{ |
|
name: "given domain is 'consul.' and altDomain is 'my-consul.', when calling trimDomain with 'my-service.consul.', it returns 'my-service.'", |
|
questionName: "my-service.consul.", |
|
domain: "consul.", |
|
altDomain: "my-consul.", |
|
expectedResult: "my-service.", |
|
}, |
|
{ |
|
name: "given domain is 'consul.' and altDomain is 'my-consul.', when calling trimDomain with 'my-service.my-consul.', it returns 'my-service.'", |
|
questionName: "my-service.my-consul.", |
|
domain: "consul.", |
|
altDomain: "my-consul.", |
|
expectedResult: "my-service.", |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.name, func(t *testing.T) { |
|
router := Router{ |
|
domain: tc.domain, |
|
altDomain: tc.altDomain, |
|
} |
|
actual := router.trimDomain(tc.questionName) |
|
require.Equal(t, tc.expectedResult, actual) |
|
}) |
|
} |
|
}
|
|
|