mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
2115 lines
48 KiB
Go
2115 lines
48 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 (
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
"github.com/kubernetes-incubator/external-dns/endpoint"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type ServiceSuite struct {
|
|
suite.Suite
|
|
sc Source
|
|
fooWithTargets *v1.Service
|
|
}
|
|
|
|
func (suite *ServiceSuite) SetupTest() {
|
|
fakeClient := fake.NewSimpleClientset()
|
|
var err error
|
|
|
|
suite.sc, err = NewServiceSource(
|
|
fakeClient,
|
|
"",
|
|
"",
|
|
"{{.Name}}",
|
|
false,
|
|
"",
|
|
false,
|
|
false,
|
|
[]string{},
|
|
false,
|
|
)
|
|
suite.fooWithTargets = &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: v1.ServiceTypeLoadBalancer,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: "foo-with-targets",
|
|
Annotations: map[string]string{},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: "8.8.8.8"},
|
|
{Hostname: "foo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
suite.NoError(err, "should initialize service source")
|
|
|
|
_, err = fakeClient.CoreV1().Services(suite.fooWithTargets.Namespace).Create(suite.fooWithTargets)
|
|
suite.NoError(err, "should successfully create service")
|
|
|
|
}
|
|
|
|
func (suite *ServiceSuite) TestResourceLabelIsSet() {
|
|
endpoints, _ := suite.sc.Endpoints()
|
|
for _, ep := range endpoints {
|
|
suite.Equal("service/default/foo-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label")
|
|
}
|
|
}
|
|
|
|
func TestServiceSource(t *testing.T) {
|
|
suite.Run(t, new(ServiceSuite))
|
|
t.Run("Interface", testServiceSourceImplementsSource)
|
|
t.Run("NewServiceSource", testServiceSourceNewServiceSource)
|
|
t.Run("Endpoints", testServiceSourceEndpoints)
|
|
}
|
|
|
|
// testServiceSourceImplementsSource tests that serviceSource is a valid Source.
|
|
func testServiceSourceImplementsSource(t *testing.T) {
|
|
assert.Implements(t, (*Source)(nil), new(serviceSource))
|
|
}
|
|
|
|
// testServiceSourceNewServiceSource tests that NewServiceSource doesn't return an error.
|
|
func testServiceSourceNewServiceSource(t *testing.T) {
|
|
for _, ti := range []struct {
|
|
title string
|
|
annotationFilter string
|
|
fqdnTemplate string
|
|
serviceTypesFilter []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: "non-empty annotation filter label",
|
|
expectError: false,
|
|
annotationFilter: "kubernetes.io/ingress.class=nginx",
|
|
},
|
|
{
|
|
title: "non-empty service types filter",
|
|
expectError: false,
|
|
serviceTypesFilter: []string{string(v1.ServiceTypeClusterIP)},
|
|
},
|
|
} {
|
|
t.Run(ti.title, func(t *testing.T) {
|
|
_, err := NewServiceSource(
|
|
fake.NewSimpleClientset(),
|
|
"",
|
|
ti.annotationFilter,
|
|
ti.fqdnTemplate,
|
|
false,
|
|
"",
|
|
false,
|
|
false,
|
|
ti.serviceTypesFilter,
|
|
false,
|
|
)
|
|
|
|
if ti.expectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// testServiceSourceEndpoints tests that various services generate the correct endpoints.
|
|
func testServiceSourceEndpoints(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
annotationFilter string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
combineFQDNAndAnnotation bool
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
clusterIP string
|
|
lbs []string
|
|
serviceTypesFilter []string
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
}{
|
|
{
|
|
"no annotated services return no endpoints",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"no annotated services return no endpoints when ignoreing annotations",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"hostname annotation on services is ignored",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"annotated ClusterIp aren't processed without explicit authorization",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"1.2.3.4",
|
|
[]string{},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"FQDN template with multiple hostnames return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"FQDN template with multiple hostnames return an endpoint with target IP when ignoreing annotations",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
|
false,
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"FQDN template and annotation both with multiple hostnames return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
|
true,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org., bar.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
|
|
true,
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org., bar.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services with multiple hostnames return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org., bar.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services with multiple hostnames and without trailing period return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org, bar.example.org",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services return an endpoint with target hostname",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"lb.example.com"}, // Kubernetes omits the trailing dot
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services can omit trailing dot",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"our controller type is dns-controller",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
controllerAnnotationKey: controllerAnnotationValue,
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"different controller types are ignored even (with template specified)",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.ext-dns.test.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
controllerAnnotationKey: "some-other-tool",
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"services are found in target namespace",
|
|
"testing",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"services that are not in target namespace are ignored",
|
|
"testing",
|
|
"",
|
|
"other-testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"services are found in all namespaces",
|
|
"",
|
|
"",
|
|
"other-testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"valid matching annotation filter expression",
|
|
"",
|
|
"service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"valid non-matching annotation filter expression",
|
|
"",
|
|
"service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
"service.beta.kubernetes.io/external-traffic": "SomethingElse",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"invalid annotation filter expression",
|
|
"",
|
|
"service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
true,
|
|
},
|
|
{
|
|
"valid matching annotation filter label",
|
|
"",
|
|
"service.beta.kubernetes.io/external-traffic=Global",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
"service.beta.kubernetes.io/external-traffic": "Global",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"valid non-matching annotation filter label",
|
|
"",
|
|
"service.beta.kubernetes.io/external-traffic=Global",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"no external entrypoints return no endpoints",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"multiple external entrypoints return a single endpoint with multiple targets",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4", "8.8.8.8"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"services annotated with legacy mate annotations are ignored in default mode",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
"zalando.org/dnsname": "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"services annotated with legacy mate annotations return an endpoint in compatibility mode",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"mate",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
"zalando.org/dnsname": "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"services annotated with legacy molecule annotations return an endpoint in compatibility mode",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"molecule",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{
|
|
"dns": "route53",
|
|
},
|
|
map[string]string{
|
|
"domainName": "foo.example.org., bar.example.org",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not annotated services with set fqdnTemplate return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.bar.example.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4", "elb.com"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated services with set fqdnTemplate annotation takes precedence",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Name}}.bar.example.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4", "elb.com"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"compatibility annotated services with tmpl. compatibility takes precedence",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"mate",
|
|
"{{.Name}}.bar.example.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
"zalando.org/dnsname": "mate.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not annotated services with unknown tmpl field should not return anything",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"{{.Calibre}}.bar.example.com",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
true,
|
|
},
|
|
{
|
|
"ttl not annotated should have RecordTTL.IsConfigured set to false",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"ttl annotated but invalid should have RecordTTL.IsConfigured set to false",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
ttlAnnotationKey: "foo",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"ttl annotated and is valid should set Record.TTL",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
ttlAnnotationKey: "10",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"Negative ttl is not valid",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
ttlAnnotationKey: "-10",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"filter on service types should include matching services",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeLoadBalancer,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{string(v1.ServiceTypeLoadBalancer)},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"filter on service types should exclude non-matching services",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeNodePort,
|
|
"",
|
|
"",
|
|
false,
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"",
|
|
[]string{"1.2.3.4"},
|
|
[]string{string(v1.ServiceTypeLoadBalancer)},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
// Create a service to test against
|
|
ingresses := []v1.LoadBalancerIngress{}
|
|
for _, lb := range tc.lbs {
|
|
if net.ParseIP(lb) != nil {
|
|
ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
|
|
} else {
|
|
ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb})
|
|
}
|
|
}
|
|
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
ClusterIP: tc.clusterIP,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: ingresses,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
tc.annotationFilter,
|
|
tc.fqdnTemplate,
|
|
tc.combineFQDNAndAnnotation,
|
|
tc.compatibility,
|
|
false,
|
|
false,
|
|
tc.serviceTypesFilter,
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
var res []*endpoint.Endpoint
|
|
|
|
// wait up to a few seconds for new resources to appear in informer cache.
|
|
err = wait.Poll(time.Second, 3*time.Second, func() (bool, error) {
|
|
res, err = client.Endpoints()
|
|
if err != nil {
|
|
// stop waiting if we get an error
|
|
return true, err
|
|
}
|
|
return len(res) >= len(tc.expected), nil
|
|
})
|
|
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, res, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// testServiceSourceEndpoints tests that various services generate the correct endpoints.
|
|
func TestClusterIpServices(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
annotationFilter string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
clusterIP string
|
|
lbs []string
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
}{
|
|
{
|
|
"annotated ClusterIp services return an endpoint with Cluster IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"1.2.3.4",
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"hostname annotated ClusterIp services are ignored",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
"1.2.3.4",
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"{{.Name}}.bar.example.com",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
"4.5.6.7",
|
|
[]string{},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"Headless services do not generate endpoints",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
v1.ClusterIPNone,
|
|
[]string{},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
// Create a service to test against
|
|
ingresses := []v1.LoadBalancerIngress{}
|
|
for _, lb := range tc.lbs {
|
|
if net.ParseIP(lb) != nil {
|
|
ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
|
|
} else {
|
|
ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb})
|
|
}
|
|
}
|
|
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
ClusterIP: tc.clusterIP,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: ingresses,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
tc.annotationFilter,
|
|
tc.fqdnTemplate,
|
|
false,
|
|
tc.compatibility,
|
|
true,
|
|
false,
|
|
[]string{},
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
endpoints, err := client.Endpoints()
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, endpoints, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// testNodePortServices tests that various services generate the correct endpoints.
|
|
func TestNodePortServices(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
annotationFilter string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
lbs []string
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
nodes []*v1.Node
|
|
}{
|
|
{
|
|
"annotated NodePort services return an endpoint with IP addresses of the cluster's nodes",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeNodePort,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
nil,
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "_30192._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},
|
|
},
|
|
false,
|
|
[]*v1.Node{{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
|
},
|
|
},
|
|
}, {
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
"hostname annotated NodePort services are ignored",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeNodePort,
|
|
"",
|
|
"",
|
|
true,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
nil,
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
[]*v1.Node{{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
|
},
|
|
},
|
|
}, {
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
"non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeNodePort,
|
|
"",
|
|
"{{.Name}}.bar.example.com",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{},
|
|
nil,
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "_30192._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},
|
|
},
|
|
false,
|
|
[]*v1.Node{{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
|
},
|
|
},
|
|
}, {
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
"annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes",
|
|
"",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeNodePort,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
nil,
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "_30192._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},
|
|
},
|
|
false,
|
|
[]*v1.Node{{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node1",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
|
},
|
|
},
|
|
}, {
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "node2",
|
|
},
|
|
Status: v1.NodeStatus{
|
|
Addresses: []v1.NodeAddress{
|
|
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
// Create the nodes
|
|
for _, node := range tc.nodes {
|
|
if _, err := kubernetes.Core().Nodes().Create(node); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Create a service to test against
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
Ports: []v1.ServicePort{
|
|
{
|
|
NodePort: 30192,
|
|
},
|
|
},
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
}
|
|
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
tc.annotationFilter,
|
|
tc.fqdnTemplate,
|
|
false,
|
|
tc.compatibility,
|
|
true,
|
|
false,
|
|
[]string{},
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
endpoints, err := client.Endpoints()
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, endpoints, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHeadlessServices tests that headless services generate the correct endpoints.
|
|
func TestHeadlessServices(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
clusterIP string
|
|
podIPs []string
|
|
selector map[string]string
|
|
lbs []string
|
|
podnames []string
|
|
hostnames []string
|
|
phases []v1.PodPhase
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
}{
|
|
{
|
|
"annotated Headless services return endpoints for each selected Pod",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
|
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"hostname annotated Headless services are ignored",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
true,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints with TTL for each selected Pod",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
ttlAnnotationKey: "1",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
|
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints for each selected Pod, which are in running state",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodFailed},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints for pods missing hostname",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"", ""},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
|
},
|
|
false,
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
ClusterIP: tc.clusterIP,
|
|
Selector: tc.selector,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.ServiceStatus{},
|
|
}
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
for i, podname := range tc.podnames {
|
|
pod := &v1.Pod{
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{},
|
|
Hostname: tc.hostnames[i],
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: podname,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.PodStatus{
|
|
PodIP: tc.podIPs[i],
|
|
Phase: tc.phases[i],
|
|
},
|
|
}
|
|
|
|
_, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(pod)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
"",
|
|
tc.fqdnTemplate,
|
|
false,
|
|
tc.compatibility,
|
|
true,
|
|
false,
|
|
[]string{},
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
endpoints, err := client.Endpoints()
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, endpoints, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHeadlessServices tests that headless services generate the correct endpoints.
|
|
func TestHeadlessServicesHostIP(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
clusterIP string
|
|
hostIPs []string
|
|
selector map[string]string
|
|
lbs []string
|
|
podnames []string
|
|
hostnames []string
|
|
phases []v1.PodPhase
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
}{
|
|
{
|
|
"annotated Headless services return endpoints for each selected Pod",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
|
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"hostname annotated Headless services are ignored",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
true,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints with TTL for each selected Pod",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
ttlAnnotationKey: "1",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)},
|
|
{DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints for each selected Pod, which are in running state",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodFailed},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"annotated Headless services return endpoints for pods missing hostname",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeClusterIP,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
v1.ClusterIPNone,
|
|
[]string{"1.1.1.1", "1.1.1.2"},
|
|
map[string]string{
|
|
"component": "foo",
|
|
},
|
|
[]string{},
|
|
[]string{"foo-0", "foo-1"},
|
|
[]string{"", ""},
|
|
[]v1.PodPhase{v1.PodRunning, v1.PodRunning},
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
|
|
},
|
|
false,
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
ClusterIP: tc.clusterIP,
|
|
Selector: tc.selector,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.ServiceStatus{},
|
|
}
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
for i, podname := range tc.podnames {
|
|
pod := &v1.Pod{
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{},
|
|
Hostname: tc.hostnames[i],
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: podname,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.PodStatus{
|
|
HostIP: tc.hostIPs[i],
|
|
Phase: tc.phases[i],
|
|
},
|
|
}
|
|
|
|
_, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(pod)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
"",
|
|
tc.fqdnTemplate,
|
|
false,
|
|
tc.compatibility,
|
|
true,
|
|
true,
|
|
[]string{},
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
endpoints, err := client.Endpoints()
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, endpoints, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestExternalServices tests that external services generate the correct endpoints.
|
|
func TestExternalServices(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
title string
|
|
targetNamespace string
|
|
svcNamespace string
|
|
svcName string
|
|
svcType v1.ServiceType
|
|
compatibility string
|
|
fqdnTemplate string
|
|
ignoreHostnameAnnotation bool
|
|
labels map[string]string
|
|
annotations map[string]string
|
|
externalName string
|
|
expected []*endpoint.Endpoint
|
|
expectError bool
|
|
}{
|
|
{
|
|
"external services return an A endpoint for the external name that is an IP address",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeExternalName,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
"111.111.111.111",
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA},
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"external services return a CNAME endpoint for the external name that is a domain",
|
|
"",
|
|
"testing",
|
|
"foo",
|
|
v1.ServiceTypeExternalName,
|
|
"",
|
|
"",
|
|
false,
|
|
map[string]string{"component": "foo"},
|
|
map[string]string{
|
|
hostnameAnnotationKey: "service.example.org",
|
|
},
|
|
"remote.example.com",
|
|
[]*endpoint.Endpoint{
|
|
{DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME},
|
|
},
|
|
false,
|
|
},
|
|
} {
|
|
t.Run(tc.title, func(t *testing.T) {
|
|
// Create a Kubernetes testing client
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
service := &v1.Service{
|
|
Spec: v1.ServiceSpec{
|
|
Type: tc.svcType,
|
|
ExternalName: tc.externalName,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: tc.svcNamespace,
|
|
Name: tc.svcName,
|
|
Labels: tc.labels,
|
|
Annotations: tc.annotations,
|
|
},
|
|
Status: v1.ServiceStatus{},
|
|
}
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(t, err)
|
|
|
|
// Create our object under test and get the endpoints.
|
|
client, _ := NewServiceSource(
|
|
kubernetes,
|
|
tc.targetNamespace,
|
|
"",
|
|
tc.fqdnTemplate,
|
|
false,
|
|
tc.compatibility,
|
|
true,
|
|
false,
|
|
[]string{},
|
|
tc.ignoreHostnameAnnotation,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
endpoints, err := client.Endpoints()
|
|
if tc.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Validate returned endpoints against desired endpoints.
|
|
validateEndpoints(t, endpoints, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkServiceEndpoints(b *testing.B) {
|
|
kubernetes := fake.NewSimpleClientset()
|
|
|
|
service := &v1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "testing",
|
|
Name: "foo",
|
|
Annotations: map[string]string{
|
|
hostnameAnnotationKey: "foo.example.org.",
|
|
},
|
|
},
|
|
Status: v1.ServiceStatus{
|
|
LoadBalancer: v1.LoadBalancerStatus{
|
|
Ingress: []v1.LoadBalancerIngress{
|
|
{IP: "1.2.3.4"},
|
|
{IP: "8.8.8.8"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := kubernetes.CoreV1().Services(service.Namespace).Create(service)
|
|
require.NoError(b, err)
|
|
|
|
client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false, []string{}, false)
|
|
require.NoError(b, err)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.Endpoints()
|
|
require.NoError(b, err)
|
|
}
|
|
}
|