Enhance compatibility and process molecule Services (#166)

* feat(service): enhance compatibility, process molecule services

* ref(service): simplify label detection for molecule servics
This commit is contained in:
Martin Linkhorst 2017-04-21 16:38:56 +02:00 committed by GitHub
parent 285cb4cb35
commit 096c68be79
5 changed files with 108 additions and 35 deletions

View File

@ -40,7 +40,7 @@ type Config struct {
Provider string Provider string
GoogleProject string GoogleProject string
Policy string Policy string
Compatibility bool Compatibility string
MetricsAddress string MetricsAddress string
Interval time.Duration Interval time.Duration
Once bool Once bool
@ -71,7 +71,7 @@ func (cfg *Config) ParseFlags(args []string) error {
flags.StringVar(&cfg.Provider, "provider", "", "the DNS provider to materialize the records in: <aws|google>") flags.StringVar(&cfg.Provider, "provider", "", "the DNS provider to materialize the records in: <aws|google>")
flags.StringVar(&cfg.GoogleProject, "google-project", "", "gcloud project to target") flags.StringVar(&cfg.GoogleProject, "google-project", "", "gcloud project to target")
flags.StringVar(&cfg.Policy, "policy", "sync", "the policy to use: <sync|upsert-only>") flags.StringVar(&cfg.Policy, "policy", "sync", "the policy to use: <sync|upsert-only>")
flags.BoolVar(&cfg.Compatibility, "compatibility", false, "enable to process annotation semantics from legacy implementations") flags.StringVar(&cfg.Compatibility, "compatibility", "", "enable to process annotation semantics from legacy implementations: <mate|molecule>")
flags.StringVar(&cfg.MetricsAddress, "metrics-address", defaultMetricsAddress, "address to expose metrics on") flags.StringVar(&cfg.MetricsAddress, "metrics-address", defaultMetricsAddress, "address to expose metrics on")
flags.StringVar(&cfg.LogFormat, "log-format", defaultLogFormat, "log format output: <text|json>") flags.StringVar(&cfg.LogFormat, "log-format", defaultLogFormat, "log format output: <text|json>")
flags.DurationVar(&cfg.Interval, "interval", time.Minute, "interval between synchronizations") flags.DurationVar(&cfg.Interval, "interval", time.Minute, "interval between synchronizations")

View File

@ -42,7 +42,7 @@ func TestParseFlags(t *testing.T) {
Provider: "", Provider: "",
GoogleProject: "", GoogleProject: "",
Policy: "sync", Policy: "sync",
Compatibility: false, Compatibility: "",
MetricsAddress: defaultMetricsAddress, MetricsAddress: defaultMetricsAddress,
Interval: time.Minute, Interval: time.Minute,
Once: false, Once: false,
@ -69,7 +69,7 @@ func TestParseFlags(t *testing.T) {
Provider: "", Provider: "",
GoogleProject: "", GoogleProject: "",
Policy: "sync", Policy: "sync",
Compatibility: false, Compatibility: "",
MetricsAddress: defaultMetricsAddress, MetricsAddress: defaultMetricsAddress,
Interval: time.Minute, Interval: time.Minute,
Once: false, Once: false,
@ -96,7 +96,7 @@ func TestParseFlags(t *testing.T) {
Provider: "", Provider: "",
GoogleProject: "", GoogleProject: "",
Policy: "sync", Policy: "sync",
Compatibility: false, Compatibility: "",
MetricsAddress: defaultMetricsAddress, MetricsAddress: defaultMetricsAddress,
Interval: time.Minute, Interval: time.Minute,
Once: false, Once: false,
@ -128,7 +128,7 @@ func TestParseFlags(t *testing.T) {
Provider: "", Provider: "",
GoogleProject: "", GoogleProject: "",
Policy: "sync", Policy: "sync",
Compatibility: false, Compatibility: "",
MetricsAddress: defaultMetricsAddress, MetricsAddress: defaultMetricsAddress,
Interval: time.Minute, Interval: time.Minute,
Once: false, Once: false,
@ -154,7 +154,7 @@ func TestParseFlags(t *testing.T) {
"--provider", "provider", "--provider", "provider",
"--google-project", "project", "--google-project", "project",
"--policy", "upsert-only", "--policy", "upsert-only",
"--compatibility", "--compatibility=mate",
"--metrics-address", "127.0.0.1:9099", "--metrics-address", "127.0.0.1:9099",
"--interval", "10m", "--interval", "10m",
"--once", "--once",
@ -175,7 +175,7 @@ func TestParseFlags(t *testing.T) {
Provider: "provider", Provider: "provider",
GoogleProject: "project", GoogleProject: "project",
Policy: "upsert-only", Policy: "upsert-only",
Compatibility: true, Compatibility: "mate",
MetricsAddress: "127.0.0.1:9099", MetricsAddress: "127.0.0.1:9099",
Interval: 10 * time.Minute, Interval: 10 * time.Minute,
Once: true, Once: true,

View File

@ -24,12 +24,20 @@ import (
const ( const (
mateAnnotationKey = "zalando.org/dnsname" mateAnnotationKey = "zalando.org/dnsname"
moleculeAnnotationKey = "domainName"
) )
// legacyEndpointsFromService tries to retrieve Endpoints from Services // legacyEndpointsFromService tries to retrieve Endpoints from Services
// annotated with legacy annotations. // annotated with legacy annotations.
func legacyEndpointsFromService(svc *v1.Service) []*endpoint.Endpoint { func legacyEndpointsFromService(svc *v1.Service, compatibility string) []*endpoint.Endpoint {
switch compatibility {
case "mate":
return legacyEndpointsFromMateService(svc) return legacyEndpointsFromMateService(svc)
case "molecule":
return legacyEndpointsFromMoleculeService(svc)
}
return []*endpoint.Endpoint{}
} }
// legacyEndpointsFromMateService tries to retrieve Endpoints from Services // legacyEndpointsFromMateService tries to retrieve Endpoints from Services
@ -55,3 +63,32 @@ func legacyEndpointsFromMateService(svc *v1.Service) []*endpoint.Endpoint {
return endpoints return endpoints
} }
// legacyEndpointsFromMoleculeService tries to retrieve Endpoints from Services
// annotated with Molecule Software's annotation semantics.
func legacyEndpointsFromMoleculeService(svc *v1.Service) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
// Check that the Service opted-in to being processed.
if svc.Labels["dns"] != "route53" {
return nil
}
// Get the desired hostname of the service from the annotation.
hostname, exists := svc.Annotations[moleculeAnnotationKey]
if !exists {
return nil
}
// Create a corresponding endpoint for each configured external entrypoint.
for _, lb := range svc.Status.LoadBalancer.Ingress {
if lb.IP != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.IP, ""))
}
if lb.Hostname != "" {
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, lb.Hostname, ""))
}
}
return endpoints
}

View File

@ -36,13 +36,13 @@ import (
type serviceSource struct { type serviceSource struct {
client kubernetes.Interface client kubernetes.Interface
namespace string namespace string
// set to true to process Services with legacy annotations // process Services with legacy annotations
compatibility bool compatibility string
fqdntemplate *template.Template fqdntemplate *template.Template
} }
// NewServiceSource creates a new serviceSource with the given client and namespace scope. // NewServiceSource creates a new serviceSource with the given client and namespace scope.
func NewServiceSource(client kubernetes.Interface, namespace, fqdntemplate string, compatibility bool) (Source, error) { func NewServiceSource(client kubernetes.Interface, namespace, fqdntemplate string, compatibility string) (Source, error) {
var tmpl *template.Template var tmpl *template.Template
var err error var err error
if fqdntemplate != "" { if fqdntemplate != "" {
@ -81,8 +81,8 @@ func (sc *serviceSource) Endpoints() ([]*endpoint.Endpoint, error) {
svcEndpoints := endpointsFromService(&svc) svcEndpoints := endpointsFromService(&svc)
// process legacy annotations if no endpoints were returned and compatibility mode is enabled. // process legacy annotations if no endpoints were returned and compatibility mode is enabled.
if len(svcEndpoints) == 0 && sc.compatibility { if len(svcEndpoints) == 0 && sc.compatibility != "" {
svcEndpoints = legacyEndpointsFromService(&svc) svcEndpoints = legacyEndpointsFromService(&svc, sc.compatibility)
} }
// apply template if none of the above is found // apply template if none of the above is found

View File

@ -55,7 +55,7 @@ func TestNewServiceSource(t *testing.T) {
}, },
} { } {
t.Run(ti.title, func(t *testing.T) { t.Run(ti.title, func(t *testing.T) {
_, err := NewServiceSource(fake.NewSimpleClientset(), "", ti.fqdntemplate, false) _, err := NewServiceSource(fake.NewSimpleClientset(), "", ti.fqdntemplate, "")
if ti.expectError && err == nil { if ti.expectError && err == nil {
t.Error("invalid template should return err") t.Error("invalid template should return err")
} }
@ -73,8 +73,9 @@ func testServiceEndpoints(t *testing.T) {
targetNamespace string targetNamespace string
svcNamespace string svcNamespace string
svcName string svcName string
compatibility bool compatibility string
fqdntemplate string fqdntemplate string
labels map[string]string
annotations map[string]string annotations map[string]string
lbs []string lbs []string
expected []*endpoint.Endpoint expected []*endpoint.Endpoint
@ -84,8 +85,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{}, map[string]string{},
[]string{"1.2.3.4"}, []string{"1.2.3.4"},
[]*endpoint.Endpoint{}, []*endpoint.Endpoint{},
@ -95,8 +97,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -110,8 +113,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -125,8 +129,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted
}, },
@ -141,8 +146,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
controllerAnnotationKey: controllerAnnotationValue, controllerAnnotationKey: controllerAnnotationValue,
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -157,8 +163,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false, "",
"{{.Name}}.ext-dns.test.com", "{{.Name}}.ext-dns.test.com",
map[string]string{},
map[string]string{ map[string]string{
controllerAnnotationKey: "some-other-tool", controllerAnnotationKey: "some-other-tool",
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
@ -171,8 +178,9 @@ func testServiceEndpoints(t *testing.T) {
"testing", "testing",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -186,8 +194,9 @@ func testServiceEndpoints(t *testing.T) {
"testing", "testing",
"other-testing", "other-testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -199,8 +208,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"other-testing", "other-testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -214,8 +224,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -227,8 +238,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
hostnameAnnotationKey: "foo.example.org.", hostnameAnnotationKey: "foo.example.org.",
}, },
@ -243,8 +255,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false,
"", "",
"",
map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "foo.example.org.", "zalando.org/dnsname": "foo.example.org.",
}, },
@ -256,8 +269,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
true, "mate",
"", "",
map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "foo.example.org.", "zalando.org/dnsname": "foo.example.org.",
}, },
@ -266,14 +280,33 @@ func testServiceEndpoints(t *testing.T) {
{DNSName: "foo.example.org", Target: "1.2.3.4"}, {DNSName: "foo.example.org", Target: "1.2.3.4"},
}, },
}, },
{
"services annotated with legacy molecule annotations return an endpoint in compatibility mode",
"",
"testing",
"foo",
"molecule",
"",
map[string]string{
"dns": "route53",
},
map[string]string{
"domainName": "foo.example.org.",
},
[]string{"1.2.3.4"},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Target: "1.2.3.4"},
},
},
{ {
"not annotated services with set fqdntemplate return an endpoint with target IP", "not annotated services with set fqdntemplate return an endpoint with target IP",
"", "",
"testing", "testing",
"foo", "foo",
false, "",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
map[string]string{}, map[string]string{},
map[string]string{},
[]string{"1.2.3.4", "elb.com"}, []string{"1.2.3.4", "elb.com"},
[]*endpoint.Endpoint{ []*endpoint.Endpoint{
{DNSName: "foo.bar.example.com", Target: "1.2.3.4"}, {DNSName: "foo.bar.example.com", Target: "1.2.3.4"},
@ -285,9 +318,10 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
false, "",
"{{.Calibre}}.bar.example.com", "{{.Calibre}}.bar.example.com",
map[string]string{}, map[string]string{},
map[string]string{},
[]string{"1.2.3.4"}, []string{"1.2.3.4"},
[]*endpoint.Endpoint{}, []*endpoint.Endpoint{},
}, },
@ -296,8 +330,9 @@ func testServiceEndpoints(t *testing.T) {
"", "",
"testing", "testing",
"foo", "foo",
true, "mate",
"{{.Name}}.bar.example.com", "{{.Name}}.bar.example.com",
map[string]string{},
map[string]string{ map[string]string{
"zalando.org/dnsname": "mate.example.org.", "zalando.org/dnsname": "mate.example.org.",
}, },
@ -325,6 +360,7 @@ func testServiceEndpoints(t *testing.T) {
ObjectMeta: v1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: tc.svcNamespace, Namespace: tc.svcNamespace,
Name: tc.svcName, Name: tc.svcName,
Labels: tc.labels,
Annotations: tc.annotations, Annotations: tc.annotations,
}, },
Status: v1.ServiceStatus{ Status: v1.ServiceStatus{
@ -379,7 +415,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
b.Fatal(err) b.Fatal(err)
} }
client, _ := NewServiceSource(kubernetes, v1.NamespaceAll, "", false) client, _ := NewServiceSource(kubernetes, v1.NamespaceAll, "", "")
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := client.Endpoints() _, err := client.Endpoints()