Support AAAA records from headless services

This commit is contained in:
John Gardiner Myers 2023-05-06 16:46:01 -07:00
parent 3a788d6a44
commit 41c705e471
2 changed files with 385 additions and 19 deletions

View File

@ -271,7 +271,7 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
endpointsType := getEndpointsTypeFromAnnotations(svc.Annotations)
targetsByHeadlessDomain := make(map[string]endpoint.Targets)
targetsByHeadlessDomainAndType := make(map[endpointKey]endpoint.Targets)
for _, subset := range endpointsObject.Subsets {
addresses := subset.Addresses
if svc.Spec.PublishNotReadyAddresses || sc.alwaysPublishNotReadyAddresses {
@ -324,18 +324,29 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
log.Debugf("Generating matching endpoint %s with EndpointAddress IP %s", headlessDomain, address.IP)
}
}
targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], targets...)
for _, target := range targets {
key := endpointKey{
dnsName: headlessDomain,
recordType: suitableType(target),
}
targetsByHeadlessDomainAndType[key] = append(targetsByHeadlessDomainAndType[key], target)
}
}
}
}
headlessDomains := []string{}
for headlessDomain := range targetsByHeadlessDomain {
headlessDomains = append(headlessDomains, headlessDomain)
headlessKeys := []endpointKey{}
for headlessKey := range targetsByHeadlessDomainAndType {
headlessKeys = append(headlessKeys, headlessKey)
}
sort.Strings(headlessDomains)
for _, headlessDomain := range headlessDomains {
allTargets := targetsByHeadlessDomain[headlessDomain]
sort.Slice(headlessKeys, func(i, j int) bool {
if headlessKeys[i].dnsName != headlessKeys[j].dnsName {
return headlessKeys[i].dnsName < headlessKeys[j].dnsName
}
return headlessKeys[i].recordType < headlessKeys[j].recordType
})
for _, headlessKey := range headlessKeys {
allTargets := targetsByHeadlessDomainAndType[headlessKey]
targets := []string{}
deduppedTargets := map[string]struct{}{}
@ -350,9 +361,9 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
}
if ttl.IsConfigured() {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, targets...))
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessKey.dnsName, headlessKey.recordType, ttl, targets...))
} else {
endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, targets...))
endpoints = append(endpoints, endpoint.NewEndpoint(headlessKey.dnsName, headlessKey.recordType, targets...))
}
}

View File

@ -2118,7 +2118,7 @@ func TestHeadlessServices(t *testing.T) {
expectError bool
}{
{
"annotated Headless services return endpoints for each selected Pod",
"annotated Headless services return IPv4 endpoints for each selected Pod",
"",
"testing",
"foo",
@ -2150,6 +2150,39 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
[]string{"", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"hostname annotated Headless services are ignored",
"",
@ -2180,7 +2213,7 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints with TTL for each selected Pod",
"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
@ -2213,6 +2246,40 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
ttlAnnotationKey: "1",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
[]string{"", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
},
false,
},
{
"annotated Headless services return endpoints for each selected Pod, which are in running state",
"",
@ -2310,7 +2377,7 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return only a unique set of targets",
"annotated Headless services return only a unique set of IPv4 targets",
"",
"testing",
"foo",
@ -2341,7 +2408,38 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from pod annotation",
"annotated Headless services return only a unique set of IPv6 targets",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::1", "2001:db8::2"},
[]string{"", "", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1", "foo-3"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from pod annotation",
"",
"testing",
"foo",
@ -2374,7 +2472,40 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from node external IP if endpoints-type annotation is set",
"annotated Headless services return IPv6 targets from pod annotation",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{
targetAnnotationKey: "2001:db8::4",
},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
@ -2417,7 +2548,98 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from hostIP if endpoints-type annotation is set",
"annotated Headless services return IPv6 targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{
{
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Type: v1.NodeInternalIP,
Address: "2001:db8::4",
},
},
},
},
},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"1.1.1.1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{
{
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Type: v1.NodeExternalIP,
Address: "1.2.3.4",
},
{
Type: v1.NodeInternalIP,
Address: "2001:db8::4",
},
},
},
},
},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from hostIP if endpoints-type annotation is set",
"",
"testing",
"foo",
@ -2448,6 +2670,38 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 targets from hostIP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeHostIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{"2001:db8::4"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
} {
tc := tc
t.Run(tc.title, func(t *testing.T) {
@ -2590,7 +2844,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
expectError bool
}{
{
"annotated Headless services return endpoints for each selected Pod",
"annotated Headless services return IPv4 endpoints for each selected Pod",
"",
"testing",
"foo",
@ -2623,6 +2877,40 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"hostname annotated Headless services are ignored",
"",
@ -2654,7 +2942,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints with TTL for each selected Pod",
"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
@ -2688,6 +2976,41 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
ttlAnnotationKey: "1",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
},
false,
},
{
"annotated Headless services return endpoints for each selected Pod, which are in running state",
"",
@ -2756,7 +3079,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints for pods missing hostname",
"annotated Headless services return IPv4 endpoints for pods missing hostname",
"",
"testing",
"foo",
@ -2787,6 +3110,38 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for pods missing hostname",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"", ""},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"annotated Headless services without a targetRef has no endpoints",
"",