Skip to content

Commit

Permalink
NET-7165 - fix address and target setting (#20403)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmurret authored Jan 30, 2024
1 parent 8799c36 commit c82b78b
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 307 deletions.
50 changes: 38 additions & 12 deletions agent/discovery/query_fetcher_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ func (f *V1DataFetcher) FetchNodes(ctx Context, req *QueryPayload) ([]*Result, e
Type: ResultTypeNode,
Metadata: node.Meta,
Target: node.Node,
Tenancy: ResultTenancy{
EnterpriseMeta: cfg.defaultEntMeta,
Datacenter: cfg.datacenter,
},
})

return results, nil
Expand Down Expand Up @@ -358,19 +362,10 @@ func (f *V1DataFetcher) fetchServiceBasedOnTenancy(ctx Context, req *QueryPayloa
out.Nodes.Shuffle()
results := make([]*Result, 0, len(out.Nodes))
for _, node := range out.Nodes {
target := node.Service.Address
resultType := ResultTypeService
// TODO (v2-dns): IMPORTANT!!!!: this needs to be revisited in how dns v1 utilizes
// the nodeaddress when the service address is an empty string. Need to figure out
// if this can be removed and dns recursion and process can work with only the
// address set to the node.address and the target set to the service.address.
// We may have to look at modifying the discovery result if more metadata is needed to send along.
if target == "" {
target = node.Node.Node
resultType = ResultTypeNode
}
address, target, resultType := getAddressTargetAndResultType(node)

results = append(results, &Result{
Address: node.Node.Address,
Address: address,
Type: resultType,
Target: target,
Weight: uint32(findWeight(node)),
Expand All @@ -386,6 +381,37 @@ func (f *V1DataFetcher) fetchServiceBasedOnTenancy(ctx Context, req *QueryPayloa
return results, nil
}

// getAddressTargetAndResultType returns the address, target and result type for a check service node.
func getAddressTargetAndResultType(node structs.CheckServiceNode) (string, string, ResultType) {
// Set address and target
// if service address is present, set target and address based on service.
// otherwise get it from the node.
address := node.Service.Address
target := node.Service.Service
resultType := ResultTypeService

addressIP := net.ParseIP(address)
if addressIP == nil {
resultType = ResultTypeNode
if node.Service.Address != "" {
// cases where service address is foo or foo.node.consul
// For usage in DNS, these discovery results necessitate a CNAME record.
// These cases can be inferred from the discovery result when Type is Node and
// target is not an IP.
target = node.Service.Address
} else {
// cases where service address is empty and the service is bound to
// node with an address. These do not require a CNAME record in.
// For usage in DNS, these discovery results do not require a CNAME record.
// These cases can be inferred from the discovery result when Type is Node and
// target is not an IP.
target = node.Node.Node
}
address = node.Node.Address
}
return address, target, resultType
}

// findWeight returns the weight of a service node.
func findWeight(node structs.CheckServiceNode) int {
// By default, when only_passing is false, warning and passing nodes are returned
Expand Down
192 changes: 191 additions & 1 deletion agent/discovery/query_fetcher_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"github.com/hashicorp/consul/sdk/testutil"
)

// Test_FetchService tests the FetchService method in scenarios where the RPC
// Test_FetchVirtualIP tests the FetchVirtualIP method in scenarios where the RPC
// call succeeds and fails.
func Test_FetchVirtualIP(t *testing.T) {
// set these to confirm that RPC call does not use them for this particular RPC
Expand Down Expand Up @@ -119,3 +119,193 @@ func Test_FetchVirtualIP(t *testing.T) {
})
}
}

// Test_FetchEndpoints tests the FetchEndpoints method in scenarios where the RPC
// call succeeds and fails.
func Test_FetchEndpoints(t *testing.T) {
// set these to confirm that RPC call does not use them for this particular RPC
rc := &config.RuntimeConfig{
DNSAllowStale: true,
DNSMaxStale: 100,
DNSUseCache: true,
DNSCacheMaxAge: 100,
}
tests := []struct {
name string
queryPayload *QueryPayload
context Context
rpcFuncForServiceNodes func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error)
expectedResults []*Result
expectedErr error
}{
{
name: "when service address is IPv4, result type is service, address is service address and target is service name",
queryPayload: &QueryPayload{
Name: "service-name",
Tenancy: QueryTenancy{
EnterpriseMeta: defaultEntMeta,
},
},
rpcFuncForServiceNodes: func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
return structs.IndexedCheckServiceNodes{
Nodes: []structs.CheckServiceNode{
{
Node: &structs.Node{
Address: "node-address",
Node: "node-name",
},
Service: &structs.NodeService{
Address: "127.0.0.1",
Service: "service-name",
},
},
},
}, cache.ResultMeta{}, nil
},
context: Context{
Token: "test-token",
},
expectedResults: []*Result{
{
Address: "127.0.0.1",
Target: "service-name",
Type: ResultTypeService,
Weight: 1,
},
},
expectedErr: nil,
},
{
name: "when service address is IPv6, result type is service, address is service address and target is service name",
queryPayload: &QueryPayload{
Name: "service-name",
Tenancy: QueryTenancy{
EnterpriseMeta: defaultEntMeta,
},
},
rpcFuncForServiceNodes: func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
return structs.IndexedCheckServiceNodes{
Nodes: []structs.CheckServiceNode{
{
Node: &structs.Node{
Address: "node-address",
Node: "node-name",
},
Service: &structs.NodeService{
Address: "2001:db8:1:2:cafe::1337",
Service: "service-name",
},
},
},
}, cache.ResultMeta{}, nil
},
context: Context{
Token: "test-token",
},
expectedResults: []*Result{
{
Address: "2001:db8:1:2:cafe::1337",
Target: "service-name",
Type: ResultTypeService,
Weight: 1,
},
},
expectedErr: nil,
},
{
name: "when service address is not IP but is not empty, result type is node, address is node address, and target is service address",
queryPayload: &QueryPayload{
Name: "service-name",
Tenancy: QueryTenancy{
EnterpriseMeta: defaultEntMeta,
},
},
rpcFuncForServiceNodes: func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
return structs.IndexedCheckServiceNodes{
Nodes: []structs.CheckServiceNode{
{
Node: &structs.Node{
Address: "node-address",
Node: "node-name",
},
Service: &structs.NodeService{
Address: "foo",
Service: "service-name",
},
},
},
}, cache.ResultMeta{}, nil
},
context: Context{
Token: "test-token",
},
expectedResults: []*Result{
{
Address: "node-address",
Target: "foo",
Type: ResultTypeNode,
Weight: 1,
},
},
expectedErr: nil,
},
{
name: "when service address is empty, result type is node, address is node address, and target is node name",
queryPayload: &QueryPayload{
Name: "service-name",
Tenancy: QueryTenancy{
EnterpriseMeta: defaultEntMeta,
},
},
rpcFuncForServiceNodes: func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
return structs.IndexedCheckServiceNodes{
Nodes: []structs.CheckServiceNode{
{
Node: &structs.Node{
Address: "node-address",
Node: "node-name",
},
Service: &structs.NodeService{
Address: "",
Service: "service-name",
},
},
},
}, cache.ResultMeta{}, nil
},
context: Context{
Token: "test-token",
},
expectedResults: []*Result{
{
Address: "node-address",
Target: "node-name",
Type: ResultTypeNode,
Weight: 1,
},
},
expectedErr: nil,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
logger := testutil.Logger(t)
mockRPC := cachetype.NewMockRPC(t)
// TODO (v2-dns): mock these properly
translateServicePortFunc := func(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int { return 0 }
rpcFuncForSamenessGroup := func(ctx context.Context, req *structs.ConfigEntryQuery) (structs.SamenessGroupConfigEntry, cache.ResultMeta, error) {
return structs.SamenessGroupConfigEntry{}, cache.ResultMeta{}, nil
}
getFromCacheFunc := func(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error) {
return nil, cache.ResultMeta{}, nil
}

df := NewV1DataFetcher(rc, acl.DefaultEnterpriseMeta(), getFromCacheFunc, mockRPC.RPC, tc.rpcFuncForServiceNodes, rpcFuncForSamenessGroup, translateServicePortFunc, logger)

results, err := df.FetchEndpoints(tc.context, tc.queryPayload, LookupTypeService)
require.Equal(t, tc.expectedErr, err)
require.Equal(t, tc.expectedResults, results)
})
}
}
2 changes: 1 addition & 1 deletion agent/dns/dns_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ func (a *dnsAddress) IsInternalFQDNOrIP(domain string) bool {

// IsExternalFQDN returns true if the address is a FQDN and is external to the domain.
func (a *dnsAddress) IsExternalFQDN(domain string) bool {
return !a.IsIP() && a.IsFQDN() && !strings.HasSuffix(a.FQDN(), domain)
return !a.IsIP() && a.IsFQDN() && strings.Count(a.FQDN(), ".") > 1 && !strings.HasSuffix(a.FQDN(), domain)
}
14 changes: 14 additions & 0 deletions agent/dns/dns_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ func Test_dnsAddress(t *testing.T) {
isInternalFQDNOrIP: true,
},
},
{
name: "server name",
input: "web.",
expectedResults: expectedResults{
isIp: false,
stringResult: "web.",
fqdn: "web.",
isFQDN: true,
isEmptyString: false,
isExternalFQDN: false,
isInternalFQDN: false,
isInternalFQDNOrIP: false,
},
},
{
name: "external FQDN without trailing period",
input: "web.service.vault",
Expand Down
42 changes: 0 additions & 42 deletions agent/dns/query_locality.go

This file was deleted.

Loading

0 comments on commit c82b78b

Please sign in to comment.