IPv6 internal node IPs are usable externally

This commit is contained in:
John Gardiner Myers 2023-05-02 22:55:08 -07:00
parent 510eb952b7
commit 683663e9c2
9 changed files with 314 additions and 51 deletions

View File

@ -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)

View File

@ -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))
}
}
}

View File

@ -76,6 +76,11 @@ func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotat
}, nil
}
type endpointsKey struct {
dnsName string
recordType string
}
// Endpoints returns endpoint objects for each service that should be processed.
func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
nodes, err := ns.nodeInformer.Lister().List(labels.Everything())
@ -88,7 +93,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
return nil, err
}
endpoints := map[string]*endpoint.Endpoint{}
endpoints := map[endpointsKey]*endpoint.Endpoint{}
// create endpoints for all nodes
for _, node := range nodes {
@ -109,8 +114,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 +138,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 := endpointsKey{
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 +172,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 {

View File

@ -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",

View File

@ -76,13 +76,18 @@ func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespac
func (*podSource) AddEventHandler(ctx context.Context, handler func()) {
}
type endpointKey struct {
domain string
recordType string
}
func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
pods, err := ps.podInformer.Lister().Pods(ps.namespace).List(labels.Everything())
if err != nil {
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 +95,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.domain, key.recordType, targets...))
}
return endpoints, nil
}
func addToEndpointMap(endpointMap map[endpointKey][]string, domain string, recordType string, address string) {
key := endpointKey{
domain: domain,
recordType: recordType,
}
if _, ok := endpointMap[key]; !ok {
endpointMap[key] = []string{}
}
endpointMap[key] = append(endpointMap[key], address)
}

View File

@ -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"},
},
},

View File

@ -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 {

View File

@ -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"},
},
},
}},

View File

@ -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) {