sigs.k8s.io/external-dns@v0.14.1/source/gateway_httproute_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package source
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/require"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	kubefake "k8s.io/client-go/kubernetes/fake"
    28  	"sigs.k8s.io/external-dns/endpoint"
    29  	v1 "sigs.k8s.io/gateway-api/apis/v1"
    30  	gatewayfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
    31  )
    32  
    33  func mustGetLabelSelector(s string) labels.Selector {
    34  	v, err := getLabelSelector(s)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	return v
    39  }
    40  
    41  func gatewayStatus(ips ...string) v1.GatewayStatus {
    42  	typ := v1.IPAddressType
    43  	addrs := make([]v1.GatewayStatusAddress, len(ips))
    44  	for i, ip := range ips {
    45  		addrs[i] = v1.GatewayStatusAddress{Type: &typ, Value: ip}
    46  	}
    47  	return v1.GatewayStatus{Addresses: addrs}
    48  }
    49  
    50  func httpRouteStatus(refs ...v1.ParentReference) v1.HTTPRouteStatus {
    51  	return v1.HTTPRouteStatus{RouteStatus: gwRouteStatus(refs...)}
    52  }
    53  
    54  func gwRouteStatus(refs ...v1.ParentReference) v1.RouteStatus {
    55  	var v v1.RouteStatus
    56  	for _, ref := range refs {
    57  		v.Parents = append(v.Parents, v1.RouteParentStatus{
    58  			ParentRef: ref,
    59  			Conditions: []metav1.Condition{
    60  				{
    61  					Type:   string(v1.RouteConditionAccepted),
    62  					Status: metav1.ConditionTrue,
    63  				},
    64  			},
    65  		})
    66  	}
    67  	return v
    68  }
    69  
    70  func gwParentRef(namespace, name string, options ...gwParentRefOption) v1.ParentReference {
    71  	group := v1.Group("gateway.networking.k8s.io")
    72  	kind := v1.Kind("Gateway")
    73  	ref := v1.ParentReference{
    74  		Group:     &group,
    75  		Kind:      &kind,
    76  		Name:      v1.ObjectName(name),
    77  		Namespace: (*v1.Namespace)(&namespace),
    78  	}
    79  	for _, opt := range options {
    80  		opt(&ref)
    81  	}
    82  	return ref
    83  }
    84  
    85  type gwParentRefOption func(*v1.ParentReference)
    86  
    87  func withSectionName(name v1.SectionName) gwParentRefOption {
    88  	return func(ref *v1.ParentReference) { ref.SectionName = &name }
    89  }
    90  
    91  func withPortNumber(port v1.PortNumber) gwParentRefOption {
    92  	return func(ref *v1.ParentReference) { ref.Port = &port }
    93  }
    94  
    95  func newTestEndpoint(dnsName, recordType string, targets ...string) *endpoint.Endpoint {
    96  	return newTestEndpointWithTTL(dnsName, recordType, 0, targets...)
    97  }
    98  
    99  func newTestEndpointWithTTL(dnsName, recordType string, ttl int64, targets ...string) *endpoint.Endpoint {
   100  	return &endpoint.Endpoint{
   101  		DNSName:    dnsName,
   102  		Targets:    append([]string(nil), targets...), // clone targets
   103  		RecordType: recordType,
   104  		RecordTTL:  endpoint.TTL(ttl),
   105  	}
   106  }
   107  
   108  func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
   109  	t.Parallel()
   110  
   111  	fromAll := v1.NamespacesFromAll
   112  	fromSame := v1.NamespacesFromSame
   113  	fromSelector := v1.NamespacesFromSelector
   114  	allowAllNamespaces := &v1.AllowedRoutes{
   115  		Namespaces: &v1.RouteNamespaces{
   116  			From: &fromAll,
   117  		},
   118  	}
   119  	objectMeta := func(namespace, name string) metav1.ObjectMeta {
   120  		return metav1.ObjectMeta{
   121  			Name:      name,
   122  			Namespace: namespace,
   123  		}
   124  	}
   125  	namespaces := func(names ...string) []*corev1.Namespace {
   126  		v := make([]*corev1.Namespace, len(names))
   127  		for i, name := range names {
   128  			v[i] = &corev1.Namespace{ObjectMeta: objectMeta("", name)}
   129  		}
   130  		return v
   131  	}
   132  	hostnames := func(names ...v1.Hostname) []v1.Hostname { return names }
   133  
   134  	tests := []struct {
   135  		title      string
   136  		config     Config
   137  		namespaces []*corev1.Namespace
   138  		gateways   []*v1.Gateway
   139  		routes     []*v1.HTTPRoute
   140  		endpoints  []*endpoint.Endpoint
   141  	}{
   142  		{
   143  			title: "GatewayNamespace",
   144  			config: Config{
   145  				GatewayNamespace: "gateway-namespace",
   146  			},
   147  			namespaces: namespaces("gateway-namespace", "not-gateway-namespace", "route-namespace"),
   148  			gateways: []*v1.Gateway{
   149  				{
   150  					ObjectMeta: objectMeta("gateway-namespace", "test"),
   151  					Spec: v1.GatewaySpec{
   152  						Listeners: []v1.Listener{{
   153  							Protocol:      v1.HTTPProtocolType,
   154  							AllowedRoutes: allowAllNamespaces,
   155  						}},
   156  					},
   157  					Status: gatewayStatus("1.2.3.4"),
   158  				},
   159  				{
   160  					ObjectMeta: objectMeta("not-gateway-namespace", "test"),
   161  					Spec: v1.GatewaySpec{
   162  						Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   163  					},
   164  					Status: gatewayStatus("2.3.4.5"),
   165  				},
   166  			},
   167  			routes: []*v1.HTTPRoute{{
   168  				ObjectMeta: objectMeta("route-namespace", "test"),
   169  				Spec: v1.HTTPRouteSpec{
   170  					Hostnames: hostnames("test.example.internal"),
   171  				},
   172  				Status: httpRouteStatus( // The route is attached to both gateways.
   173  					gwParentRef("gateway-namespace", "test"),
   174  					gwParentRef("not-gateway-namespace", "test"),
   175  				),
   176  			}},
   177  			endpoints: []*endpoint.Endpoint{
   178  				newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
   179  			},
   180  		},
   181  		{
   182  			title: "RouteNamespace",
   183  			config: Config{
   184  				Namespace: "route-namespace",
   185  			},
   186  			namespaces: namespaces("gateway-namespace", "route-namespace", "not-route-namespace"),
   187  			gateways: []*v1.Gateway{{
   188  				ObjectMeta: objectMeta("gateway-namespace", "test"),
   189  				Spec: v1.GatewaySpec{
   190  					Listeners: []v1.Listener{{
   191  						Protocol:      v1.HTTPProtocolType,
   192  						AllowedRoutes: allowAllNamespaces,
   193  					}},
   194  				},
   195  				Status: gatewayStatus("1.2.3.4"),
   196  			}},
   197  			routes: []*v1.HTTPRoute{
   198  				{
   199  					ObjectMeta: objectMeta("route-namespace", "test"),
   200  					Spec: v1.HTTPRouteSpec{
   201  						Hostnames: hostnames("route-namespace.example.internal"),
   202  					},
   203  					Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")),
   204  				},
   205  				{
   206  					ObjectMeta: objectMeta("not-route-namespace", "test"),
   207  					Spec: v1.HTTPRouteSpec{
   208  						Hostnames: hostnames("not-route-namespace.example.internal"),
   209  					},
   210  					Status: httpRouteStatus(gwParentRef("gateway-namespace", "test")),
   211  				},
   212  			},
   213  			endpoints: []*endpoint.Endpoint{
   214  				newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"),
   215  			},
   216  		},
   217  		{
   218  			title: "GatewayLabelFilter",
   219  			config: Config{
   220  				GatewayLabelFilter: "foo=bar",
   221  			},
   222  			namespaces: namespaces("default"),
   223  			gateways: []*v1.Gateway{
   224  				{
   225  					ObjectMeta: metav1.ObjectMeta{
   226  						Name:      "labels-match",
   227  						Namespace: "default",
   228  						Labels:    map[string]string{"foo": "bar"},
   229  					},
   230  					Spec: v1.GatewaySpec{
   231  						Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   232  					},
   233  					Status: gatewayStatus("1.2.3.4"),
   234  				},
   235  				{
   236  					ObjectMeta: metav1.ObjectMeta{
   237  						Name:      "labels-dont-match",
   238  						Namespace: "default",
   239  						Labels:    map[string]string{"foo": "qux"},
   240  					},
   241  					Spec: v1.GatewaySpec{
   242  						Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   243  					},
   244  					Status: gatewayStatus("2.3.4.5"),
   245  				},
   246  			},
   247  			routes: []*v1.HTTPRoute{{
   248  				ObjectMeta: objectMeta("default", "test"),
   249  				Spec: v1.HTTPRouteSpec{
   250  					Hostnames: hostnames("test.example.internal"),
   251  				},
   252  				Status: httpRouteStatus( // The route is attached to both gateways.
   253  					gwParentRef("default", "labels-match"),
   254  					gwParentRef("default", "labels-dont-match"),
   255  				),
   256  			}},
   257  			endpoints: []*endpoint.Endpoint{
   258  				newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
   259  			},
   260  		},
   261  		{
   262  			title: "RouteLabelFilter",
   263  			config: Config{
   264  				LabelFilter: mustGetLabelSelector("foo=bar"),
   265  			},
   266  			namespaces: namespaces("default"),
   267  			gateways: []*v1.Gateway{{
   268  				ObjectMeta: objectMeta("default", "test"),
   269  				Spec: v1.GatewaySpec{
   270  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   271  				},
   272  				Status: gatewayStatus("1.2.3.4"),
   273  			}},
   274  			routes: []*v1.HTTPRoute{
   275  				{
   276  					ObjectMeta: metav1.ObjectMeta{
   277  						Name:      "labels-match",
   278  						Namespace: "default",
   279  						Labels:    map[string]string{"foo": "bar"},
   280  					},
   281  					Spec: v1.HTTPRouteSpec{
   282  						Hostnames: hostnames("labels-match.example.internal"),
   283  					},
   284  					Status: httpRouteStatus(gwParentRef("default", "test")),
   285  				},
   286  				{
   287  					ObjectMeta: metav1.ObjectMeta{
   288  						Name:      "labels-dont-match",
   289  						Namespace: "default",
   290  						Labels:    map[string]string{"foo": "qux"},
   291  					},
   292  					Spec: v1.HTTPRouteSpec{
   293  						Hostnames: hostnames("labels-dont-match.example.internal"),
   294  					},
   295  					Status: httpRouteStatus(gwParentRef("default", "test")),
   296  				},
   297  			},
   298  			endpoints: []*endpoint.Endpoint{
   299  				newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"),
   300  			},
   301  		},
   302  		{
   303  			title: "RouteAnnotationFilter",
   304  			config: Config{
   305  				AnnotationFilter: "foo=bar",
   306  			},
   307  			namespaces: namespaces("default"),
   308  			gateways: []*v1.Gateway{{
   309  				ObjectMeta: objectMeta("default", "test"),
   310  				Spec: v1.GatewaySpec{
   311  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   312  				},
   313  				Status: gatewayStatus("1.2.3.4"),
   314  			}},
   315  			routes: []*v1.HTTPRoute{
   316  				{
   317  					ObjectMeta: metav1.ObjectMeta{
   318  						Name:        "annotations-match",
   319  						Namespace:   "default",
   320  						Annotations: map[string]string{"foo": "bar"},
   321  					},
   322  					Spec: v1.HTTPRouteSpec{
   323  						Hostnames: hostnames("annotations-match.example.internal"),
   324  					},
   325  					Status: httpRouteStatus(gwParentRef("default", "test")),
   326  				},
   327  				{
   328  					ObjectMeta: metav1.ObjectMeta{
   329  						Name:        "annotations-dont-match",
   330  						Namespace:   "default",
   331  						Annotations: map[string]string{"foo": "qux"},
   332  					},
   333  					Spec: v1.HTTPRouteSpec{
   334  						Hostnames: hostnames("annotations-dont-match.example.internal"),
   335  					},
   336  					Status: httpRouteStatus(gwParentRef("default", "test")),
   337  				},
   338  			},
   339  			endpoints: []*endpoint.Endpoint{
   340  				newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"),
   341  			},
   342  		},
   343  		{
   344  			title:      "SkipControllerAnnotation",
   345  			config:     Config{},
   346  			namespaces: namespaces("default"),
   347  			gateways: []*v1.Gateway{{
   348  				ObjectMeta: objectMeta("default", "test"),
   349  				Spec: v1.GatewaySpec{
   350  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   351  				},
   352  				Status: gatewayStatus("1.2.3.4"),
   353  			}},
   354  			routes: []*v1.HTTPRoute{{
   355  				ObjectMeta: metav1.ObjectMeta{
   356  					Name:      "api",
   357  					Namespace: "default",
   358  					Annotations: map[string]string{
   359  						controllerAnnotationKey: "something-else",
   360  					},
   361  				},
   362  				Spec: v1.HTTPRouteSpec{
   363  					Hostnames: hostnames("api.example.internal"),
   364  				},
   365  				Status: httpRouteStatus(gwParentRef("default", "test")),
   366  			}},
   367  			endpoints: nil,
   368  		},
   369  		{
   370  			title:      "MultipleGateways",
   371  			config:     Config{},
   372  			namespaces: namespaces("default"),
   373  			gateways: []*v1.Gateway{
   374  				{
   375  					ObjectMeta: objectMeta("default", "one"),
   376  					Spec: v1.GatewaySpec{
   377  						Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   378  					},
   379  					Status: gatewayStatus("1.2.3.4"),
   380  				},
   381  				{
   382  					ObjectMeta: objectMeta("default", "two"),
   383  					Spec: v1.GatewaySpec{
   384  						Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   385  					},
   386  					Status: gatewayStatus("2.3.4.5"),
   387  				},
   388  			},
   389  			routes: []*v1.HTTPRoute{{
   390  				ObjectMeta: objectMeta("default", "test"),
   391  				Spec: v1.HTTPRouteSpec{
   392  					Hostnames: hostnames("test.example.internal"),
   393  				},
   394  				Status: httpRouteStatus(
   395  					gwParentRef("default", "one"),
   396  					gwParentRef("default", "two"),
   397  				),
   398  			}},
   399  			endpoints: []*endpoint.Endpoint{
   400  				newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"),
   401  			},
   402  		},
   403  		{
   404  			title:      "MultipleListeners",
   405  			config:     Config{},
   406  			namespaces: namespaces("default"),
   407  			gateways: []*v1.Gateway{{
   408  				ObjectMeta: objectMeta("default", "one"),
   409  				Spec: v1.GatewaySpec{
   410  					Listeners: []v1.Listener{
   411  						{
   412  							Name:     "foo",
   413  							Protocol: v1.HTTPProtocolType,
   414  							Hostname: hostnamePtr("foo.example.internal"),
   415  						},
   416  						{
   417  							Name:     "bar",
   418  							Protocol: v1.HTTPProtocolType,
   419  							Hostname: hostnamePtr("bar.example.internal"),
   420  						},
   421  					},
   422  				},
   423  				Status: gatewayStatus("1.2.3.4"),
   424  			}},
   425  			routes: []*v1.HTTPRoute{{
   426  				ObjectMeta: objectMeta("default", "test"),
   427  				Spec: v1.HTTPRouteSpec{
   428  					Hostnames: hostnames("*.example.internal"),
   429  				},
   430  				Status: httpRouteStatus(
   431  					gwParentRef("default", "one"),
   432  				),
   433  			}},
   434  			endpoints: []*endpoint.Endpoint{
   435  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   436  				newTestEndpoint("bar.example.internal", "A", "1.2.3.4"),
   437  			},
   438  		},
   439  		{
   440  			title:      "SectionNameMatch",
   441  			config:     Config{},
   442  			namespaces: namespaces("default"),
   443  			gateways: []*v1.Gateway{{
   444  				ObjectMeta: objectMeta("default", "test"),
   445  				Spec: v1.GatewaySpec{
   446  					Listeners: []v1.Listener{
   447  						{
   448  							Name:     "foo",
   449  							Protocol: v1.HTTPProtocolType,
   450  							Hostname: hostnamePtr("foo.example.internal"),
   451  						},
   452  						{
   453  							Name:     "bar",
   454  							Protocol: v1.HTTPProtocolType,
   455  							Hostname: hostnamePtr("bar.example.internal"),
   456  						},
   457  					},
   458  				},
   459  				Status: gatewayStatus("1.2.3.4"),
   460  			}},
   461  			routes: []*v1.HTTPRoute{{
   462  				ObjectMeta: objectMeta("default", "test"),
   463  				Spec: v1.HTTPRouteSpec{
   464  					Hostnames: hostnames("*.example.internal"),
   465  				},
   466  				Status: httpRouteStatus(
   467  					gwParentRef("default", "test", withSectionName("foo")),
   468  				),
   469  			}},
   470  			endpoints: []*endpoint.Endpoint{
   471  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   472  			},
   473  		},
   474  		{
   475  			// EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/
   476  			title:      "PortNumberMatch",
   477  			config:     Config{},
   478  			namespaces: namespaces("default"),
   479  			gateways: []*v1.Gateway{{
   480  				ObjectMeta: objectMeta("default", "test"),
   481  				Spec: v1.GatewaySpec{
   482  					Listeners: []v1.Listener{
   483  						{
   484  							Name:     "foo",
   485  							Protocol: v1.HTTPProtocolType,
   486  							Hostname: hostnamePtr("foo.example.internal"),
   487  							Port:     80,
   488  						},
   489  						{
   490  							Name:     "bar",
   491  							Protocol: v1.HTTPProtocolType,
   492  							Hostname: hostnamePtr("bar.example.internal"),
   493  							Port:     80,
   494  						},
   495  						{
   496  							Name:     "qux",
   497  							Protocol: v1.HTTPProtocolType,
   498  							Hostname: hostnamePtr("qux.example.internal"),
   499  							Port:     8080,
   500  						},
   501  					},
   502  				},
   503  				Status: gatewayStatus("1.2.3.4"),
   504  			}},
   505  			routes: []*v1.HTTPRoute{{
   506  				ObjectMeta: objectMeta("default", "test"),
   507  				Spec: v1.HTTPRouteSpec{
   508  					Hostnames: hostnames("*.example.internal"),
   509  				},
   510  				Status: httpRouteStatus(
   511  					gwParentRef("default", "test", withPortNumber(80)),
   512  				),
   513  			}},
   514  			endpoints: []*endpoint.Endpoint{
   515  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   516  				newTestEndpoint("bar.example.internal", "A", "1.2.3.4"),
   517  			},
   518  		},
   519  		{
   520  			title:      "WildcardInGateway",
   521  			config:     Config{},
   522  			namespaces: namespaces("default"),
   523  			gateways: []*v1.Gateway{{
   524  				ObjectMeta: objectMeta("default", "test"),
   525  				Spec: v1.GatewaySpec{
   526  					Listeners: []v1.Listener{{
   527  						Protocol: v1.HTTPProtocolType,
   528  						Hostname: hostnamePtr("*.example.internal"),
   529  					}},
   530  				},
   531  				Status: gatewayStatus("1.2.3.4"),
   532  			}},
   533  			routes: []*v1.HTTPRoute{{
   534  				ObjectMeta: objectMeta("default", "no-hostname"),
   535  				Spec: v1.HTTPRouteSpec{
   536  					Hostnames: []v1.Hostname{
   537  						"foo.example.internal",
   538  					},
   539  				},
   540  				Status: httpRouteStatus(gwParentRef("default", "test")),
   541  			}},
   542  			endpoints: []*endpoint.Endpoint{
   543  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   544  			},
   545  		},
   546  		{
   547  			title:      "WildcardInRoute",
   548  			config:     Config{},
   549  			namespaces: namespaces("default"),
   550  			gateways: []*v1.Gateway{{
   551  				ObjectMeta: objectMeta("default", "test"),
   552  				Spec: v1.GatewaySpec{
   553  					Listeners: []v1.Listener{{
   554  						Protocol: v1.HTTPProtocolType,
   555  						Hostname: hostnamePtr("foo.example.internal"),
   556  					}},
   557  				},
   558  				Status: gatewayStatus("1.2.3.4"),
   559  			}},
   560  			routes: []*v1.HTTPRoute{{
   561  				ObjectMeta: objectMeta("default", "no-hostname"),
   562  				Spec: v1.HTTPRouteSpec{
   563  					Hostnames: []v1.Hostname{
   564  						"*.example.internal",
   565  					},
   566  				},
   567  				Status: httpRouteStatus(gwParentRef("default", "test")),
   568  			}},
   569  			endpoints: []*endpoint.Endpoint{
   570  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   571  			},
   572  		},
   573  		{
   574  			title:      "WildcardInRouteAndGateway",
   575  			config:     Config{},
   576  			namespaces: namespaces("default"),
   577  			gateways: []*v1.Gateway{{
   578  				ObjectMeta: objectMeta("default", "test"),
   579  				Spec: v1.GatewaySpec{
   580  					Listeners: []v1.Listener{{
   581  						Protocol: v1.HTTPProtocolType,
   582  						Hostname: hostnamePtr("*.example.internal"),
   583  					}},
   584  				},
   585  				Status: gatewayStatus("1.2.3.4"),
   586  			}},
   587  			routes: []*v1.HTTPRoute{{
   588  				ObjectMeta: objectMeta("default", "no-hostname"),
   589  				Spec: v1.HTTPRouteSpec{
   590  					Hostnames: []v1.Hostname{
   591  						"*.example.internal",
   592  					},
   593  				},
   594  				Status: httpRouteStatus(gwParentRef("default", "test")),
   595  			}},
   596  			endpoints: []*endpoint.Endpoint{
   597  				newTestEndpoint("*.example.internal", "A", "1.2.3.4"),
   598  			},
   599  		},
   600  		{
   601  			title:      "NoRouteHostname",
   602  			config:     Config{},
   603  			namespaces: namespaces("default"),
   604  			gateways: []*v1.Gateway{{
   605  				ObjectMeta: objectMeta("default", "test"),
   606  				Spec: v1.GatewaySpec{
   607  					Listeners: []v1.Listener{{
   608  						Protocol: v1.HTTPProtocolType,
   609  						Hostname: hostnamePtr("foo.example.internal"),
   610  					}},
   611  				},
   612  				Status: gatewayStatus("1.2.3.4"),
   613  			}},
   614  			routes: []*v1.HTTPRoute{{
   615  				ObjectMeta: objectMeta("default", "no-hostname"),
   616  				Spec: v1.HTTPRouteSpec{
   617  					Hostnames: nil,
   618  				},
   619  				Status: httpRouteStatus(gwParentRef("default", "test")),
   620  			}},
   621  			endpoints: []*endpoint.Endpoint{
   622  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
   623  			},
   624  		},
   625  		{
   626  			title:      "NoGateways",
   627  			config:     Config{},
   628  			namespaces: namespaces("default"),
   629  			gateways:   nil,
   630  			routes: []*v1.HTTPRoute{{
   631  				ObjectMeta: objectMeta("default", "test"),
   632  				Spec: v1.HTTPRouteSpec{
   633  					Hostnames: hostnames("example.internal"),
   634  				},
   635  				Status: httpRouteStatus(),
   636  			}},
   637  			endpoints: nil,
   638  		},
   639  		{
   640  			title:      "NoHostnames",
   641  			config:     Config{},
   642  			namespaces: namespaces("default"),
   643  			gateways: []*v1.Gateway{{
   644  				ObjectMeta: objectMeta("default", "test"),
   645  				Spec: v1.GatewaySpec{
   646  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   647  				},
   648  				Status: gatewayStatus("1.2.3.4"),
   649  			}},
   650  			routes: []*v1.HTTPRoute{{
   651  				ObjectMeta: objectMeta("default", "no-hostname"),
   652  				Spec: v1.HTTPRouteSpec{
   653  					Hostnames: nil,
   654  				},
   655  				Status: httpRouteStatus(gwParentRef("default", "test")),
   656  			}},
   657  			endpoints: nil,
   658  		},
   659  		{
   660  			title:      "HostnameAnnotation",
   661  			config:     Config{},
   662  			namespaces: namespaces("default"),
   663  			gateways: []*v1.Gateway{{
   664  				ObjectMeta: objectMeta("default", "test"),
   665  				Spec: v1.GatewaySpec{
   666  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   667  				},
   668  				Status: gatewayStatus("1.2.3.4"),
   669  			}},
   670  			routes: []*v1.HTTPRoute{
   671  				{
   672  					ObjectMeta: metav1.ObjectMeta{
   673  						Name:      "without-hostame",
   674  						Namespace: "default",
   675  						Annotations: map[string]string{
   676  							hostnameAnnotationKey: "annotation.without-hostname.internal",
   677  						},
   678  					},
   679  					Spec: v1.HTTPRouteSpec{
   680  						Hostnames: nil,
   681  					},
   682  					Status: httpRouteStatus(gwParentRef("default", "test")),
   683  				},
   684  				{
   685  					ObjectMeta: metav1.ObjectMeta{
   686  						Name:      "with-hostame",
   687  						Namespace: "default",
   688  						Annotations: map[string]string{
   689  							hostnameAnnotationKey: "annotation.with-hostname.internal",
   690  						},
   691  					},
   692  					Spec: v1.HTTPRouteSpec{
   693  						Hostnames: hostnames("with-hostname.internal"),
   694  					},
   695  					Status: httpRouteStatus(gwParentRef("default", "test")),
   696  				},
   697  			},
   698  			endpoints: []*endpoint.Endpoint{
   699  				newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"),
   700  				newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"),
   701  				newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"),
   702  			},
   703  		},
   704  		{
   705  			title: "IgnoreHostnameAnnotation",
   706  			config: Config{
   707  				IgnoreHostnameAnnotation: true,
   708  			},
   709  			namespaces: namespaces("default"),
   710  			gateways: []*v1.Gateway{{
   711  				ObjectMeta: objectMeta("default", "test"),
   712  				Spec: v1.GatewaySpec{
   713  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   714  				},
   715  				Status: gatewayStatus("1.2.3.4"),
   716  			}},
   717  			routes: []*v1.HTTPRoute{{
   718  				ObjectMeta: metav1.ObjectMeta{
   719  					Name:      "with-hostame",
   720  					Namespace: "default",
   721  					Annotations: map[string]string{
   722  						hostnameAnnotationKey: "annotation.with-hostname.internal",
   723  					},
   724  				},
   725  				Spec: v1.HTTPRouteSpec{
   726  					Hostnames: hostnames("with-hostname.internal"),
   727  				},
   728  				Status: httpRouteStatus(gwParentRef("default", "test")),
   729  			}},
   730  			endpoints: []*endpoint.Endpoint{
   731  				newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"),
   732  			},
   733  		},
   734  		{
   735  			title: "FQDNTemplate",
   736  			config: Config{
   737  				FQDNTemplate: "{{.Name}}.zero.internal, {{.Name}}.one.internal. ,  {{.Name}}.two.internal  ",
   738  			},
   739  			namespaces: namespaces("default"),
   740  			gateways: []*v1.Gateway{{
   741  				ObjectMeta: objectMeta("default", "test"),
   742  				Spec: v1.GatewaySpec{
   743  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   744  				},
   745  				Status: gatewayStatus("1.2.3.4"),
   746  			}},
   747  			routes: []*v1.HTTPRoute{
   748  				{
   749  					ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
   750  					Spec: v1.HTTPRouteSpec{
   751  						Hostnames: hostnames("fqdn-with-hostnames.internal"),
   752  					},
   753  					Status: httpRouteStatus(gwParentRef("default", "test")),
   754  				},
   755  				{
   756  					ObjectMeta: objectMeta("default", "fqdn-without-hostnames"),
   757  					Spec: v1.HTTPRouteSpec{
   758  						Hostnames: nil,
   759  					},
   760  					Status: httpRouteStatus(gwParentRef("default", "test")),
   761  				},
   762  			},
   763  			endpoints: []*endpoint.Endpoint{
   764  				newTestEndpoint("fqdn-without-hostnames.zero.internal", "A", "1.2.3.4"),
   765  				newTestEndpoint("fqdn-without-hostnames.one.internal", "A", "1.2.3.4"),
   766  				newTestEndpoint("fqdn-without-hostnames.two.internal", "A", "1.2.3.4"),
   767  				newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"),
   768  			},
   769  		},
   770  		{
   771  			title: "CombineFQDN",
   772  			config: Config{
   773  				FQDNTemplate:             "combine-{{.Name}}.internal",
   774  				CombineFQDNAndAnnotation: true,
   775  			},
   776  			namespaces: namespaces("default"),
   777  			gateways: []*v1.Gateway{{
   778  				ObjectMeta: objectMeta("default", "test"),
   779  				Spec: v1.GatewaySpec{
   780  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   781  				},
   782  				Status: gatewayStatus("1.2.3.4"),
   783  			}},
   784  			routes: []*v1.HTTPRoute{{
   785  				ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
   786  				Spec: v1.HTTPRouteSpec{
   787  					Hostnames: hostnames("fqdn-with-hostnames.internal"),
   788  				},
   789  				Status: httpRouteStatus(gwParentRef("default", "test")),
   790  			}},
   791  			endpoints: []*endpoint.Endpoint{
   792  				newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"),
   793  				newTestEndpoint("combine-fqdn-with-hostnames.internal", "A", "1.2.3.4"),
   794  			},
   795  		},
   796  		{
   797  			title:      "TTL",
   798  			config:     Config{},
   799  			namespaces: namespaces("default"),
   800  			gateways: []*v1.Gateway{{
   801  				ObjectMeta: objectMeta("default", "test"),
   802  				Spec: v1.GatewaySpec{
   803  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   804  				},
   805  				Status: gatewayStatus("1.2.3.4"),
   806  			}},
   807  			routes: []*v1.HTTPRoute{
   808  				{
   809  					ObjectMeta: metav1.ObjectMeta{
   810  						Name:        "valid-ttl",
   811  						Namespace:   "default",
   812  						Annotations: map[string]string{ttlAnnotationKey: "15s"},
   813  					},
   814  					Spec: v1.HTTPRouteSpec{
   815  						Hostnames: hostnames("valid-ttl.internal"),
   816  					},
   817  					Status: httpRouteStatus(gwParentRef("default", "test")),
   818  				},
   819  				{
   820  					ObjectMeta: metav1.ObjectMeta{
   821  						Name:        "invalid-ttl",
   822  						Namespace:   "default",
   823  						Annotations: map[string]string{ttlAnnotationKey: "abc"},
   824  					},
   825  					Spec: v1.HTTPRouteSpec{
   826  						Hostnames: hostnames("invalid-ttl.internal"),
   827  					},
   828  					Status: httpRouteStatus(gwParentRef("default", "test")),
   829  				},
   830  			},
   831  			endpoints: []*endpoint.Endpoint{
   832  				newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"),
   833  				newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"),
   834  			},
   835  		},
   836  		{
   837  			title:      "ProviderAnnotations",
   838  			config:     Config{},
   839  			namespaces: namespaces("default"),
   840  			gateways: []*v1.Gateway{{
   841  				ObjectMeta: objectMeta("default", "test"),
   842  				Spec: v1.GatewaySpec{
   843  					Listeners: []v1.Listener{{Protocol: v1.HTTPProtocolType}},
   844  				},
   845  				Status: gatewayStatus("1.2.3.4"),
   846  			}},
   847  			routes: []*v1.HTTPRoute{{
   848  				ObjectMeta: metav1.ObjectMeta{
   849  					Name:      "provider-annotations",
   850  					Namespace: "default",
   851  					Annotations: map[string]string{
   852  						SetIdentifierKey:   "test-set-identifier",
   853  						aliasAnnotationKey: "true",
   854  					},
   855  				},
   856  				Spec: v1.HTTPRouteSpec{
   857  					Hostnames: hostnames("provider-annotations.com"),
   858  				},
   859  				Status: httpRouteStatus(gwParentRef("default", "test")),
   860  			}},
   861  			endpoints: []*endpoint.Endpoint{
   862  				newTestEndpoint("provider-annotations.com", "A", "1.2.3.4").
   863  					WithProviderSpecific("alias", "true").
   864  					WithSetIdentifier("test-set-identifier"),
   865  			},
   866  		},
   867  		{
   868  			title:      "DifferentHostnameDifferentGateway",
   869  			config:     Config{},
   870  			namespaces: namespaces("default"),
   871  			gateways: []*v1.Gateway{
   872  				{
   873  					ObjectMeta: objectMeta("default", "one"),
   874  					Spec: v1.GatewaySpec{
   875  						Listeners: []v1.Listener{{
   876  							Hostname: hostnamePtr("*.one.internal"),
   877  							Protocol: v1.HTTPProtocolType,
   878  						}},
   879  					},
   880  					Status: gatewayStatus("1.2.3.4"),
   881  				},
   882  				{
   883  					ObjectMeta: objectMeta("default", "two"),
   884  					Spec: v1.GatewaySpec{
   885  						Listeners: []v1.Listener{{
   886  							Hostname: hostnamePtr("*.two.internal"),
   887  							Protocol: v1.HTTPProtocolType,
   888  						}},
   889  					},
   890  					Status: gatewayStatus("2.3.4.5"),
   891  				},
   892  			},
   893  			routes: []*v1.HTTPRoute{{
   894  				ObjectMeta: objectMeta("default", "test"),
   895  				Spec: v1.HTTPRouteSpec{
   896  					Hostnames: hostnames("test.one.internal", "test.two.internal"),
   897  				},
   898  				Status: httpRouteStatus(
   899  					gwParentRef("default", "one"),
   900  					gwParentRef("default", "two"),
   901  				),
   902  			}},
   903  			endpoints: []*endpoint.Endpoint{
   904  				newTestEndpoint("test.one.internal", "A", "1.2.3.4"),
   905  				newTestEndpoint("test.two.internal", "A", "2.3.4.5"),
   906  			},
   907  		},
   908  		{
   909  			title:      "AllowedRoutesSameNamespace",
   910  			config:     Config{},
   911  			namespaces: namespaces("same-namespace", "other-namespace"),
   912  			gateways: []*v1.Gateway{{
   913  				ObjectMeta: objectMeta("same-namespace", "test"),
   914  				Spec: v1.GatewaySpec{
   915  					Listeners: []v1.Listener{{
   916  						Protocol: v1.HTTPProtocolType,
   917  						AllowedRoutes: &v1.AllowedRoutes{
   918  							Namespaces: &v1.RouteNamespaces{
   919  								From: &fromSame,
   920  							},
   921  						},
   922  					}},
   923  				},
   924  				Status: gatewayStatus("1.2.3.4"),
   925  			}},
   926  			routes: []*v1.HTTPRoute{
   927  				{
   928  					ObjectMeta: objectMeta("same-namespace", "test"),
   929  					Spec: v1.HTTPRouteSpec{
   930  						Hostnames: hostnames("same-namespace.example.internal"),
   931  					},
   932  					Status: httpRouteStatus(gwParentRef("same-namespace", "test")),
   933  				},
   934  				{
   935  					ObjectMeta: objectMeta("other-namespace", "test"),
   936  					Spec: v1.HTTPRouteSpec{
   937  						Hostnames: hostnames("other-namespace.example.internal"),
   938  					},
   939  					Status: httpRouteStatus(gwParentRef("same-namespace", "test")),
   940  				},
   941  			},
   942  			endpoints: []*endpoint.Endpoint{
   943  				newTestEndpoint("same-namespace.example.internal", "A", "1.2.3.4"),
   944  			},
   945  		},
   946  		{
   947  			title:  "AllowedRoutesNamespaceSelector",
   948  			config: Config{},
   949  			namespaces: []*corev1.Namespace{
   950  				{
   951  					ObjectMeta: metav1.ObjectMeta{
   952  						Name: "default",
   953  					},
   954  				},
   955  				{
   956  					ObjectMeta: metav1.ObjectMeta{
   957  						Name:   "foo",
   958  						Labels: map[string]string{"team": "foo"},
   959  					},
   960  				},
   961  				{
   962  					ObjectMeta: metav1.ObjectMeta{
   963  						Name:   "bar",
   964  						Labels: map[string]string{"team": "bar"},
   965  					},
   966  				},
   967  			},
   968  			gateways: []*v1.Gateway{{
   969  				ObjectMeta: objectMeta("default", "test"),
   970  				Spec: v1.GatewaySpec{
   971  					Listeners: []v1.Listener{{
   972  						Protocol: v1.HTTPProtocolType,
   973  						AllowedRoutes: &v1.AllowedRoutes{
   974  							Namespaces: &v1.RouteNamespaces{
   975  								From: &fromSelector,
   976  								Selector: &metav1.LabelSelector{
   977  									MatchLabels: map[string]string{"team": "foo"},
   978  								},
   979  							},
   980  						},
   981  					}},
   982  				},
   983  				Status: gatewayStatus("1.2.3.4"),
   984  			}},
   985  			routes: []*v1.HTTPRoute{
   986  				{
   987  					ObjectMeta: objectMeta("foo", "test"),
   988  					Spec: v1.HTTPRouteSpec{
   989  						Hostnames: hostnames("foo.example.internal"),
   990  					},
   991  					Status: httpRouteStatus(gwParentRef("default", "test")),
   992  				},
   993  				{
   994  					ObjectMeta: objectMeta("bar", "test"),
   995  					Spec: v1.HTTPRouteSpec{
   996  						Hostnames: hostnames("bar.example.internal"),
   997  					},
   998  					Status: httpRouteStatus(gwParentRef("default", "test")),
   999  				},
  1000  			},
  1001  			endpoints: []*endpoint.Endpoint{
  1002  				newTestEndpoint("foo.example.internal", "A", "1.2.3.4"),
  1003  			},
  1004  		},
  1005  		{
  1006  			title:      "MissingNamespace",
  1007  			config:     Config{},
  1008  			namespaces: nil,
  1009  			gateways: []*v1.Gateway{{
  1010  				ObjectMeta: objectMeta("default", "test"),
  1011  				Spec: v1.GatewaySpec{
  1012  					Listeners: []v1.Listener{{
  1013  						Protocol: v1.HTTPProtocolType,
  1014  						AllowedRoutes: &v1.AllowedRoutes{
  1015  							Namespaces: &v1.RouteNamespaces{
  1016  								// Namespace selector triggers namespace lookup.
  1017  								From: &fromSelector,
  1018  								Selector: &metav1.LabelSelector{
  1019  									MatchLabels: map[string]string{"foo": "bar"},
  1020  								},
  1021  							},
  1022  						},
  1023  					}},
  1024  				},
  1025  				Status: gatewayStatus("1.2.3.4"),
  1026  			}},
  1027  			routes: []*v1.HTTPRoute{{
  1028  				ObjectMeta: objectMeta("default", "test"),
  1029  				Spec: v1.HTTPRouteSpec{
  1030  					Hostnames: hostnames("example.internal"),
  1031  				},
  1032  				Status: httpRouteStatus(gwParentRef("default", "test")),
  1033  			}},
  1034  			endpoints: nil,
  1035  		},
  1036  		{
  1037  			title: "AnnotationOverride",
  1038  			config: Config{
  1039  				GatewayNamespace: "gateway-namespace",
  1040  			},
  1041  			namespaces: namespaces("gateway-namespace", "route-namespace"),
  1042  			gateways: []*v1.Gateway{
  1043  				{
  1044  					ObjectMeta: metav1.ObjectMeta{
  1045  						Name:      "overriden-gateway",
  1046  						Namespace: "gateway-namespace",
  1047  						Annotations: map[string]string{
  1048  							targetAnnotationKey: "4.3.2.1",
  1049  						},
  1050  					},
  1051  					Spec: v1.GatewaySpec{
  1052  						Listeners: []v1.Listener{{
  1053  							Protocol:      v1.HTTPProtocolType,
  1054  							AllowedRoutes: allowAllNamespaces,
  1055  						}},
  1056  					},
  1057  					Status: gatewayStatus("1.2.3.4"),
  1058  				},
  1059  			},
  1060  			routes: []*v1.HTTPRoute{{
  1061  				ObjectMeta: objectMeta("route-namespace", "test"),
  1062  				Spec: v1.HTTPRouteSpec{
  1063  					Hostnames: hostnames("test.example.internal"),
  1064  				},
  1065  				Status: httpRouteStatus( // The route is attached to both gateways.
  1066  					gwParentRef("gateway-namespace", "overriden-gateway"),
  1067  				),
  1068  			}},
  1069  			endpoints: []*endpoint.Endpoint{
  1070  				newTestEndpoint("test.example.internal", "A", "4.3.2.1"),
  1071  			},
  1072  		},
  1073  		{
  1074  			title: "MutlipleGatewaysOneAnnotationOverride",
  1075  			config: Config{
  1076  				GatewayNamespace: "gateway-namespace",
  1077  			},
  1078  			namespaces: namespaces("gateway-namespace", "route-namespace"),
  1079  			gateways: []*v1.Gateway{
  1080  				{
  1081  					ObjectMeta: metav1.ObjectMeta{
  1082  						Name:      "overriden-gateway",
  1083  						Namespace: "gateway-namespace",
  1084  						Annotations: map[string]string{
  1085  							targetAnnotationKey: "4.3.2.1",
  1086  						},
  1087  					},
  1088  					Spec: v1.GatewaySpec{
  1089  						Listeners: []v1.Listener{{
  1090  							Protocol:      v1.HTTPProtocolType,
  1091  							AllowedRoutes: allowAllNamespaces,
  1092  						}},
  1093  					},
  1094  					Status: gatewayStatus("1.2.3.4"),
  1095  				},
  1096  				{
  1097  					ObjectMeta: objectMeta("gateway-namespace", "test"),
  1098  					Spec: v1.GatewaySpec{
  1099  						Listeners: []v1.Listener{{
  1100  							Protocol:      v1.HTTPProtocolType,
  1101  							AllowedRoutes: allowAllNamespaces,
  1102  						}},
  1103  					},
  1104  					Status: gatewayStatus("2.3.4.5"),
  1105  				},
  1106  			},
  1107  			routes: []*v1.HTTPRoute{{
  1108  				ObjectMeta: objectMeta("route-namespace", "test"),
  1109  				Spec: v1.HTTPRouteSpec{
  1110  					Hostnames: hostnames("test.example.internal"),
  1111  				},
  1112  				Status: httpRouteStatus( // The route is attached to both gateways.
  1113  					gwParentRef("gateway-namespace", "overriden-gateway"),
  1114  					gwParentRef("gateway-namespace", "test"),
  1115  				),
  1116  			}},
  1117  			endpoints: []*endpoint.Endpoint{
  1118  				newTestEndpoint("test.example.internal", "A", "4.3.2.1", "2.3.4.5"),
  1119  			},
  1120  		},
  1121  	}
  1122  	for _, tt := range tests {
  1123  		tt := tt
  1124  		t.Run(tt.title, func(t *testing.T) {
  1125  			t.Parallel()
  1126  
  1127  			ctx := context.Background()
  1128  			gwClient := gatewayfake.NewSimpleClientset()
  1129  			for _, gw := range tt.gateways {
  1130  				_, err := gwClient.GatewayV1().Gateways(gw.Namespace).Create(ctx, gw, metav1.CreateOptions{})
  1131  				require.NoError(t, err, "failed to create Gateway")
  1132  
  1133  			}
  1134  			for _, rt := range tt.routes {
  1135  				_, err := gwClient.GatewayV1().HTTPRoutes(rt.Namespace).Create(ctx, rt, metav1.CreateOptions{})
  1136  				require.NoError(t, err, "failed to create HTTPRoute")
  1137  			}
  1138  			kubeClient := kubefake.NewSimpleClientset()
  1139  			for _, ns := range tt.namespaces {
  1140  				_, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
  1141  				require.NoError(t, err, "failed to create Namespace")
  1142  			}
  1143  
  1144  			clients := new(MockClientGenerator)
  1145  			clients.On("GatewayClient").Return(gwClient, nil)
  1146  			clients.On("KubeClient").Return(kubeClient, nil)
  1147  
  1148  			src, err := NewGatewayHTTPRouteSource(clients, &tt.config)
  1149  			require.NoError(t, err, "failed to create Gateway HTTPRoute Source")
  1150  
  1151  			endpoints, err := src.Endpoints(ctx)
  1152  			require.NoError(t, err, "failed to get Endpoints")
  1153  			validateEndpoints(t, endpoints, tt.endpoints)
  1154  		})
  1155  	}
  1156  }
  1157  
  1158  func hostnamePtr(val v1.Hostname) *v1.Hostname { return &val }