mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 09:06:58 +02:00
gateway-httproute: add source
This commit is contained in:
parent
48203e64c9
commit
3a1d86be20
2
main.go
2
main.go
@ -114,6 +114,8 @@ func main() {
|
||||
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
|
||||
IgnoreIngressTLSSpec: cfg.IgnoreIngressTLSSpec,
|
||||
IgnoreIngressRulesSpec: cfg.IgnoreIngressRulesSpec,
|
||||
GatewayNamespace: cfg.GatewayNamespace,
|
||||
GatewayLabelFilter: cfg.GatewayLabelFilter,
|
||||
Compatibility: cfg.Compatibility,
|
||||
PublishInternal: cfg.PublishInternal,
|
||||
PublishHostIP: cfg.PublishHostIP,
|
||||
|
@ -60,6 +60,8 @@ type Config struct {
|
||||
IgnoreHostnameAnnotation bool
|
||||
IgnoreIngressTLSSpec bool
|
||||
IgnoreIngressRulesSpec bool
|
||||
GatewayNamespace string
|
||||
GatewayLabelFilter string
|
||||
Compatibility string
|
||||
PublishInternal bool
|
||||
PublishHostIP bool
|
||||
@ -204,6 +206,8 @@ var defaultConfig = &Config{
|
||||
IgnoreHostnameAnnotation: false,
|
||||
IgnoreIngressTLSSpec: false,
|
||||
IgnoreIngressRulesSpec: false,
|
||||
GatewayNamespace: "",
|
||||
GatewayLabelFilter: "",
|
||||
Compatibility: "",
|
||||
PublishInternal: false,
|
||||
PublishHostIP: false,
|
||||
@ -375,7 +379,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
|
||||
|
||||
// Flags related to processing source
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress")
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, gateway-httproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress")
|
||||
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
|
||||
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
|
||||
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
|
||||
@ -384,6 +388,8 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
|
||||
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
|
||||
app.Flag("ignore-ingress-tls-spec", "Ignore tls spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressTLSSpec)
|
||||
app.Flag("gateway-namespace", "Limit Gateways of Route endpoints to a specific namespace (default: all namespaces)").StringVar(&cfg.GatewayNamespace)
|
||||
app.Flag("gateway-label-filter", "Filter Gateways of Route endpoints via label selector (default: all gateways)").StringVar(&cfg.GatewayLabelFilter)
|
||||
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule", "kops-dns-controller")
|
||||
app.Flag("ignore-ingress-rules-spec", "Ignore rules spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressRulesSpec)
|
||||
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
|
||||
|
327
source/gateway.go
Normal file
327
source/gateway.go
Normal file
@ -0,0 +1,327 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned"
|
||||
informers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"
|
||||
informers_v1a2 "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions/apis/v1alpha2"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
type gatewayRoute interface {
|
||||
// Object returns the underlying Route object to be used by templates.
|
||||
Object() kubeObject
|
||||
// Metadata returns the Route's metadata.
|
||||
Metadata() *metav1.ObjectMeta
|
||||
// Hostnames returns the Route's specified hostnames.
|
||||
Hostnames() []v1alpha2.Hostname
|
||||
// Status returns the Route's status, including associated gateways.
|
||||
Status() v1alpha2.RouteStatus
|
||||
}
|
||||
|
||||
type newGatewayRouteInformerFunc func(informers.SharedInformerFactory) gatewayRouteInfomer
|
||||
|
||||
type gatewayRouteInfomer interface {
|
||||
List(namespace string, selector labels.Selector) ([]gatewayRoute, error)
|
||||
Informer() cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
func newGatewayInformerFactory(client gateway.Interface, namespace string, labelSelector labels.Selector) informers.SharedInformerFactory {
|
||||
var opts []informers.SharedInformerOption
|
||||
if namespace != "" {
|
||||
opts = append(opts, informers.WithNamespace(namespace))
|
||||
}
|
||||
if labelSelector != nil && !labelSelector.Empty() {
|
||||
lbls := labelSelector.String()
|
||||
opts = append(opts, informers.WithTweakListOptions(func(o *metav1.ListOptions) {
|
||||
o.LabelSelector = lbls
|
||||
}))
|
||||
}
|
||||
return informers.NewSharedInformerFactoryWithOptions(client, 0, opts...)
|
||||
}
|
||||
|
||||
type gatewayRouteSource struct {
|
||||
gwNamespace string
|
||||
gwLabels labels.Selector
|
||||
gwInformer informers_v1a2.GatewayInformer
|
||||
|
||||
rtKind string
|
||||
rtNamespace string
|
||||
rtLabels labels.Selector
|
||||
rtAnnotations labels.Selector
|
||||
rtInformer gatewayRouteInfomer
|
||||
|
||||
fqdnTemplate *template.Template
|
||||
combineFQDNAnnotation bool
|
||||
ignoreHostnameAnnotation bool
|
||||
}
|
||||
|
||||
func newGatewayRouteSource(clients ClientGenerator, config *Config, kind string, newInformerFn newGatewayRouteInformerFunc) (Source, error) {
|
||||
gwLabels, err := getLabelSelector(config.GatewayLabelFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rtLabels := config.LabelFilter
|
||||
if rtLabels == nil {
|
||||
rtLabels = labels.Everything()
|
||||
}
|
||||
rtAnnotations, err := getLabelSelector(config.AnnotationFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpl, err := parseTemplate(config.FQDNTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := clients.GatewayClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
informerFactory := newGatewayInformerFactory(client, config.GatewayNamespace, gwLabels)
|
||||
gwInformer := informerFactory.Gateway().V1alpha2().Gateways() // TODO: gateway informer should be shared across gateway sources
|
||||
gwInformer.Informer() // Register with factory before starting
|
||||
|
||||
rtInformerFactory := informerFactory
|
||||
if config.Namespace != config.GatewayNamespace || !selectorsEqual(rtLabels, gwLabels) {
|
||||
rtInformerFactory = newGatewayInformerFactory(client, config.Namespace, rtLabels)
|
||||
}
|
||||
rtInformer := newInformerFn(rtInformerFactory)
|
||||
rtInformer.Informer() // Register with factory before starting
|
||||
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
if rtInformerFactory != informerFactory {
|
||||
rtInformerFactory.Start(wait.NeverStop)
|
||||
|
||||
if err := waitForCacheSync(context.Background(), rtInformerFactory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
src := &gatewayRouteSource{
|
||||
gwNamespace: config.GatewayNamespace,
|
||||
gwLabels: gwLabels,
|
||||
gwInformer: gwInformer,
|
||||
|
||||
rtKind: kind,
|
||||
rtNamespace: config.Namespace,
|
||||
rtLabels: rtLabels,
|
||||
rtAnnotations: rtAnnotations,
|
||||
rtInformer: rtInformer,
|
||||
|
||||
fqdnTemplate: tmpl,
|
||||
combineFQDNAnnotation: config.CombineFQDNAndAnnotation,
|
||||
ignoreHostnameAnnotation: config.IgnoreHostnameAnnotation,
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func (src *gatewayRouteSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
log.Debugf("Adding event handler for %s", src.rtKind)
|
||||
src.gwInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||
src.rtInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||
}
|
||||
|
||||
func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
routes, err := src.rtInformer.List(src.rtNamespace, src.rtLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gwList, err := src.gwInformer.Lister().Gateways(src.gwNamespace).List(src.gwLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gateways := gatewaysByRef(gwList)
|
||||
for _, rt := range routes {
|
||||
eps, err := src.endpoints(rt, gateways)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoints = append(endpoints, eps...)
|
||||
}
|
||||
for _, ep := range endpoints {
|
||||
sort.Sort(ep.Targets)
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (src *gatewayRouteSource) endpoints(rt gatewayRoute, gateways map[types.NamespacedName]*v1alpha2.Gateway) ([]*endpoint.Endpoint, error) {
|
||||
// Filter by annotations.
|
||||
meta := rt.Metadata()
|
||||
annotations := meta.Annotations
|
||||
if !src.rtAnnotations.Matches(labels.Set(meta.Annotations)) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Check controller annotation to see if we are responsible.
|
||||
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)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get hostnames.
|
||||
hostnames, err := src.hostnames(rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hostnames) == 0 {
|
||||
log.Debugf("No hostnames could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get targets.
|
||||
targets := src.targets(rt, gateways)
|
||||
if len(targets) == 0 {
|
||||
log.Debugf("No targets could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create endpoints.
|
||||
ttl, err := getTTLFromAnnotations(annotations)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations)
|
||||
var endpoints []*endpoint.Endpoint
|
||||
for _, hostname := range hostnames {
|
||||
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...)
|
||||
}
|
||||
log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints)
|
||||
|
||||
kind := strings.ToLower(src.rtKind)
|
||||
resourceKey := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name)
|
||||
for _, ep := range endpoints {
|
||||
ep.Labels[endpoint.ResourceLabelKey] = resourceKey
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (src *gatewayRouteSource) hostnames(rt gatewayRoute) ([]string, error) {
|
||||
var hostnames []string
|
||||
for _, name := range rt.Hostnames() {
|
||||
hostnames = append(hostnames, string(name))
|
||||
}
|
||||
meta := rt.Metadata()
|
||||
// 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 {
|
||||
hostnames = append(hostnames, getHostnamesFromAnnotations(meta.Annotations)...)
|
||||
}
|
||||
// TODO: The combine-fqdn-annotation flag is similarly vague.
|
||||
if src.fqdnTemplate != nil && (len(hostnames) == 0 || src.combineFQDNAnnotation) {
|
||||
hosts, err := execTemplate(src.fqdnTemplate, rt.Object())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hostnames = append(hostnames, hosts...)
|
||||
}
|
||||
return hostnames, nil
|
||||
}
|
||||
|
||||
func (src *gatewayRouteSource) targets(rt gatewayRoute, gateways map[types.NamespacedName]*v1alpha2.Gateway) endpoint.Targets {
|
||||
var targets endpoint.Targets
|
||||
meta := rt.Metadata()
|
||||
for _, rps := range rt.Status().Parents {
|
||||
ref := rps.ParentRef
|
||||
if (ref.Group != nil && *ref.Group != "gateway.networking.k8s.io") || (ref.Kind != nil && *ref.Kind != "Gateway") {
|
||||
log.Debugf("Unsupported parent %v/%v for %s %s/%s", ref.Group, ref.Kind, src.rtKind, meta.Namespace, meta.Name)
|
||||
continue
|
||||
}
|
||||
namespace := meta.Namespace
|
||||
if ref.Namespace != nil {
|
||||
namespace = string(*ref.Namespace)
|
||||
}
|
||||
gw, ok := gateways[types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: string(ref.Name),
|
||||
}]
|
||||
if !ok {
|
||||
log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, src.rtKind, meta.Namespace, meta.Name)
|
||||
continue
|
||||
}
|
||||
if !gwRouteIsAdmitted(rps.Conditions) {
|
||||
log.Debugf("Gateway %s/%s has not admitted %s %s/%s", namespace, ref.Name, src.rtKind, meta.Namespace, meta.Name)
|
||||
continue
|
||||
}
|
||||
for _, addr := range gw.Status.Addresses {
|
||||
// TODO: Should we validate address type?
|
||||
// The spec says it should always be an IP.
|
||||
targets = append(targets, addr.Value)
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func gwRouteIsAdmitted(conds []metav1.Condition) bool {
|
||||
for _, c := range conds {
|
||||
if v1alpha2.RouteConditionType(c.Type) == v1alpha2.ConditionRouteAccepted {
|
||||
return c.Status == metav1.ConditionTrue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gatewaysByRef(list []*v1alpha2.Gateway) map[types.NamespacedName]*v1alpha2.Gateway {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
set := make(map[types.NamespacedName]*v1alpha2.Gateway, len(list))
|
||||
for _, gw := range list {
|
||||
set[types.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}] = gw
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func selectorsEqual(a, b labels.Selector) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
aReq, aOK := a.DeepCopySelector().Requirements()
|
||||
bReq, bOK := b.DeepCopySelector().Requirements()
|
||||
if aOK != bOK || len(aReq) != len(bReq) {
|
||||
return false
|
||||
}
|
||||
sort.Stable(labels.ByKey(aReq))
|
||||
sort.Stable(labels.ByKey(bReq))
|
||||
for i, r := range aReq {
|
||||
if !r.Equal(bReq[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
55
source/gateway_httproute.go
Normal file
55
source/gateway_httproute.go
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2021 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 (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
informers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"
|
||||
informers_v1a2 "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions/apis/v1alpha2"
|
||||
)
|
||||
|
||||
// NewGatewayHTTPRouteSource creates a new Gateway HTTPRoute source with the given config.
|
||||
func NewGatewayHTTPRouteSource(clients ClientGenerator, config *Config) (Source, error) {
|
||||
return newGatewayRouteSource(clients, config, "HTTPRoute", func(factory informers.SharedInformerFactory) gatewayRouteInfomer {
|
||||
return &gatewayHTTPRouteInformer{factory.Gateway().V1alpha2().HTTPRoutes()}
|
||||
})
|
||||
}
|
||||
|
||||
type gatewayHTTPRoute struct{ route *v1alpha2.HTTPRoute }
|
||||
|
||||
func (rt *gatewayHTTPRoute) Object() kubeObject { return rt.route }
|
||||
func (rt *gatewayHTTPRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
|
||||
func (rt *gatewayHTTPRoute) Hostnames() []v1alpha2.Hostname { return rt.route.Spec.Hostnames }
|
||||
func (rt *gatewayHTTPRoute) Status() v1alpha2.RouteStatus { return rt.route.Status.RouteStatus }
|
||||
|
||||
type gatewayHTTPRouteInformer struct {
|
||||
informers_v1a2.HTTPRouteInformer
|
||||
}
|
||||
|
||||
func (inf gatewayHTTPRouteInformer) List(namespace string, selector labels.Selector) ([]gatewayRoute, error) {
|
||||
list, err := inf.HTTPRouteInformer.Lister().HTTPRoutes(namespace).List(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routes := make([]gatewayRoute, len(list))
|
||||
for i, rt := range list {
|
||||
routes[i] = &gatewayHTTPRoute{rt}
|
||||
}
|
||||
return routes, nil
|
||||
}
|
664
source/gateway_httproute_test.go
Normal file
664
source/gateway_httproute_test.go
Normal file
@ -0,0 +1,664 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
gatewayfake "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned/fake"
|
||||
)
|
||||
|
||||
func mustGetLabelSelector(s string) labels.Selector {
|
||||
v, err := getLabelSelector(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func gatewayStatus(ips ...string) v1alpha2.GatewayStatus {
|
||||
typ := v1alpha2.IPAddressType
|
||||
addrs := make([]v1alpha2.GatewayAddress, len(ips))
|
||||
for i, ip := range ips {
|
||||
addrs[i] = v1alpha2.GatewayAddress{Type: &typ, Value: ip}
|
||||
}
|
||||
return v1alpha2.GatewayStatus{Addresses: addrs}
|
||||
}
|
||||
|
||||
func routeStatus(refs ...v1alpha2.ParentRef) v1alpha2.RouteStatus {
|
||||
var v v1alpha2.RouteStatus
|
||||
for _, ref := range refs {
|
||||
v.Parents = append(v.Parents, v1alpha2.RouteParentStatus{
|
||||
ParentRef: ref,
|
||||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: string(v1alpha2.ConditionRouteAccepted),
|
||||
Status: metav1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func httpRouteStatus(refs ...v1alpha2.ParentRef) v1alpha2.HTTPRouteStatus {
|
||||
return v1alpha2.HTTPRouteStatus{RouteStatus: routeStatus(refs...)}
|
||||
}
|
||||
|
||||
func gatewayParentRef(namespace, name string) v1alpha2.ParentRef {
|
||||
group := v1alpha2.Group("gateway.networking.k8s.io")
|
||||
kind := v1alpha2.Kind("Gateway")
|
||||
return v1alpha2.ParentRef{
|
||||
Group: &group,
|
||||
Kind: &kind,
|
||||
Name: v1alpha2.ObjectName(name),
|
||||
Namespace: (*v1alpha2.Namespace)(&namespace),
|
||||
}
|
||||
}
|
||||
|
||||
func newTestEndpoint(dnsName, recordType string, targets ...string) *endpoint.Endpoint {
|
||||
return newTestEndpointWithTTL(dnsName, recordType, 0, targets...)
|
||||
}
|
||||
|
||||
func newTestEndpointWithTTL(dnsName, recordType string, ttl int64, targets ...string) *endpoint.Endpoint {
|
||||
return &endpoint.Endpoint{
|
||||
DNSName: dnsName,
|
||||
Targets: append([]string(nil), targets...), // clone targets
|
||||
RecordType: recordType,
|
||||
RecordTTL: endpoint.TTL(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func joinTargets(targets ...[]string) []string {
|
||||
var s []string
|
||||
for _, v := range targets {
|
||||
s = append(s, v...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
objectMeta := func(namespace, name string) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
}
|
||||
namespaces := func(names ...string) []*corev1.Namespace {
|
||||
v := make([]*corev1.Namespace, len(names))
|
||||
for i, name := range names {
|
||||
v[i] = &corev1.Namespace{ObjectMeta: objectMeta("", name)}
|
||||
}
|
||||
return v
|
||||
}
|
||||
hostnames := func(names ...v1alpha2.Hostname) []v1alpha2.Hostname { return names }
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
config Config
|
||||
namespaces []*corev1.Namespace
|
||||
gateways []*v1alpha2.Gateway
|
||||
routes []*v1alpha2.HTTPRoute
|
||||
endpoints []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
title: "GatewayNamespace",
|
||||
config: Config{
|
||||
GatewayNamespace: "gateway-namespace",
|
||||
},
|
||||
namespaces: namespaces("gateway-namespace", "not-gateway-namespace", "route-namespace"),
|
||||
gateways: []*v1alpha2.Gateway{
|
||||
{
|
||||
ObjectMeta: objectMeta("gateway-namespace", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
},
|
||||
{
|
||||
ObjectMeta: objectMeta("not-gateway-namespace", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("2.3.4.5"),
|
||||
},
|
||||
},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("route-namespace", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("test.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus( // The route is attached to both gateways.
|
||||
gatewayParentRef("gateway-namespace", "test"),
|
||||
gatewayParentRef("not-gateway-namespace", "test"),
|
||||
),
|
||||
}},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "RouteNamespace",
|
||||
config: Config{
|
||||
Namespace: "route-namespace",
|
||||
},
|
||||
namespaces: namespaces("gateway-namespace", "route-namespace", "not-route-namespace"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("gateway-namespace", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: objectMeta("route-namespace", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("route-namespace.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("gateway-namespace", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: objectMeta("not-route-namespace", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("not-route-namespace.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("gateway-namespace", "test")),
|
||||
},
|
||||
},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "GatewayLabelFilter",
|
||||
config: Config{
|
||||
GatewayLabelFilter: "foo=bar",
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labels-match",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labels-dont-match",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"foo": "qux"},
|
||||
},
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("2.3.4.5"),
|
||||
},
|
||||
},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("test.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus( // The route is attached to both gateways.
|
||||
gatewayParentRef("default", "labels-match"),
|
||||
gatewayParentRef("default", "labels-dont-match"),
|
||||
),
|
||||
}},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "RouteLabelFilter",
|
||||
config: Config{
|
||||
LabelFilter: mustGetLabelSelector("foo=bar"),
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labels-match",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("labels-match.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labels-dont-match",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"foo": "qux"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("labels-dont-match.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "RouteAnnotationFilter",
|
||||
config: Config{
|
||||
AnnotationFilter: "foo=bar",
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "annotations-match",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("annotations-match.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "annotations-dont-match",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{"foo": "qux"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("annotations-dont-match.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "SkipControllerAnnotation",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "api",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
controllerAnnotationKey: "something-else",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("api.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
}},
|
||||
endpoints: nil,
|
||||
},
|
||||
{
|
||||
title: "MultipleGateways",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{
|
||||
{
|
||||
ObjectMeta: objectMeta("default", "one"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
},
|
||||
{
|
||||
ObjectMeta: objectMeta("default", "two"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("2.3.4.5"),
|
||||
},
|
||||
},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("test.example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(
|
||||
gatewayParentRef("default", "one"),
|
||||
gatewayParentRef("default", "two"),
|
||||
),
|
||||
}},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "NoGateways",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: nil,
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("example.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(),
|
||||
}},
|
||||
endpoints: nil,
|
||||
},
|
||||
{
|
||||
title: "NoHostnames",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("default", "no-hostame"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: nil,
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
}},
|
||||
endpoints: nil,
|
||||
},
|
||||
{
|
||||
title: "HostnameAnnotation",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "without-hostame",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
hostnameAnnotationKey: "annotation.without-hostname.internal",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: nil,
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "with-hostame",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
hostnameAnnotationKey: "annotation.with-hostname.internal",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("with-hostname.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
},
|
||||
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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "IgnoreHostnameAnnotation",
|
||||
config: Config{
|
||||
IgnoreHostnameAnnotation: true,
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "with-hostame",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
hostnameAnnotationKey: "annotation.with-hostname.internal",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("with-hostname.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
}},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "FQDNTemplate",
|
||||
config: Config{
|
||||
FQDNTemplate: "{{.Name}}.zero.internal, {{.Name}}.one.internal. , {{.Name}}.two.internal ",
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("fqdn-with-hostnames.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: objectMeta("default", "fqdn-without-hostnames"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: nil,
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
},
|
||||
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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "CombineFQDN",
|
||||
config: Config{
|
||||
FQDNTemplate: "combine-{{.Name}}.internal",
|
||||
CombineFQDNAndAnnotation: true,
|
||||
},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("fqdn-with-hostnames.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "TTL",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-ttl",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{ttlAnnotationKey: "15s"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("valid-ttl.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "invalid-ttl",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{ttlAnnotationKey: "abc"},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("invalid-ttl.internal"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
},
|
||||
},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"),
|
||||
newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ProviderAnnotations",
|
||||
config: Config{},
|
||||
namespaces: namespaces("default"),
|
||||
gateways: []*v1alpha2.Gateway{{
|
||||
ObjectMeta: objectMeta("default", "test"),
|
||||
Spec: v1alpha2.GatewaySpec{
|
||||
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||
},
|
||||
Status: gatewayStatus("1.2.3.4"),
|
||||
}},
|
||||
routes: []*v1alpha2.HTTPRoute{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "provider-annotations",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
SetIdentifierKey: "test-set-identifier",
|
||||
aliasAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
Spec: v1alpha2.HTTPRouteSpec{
|
||||
Hostnames: hostnames("provider-annotations.com"),
|
||||
},
|
||||
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||
}},
|
||||
endpoints: []*endpoint.Endpoint{
|
||||
newTestEndpoint("provider-annotations.com", "A", "1.2.3.4").
|
||||
WithProviderSpecific("alias", "true").
|
||||
WithSetIdentifier("test-set-identifier"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.title, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
gwClient := gatewayfake.NewSimpleClientset()
|
||||
for _, gw := range tt.gateways {
|
||||
_, err := gwClient.GatewayV1alpha2().Gateways(gw.Namespace).Create(ctx, gw, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "failed to create Gateway")
|
||||
|
||||
}
|
||||
for _, rt := range tt.routes {
|
||||
_, err := gwClient.GatewayV1alpha2().HTTPRoutes(rt.Namespace).Create(ctx, rt, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "failed to create HTTPRoute")
|
||||
}
|
||||
kubeClient := kubefake.NewSimpleClientset()
|
||||
for _, ns := range tt.namespaces {
|
||||
_, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "failed to create Namespace")
|
||||
}
|
||||
|
||||
clients := new(MockClientGenerator)
|
||||
clients.On("GatewayClient").Return(gwClient, nil)
|
||||
clients.On("KubeClient").Return(kubeClient, nil)
|
||||
|
||||
src, err := NewGatewayHTTPRouteSource(clients, &tt.config)
|
||||
require.NoError(t, err, "failed to create Gateway HTTPRoute Source")
|
||||
|
||||
endpoints, err := src.Endpoints(ctx)
|
||||
require.NoError(t, err, "failed to get Endpoints")
|
||||
validateEndpoints(t, endpoints, tt.endpoints)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(val string) *string { return &val }
|
||||
|
||||
func hostnamePtr(val v1alpha2.Hostname) *v1alpha2.Hostname { return &val }
|
@ -51,6 +51,8 @@ type Config struct {
|
||||
IgnoreHostnameAnnotation bool
|
||||
IgnoreIngressTLSSpec bool
|
||||
IgnoreIngressRulesSpec bool
|
||||
GatewayNamespace string
|
||||
GatewayLabelFilter string
|
||||
Compatibility string
|
||||
PublishInternal bool
|
||||
PublishHostIP bool
|
||||
@ -225,6 +227,8 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
||||
return nil, err
|
||||
}
|
||||
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility)
|
||||
case "gateway-httproute":
|
||||
return NewGatewayHTTPRouteSource(p, cfg)
|
||||
case "istio-gateway":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user