From aa138f2f3ca0f6a774be3d54b46803775c5a0524 Mon Sep 17 00:00:00 2001 From: Peter Stokes Date: Tue, 12 Nov 2024 14:04:38 +0000 Subject: [PATCH 1/4] Add validation of Endpoint.Labels to unit-test expectations. --- endpoint/endpoint.go | 6 ++ source/gateway_grpcroute_test.go | 9 ++- source/gateway_httproute_test.go | 109 ++++++++++++++++++++----------- source/gateway_tcproute_test.go | 6 +- source/gateway_tlsroute_test.go | 9 ++- source/gateway_udproute_test.go | 6 +- source/shared_test.go | 37 +++++------ 7 files changed, 116 insertions(+), 66 deletions(-) diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index 1bd2d54fb8..bbeff68ab1 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -250,6 +250,12 @@ func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string) } } +// WithLabel attaches a label key/value pair to the Endpoint and returns the Endpoint. +func (e *Endpoint) WithLabel(key, value string) *Endpoint { + e.Labels[key] = value + return e +} + // WithSetIdentifier applies the given set identifier to the endpoint. func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint { e.SetIdentifier = setIdentifier diff --git a/source/gateway_grpcroute_test.go b/source/gateway_grpcroute_test.go index f92070eb42..3834a1698d 100644 --- a/source/gateway_grpcroute_test.go +++ b/source/gateway_grpcroute_test.go @@ -91,8 +91,11 @@ func TestGatewayGRPCRouteSourceEndpoints(t *testing.T) { endpoints, err := src.Endpoints(ctx) require.NoError(t, err, "failed to get Endpoints") validateEndpoints(t, endpoints, []*endpoint.Endpoint{ - newTestEndpoint("api-annotation.foobar.internal", "A", ips...), - newTestEndpoint("api-hostnames.foobar.internal", "A", ips...), - newTestEndpoint("api-template.foobar.internal", "A", ips...), + newTestEndpoint("api-annotation.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "grpcroute/default/api"), + newTestEndpoint("api-hostnames.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "grpcroute/default/api"), + newTestEndpoint("api-template.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "grpcroute/default/api"), }) } diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go index 62143c1d19..712a920d83 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -102,6 +102,7 @@ func newTestEndpointWithTTL(dnsName, recordType string, ttl int64, targets ...st DNSName: dnsName, Targets: append([]string(nil), targets...), // clone targets RecordType: recordType, + Labels: endpoint.NewLabels(), RecordTTL: endpoint.TTL(ttl), } } @@ -176,7 +177,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.example.internal", "A", "1.2.3.4"), + newTestEndpoint("test.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/route-namespace/test"), }, }, { @@ -212,7 +214,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"), + newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/route-namespace/test"), }, }, { @@ -256,7 +259,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.example.internal", "A", "1.2.3.4"), + newTestEndpoint("test.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -297,7 +301,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"), + newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/labels-match"), }, }, { @@ -338,7 +343,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"), + newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/annotations-match"), }, }, { @@ -398,7 +404,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"), + newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -433,8 +440,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), - newTestEndpoint("bar.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), + newTestEndpoint("bar.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -469,7 +478,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -513,8 +523,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), - newTestEndpoint("bar.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), + newTestEndpoint("bar.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -541,7 +553,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/no-hostname"), }, }, { @@ -568,7 +581,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/no-hostname"), }, }, { @@ -595,7 +609,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("*.example.internal", "A", "1.2.3.4"), + newTestEndpoint("*.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/no-hostname"), }, }, { @@ -620,7 +635,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/no-hostname"), }, }, { @@ -671,7 +687,7 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { routes: []*v1beta1.HTTPRoute{ { ObjectMeta: metav1.ObjectMeta{ - Name: "without-hostame", + Name: "without-hostname", Namespace: "default", Annotations: map[string]string{ hostnameAnnotationKey: "annotation.without-hostname.internal", @@ -684,7 +700,7 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, { ObjectMeta: metav1.ObjectMeta{ - Name: "with-hostame", + Name: "with-hostname", Namespace: "default", Annotations: map[string]string{ hostnameAnnotationKey: "annotation.with-hostname.internal", @@ -697,9 +713,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"), - newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"), - newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"), + newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/without-hostname"), + newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/with-hostname"), + newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/with-hostname"), }, }, { @@ -717,7 +736,7 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }}, routes: []*v1beta1.HTTPRoute{{ ObjectMeta: metav1.ObjectMeta{ - Name: "with-hostame", + Name: "test", Namespace: "default", Annotations: map[string]string{ hostnameAnnotationKey: "annotation.with-hostname.internal", @@ -729,7 +748,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"), + newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -762,10 +782,14 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("fqdn-without-hostnames.zero.internal", "A", "1.2.3.4"), - newTestEndpoint("fqdn-without-hostnames.one.internal", "A", "1.2.3.4"), - newTestEndpoint("fqdn-without-hostnames.two.internal", "A", "1.2.3.4"), - newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"), + newTestEndpoint("fqdn-without-hostnames.zero.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-without-hostnames"), + newTestEndpoint("fqdn-without-hostnames.one.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-without-hostnames"), + newTestEndpoint("fqdn-without-hostnames.two.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-without-hostnames"), + newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-with-hostnames"), }, }, { @@ -790,8 +814,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { Status: httpRouteStatus(gwParentRef("default", "test")), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"), - newTestEndpoint("combine-fqdn-with-hostnames.internal", "A", "1.2.3.4"), + newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-with-hostnames"), + newTestEndpoint("combine-fqdn-with-hostnames.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/fqdn-with-hostnames"), }, }, { @@ -830,8 +856,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"), - newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"), + newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/invalid-ttl"), + newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/valid-ttl"), }, }, { @@ -861,6 +889,7 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }}, endpoints: []*endpoint.Endpoint{ newTestEndpoint("provider-annotations.com", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/provider-annotations"). WithProviderSpecific("alias", "true"). WithSetIdentifier("test-set-identifier"), }, @@ -902,8 +931,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.one.internal", "A", "1.2.3.4"), - newTestEndpoint("test.two.internal", "A", "2.3.4.5"), + newTestEndpoint("test.one.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), + newTestEndpoint("test.two.internal", "A", "2.3.4.5"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/test"), }, }, { @@ -941,7 +972,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("same-namespace.example.internal", "A", "1.2.3.4"), + newTestEndpoint("same-namespace.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/same-namespace/test"), }, }, { @@ -1000,7 +1032,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("foo.example.internal", "A", "1.2.3.4"), + newTestEndpoint("foo.example.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/foo/test"), }, }, { @@ -1068,11 +1101,12 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.example.internal", "A", "4.3.2.1"), + newTestEndpoint("test.example.internal", "A", "4.3.2.1"). + WithLabel(endpoint.ResourceLabelKey, "httproute/route-namespace/test"), }, }, { - title: "MutlipleGatewaysOneAnnotationOverride", + title: "MultipleGatewaysOneAnnotationOverride", config: Config{ GatewayNamespace: "gateway-namespace", }, @@ -1116,7 +1150,8 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { ), }}, endpoints: []*endpoint.Endpoint{ - newTestEndpoint("test.example.internal", "A", "4.3.2.1", "2.3.4.5"), + newTestEndpoint("test.example.internal", "A", "4.3.2.1", "2.3.4.5"). + WithLabel(endpoint.ResourceLabelKey, "httproute/route-namespace/test"), }, }, } diff --git a/source/gateway_tcproute_test.go b/source/gateway_tcproute_test.go index a6039ebf14..1a7f2de088 100644 --- a/source/gateway_tcproute_test.go +++ b/source/gateway_tcproute_test.go @@ -90,7 +90,9 @@ func TestGatewayTCPRouteSourceEndpoints(t *testing.T) { endpoints, err := src.Endpoints(ctx) require.NoError(t, err, "failed to get Endpoints") validateEndpoints(t, endpoints, []*endpoint.Endpoint{ - newTestEndpoint("api-annotation.foobar.internal", "A", ips...), - newTestEndpoint("api-template.foobar.internal", "A", ips...), + newTestEndpoint("api-annotation.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "tcproute/default/api"), + newTestEndpoint("api-template.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "tcproute/default/api"), }) } diff --git a/source/gateway_tlsroute_test.go b/source/gateway_tlsroute_test.go index e6d4660e51..59f55f911a 100644 --- a/source/gateway_tlsroute_test.go +++ b/source/gateway_tlsroute_test.go @@ -92,8 +92,11 @@ func TestGatewayTLSRouteSourceEndpoints(t *testing.T) { endpoints, err := src.Endpoints(ctx) require.NoError(t, err, "failed to get Endpoints") validateEndpoints(t, endpoints, []*endpoint.Endpoint{ - newTestEndpoint("api-annotation.foobar.internal", "A", ips...), - newTestEndpoint("api-hostnames.foobar.internal", "A", ips...), - newTestEndpoint("api-template.foobar.internal", "A", ips...), + newTestEndpoint("api-annotation.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "tlsroute/default/api"), + newTestEndpoint("api-hostnames.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "tlsroute/default/api"), + newTestEndpoint("api-template.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "tlsroute/default/api"), }) } diff --git a/source/gateway_udproute_test.go b/source/gateway_udproute_test.go index 570e3d10c3..cf3fce9425 100644 --- a/source/gateway_udproute_test.go +++ b/source/gateway_udproute_test.go @@ -90,7 +90,9 @@ func TestGatewayUDPRouteSourceEndpoints(t *testing.T) { endpoints, err := src.Endpoints(ctx) require.NoError(t, err, "failed to get Endpoints") validateEndpoints(t, endpoints, []*endpoint.Endpoint{ - newTestEndpoint("api-annotation.foobar.internal", "A", ips...), - newTestEndpoint("api-template.foobar.internal", "A", ips...), + newTestEndpoint("api-annotation.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "udproute/default/api"), + newTestEndpoint("api-template.foobar.internal", "A", ips...). + WithLabel(endpoint.ResourceLabelKey, "udproute/default/api"), }) } diff --git a/source/shared_test.go b/source/shared_test.go index 11828dbe2e..652ab72dcd 100644 --- a/source/shared_test.go +++ b/source/shared_test.go @@ -17,36 +17,35 @@ limitations under the License. package source import ( + "cmp" "reflect" - "sort" + "maps" + "slices" "testing" "sigs.k8s.io/external-dns/endpoint" ) func sortEndpoints(endpoints []*endpoint.Endpoint) { - for _, ep := range endpoints { - sort.Strings([]string(ep.Targets)) - } - sort.Slice(endpoints, func(i, k int) bool { - // Sort by DNSName, RecordType, and Targets - ei, ek := endpoints[i], endpoints[k] - if ei.DNSName != ek.DNSName { - return ei.DNSName < ek.DNSName + slices.SortStableFunc(endpoints, func(a, b *endpoint.Endpoint) int { + if c := cmp.Compare(a.DNSName, b.DNSName); c != 0 { + return c } - if ei.RecordType != ek.RecordType { - return ei.RecordType < ek.RecordType + if c := cmp.Compare(a.RecordType, b.RecordType); c != 0 { + return c } - // Targets are sorted ahead of time. - for j, ti := range ei.Targets { - if j >= len(ek.Targets) { - return true - } - if tk := ek.Targets[j]; ti != tk { - return ti < tk + if c := slices.Compare(slices.Sorted(slices.Values(a.Targets)), slices.Sorted(slices.Values(b.Targets))); c != 0 { + return c + } + if c := slices.Compare(slices.Sorted(maps.Keys(a.Labels)), slices.Sorted(maps.Keys(b.Labels))); c != 0 { + return c + } + for key, value := range a.Labels { + if c := cmp.Compare(value, b.Labels[key]); c != 0 { + return c } } - return false + return 0 }) } From 958666562d6e6a6574cd8d8ff261517c7debb14c Mon Sep 17 00:00:00 2001 From: Peter Stokes Date: Mon, 11 Nov 2024 09:53:42 +0000 Subject: [PATCH 2/4] Refactor Gateway source processing. --- source/gateway.go | 176 +++++++++++++------------------ source/gateway_httproute_test.go | 1 - 2 files changed, 73 insertions(+), 104 deletions(-) diff --git a/source/gateway.go b/source/gateway.go index 5d8474999d..668a7d8ad4 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -223,17 +223,59 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo continue } - // Get Route hostnames and their targets. - hostTargets, err := resolver.resolve(rt) - if err != nil { - return nil, err - } - if len(hostTargets) == 0 { + // Get Gateway Listeners associated with Route. + gwListeners := resolver.resolve(rt) + if len(gwListeners) == 0 { log.Debugf("No endpoints could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name) continue } - // Create endpoints from hostnames and targets. + // Create endpoints for Route and associated Gateway Listeners + rtHosts := rt.Hostnames() + if len(rtHosts) == 0 { + // This means that the route doesn't specify a hostname and should use any provided by + // attached Gateway Listeners. + rtHosts = []v1.Hostname{""} + } + + hostTargets := make(map[string]endpoint.Targets) + for gateway, listeners := range gwListeners { + var hosts []string + for _, listener := range listeners { + // Find all overlapping hostnames between the Route and Listener. + gwHost := getVal(listener.Hostname, "") + for _, rtHost := range rtHosts { + host, ok := gwMatchingHost(string(gwHost), string(rtHost)) + if !ok || host == "" { + continue + } + hosts = append(hosts, host) + } + } + // TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template" + // but other sources don't check if fqdn-template is set. Which should it be? + if !src.ignoreHostnameAnnotation { + hosts = append(hosts, getHostnamesFromAnnotations(annots)...) + } + // TODO: The combine-fqdn-annotation flag is similarly vague. + if src.fqdnTemplate != nil && (len(hosts) == 0 || src.combineFQDNAnnotation) { + templated, err := execTemplate(src.fqdnTemplate, rt.Object()) + if err != nil { + return nil, err + } + hosts = append(hosts, templated...) + } + for _, host := range hosts { + override := getTargetsFromTargetAnnotation(gateway.Annotations) + hostTargets[host] = append(hostTargets[host], override...) + if len(override) == 0 { + for _, addr := range gateway.Status.Addresses { + hostTargets[host] = append(hostTargets[host], addr.Value) + } + } + } + } + resource := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name) providerSpecific, setIdentifier := getProviderSpecificAnnotations(annots) ttl := getTTLFromAnnotations(annots, resource) @@ -287,25 +329,21 @@ func newGatewayRouteResolver(src *gatewayRouteSource, gateways []*v1beta1.Gatewa } } -func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Targets, error) { - rtHosts, err := c.hosts(rt) - if err != nil { - return nil, err - } - hostTargets := make(map[string]endpoint.Targets) +func (c *gatewayRouteResolver) resolve(rt gatewayRoute) map[*v1beta1.Gateway][]*v1.Listener { + gwListeners := map[*v1beta1.Gateway][]*v1.Listener{} meta := rt.Metadata() for _, rps := range rt.RouteStatus().Parents { // Confirm the Parent is the standard Gateway kind. ref := rps.ParentRef - group := strVal((*string)(ref.Group), gatewayGroup) - kind := strVal((*string)(ref.Kind), gatewayKind) + group := getVal(ref.Group, gatewayGroup) + kind := getVal(ref.Kind, gatewayKind) if group != gatewayGroup || kind != gatewayKind { log.Debugf("Unsupported parent %s/%s for %s %s/%s", group, kind, c.src.rtKind, meta.Namespace, meta.Name) continue } // Lookup the Gateway and its Listeners. - namespace := strVal((*string)(ref.Namespace), meta.Namespace) + namespace := getVal((*string)(ref.Namespace), meta.Namespace) gw, ok := c.gws[namespacedName(namespace, string(ref.Name))] if !ok { log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name) @@ -318,7 +356,7 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar } // Match the Route to all possible Listeners. match := false - section := sectionVal(ref.SectionName, "") + section := getVal(ref.SectionName, "") listeners := gw.listeners[section] for i := range listeners { lis := &listeners[i] @@ -327,7 +365,6 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar continue } // Confirm that the Listener and Route ports match, if specified. - // EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/ if ref.Port != nil && *ref.Port != lis.Port { continue } @@ -335,70 +372,28 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar if !c.routeIsAllowed(gw.gateway, lis, rt) { continue } - // Find all overlapping hostnames between the Route and Listener. - // For {TCP,UDP}Routes, all annotation-generated hostnames should match since the Listener doesn't specify a hostname. - // For {HTTP,TLS}Routes, hostnames (including any annotation-generated) will be required to match any Listeners specified hostname. - gwHost := "" - if lis.Hostname != nil { - gwHost = string(*lis.Hostname) - } - for _, rtHost := range rtHosts { - if gwHost == "" && rtHost == "" { - // For {HTTP,TLS}Routes, this means the Route and the Listener both allow _any_ hostnames. - // For {TCP,UDP}Routes, this should always happen since neither specifies hostnames. - continue - } - host, ok := gwMatchingHost(gwHost, rtHost) - if !ok { - continue - } - override := getTargetsFromTargetAnnotation(gw.gateway.Annotations) - hostTargets[host] = append(hostTargets[host], override...) - if len(override) == 0 { - for _, addr := range gw.gateway.Status.Addresses { - hostTargets[host] = append(hostTargets[host], addr.Value) + // Confirm that the Listener and Route hostnames overlap, if specified. + overlap := true + if len(rt.Hostnames()) != 0 { + gwHost := getVal(lis.Hostname, "") + for _, rtHost := range rt.Hostnames() { + _, overlap = gwMatchingHost(string(gwHost), string(rtHost)) + if overlap { + break } } - match = true } + if !overlap { + continue + } + gwListeners[gw.gateway] = append(gwListeners[gw.gateway], lis) + match = true } if !match { - log.Debugf("Gateway %s/%s section %q does not match %s %s/%s hostnames %q", namespace, ref.Name, section, c.src.rtKind, meta.Namespace, meta.Name, rtHosts) + log.Debugf("Gateway %s/%s section %q does not match %s %s/%s hostnames %q", namespace, ref.Name, section, c.src.rtKind, meta.Namespace, meta.Name, rt.Hostnames()) } } - // If a Gateway has multiple matching Listeners for the same host, then we'll - // add its IPs to the target list multiple times and should dedupe them. - for host, targets := range hostTargets { - hostTargets[host] = uniqueTargets(targets) - } - return hostTargets, nil -} - -func (c *gatewayRouteResolver) hosts(rt gatewayRoute) ([]string, error) { - var hostnames []string - for _, name := range rt.Hostnames() { - hostnames = append(hostnames, string(name)) - } - // TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template" - // but other sources don't check if fqdn-template is set. Which should it be? - if !c.src.ignoreHostnameAnnotation { - hostnames = append(hostnames, getHostnamesFromAnnotations(rt.Metadata().Annotations)...) - } - // TODO: The combine-fqdn-annotation flag is similarly vague. - if c.src.fqdnTemplate != nil && (len(hostnames) == 0 || c.src.combineFQDNAnnotation) { - hosts, err := execTemplate(c.src.fqdnTemplate, rt.Object()) - if err != nil { - return nil, err - } - hostnames = append(hostnames, hosts...) - } - // This means that the route doesn't specify a hostname and should use any provided by - // attached Gateway Listeners. This is only useful for {HTTP,TLS}Routes, but it doesn't - // break {TCP,UDP}Routes. - if len(rt.Hostnames()) == 0 { - hostnames = append(hostnames, "") - } - return hostnames, nil + return gwListeners } func (c *gatewayRouteResolver) routeIsAllowed(gw *v1beta1.Gateway, lis *v1.Listener, rt gatewayRoute) bool { @@ -445,7 +440,7 @@ func (c *gatewayRouteResolver) routeIsAllowed(gw *v1beta1.Gateway, lis *v1.Liste } gvk := rt.Object().GetObjectKind().GroupVersionKind() for _, gk := range allow.Kinds { - group := strVal((*string)(gk.Group), gatewayGroup) + group := string(getVal(gk.Group, gatewayGroup)) if gvk.Group == group && gvk.Kind == string(gk.Kind) { return true } @@ -462,24 +457,6 @@ func gwRouteIsAccepted(conds []metav1.Condition) bool { return false } -func uniqueTargets(targets endpoint.Targets) endpoint.Targets { - if len(targets) < 2 { - return targets - } - sort.Strings([]string(targets)) - prev := targets[0] - n := 1 - for _, v := range targets[1:] { - if v == prev { - continue - } - prev = v - targets[n] = v - n++ - } - return targets[:n] -} - // gwProtocolMatches returns whether a and b are the same protocol, // where HTTP and HTTPS are considered the same. // and TLS and TCP are considered the same. @@ -583,15 +560,8 @@ func isAlphaNum(b byte) bool { } } -func strVal(ptr *string, def string) string { - if ptr == nil || *ptr == "" { - return def - } - return *ptr -} - -func sectionVal(ptr *v1.SectionName, def v1.SectionName) v1.SectionName { - if ptr == nil || *ptr == "" { +func getVal[T any](ptr *T, def T) T { + if ptr == nil { return def } return *ptr diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go index 712a920d83..1f8e7175ac 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -483,7 +483,6 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, { - // EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/ title: "PortNumberMatch", config: Config{}, namespaces: namespaces("default"), From cc10e6dc2360ab132c7dff2c7f51d89e10266382 Mon Sep 17 00:00:00 2001 From: Peter Stokes Date: Tue, 12 Nov 2024 13:17:41 +0000 Subject: [PATCH 3/4] Honour annotations specified on Gateway resources. --- source/gateway.go | 106 ++++++++++++------ source/gateway_httproute_test.go | 187 ++++++++++++++++++++++++++++++- source/gateway_test.go | 45 -------- 3 files changed, 257 insertions(+), 81 deletions(-) diff --git a/source/gateway.go b/source/gateway.go index 668a7d8ad4..c24f76903c 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -18,8 +18,8 @@ package source import ( "context" - "fmt" "net/netip" + "slices" "sort" "strings" "text/template" @@ -206,30 +206,25 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo if err != nil { return nil, err } - kind := strings.ToLower(src.rtKind) resolver := newGatewayRouteResolver(src, gateways, namespaces) for _, rt := range routes { // Filter by annotations. meta := rt.Metadata() - annots := meta.Annotations - if !src.rtAnnotations.Matches(labels.Set(annots)) { + if !src.rtAnnotations.Matches(labels.Set(meta.Annotations)) { continue } - // Check controller annotation to see if we are responsible. - if v, ok := annots[controllerAnnotationKey]; ok && v != controllerAnnotationValue { + if v, ok := meta.Annotations[controllerAnnotationKey]; ok && v != controllerAnnotationValue { log.Debugf("Skipping %s %s/%s because controller value does not match, found: %s, required: %s", src.rtKind, meta.Namespace, meta.Name, v, controllerAnnotationValue) continue } - // Get Gateway Listeners associated with Route. gwListeners := resolver.resolve(rt) if len(gwListeners) == 0 { log.Debugf("No endpoints could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name) continue } - // Create endpoints for Route and associated Gateway Listeners rtHosts := rt.Hostnames() if len(rtHosts) == 0 { @@ -237,10 +232,16 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo // attached Gateway Listeners. rtHosts = []v1.Hostname{""} } - - hostTargets := make(map[string]endpoint.Targets) + resource := strings.Join([]string{strings.ToLower(src.rtKind), meta.Namespace, meta.Name}, "/") + hostGateways := map[string][]*v1beta1.Gateway{} + ttl := getTTLFromAnnotations(meta.Annotations, resource) + dualstack := false + if v, ok := meta.Annotations[gatewayAPIDualstackAnnotationKey]; ok && v == gatewayAPIDualstackAnnotationValue { + dualstack = true + } + providerSpecific, setIdentifier := getProviderSpecificAnnotations(meta.Annotations) for gateway, listeners := range gwListeners { - var hosts []string + hosts := map[string]struct{}{} for _, listener := range listeners { // Find all overlapping hostnames between the Route and Listener. gwHost := getVal(listener.Hostname, "") @@ -249,13 +250,18 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo if !ok || host == "" { continue } - hosts = append(hosts, host) + hosts[host] = struct{}{} } } // TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template" // but other sources don't check if fqdn-template is set. Which should it be? if !src.ignoreHostnameAnnotation { - hosts = append(hosts, getHostnamesFromAnnotations(annots)...) + for _, host := range getHostnamesFromAnnotations(gateway.Annotations) { + hosts[host] = struct{}{} + } + for _, host := range getHostnamesFromAnnotations(meta.Annotations) { + hosts[host] = struct{}{} + } } // TODO: The combine-fqdn-annotation flag is similarly vague. if src.fqdnTemplate != nil && (len(hosts) == 0 || src.combineFQDNAnnotation) { @@ -263,26 +269,66 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo if err != nil { return nil, err } - hosts = append(hosts, templated...) + for _, host := range templated { + hosts[host] = struct{}{} + } + } + if len(hosts) == 0 { + continue + } + for host := range hosts { + hostGateways[host] = append(hostGateways[host], gateway) + } + // Merge Gateway annotations + gwTTL := getTTLFromAnnotations(gateway.Annotations, strings.Join([]string{strings.ToLower(gateway.Kind), gateway.Namespace, gateway.Name}, "/")) + if gwTTL.IsConfigured() { + if !ttl.IsConfigured() || ttl > gwTTL { + ttl = gwTTL + } + } + if v, ok := gateway.Annotations[gatewayAPIDualstackAnnotationKey]; ok && v == gatewayAPIDualstackAnnotationValue { + dualstack = true + } + gwProviderSpecific, gwSetIdentifier := getProviderSpecificAnnotations(gateway.Annotations) + for _, gwProperty := range gwProviderSpecific { + present := false + for _, property := range providerSpecific { + if property.Name == gwProperty.Name { + present = true + break + } + } + if !present { + providerSpecific = append(providerSpecific, gwProperty) + } } - for _, host := range hosts { + if setIdentifier == "" { + setIdentifier = gwSetIdentifier + } + } + for host, gateways := range hostGateways { + var targets endpoint.Targets + for _, gateway := range gateways { override := getTargetsFromTargetAnnotation(gateway.Annotations) - hostTargets[host] = append(hostTargets[host], override...) + targets = append(targets, override...) if len(override) == 0 { for _, addr := range gateway.Status.Addresses { - hostTargets[host] = append(hostTargets[host], addr.Value) + targets = append(targets, addr.Value) } } } + origin := resource + if len(gateways) == 1 && !src.ignoreHostnameAnnotation && slices.Contains(getHostnamesFromAnnotations(gateways[0].Annotations), host) { + // Annotated hostnames from a single Gateway are attributed to the Gateway rather than the Route + origin = strings.Join([]string{strings.ToLower(gateways[0].Kind), gateways[0].Namespace, gateways[0].Name}, "/") + } + for _, ep := range endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, origin) { + if dualstack { + ep.Labels[endpoint.DualstackLabelKey] = "true" + } + endpoints = append(endpoints, ep) + } } - - resource := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name) - providerSpecific, setIdentifier := getProviderSpecificAnnotations(annots) - ttl := getTTLFromAnnotations(annots, resource) - for host, targets := range hostTargets { - endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...) - } - setDualstackLabel(rt, endpoints) log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints) } return endpoints, nil @@ -585,13 +631,3 @@ func selectorsEqual(a, b labels.Selector) bool { } return true } - -func setDualstackLabel(rt gatewayRoute, endpoints []*endpoint.Endpoint) { - val, ok := rt.Metadata().Annotations[gatewayAPIDualstackAnnotationKey] - if ok && val == gatewayAPIDualstackAnnotationValue { - log.Debugf("Adding dualstack label to GatewayRoute %s/%s.", rt.Metadata().Namespace, rt.Metadata().Name) - for _, ep := range endpoints { - ep.Labels[endpoint.DualstackLabelKey] = "true" - } - } -} diff --git a/source/gateway_httproute_test.go b/source/gateway_httproute_test.go index 1f8e7175ac..ff092040d6 100644 --- a/source/gateway_httproute_test.go +++ b/source/gateway_httproute_test.go @@ -677,7 +677,16 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { config: Config{}, namespaces: namespaces("default"), gateways: []*v1beta1.Gateway{{ - ObjectMeta: objectMeta("default", "test"), + TypeMeta: metav1.TypeMeta{ + Kind: "Gateway", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + hostnameAnnotationKey: "annotation.gateway.internal", + }, + }, Spec: v1.GatewaySpec{ Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, }, @@ -712,6 +721,10 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { }, }, endpoints: []*endpoint.Endpoint{ + newTestEndpoint("annotation.gateway.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "gateway/default/test"), + newTestEndpoint("annotation.gateway.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "gateway/default/test"), newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"). WithLabel(endpoint.ResourceLabelKey, "httproute/default/without-hostname"), newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"). @@ -861,6 +874,64 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { WithLabel(endpoint.ResourceLabelKey, "httproute/default/valid-ttl"), }, }, + { + title: "TTLGateway", + config: Config{}, + namespaces: namespaces("default"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ttlAnnotationKey: "15s"}, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, + }, + Status: gatewayStatus("1.2.3.4"), + }}, + routes: []*v1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "no-ttl", + Namespace: "default", + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("no-ttl.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "longer-ttl", + Namespace: "default", + Annotations: map[string]string{ttlAnnotationKey: "20s"}, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("longer-ttl.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "shorter-ttl", + Namespace: "default", + Annotations: map[string]string{ttlAnnotationKey: "5s"}, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("shorter-ttl.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + }, + endpoints: []*endpoint.Endpoint{ + newTestEndpointWithTTL("no-ttl.internal", "A", 15, "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/no-ttl"), + newTestEndpointWithTTL("longer-ttl.internal", "A", 15, "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/longer-ttl"), + newTestEndpointWithTTL("shorter-ttl.internal", "A", 5, "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/shorter-ttl"), + }, + }, { title: "ProviderAnnotations", config: Config{}, @@ -893,6 +964,61 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { WithSetIdentifier("test-set-identifier"), }, }, + { + title: "ProviderAnnotationsGateway", + config: Config{}, + namespaces: namespaces("default"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + Annotations: map[string]string{ + SetIdentifierKey: "gateway", + "external-dns.alpha.kubernetes.io/webhook-property": "gateway", + }, + }, + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, + }, + Status: gatewayStatus("1.2.3.4"), + }}, + routes: []*v1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "with-provider-annotations", + Namespace: "default", + Annotations: map[string]string{ + SetIdentifierKey: "route", + "external-dns.alpha.kubernetes.io/webhook-property": "route", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("with-provider-annotations.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "without-provider-annotations", + Namespace: "default", + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("without-provider-annotations.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + }, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("with-provider-annotations.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/with-provider-annotations"). + WithProviderSpecific("webhook/property", "route"). + WithSetIdentifier("route"), + newTestEndpoint("without-provider-annotations.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/without-provider-annotations"). + WithProviderSpecific("webhook/property", "gateway"). + WithSetIdentifier("gateway"), + }, + }, { title: "DifferentHostnameDifferentGateway", config: Config{}, @@ -1153,6 +1279,65 @@ func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) { WithLabel(endpoint.ResourceLabelKey, "httproute/route-namespace/test"), }, }, + { + title: "DualstackAnnotation", + config: Config{}, + namespaces: namespaces("default"), + gateways: []*v1beta1.Gateway{{ + ObjectMeta: objectMeta("default", "test"), + Spec: v1.GatewaySpec{ + Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}}, + }, + Status: gatewayStatus("1.2.3.4"), + }}, + routes: []*v1beta1.HTTPRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-dualstack-annotation", + Namespace: "default", + Annotations: map[string]string{ + gatewayAPIDualstackAnnotationKey: "invalid", + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("invalid-dualstack-annotation.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "with-dualstack-annotation", + Namespace: "default", + Annotations: map[string]string{ + gatewayAPIDualstackAnnotationKey: gatewayAPIDualstackAnnotationValue, + }, + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("with-dualstack-annotation.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "without-dualstack-annotation", + Namespace: "default", + }, + Spec: v1.HTTPRouteSpec{ + Hostnames: hostnames("without-dualstack-annotation.internal"), + }, + Status: httpRouteStatus(gwParentRef("default", "test")), + }, + }, + endpoints: []*endpoint.Endpoint{ + newTestEndpoint("invalid-dualstack-annotation.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/invalid-dualstack-annotation"), + newTestEndpoint("with-dualstack-annotation.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/with-dualstack-annotation"). + WithLabel(endpoint.DualstackLabelKey, "true"), + newTestEndpoint("without-dualstack-annotation.internal", "A", "1.2.3.4"). + WithLabel(endpoint.ResourceLabelKey, "httproute/default/without-dualstack-annotation"), + }, + }, } for _, tt := range tests { tt := tt diff --git a/source/gateway_test.go b/source/gateway_test.go index 19d603a24b..940622e958 100644 --- a/source/gateway_test.go +++ b/source/gateway_test.go @@ -20,7 +20,6 @@ import ( "strings" "testing" - "sigs.k8s.io/external-dns/endpoint" v1 "sigs.k8s.io/gateway-api/apis/v1" ) @@ -246,47 +245,3 @@ func TestIsDNS1123Domain(t *testing.T) { }) } } - -func TestDualStackLabel(t *testing.T) { - tests := []struct { - desc string - in map[string](string) - setsLabel bool - }{ - { - desc: "empty-annotation", - setsLabel: false, - }, - { - desc: "correct-annotation-key-and-value", - in: map[string]string{gatewayAPIDualstackAnnotationKey: gatewayAPIDualstackAnnotationValue}, - setsLabel: true, - }, - { - desc: "correct-annotation-key-incorrect-value", - in: map[string]string{gatewayAPIDualstackAnnotationKey: "foo"}, - setsLabel: false, - }, - { - desc: "incorrect-annotation-key-correct-value", - in: map[string]string{"FOO": gatewayAPIDualstackAnnotationValue}, - setsLabel: false, - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - endpoints := make([]*endpoint.Endpoint, 0) - endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3")) - - rt := &gatewayHTTPRoute{} - rt.Metadata().Annotations = tt.in - - setDualstackLabel(rt, endpoints) - got := endpoints[0].Labels[endpoint.DualstackLabelKey] == "true" - - if got != tt.setsLabel { - t.Errorf("setDualstackLabel(%q); got: %v; want: %v", tt.in, got, tt.setsLabel) - } - }) - } -} From 304439d0921adfa6ba9b18492ff1893abfdc5c14 Mon Sep 17 00:00:00 2001 From: Peter Stokes Date: Thu, 14 Nov 2024 10:33:52 +0000 Subject: [PATCH 4/4] Update documentation. --- docs/sources/gateway.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/gateway.md b/docs/sources/gateway.md index 61913e84f7..f8053fce88 100644 --- a/docs/sources/gateway.md +++ b/docs/sources/gateway.md @@ -29,13 +29,13 @@ The set of domain names from a \*Route is sourced from the following places: - Adds the hostnames from any `external-dns.alpha.kubernetes.io/hostname` annotation on the \*Route. This behavior is suppressed if the `--ignore-hostname-annotation` flag was specified. +- Adds the hostnames from any `external-dns.alpha.kubernetes.io/hostname` annotation on associated Listeners' Gateways. + This behavior is suppressed if the `--ignore-hostname-annotation` flag was specified. + - If no endpoints were produced by the previous steps or the `--combine-fqdn-annotation` flag was specified, then adds hostnames generated from any`--fqdn-template` flag. -- If no endpoints were produced by the previous steps, each - attached Gateway listener will use its `hostname`, if present. - ### Matching Gateways Matching Gateways are discovered by iterating over the \*Route's `status.parents`: