mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-04-15 21:11:01 +02:00
* refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(source): extract FQDN template logic into fqdn.TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * efactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> * refactor(fqdn): encapsulate FQDN template logic into TemplateEngine Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com> --------- Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
525 lines
14 KiB
Go
525 lines
14 KiB
Go
/*
|
|
Copyright 2017 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 (
|
|
"context"
|
|
"testing"
|
|
|
|
"sigs.k8s.io/external-dns/internal/testutils"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
routev1 "github.com/openshift/api/route/v1"
|
|
"github.com/openshift/client-go/route/clientset/versioned/fake"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"sigs.k8s.io/external-dns/endpoint"
|
|
"sigs.k8s.io/external-dns/source/annotations"
|
|
templatetest "sigs.k8s.io/external-dns/source/template/testutil"
|
|
)
|
|
|
|
type OCPRouteSuite struct {
|
|
suite.Suite
|
|
sc Source
|
|
routeWithTargets *routev1.Route
|
|
}
|
|
|
|
func (suite *OCPRouteSuite) SetupTest() {
|
|
fakeClient := fake.NewClientset()
|
|
var err error
|
|
|
|
suite.sc, err = NewOcpRouteSource(
|
|
context.TODO(),
|
|
fakeClient,
|
|
&Config{
|
|
TemplateEngine: templatetest.MustEngine(suite.T(), "{{.Name}}", "", "", false),
|
|
LabelFilter: labels.Everything(),
|
|
},
|
|
)
|
|
|
|
suite.routeWithTargets = &routev1.Route{
|
|
Spec: routev1.RouteSpec{
|
|
Host: "my-domain.com",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-targets",
|
|
Annotations: map[string]string{},
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
RouterCanonicalHostname: "apps.my-domain.com",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
suite.NoError(err, "should initialize route source")
|
|
|
|
_, err = fakeClient.RouteV1().Routes(suite.routeWithTargets.Namespace).Create(context.Background(), suite.routeWithTargets, metav1.CreateOptions{})
|
|
suite.NoError(err, "should successfully create route")
|
|
}
|
|
|
|
func (suite *OCPRouteSuite) TestResourceLabelIsSet() {
|
|
endpoints, _ := suite.sc.Endpoints(context.Background())
|
|
for _, ep := range endpoints {
|
|
suite.Equal("route/default/route-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label")
|
|
}
|
|
}
|
|
|
|
func TestOcpRouteSource(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
suite.Run(t, new(OCPRouteSuite))
|
|
t.Run("Interface", testOcpRouteSourceImplementsSource)
|
|
t.Run("Endpoints", testOcpRouteSourceEndpoints)
|
|
}
|
|
|
|
// testOcpRouteSourceImplementsSource tests that ocpRouteSource is a valid Source.
|
|
func testOcpRouteSourceImplementsSource(t *testing.T) {
|
|
assert.Implements(t, (*Source)(nil), new(ocpRouteSource))
|
|
}
|
|
|
|
// testOcpRouteSourceEndpoints tests that various OCP routes generate the correct endpoints.
|
|
func testOcpRouteSourceEndpoints(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
ocpRoute *routev1.Route
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
labelFilter string
|
|
ocpRouterName string
|
|
}{
|
|
{
|
|
title: "route with basic hostname and route status target",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterCanonicalHostname: "apps.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"apps.my-domain.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route with basic hostname, route status target and ocpRouterName defined",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ocpRouterName: "default",
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"router-default.my-domain.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route with basic hostname, route status target, one ocpRouterName and two router canonical names",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "test",
|
|
RouterCanonicalHostname: "router-test.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ocpRouterName: "default",
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"router-default.my-domain.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route not admitted by the given router",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "test",
|
|
RouterCanonicalHostname: "router-test.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ocpRouterName: "test",
|
|
expected: []*endpoint.Endpoint{},
|
|
},
|
|
{
|
|
title: "route not admitted by any router",
|
|
ocpRoute: &routev1.Route{
|
|
Spec: routev1.RouteSpec{
|
|
Host: "my-domain.com",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "test",
|
|
RouterCanonicalHostname: "router-test.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{},
|
|
},
|
|
{
|
|
title: "route admitted by first appropriate router",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-target",
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionFalse,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterName: "test",
|
|
RouterCanonicalHostname: "router-test.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"router-test.my-domain.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route with incorrect externalDNS controller annotation",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-ignore-annotation",
|
|
Annotations: map[string]string{
|
|
"external-dns.alpha.kubernetes.io/controller": "foo",
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{},
|
|
},
|
|
{
|
|
title: "route with basic hostname and annotation target",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-annotation-target",
|
|
Annotations: map[string]string{
|
|
"external-dns.alpha.kubernetes.io/target": "my.site.foo.com",
|
|
},
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-annotation-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-annotation-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"my.site.foo.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route with matching labels",
|
|
labelFilter: "app=web-external",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-matching-labels",
|
|
Annotations: map[string]string{
|
|
"external-dns.alpha.kubernetes.io/target": "my.site.foo.com",
|
|
},
|
|
Labels: map[string]string{
|
|
"app": "web-external",
|
|
"name": "service-frontend",
|
|
},
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-annotation-domain.com",
|
|
RouterName: "default",
|
|
RouterCanonicalHostname: "router-default.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-annotation-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{
|
|
"my.site.foo.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
title: "route without matching labels",
|
|
labelFilter: "app=web-external",
|
|
ocpRoute: &routev1.Route{
|
|
Spec: routev1.RouteSpec{
|
|
Host: "my-annotation-domain.com",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-without-matching-labels",
|
|
Annotations: map[string]string{
|
|
"external-dns.alpha.kubernetes.io/target": "my.site.foo.com",
|
|
},
|
|
Labels: map[string]string{
|
|
"app": "web-internal",
|
|
"name": "service-frontend",
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{},
|
|
},
|
|
{
|
|
title: "route with provider-specific annotation",
|
|
ocpRoute: &routev1.Route{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "route-with-provider-specific",
|
|
Annotations: map[string]string{
|
|
annotations.AWSPrefix + "weight": "10",
|
|
},
|
|
},
|
|
Status: routev1.RouteStatus{
|
|
Ingress: []routev1.RouteIngress{
|
|
{
|
|
Host: "my-domain.com",
|
|
RouterCanonicalHostname: "apps.my-domain.com",
|
|
Conditions: []routev1.RouteIngressCondition{
|
|
{
|
|
Type: routev1.RouteAdmitted,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []*endpoint.Endpoint{
|
|
{
|
|
DNSName: "my-domain.com",
|
|
RecordType: endpoint.RecordTypeCNAME,
|
|
Targets: []string{"apps.my-domain.com"},
|
|
ProviderSpecific: endpoint.ProviderSpecific{
|
|
{Name: "aws/weight", Value: "10"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Create a Kubernetes testing client
|
|
fakeClient := fake.NewClientset()
|
|
_, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(t.Context(), tc.ocpRoute, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
labelSelector, err := labels.Parse(tc.labelFilter)
|
|
require.NoError(t, err)
|
|
|
|
source, err := NewOcpRouteSource(
|
|
t.Context(),
|
|
fakeClient,
|
|
&Config{
|
|
TemplateEngine: templatetest.MustEngine(t, "{{.Name}}", "", "", false),
|
|
LabelFilter: labelSelector,
|
|
OCPRouterName: tc.ocpRouterName,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
res, err := source.Endpoints(t.Context())
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
testutils.ValidateEndpoints(t, res, tc.expected)
|
|
})
|
|
}
|
|
}
|