mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 01:56:57 +02:00
feat(services): implement Kubernetes services source
This commit is contained in:
parent
786055f3b3
commit
84910c4844
6
endpoint/endpoint.go
Normal file
6
endpoint/endpoint.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
DNSName string
|
||||||
|
Target string
|
||||||
|
}
|
63
source/service.go
Normal file
63
source/service.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The annotation used for figuring out which controller is responsible
|
||||||
|
controllerAnnotationKey = "external-dns.kubernetes.io/controller"
|
||||||
|
// The annotation used for defining the desired hostname
|
||||||
|
hostnameAnnotationKey = "external-dns.kubernetes.io/hostname"
|
||||||
|
// The value of the controller annotation so that we feel resposible
|
||||||
|
controllerAnnotationValue = "dns-controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceSource is an implementation of Source for Kubernetes service objects.
|
||||||
|
// It will find all services that are under our jurisdiction, i.e. annotated
|
||||||
|
// desired hostname and matching or no controller annotation. For each of the
|
||||||
|
// matches services' external entrypoints it will return a corresponding
|
||||||
|
// Endpoint object.
|
||||||
|
type ServiceSource struct {
|
||||||
|
Client kubernetes.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoints returns endpoint objects for each service that should be processed.
|
||||||
|
func (sc *ServiceSource) Endpoints() ([]endpoint.Endpoint, error) {
|
||||||
|
services, err := sc.Client.Core().Services(v1.NamespaceAll).List(v1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints := []endpoint.Endpoint{}
|
||||||
|
|
||||||
|
for _, svc := range services.Items {
|
||||||
|
// Check controller annotation to see if we are responsible.
|
||||||
|
controller, exists := svc.Annotations[controllerAnnotationKey]
|
||||||
|
if exists && controller != controllerAnnotationValue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the desired hostname of the service from the annotation.
|
||||||
|
hostname, exists := svc.Annotations[hostnameAnnotationKey]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an endpoint matching the desired hostname.
|
||||||
|
endpoint := endpoint.Endpoint{
|
||||||
|
DNSName: hostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a corresponding endpoint for each configured external entrypoint.
|
||||||
|
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||||
|
endpoint.Target = lb.IP
|
||||||
|
endpoints = append(endpoints, endpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints, nil
|
||||||
|
}
|
201
source/service_test.go
Normal file
201
source/service_test.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
|
|
||||||
|
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestEndpoints tests that various services generate the correct endpoints.
|
||||||
|
func TestEndpoints(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
namespace string
|
||||||
|
name string
|
||||||
|
annotations map[string]string
|
||||||
|
lbs []string
|
||||||
|
expected []endpoint.Endpoint
|
||||||
|
}{
|
||||||
|
// Completely opted-out: no endpoints returned.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{},
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]endpoint.Endpoint{},
|
||||||
|
},
|
||||||
|
// Opt-in by setting desired hostname.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Opt-in by setting desired hostname and this controller.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/controller": "dns-controller",
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Opt-out by setting a different controller.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/controller": "some-other-tool",
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]endpoint.Endpoint{},
|
||||||
|
},
|
||||||
|
// Make sure services are found in all namespaces.
|
||||||
|
{
|
||||||
|
"other-testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{"1.2.3.4"},
|
||||||
|
[]endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// No external entrypoints lead to no endpoints.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
[]endpoint.Endpoint{},
|
||||||
|
},
|
||||||
|
// Multiple external entrypoints lead to multiple endpoints.
|
||||||
|
{
|
||||||
|
"testing",
|
||||||
|
"foo",
|
||||||
|
map[string]string{
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
[]string{"1.2.3.4", "8.8.8.8"},
|
||||||
|
[]endpoint.Endpoint{
|
||||||
|
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||||
|
{DNSName: "foo.example.org", Target: "8.8.8.8"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
// Create a Kubernetes testing client
|
||||||
|
kubernetes := fake.NewSimpleClientset()
|
||||||
|
|
||||||
|
// Create a service to test against
|
||||||
|
ingresses := []v1.LoadBalancerIngress{}
|
||||||
|
for _, lb := range tc.lbs {
|
||||||
|
ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
|
||||||
|
}
|
||||||
|
|
||||||
|
service := &v1.Service{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Namespace: tc.namespace,
|
||||||
|
Name: tc.name,
|
||||||
|
Annotations: tc.annotations,
|
||||||
|
},
|
||||||
|
Status: v1.ServiceStatus{
|
||||||
|
LoadBalancer: v1.LoadBalancerStatus{
|
||||||
|
Ingress: ingresses,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := kubernetes.Core().Services(service.Namespace).Create(service)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our object under test and get the endpoints.
|
||||||
|
client := &ServiceSource{
|
||||||
|
Client: kubernetes,
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints, err := client.Endpoints()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returned endpoints against desired endpoints.
|
||||||
|
validateEndpoints(t, endpoints, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEndpoints(b *testing.B) {
|
||||||
|
kubernetes := fake.NewSimpleClientset()
|
||||||
|
|
||||||
|
service := &v1.Service{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Namespace: "testing",
|
||||||
|
Name: "foo",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: v1.ServiceStatus{
|
||||||
|
LoadBalancer: v1.LoadBalancerStatus{
|
||||||
|
Ingress: []v1.LoadBalancerIngress{
|
||||||
|
{IP: "1.2.3.4"},
|
||||||
|
{IP: "8.8.8.8"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := kubernetes.Core().Services(service.Namespace).Create(service)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &ServiceSource{
|
||||||
|
Client: kubernetes,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := client.Endpoints()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test helper functions
|
||||||
|
|
||||||
|
func validateEndpoints(t *testing.T, endpoints, expected []endpoint.Endpoint) {
|
||||||
|
if len(endpoints) != len(expected) {
|
||||||
|
t.Fatalf("expected %d endpoints, got %d", len(expected), len(endpoints))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range endpoints {
|
||||||
|
validateEndpoint(t, endpoints[i], expected[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateEndpoint(t *testing.T, endpoint, expected endpoint.Endpoint) {
|
||||||
|
if endpoint.DNSName != expected.DNSName {
|
||||||
|
t.Errorf("expected %s, got %s", expected.DNSName, endpoint.DNSName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endpoint.Target != expected.Target {
|
||||||
|
t.Errorf("expected %s, got %s", expected.Target, endpoint.Target)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user