mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
chore(source/node): template expansion (#5498)
This commit is contained in:
parent
93d4d47bff
commit
f55be38b45
@ -316,7 +316,7 @@ By setting the hostname annotation in the ingress resource, ExternalDNS construc
|
||||
|
||||
```yml
|
||||
args:
|
||||
- --fqdn-template="{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.example.com
|
||||
- --fqdn-template="{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.example.com"
|
||||
```
|
||||
|
||||
This is a complex template that iternates through a list of a Node's Addresses and creates a FQDN with public IPv4 addresses.
|
||||
|
@ -48,7 +48,7 @@ type nodeSource struct {
|
||||
}
|
||||
|
||||
// NewNodeSource creates a new nodeSource with the given config.
|
||||
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6 bool, excludeUnschedulable bool) (Source, error) {
|
||||
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector, exposeInternalIPv6, excludeUnschedulable bool) (Source, error) {
|
||||
tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -103,8 +103,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
// create endpoints for all nodes
|
||||
for _, node := range nodes {
|
||||
// Check controller annotation to see if we are responsible.
|
||||
controller, ok := node.Annotations[controllerAnnotationKey]
|
||||
if ok && controller != controllerAnnotationValue {
|
||||
if controller, ok := node.Annotations[controllerAnnotationKey]; ok && controller != controllerAnnotationValue {
|
||||
log.Debugf("Skipping node %s because controller value does not match, found: %s, required: %s",
|
||||
node.Name, controller, controllerAnnotationValue)
|
||||
continue
|
||||
@ -119,28 +118,8 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
|
||||
ttl := annotations.TTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
|
||||
|
||||
// create new endpoint with the information we already have
|
||||
ep := &endpoint.Endpoint{
|
||||
RecordTTL: ttl,
|
||||
}
|
||||
|
||||
if ns.fqdnTemplate != nil {
|
||||
hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostname := ""
|
||||
if len(hostnames) > 0 {
|
||||
hostname = hostnames[0]
|
||||
}
|
||||
ep.DNSName = hostname
|
||||
log.Debugf("applied template for %s, converting to %s", node.Name, ep.DNSName)
|
||||
} else {
|
||||
ep.DNSName = node.Name
|
||||
log.Debugf("not applying template for %s", node.Name)
|
||||
}
|
||||
|
||||
addrs := annotations.TargetsFromTargetAnnotation(node.Annotations)
|
||||
|
||||
if len(addrs) == 0 {
|
||||
addrs, err = ns.nodeAddresses(node)
|
||||
if err != nil {
|
||||
@ -148,19 +127,40 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
}
|
||||
}
|
||||
|
||||
ep.Labels = endpoint.NewLabels()
|
||||
for _, addr := range addrs {
|
||||
log.Debugf("adding endpoint %s target %s", ep, addr)
|
||||
key := endpoint.EndpointKey{
|
||||
DNSName: ep.DNSName,
|
||||
RecordType: suitableType(addr),
|
||||
dnsNames := make(map[string]bool)
|
||||
|
||||
if ns.fqdnTemplate != nil {
|
||||
hostnames, err := fqdn.ExecTemplate(ns.fqdnTemplate, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := endpoints[key]; !ok {
|
||||
epCopy := *ep
|
||||
epCopy.RecordType = key.RecordType
|
||||
endpoints[key] = &epCopy
|
||||
|
||||
for _, name := range hostnames {
|
||||
dnsNames[name] = true
|
||||
log.Debugf("applied template for %s, converting to %s", node.Name, name)
|
||||
}
|
||||
} else {
|
||||
dnsNames[node.Name] = true
|
||||
log.Debugf("not applying template for %s", node.Name)
|
||||
}
|
||||
|
||||
for dns := range dnsNames {
|
||||
log.Debugf("adding endpoint with %d targets", len(addrs))
|
||||
|
||||
for _, addr := range addrs {
|
||||
ep := endpoint.NewEndpointWithTTL(dns, suitableType(addr), ttl)
|
||||
log.Debugf("adding endpoint %s target %s", ep, addr)
|
||||
key := endpoint.EndpointKey{
|
||||
DNSName: ep.DNSName,
|
||||
RecordType: ep.RecordType,
|
||||
}
|
||||
if _, ok := endpoints[key]; !ok {
|
||||
epCopy := *ep
|
||||
epCopy.RecordType = key.RecordType
|
||||
endpoints[key] = &epCopy
|
||||
}
|
||||
endpoints[key].Targets = append(endpoints[key].Targets, addr)
|
||||
}
|
||||
endpoints[key].Targets = append(endpoints[key].Targets, addr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,10 +172,10 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
return endpointsSlice, nil
|
||||
}
|
||||
|
||||
func (ns *nodeSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
func (ns *nodeSource) AddEventHandler(_ context.Context, _ func()) {
|
||||
}
|
||||
|
||||
// nodeAddress returns node's externalIP and if that's not found, node's internalIP
|
||||
// nodeAddress returns the node's externalIP and if that's not found, the node's internalIP
|
||||
// basically what k8s.io/kubernetes/pkg/util/node.GetPreferredNodeAddress does
|
||||
func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
|
||||
addresses := map[v1.NodeAddressType][]string{
|
||||
@ -223,7 +223,7 @@ func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error)
|
||||
var filteredList []*v1.Node
|
||||
|
||||
for _, node := range nodes {
|
||||
// include node if its annotations match the selector
|
||||
// include a node if its annotations match the selector
|
||||
if selector.Matches(labels.Set(node.Annotations)) {
|
||||
filteredList = append(filteredList, node)
|
||||
}
|
||||
|
322
source/node_fqdn_test.go
Normal file
322
source/node_fqdn_test.go
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
func TestNodeSourceNewNodeSourceWithFqdn(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
title string
|
||||
annotationFilter string
|
||||
fqdnTemplate string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
title: "invalid template",
|
||||
expectError: true,
|
||||
fqdnTemplate: "{{.Name",
|
||||
},
|
||||
{
|
||||
title: "valid empty template",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
title: "valid template",
|
||||
expectError: false,
|
||||
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
|
||||
},
|
||||
{
|
||||
title: "complex template",
|
||||
expectError: false,
|
||||
fqdnTemplate: "{{range .Status.Addresses}}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}{{.Address | replace \".\" \"-\"}}{{break}}{{end}}{{end}}.ext-dns.test.com",
|
||||
},
|
||||
} {
|
||||
t.Run(tt.title, func(t *testing.T) {
|
||||
_, err := NewNodeSource(
|
||||
t.Context(),
|
||||
fake.NewClientset(),
|
||||
tt.annotationFilter,
|
||||
tt.fqdnTemplate,
|
||||
labels.Everything(),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSourceFqdnTemplatingExamples(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
title string
|
||||
nodes []*v1.Node
|
||||
fqdnTemplate string
|
||||
expected []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
title: "templating expansion with multiple domains",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ip-10-1-176-5.internal",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: "10.1.176.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.domainA.com,{{.Name}}.domainB.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "ip-10-1-176-5.internal.domainA.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
|
||||
{DNSName: "ip-10-1-176-5.internal.domainA.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
|
||||
{DNSName: "ip-10-1-176-5.internal.domainB.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
|
||||
{DNSName: "ip-10-1-176-5.internal.domainB.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating contains namespace when node namespace is not a valid variable",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: "10.1.176.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.domainA.com,{{ .Name }}.{{ .Namespace }}.example.tld",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "node-name.domainA.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
|
||||
{DNSName: "node-name..example.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.176.1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with external IP and range of addresses",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ip-10-1-176-1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{ range .Status.Addresses }}{{if and (eq .Type \"ExternalIP\") (isIPv4 .Address)}}ip-{{ .Address | replace \".\" \"-\" }}{{ break }}{{ end }}{{ end }}.example.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "ip-243-186-136-160.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
{DNSName: "ip-243-186-136-160.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with name definition and ipv4 check",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name-ip",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{ $name := .Name }}{{ range .Status.Addresses }}{{if (isIPv4 .Address)}}{{ $name }}.ipv4{{ break }}{{ end }}{{ end }}.example.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "node-name-ip.ipv4.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
{DNSName: "node-name-ip.ipv4.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with hostname annotation",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ip-10-1-176-1",
|
||||
Annotations: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/hostname": "ip-10-1-176-1.internal.domain.com",
|
||||
},
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.example.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "ip-10-1-176-1.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
{DNSName: "ip-10-1-176-1.example.com", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"fc00:f853:ccd:e793::1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating when target annotation and no external IP",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name",
|
||||
Labels: nil,
|
||||
Annotations: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/target": "203.2.45.22",
|
||||
},
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
{Type: v1.NodeInternalIP, Address: "fc00:f853:ccd:e793::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{.Name}}.example.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "node-name.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"203.2.45.22"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with simple annotation expansion",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name",
|
||||
Annotations: map[string]string{
|
||||
"workload": "cluster-resources",
|
||||
},
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{ .Name }}.{{ .Annotations.workload }}.domain.tld",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "node-name.cluster-resources.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with complex labels expansion",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name",
|
||||
Labels: map[string]string{
|
||||
"topology.kubernetes.io/region": "eu-west-1",
|
||||
},
|
||||
Annotations: nil,
|
||||
},
|
||||
Spec: v1.NodeSpec{
|
||||
Unschedulable: false,
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{ .Name }}.{{ index .ObjectMeta.Labels \"topology.kubernetes.io/region\" }}.domain.tld",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "node-name.eu-west-1.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "templating with shared all domain",
|
||||
nodes: []*v1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name-1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.160"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-name-2",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "243.186.136.178"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fqdnTemplate: "{{ .Name }}.domain.tld,all.example.com",
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "all.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160", "243.186.136.178"}},
|
||||
{DNSName: "node-name-1.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.160"}},
|
||||
{DNSName: "node-name-2.domain.tld", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"243.186.136.178"}},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.title, func(t *testing.T) {
|
||||
kubeClient := fake.NewClientset()
|
||||
|
||||
for _, node := range tt.nodes {
|
||||
_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), node, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
src, err := NewNodeSource(
|
||||
t.Context(),
|
||||
kubeClient,
|
||||
"",
|
||||
tt.fqdnTemplate,
|
||||
labels.Everything(),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
endpoints, err := src.Endpoints(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, endpoints, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
@ -18,10 +18,15 @@ package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
|
||||
@ -84,7 +89,7 @@ func testNodeSourceNewNodeSource(t *testing.T) {
|
||||
|
||||
_, err := NewNodeSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
fake.NewClientset(),
|
||||
ti.annotationFilter,
|
||||
ti.fqdnTemplate,
|
||||
labels.Everything(),
|
||||
@ -407,7 +412,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a Kubernetes testing client
|
||||
kubernetes := fake.NewSimpleClientset()
|
||||
kubeClient := fake.NewClientset()
|
||||
|
||||
node := &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -423,13 +428,13 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := kubernetes.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
|
||||
_, err := kubeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, err := NewNodeSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
kubeClient,
|
||||
tc.annotationFilter,
|
||||
tc.fqdnTemplate,
|
||||
labelSelector,
|
||||
@ -519,7 +524,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a Kubernetes testing client
|
||||
kubernetes := fake.NewSimpleClientset()
|
||||
kubeClient := fake.NewClientset()
|
||||
|
||||
node := &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -535,7 +540,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := kubernetes.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
|
||||
_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), node, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var hook *test.Hook
|
||||
@ -545,8 +550,8 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, err := NewNodeSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
t.Context(),
|
||||
kubeClient,
|
||||
tc.annotationFilter,
|
||||
tc.fqdnTemplate,
|
||||
labelSelector,
|
||||
@ -555,7 +560,7 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
endpoints, err := client.Endpoints(context.Background())
|
||||
endpoints, err := client.Endpoints(t.Context())
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@ -570,3 +575,101 @@ func testNodeEndpointsWithIPv6(t *testing.T) {
|
||||
validateEndpoints(t, endpoints, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceLabelIsSetForEachNodeEndpoint(t *testing.T) {
|
||||
kubeClient := fake.NewClientset()
|
||||
|
||||
nodes := helperNodeBuilder().
|
||||
withNode(nil).
|
||||
withNode(nil).
|
||||
withNode(nil).
|
||||
withNode(nil).
|
||||
build()
|
||||
|
||||
for _, node := range nodes.Items {
|
||||
_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), &node, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "Failed to create node %s", node.Name)
|
||||
}
|
||||
|
||||
client, err := NewNodeSource(
|
||||
t.Context(),
|
||||
kubeClient,
|
||||
"",
|
||||
"",
|
||||
labels.Everything(),
|
||||
false,
|
||||
true,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := client.Endpoints(t.Context())
|
||||
require.NoError(t, err)
|
||||
for _, ep := range got {
|
||||
// TODO: node source should always set the resource label key. currently not supported by the node source.
|
||||
assert.Empty(t, ep.Labels, "Labels should not be empty for endpoint %s", ep.DNSName)
|
||||
assert.NotContains(t, ep.Labels, endpoint.ResourceLabelKey)
|
||||
}
|
||||
}
|
||||
|
||||
type nodeListBuilder struct {
|
||||
nodes []v1.Node
|
||||
}
|
||||
|
||||
func helperNodeBuilder() *nodeListBuilder {
|
||||
return &nodeListBuilder{nodes: []v1.Node{}}
|
||||
}
|
||||
|
||||
func (b *nodeListBuilder) withNode(labels map[string]string) *nodeListBuilder {
|
||||
idx := len(b.nodes) + 1
|
||||
nodeName := fmt.Sprintf("ip-10-1-176-%d.internal", idx)
|
||||
b.nodes = append(b.nodes, v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nodeName,
|
||||
Labels: func() map[string]string {
|
||||
base := map[string]string{
|
||||
"test-label": "test-value",
|
||||
"name": nodeName,
|
||||
"topology.kubernetes.io/region": "eu-west-1",
|
||||
"node.kubernetes.io/lifecycle": "spot",
|
||||
}
|
||||
maps.Copy(base, labels)
|
||||
return base
|
||||
}(),
|
||||
Annotations: map[string]string{
|
||||
"volumes.kubernetes.io/controller-managed-attach-detach": "true",
|
||||
"alpha.kubernetes.io/provided-node-ip": fmt.Sprintf("10.1.176.%d", idx),
|
||||
"external-dns.alpha.kubernetes.io/hostname": fmt.Sprintf("node-%d.example.com", idx),
|
||||
},
|
||||
},
|
||||
Spec: v1.NodeSpec{
|
||||
Unschedulable: false,
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: fmt.Sprintf("10.1.176.%d", idx)},
|
||||
{Type: v1.NodeInternalIP, Address: fmt.Sprintf("fc00:f853:ccd:e793::%d", idx)},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *nodeListBuilder) build() v1.NodeList {
|
||||
if len(b.nodes) > 1 {
|
||||
// Shuffle the result to ensure randomness in the order.
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
rand.Shuffle(len(b.nodes), func(i, j int) {
|
||||
b.nodes[i], b.nodes[j] = b.nodes[j], b.nodes[i]
|
||||
})
|
||||
}
|
||||
return v1.NodeList{Items: b.nodes}
|
||||
}
|
||||
|
||||
func (b *nodeListBuilder) apply(t *testing.T, kubeClient kubernetes.Interface) v1.NodeList {
|
||||
for _, node := range b.nodes {
|
||||
_, err := kubeClient.CoreV1().Nodes().Create(t.Context(), &node, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "Failed to create node %s", node.Name)
|
||||
}
|
||||
return v1.NodeList{Items: b.nodes}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user