diff --git a/docs/tutorials/nodes.md b/docs/tutorials/nodes.md index 46f21da5d8..b99a2f9ca3 100644 --- a/docs/tutorials/nodes.md +++ b/docs/tutorials/nodes.md @@ -3,8 +3,9 @@ This tutorial describes how to configure ExternalDNS to use the cluster nodes as source. Using nodes (`--source=node`) as source is possible to synchronize a DNS zone with the nodes of a cluster. -The node source adds an `A` record per each node `externalIP` (if not found, node's `internalIP` is used). -The TTL record can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation. +The node source adds an `A` record per each node `externalIP` (if not found, any IPv4 `internalIP` is used instead). +It also adds an `AAAA` record per each node IPv6 `internalIP`. +The TTL of the records can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation. ## Manifest (for cluster without RBAC enabled) diff --git a/source/compatibility.go b/source/compatibility.go index bc6e19abf9..1953b76ca9 100644 --- a/source/compatibility.go +++ b/source/compatibility.go @@ -157,11 +157,13 @@ func legacyEndpointsFromDNSControllerNodePortService(svc *v1.Service, sc *servic continue } for _, address := range node.Status.Addresses { - if address.Type == v1.NodeExternalIP && isExternal { - endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, address.Address)) + recordType := suitableType(address.Address) + // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. + if isExternal && (address.Type == v1.NodeExternalIP || (address.Type == v1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA)) { + endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address)) } - if address.Type == v1.NodeInternalIP && isInternal { - endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, address.Address)) + if isInternal && address.Type == v1.NodeInternalIP { + endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address)) } } } diff --git a/source/node.go b/source/node.go index b0e672d732..5e287e9a00 100644 --- a/source/node.go +++ b/source/node.go @@ -88,7 +88,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro return nil, err } - endpoints := map[string]*endpoint.Endpoint{} + endpoints := map[endpointKey]*endpoint.Endpoint{} // create endpoints for all nodes for _, node := range nodes { @@ -109,8 +109,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro // create new endpoint with the information we already have ep := &endpoint.Endpoint{ - RecordType: "A", // hardcoded DNS record type - RecordTTL: ttl, + RecordTTL: ttl, } if ns.fqdnTemplate != nil { @@ -134,14 +133,19 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro return nil, fmt.Errorf("failed to get node address from %s: %s", node.Name, err.Error()) } - ep.Targets = endpoint.Targets(addrs) ep.Labels = endpoint.NewLabels() - - log.Debugf("adding endpoint %s", ep) - if _, ok := endpoints[ep.DNSName]; ok { - endpoints[ep.DNSName].Targets = append(endpoints[ep.DNSName].Targets, ep.Targets...) - } else { - endpoints[ep.DNSName] = ep + for _, addr := range addrs { + log.Debugf("adding endpoint %s target %s", ep, addr) + key := endpointKey{ + dnsName: ep.DNSName, + recordType: suitableType(addr), + } + if _, ok := endpoints[key]; !ok { + epCopy := *ep + epCopy.RecordType = key.recordType + endpoints[key] = &epCopy + } + endpoints[key].Targets = append(endpoints[key].Targets, addr) } } @@ -163,13 +167,18 @@ func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) { v1.NodeExternalIP: {}, v1.NodeInternalIP: {}, } + var ipv6Addresses []string for _, addr := range node.Status.Addresses { addresses[addr.Type] = append(addresses[addr.Type], addr.Address) + // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. + if addr.Type == v1.NodeInternalIP && suitableType(addr.Address) == endpoint.RecordTypeAAAA { + ipv6Addresses = append(ipv6Addresses, addr.Address) + } } if len(addresses[v1.NodeExternalIP]) > 0 { - return addresses[v1.NodeExternalIP], nil + return append(addresses[v1.NodeExternalIP], ipv6Addresses...), nil } if len(addresses[v1.NodeInternalIP]) > 0 { diff --git a/source/node_test.go b/source/node_test.go index 901c1baa11..885d9f54e1 100644 --- a/source/node_test.go +++ b/source/node_test.go @@ -127,6 +127,19 @@ func testNodeSourceEndpoints(t *testing.T) { }, false, }, + { + "ipv6 node with fqdn returns one endpoint", + "", + "", + "node1.example.org", + []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "2001:DB8::8"}}, + map[string]string{}, + map[string]string{}, + []*endpoint.Endpoint{ + {RecordType: "AAAA", DNSName: "node1.example.org", Targets: endpoint.Targets{"2001:DB8::8"}}, + }, + false, + }, { "node with fqdn template returns endpoint with expanded hostname", "", @@ -166,6 +179,20 @@ func testNodeSourceEndpoints(t *testing.T) { }, false, }, + { + "node with fqdn template returns two endpoints with dual-stack IP addresses and expanded hostname", + "", + "{{.Name}}.example.org", + "node1", + []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}}, + map[string]string{}, + map[string]string{}, + []*endpoint.Endpoint{ + {RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, + {RecordType: "AAAA", DNSName: "node1.example.org", Targets: endpoint.Targets{"2001:DB8::8"}}, + }, + false, + }, { "node with both external and internal IP returns an endpoint with external IP", "", @@ -179,6 +206,20 @@ func testNodeSourceEndpoints(t *testing.T) { }, false, }, + { + "node with both external, internal, and IPv6 IP returns endpoints with external IPs", + "", + "", + "node1", + []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "2.3.4.5"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}}, + map[string]string{}, + map[string]string{}, + []*endpoint.Endpoint{ + {RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}}, + {RecordType: "AAAA", DNSName: "node1", Targets: endpoint.Targets{"2001:DB8::8"}}, + }, + false, + }, { "node with only internal IP returns an endpoint with internal IP", "", @@ -192,6 +233,20 @@ func testNodeSourceEndpoints(t *testing.T) { }, false, }, + { + "node with only internal IPs returns endpoints with internal IPs", + "", + "", + "node1", + []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "2.3.4.5"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}}, + map[string]string{}, + map[string]string{}, + []*endpoint.Endpoint{ + {RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"2.3.4.5"}}, + {RecordType: "AAAA", DNSName: "node1", Targets: endpoint.Targets{"2001:DB8::8"}}, + }, + false, + }, { "node with neither external nor internal IP returns no endpoints", "", @@ -318,7 +373,7 @@ func testNodeSourceEndpoints(t *testing.T) { false, }, { - "node with nil Lables returns valid endpoint", + "node with nil Labels returns valid endpoint", "", "", "node1", diff --git a/source/pod.go b/source/pod.go index 36e6ffe50e..123468539f 100644 --- a/source/pod.go +++ b/source/pod.go @@ -82,7 +82,7 @@ func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error return nil, err } - domains := make(map[string][]string) + endpointMap := make(map[endpointKey][]string) for _, pod := range pods { if !pod.Spec.HostNetwork { log.Debugf("skipping pod %s. hostNetwork=false", pod.Name) @@ -90,50 +90,51 @@ func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error } if domain, ok := pod.Annotations[internalHostnameAnnotationKey]; ok { - if _, ok := domains[domain]; !ok { - domains[domain] = []string{} - } - domains[domain] = append(domains[domain], pod.Status.PodIP) + addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP) } if domain, ok := pod.Annotations[hostnameAnnotationKey]; ok { - if _, ok := domains[domain]; !ok { - domains[domain] = []string{} - } - node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName) for _, address := range node.Status.Addresses { - if address.Type == corev1.NodeExternalIP { - domains[domain] = append(domains[domain], address.Address) + recordType := suitableType(address.Address) + // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. + if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) { + addToEndpointMap(endpointMap, domain, recordType, address.Address) } } } if ps.compatibility == "kops-dns-controller" { if domain, ok := pod.Annotations[kopsDNSControllerInternalHostnameAnnotationKey]; ok { - if _, ok := domains[domain]; !ok { - domains[domain] = []string{} - } - domains[domain] = append(domains[domain], pod.Status.PodIP) + addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP) } if domain, ok := pod.Annotations[kopsDNSControllerHostnameAnnotationKey]; ok { - if _, ok := domains[domain]; !ok { - domains[domain] = []string{} - } - node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName) for _, address := range node.Status.Addresses { - if address.Type == corev1.NodeExternalIP { - domains[domain] = append(domains[domain], address.Address) + recordType := suitableType(address.Address) + // IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well. + if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) { + addToEndpointMap(endpointMap, domain, recordType, address.Address) } } } } } endpoints := []*endpoint.Endpoint{} - for domain, targets := range domains { - endpoints = append(endpoints, endpoint.NewEndpoint(domain, endpoint.RecordTypeA, targets...)) + for key, targets := range endpointMap { + endpoints = append(endpoints, endpoint.NewEndpoint(key.dnsName, key.recordType, targets...)) } return endpoints, nil } + +func addToEndpointMap(endpointMap map[endpointKey][]string, domain string, recordType string, address string) { + key := endpointKey{ + dnsName: domain, + recordType: recordType, + } + if _, ok := endpointMap[key]; !ok { + endpointMap[key] = []string{} + } + endpointMap[key] = append(endpointMap[key], address) +} diff --git a/source/pod_test.go b/source/pod_test.go index c138aaf5fd..5a57aedc26 100644 --- a/source/pod_test.go +++ b/source/pod_test.go @@ -41,7 +41,7 @@ func TestPodSource(t *testing.T) { pods []*corev1.Pod }{ { - "create records based on pod's external and internal IPs", + "create IPv4 records based on pod's external and internal IPs", "", "", []*endpoint.Endpoint{ @@ -111,7 +111,7 @@ func TestPodSource(t *testing.T) { }, }, { - "create records based on pod's external and internal IPs using DNS Controller annotations", + "create IPv4 records based on pod's external and internal IPs using DNS Controller annotations", "", "kops-dns-controller", []*endpoint.Endpoint{ @@ -180,12 +180,149 @@ func TestPodSource(t *testing.T) { }, }, }, + { + "create IPv6 records based on pod's external and internal IPs", + "", + "", + []*endpoint.Endpoint{ + {DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, + {DNSName: "internal.a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, + }, + false, + []*corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node1", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "2001:DB8::1"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node2", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "2001:DB8::2"}, + }, + }, + }, + }, + []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod1", + Namespace: "kube-system", + Annotations: map[string]string{ + internalHostnameAnnotationKey: "internal.a.foo.example.org", + hostnameAnnotationKey: "a.foo.example.org", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node1", + }, + Status: corev1.PodStatus{ + PodIP: "2001:DB8::1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod2", + Namespace: "kube-system", + Annotations: map[string]string{ + internalHostnameAnnotationKey: "internal.a.foo.example.org", + hostnameAnnotationKey: "a.foo.example.org", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node2", + }, + Status: corev1.PodStatus{ + PodIP: "2001:DB8::2", + }, + }, + }, + }, + { + "create IPv6 records based on pod's external and internal IPs using DNS Controller annotations", + "", + "kops-dns-controller", + []*endpoint.Endpoint{ + {DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, + {DNSName: "internal.a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, + }, + false, + []*corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node1", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "2001:DB8::1"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-node2", + }, + Status: corev1.NodeStatus{ + Addresses: []corev1.NodeAddress{ + {Type: corev1.NodeInternalIP, Address: "2001:DB8::2"}, + }, + }, + }, + }, + []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod1", + Namespace: "kube-system", + Annotations: map[string]string{ + kopsDNSControllerInternalHostnameAnnotationKey: "internal.a.foo.example.org", + kopsDNSControllerHostnameAnnotationKey: "a.foo.example.org", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node1", + }, + Status: corev1.PodStatus{ + PodIP: "2001:DB8::1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod2", + Namespace: "kube-system", + Annotations: map[string]string{ + kopsDNSControllerInternalHostnameAnnotationKey: "internal.a.foo.example.org", + kopsDNSControllerHostnameAnnotationKey: "a.foo.example.org", + }, + }, + Spec: corev1.PodSpec{ + HostNetwork: true, + NodeName: "my-node2", + }, + Status: corev1.PodStatus{ + PodIP: "2001:DB8::2", + }, + }, + }, + }, { "create multiple records", "", "", []*endpoint.Endpoint{ {DNSName: "a.foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1"}, RecordType: endpoint.RecordTypeAAAA}, {DNSName: "b.foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, false, @@ -197,6 +334,7 @@ func TestPodSource(t *testing.T) { Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ {Type: corev1.NodeExternalIP, Address: "54.10.11.1"}, + {Type: corev1.NodeInternalIP, Address: "2001:DB8::1"}, {Type: corev1.NodeInternalIP, Address: "10.0.1.1"}, }, }, diff --git a/source/service.go b/source/service.go index 925c327f24..58270cdcc3 100644 --- a/source/service.go +++ b/source/service.go @@ -216,7 +216,10 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e }) // Use stable sort to not disrupt the order of services sort.SliceStable(endpoints, func(i, j int) bool { - return endpoints[i].DNSName < endpoints[j].DNSName + if endpoints[i].DNSName != endpoints[j].DNSName { + return endpoints[i].DNSName < endpoints[j].DNSName + } + return endpoints[i].RecordType < endpoints[j].RecordType }) mergedEndpoints := []*endpoint.Endpoint{} mergedEndpoints = append(mergedEndpoints, endpoints[0]) @@ -308,8 +311,8 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri return endpoints } for _, address := range node.Status.Addresses { - if address.Type == v1.NodeExternalIP { - targets = endpoint.Targets{address.Address} + if address.Type == v1.NodeExternalIP || (address.Type == v1.NodeInternalIP && suitableType(address.Address) == endpoint.RecordTypeAAAA) { + targets = append(targets, address.Address) log.Debugf("Generating matching endpoint %s with NodeExternalIP %s", headlessDomain, address.Address) } } @@ -499,7 +502,7 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro log.Errorf("Unable to extract targets from service %s/%s error: %v", svc.Namespace, svc.Name, err) return endpoints } - endpoints = append(endpoints, sc.extractNodePortEndpoints(svc, targets, hostname, ttl)...) + endpoints = append(endpoints, sc.extractNodePortEndpoints(svc, hostname, ttl)...) case v1.ServiceTypeExternalName: targets = append(targets, extractServiceExternalName(svc)...) } @@ -587,6 +590,7 @@ func (sc *serviceSource) extractNodePortTargets(svc *v1.Service) (endpoint.Targe var ( internalIPs endpoint.Targets externalIPs endpoint.Targets + ipv6IPs endpoint.Targets nodes []*v1.Node err error ) @@ -634,24 +638,27 @@ func (sc *serviceSource) extractNodePortTargets(svc *v1.Service) (endpoint.Targe externalIPs = append(externalIPs, address.Address) case v1.NodeInternalIP: internalIPs = append(internalIPs, address.Address) + if suitableType(address.Address) == endpoint.RecordTypeAAAA { + ipv6IPs = append(ipv6IPs, address.Address) + } } } } access := getAccessFromAnnotations(svc.Annotations) if access == "public" { - return externalIPs, nil + return append(externalIPs, ipv6IPs...), nil } if access == "private" { return internalIPs, nil } if len(externalIPs) > 0 { - return externalIPs, nil + return append(externalIPs, ipv6IPs...), nil } return internalIPs, nil } -func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets endpoint.Targets, hostname string, ttl endpoint.TTL) []*endpoint.Endpoint { +func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, hostname string, ttl endpoint.TTL) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint for _, port := range svc.Spec.Ports { diff --git a/source/service_test.go b/source/service_test.go index 98b400fca3..45737adb7c 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -1518,6 +1518,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1527,6 +1528,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1537,6 +1539,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1559,6 +1562,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1569,6 +1573,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1584,6 +1589,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1593,6 +1599,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1603,6 +1610,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1619,6 +1627,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1627,6 +1636,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Status: v1.NodeStatus{ Addresses: []v1.NodeAddress{ {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1636,6 +1646,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Status: v1.NodeStatus{ Addresses: []v1.NodeAddress{ {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1652,6 +1663,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1661,6 +1673,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1671,6 +1684,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1691,6 +1705,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1700,6 +1715,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1710,6 +1726,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1731,6 +1748,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1740,6 +1758,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1750,6 +1769,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1768,6 +1788,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, + {DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1777,6 +1798,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1787,6 +1809,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1804,7 +1827,9 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}}, + {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}}, + {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1817,6 +1842,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1830,6 +1856,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1846,7 +1873,9 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, + {DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, + {DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1859,6 +1888,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1872,6 +1902,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1888,7 +1919,9 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, + {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, + {DNSName: "bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}}, }, nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ @@ -1901,6 +1934,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1914,6 +1948,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, @@ -1942,6 +1977,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.1"}, {Type: v1.NodeInternalIP, Address: "10.0.1.1"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::1"}, }, }, }, { @@ -1955,6 +1991,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { Addresses: []v1.NodeAddress{ {Type: v1.NodeExternalIP, Address: "54.10.11.2"}, {Type: v1.NodeInternalIP, Address: "10.0.1.2"}, + {Type: v1.NodeInternalIP, Address: "2001:DB8::2"}, }, }, }}, diff --git a/source/shared_test.go b/source/shared_test.go index 9cdc58d22a..11828dbe2e 100644 --- a/source/shared_test.go +++ b/source/shared_test.go @@ -29,11 +29,14 @@ func sortEndpoints(endpoints []*endpoint.Endpoint) { sort.Strings([]string(ep.Targets)) } sort.Slice(endpoints, func(i, k int) bool { - // Sort by DNSName and Targets + // Sort by DNSName, RecordType, and Targets ei, ek := endpoints[i], endpoints[k] if ei.DNSName != ek.DNSName { return ei.DNSName < ek.DNSName } + if ei.RecordType != ek.RecordType { + return ei.RecordType < ek.RecordType + } // Targets are sorted ahead of time. for j, ti := range ei.Targets { if j >= len(ek.Targets) { diff --git a/source/source.go b/source/source.go index 8573772c37..91b83bb4fc 100644 --- a/source/source.go +++ b/source/source.go @@ -86,6 +86,12 @@ type Source interface { AddEventHandler(context.Context, func()) } +// endpointKey is the type of a map key for separating endpoints or targets. +type endpointKey struct { + dnsName string + recordType string +} + func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error) { ttlNotConfigured := endpoint.TTL(0) ttlAnnotation, exists := annotations[ttlAnnotationKey]