github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/httproute_reconcile_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package gateway_api
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	corev1 "k8s.io/api/core/v1"
    13  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/types"
    16  	ctrl "sigs.k8s.io/controller-runtime"
    17  	"sigs.k8s.io/controller-runtime/pkg/client"
    18  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    19  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    20  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    21  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    22  	mcsapicontrollers "sigs.k8s.io/mcs-api/pkg/controllers"
    23  
    24  	"github.com/cilium/cilium/operator/pkg/model"
    25  )
    26  
    27  var (
    28  	httpRFFinalizer = "batch.gateway.io/finalizer"
    29  
    30  	crdsFixture = []client.Object{
    31  		// Minimal ServiceImport CRD for existence checking
    32  		&apiextensionsv1.CustomResourceDefinition{
    33  			ObjectMeta: metav1.ObjectMeta{
    34  				Name: "serviceimports.multicluster.x-k8s.io",
    35  			},
    36  			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
    37  				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{{
    38  					Name: "v1alpha1",
    39  				}},
    40  			},
    41  		},
    42  	}
    43  
    44  	httpRouteServiceImportFixture = []client.Object{
    45  		// Service for valid HTTPRoute
    46  		&mcsapiv1alpha1.ServiceImport{
    47  			ObjectMeta: metav1.ObjectMeta{
    48  				Name:      "dummy-backend",
    49  				Namespace: "default",
    50  				Annotations: map[string]string{
    51  					mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend",
    52  				},
    53  			},
    54  		},
    55  
    56  		// Service in another namespace
    57  		&mcsapiv1alpha1.ServiceImport{
    58  			ObjectMeta: metav1.ObjectMeta{
    59  				Name:      "dummy-backend",
    60  				Namespace: "another-namespace",
    61  				Annotations: map[string]string{
    62  					mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend",
    63  				},
    64  			},
    65  		},
    66  
    67  		// Service for reference grant in another namespace
    68  		&mcsapiv1alpha1.ServiceImport{
    69  			ObjectMeta: metav1.ObjectMeta{
    70  				Name:      "dummy-backend-grant",
    71  				Namespace: "another-namespace",
    72  				Annotations: map[string]string{
    73  					mcsapicontrollers.DerivedServiceAnnotation: "dummy-backend-grant",
    74  				},
    75  			},
    76  		},
    77  
    78  		// ServiceImport with Service that doesn't exists
    79  		&mcsapiv1alpha1.ServiceImport{
    80  			ObjectMeta: metav1.ObjectMeta{
    81  				Name:      "dummy-backend-no-svc",
    82  				Namespace: "default",
    83  				Annotations: map[string]string{
    84  					mcsapicontrollers.DerivedServiceAnnotation: "nonexistent-derived-svc",
    85  				},
    86  			},
    87  		},
    88  		&mcsapiv1alpha1.ServiceImport{
    89  			ObjectMeta: metav1.ObjectMeta{
    90  				Name:      "dummy-backend-no-svc-annotation",
    91  				Namespace: "default",
    92  			},
    93  		},
    94  	}
    95  
    96  	httpRouteFixture = []client.Object{
    97  		// GatewayClass
    98  		&gatewayv1.GatewayClass{
    99  			ObjectMeta: metav1.ObjectMeta{
   100  				Name: "cilium",
   101  			},
   102  			Spec: gatewayv1.GatewayClassSpec{
   103  				ControllerName: "io.cilium/gateway-controller",
   104  			},
   105  		},
   106  
   107  		// Gateway for valid HTTPRoute
   108  		&gatewayv1.Gateway{
   109  			ObjectMeta: metav1.ObjectMeta{
   110  				Name:      "dummy-gateway",
   111  				Namespace: "default",
   112  			},
   113  			Spec: gatewayv1.GatewaySpec{
   114  				GatewayClassName: "cilium",
   115  				Listeners: []gatewayv1.Listener{
   116  					{
   117  						Name:     "http",
   118  						Port:     80,
   119  						Hostname: model.AddressOf[gatewayv1.Hostname]("*.cilium.io"),
   120  					},
   121  				},
   122  			},
   123  			Status: gatewayv1.GatewayStatus{},
   124  		},
   125  
   126  		// Gateway for valid HTTPRoute with hostnames
   127  		&gatewayv1.Gateway{
   128  			ObjectMeta: metav1.ObjectMeta{
   129  				Name:      "dummy-gateway-hostnames",
   130  				Namespace: "default",
   131  			},
   132  			Spec: gatewayv1.GatewaySpec{
   133  				GatewayClassName: "cilium",
   134  				Listeners: []gatewayv1.Listener{
   135  					{
   136  						Name:     "http",
   137  						Port:     80,
   138  						Hostname: model.AddressOf[gatewayv1.Hostname]("bar.foo.com"),
   139  					},
   140  				},
   141  			},
   142  			Status: gatewayv1.GatewayStatus{},
   143  		},
   144  
   145  		// Gateway in another namespace
   146  		&gatewayv1.Gateway{
   147  			ObjectMeta: metav1.ObjectMeta{
   148  				Name:      "dummy-gateway",
   149  				Namespace: "another-namespace",
   150  			},
   151  			Spec: gatewayv1.GatewaySpec{
   152  				GatewayClassName: "cilium",
   153  				Listeners: []gatewayv1.Listener{
   154  					{
   155  						Name: "http",
   156  						Port: 80,
   157  						AllowedRoutes: &gatewayv1.AllowedRoutes{
   158  							Namespaces: &gatewayv1.RouteNamespaces{
   159  								From: model.AddressOf(gatewayv1.NamespacesFromSame),
   160  							},
   161  						},
   162  					},
   163  				},
   164  			},
   165  			Status: gatewayv1.GatewayStatus{},
   166  		},
   167  
   168  		// Gateway in default namespace
   169  		&gatewayv1.Gateway{
   170  			ObjectMeta: metav1.ObjectMeta{
   171  				Name:      "dummy-gateway-two-listeners",
   172  				Namespace: "default",
   173  			},
   174  			Spec: gatewayv1.GatewaySpec{
   175  				GatewayClassName: "cilium",
   176  				Listeners: []gatewayv1.Listener{
   177  					{
   178  						Name: "http",
   179  						Port: 80,
   180  						AllowedRoutes: &gatewayv1.AllowedRoutes{
   181  							Namespaces: &gatewayv1.RouteNamespaces{
   182  								From: model.AddressOf(gatewayv1.NamespacesFromSame),
   183  							},
   184  						},
   185  					},
   186  					{
   187  						Name: "https",
   188  						Port: 443,
   189  						AllowedRoutes: &gatewayv1.AllowedRoutes{
   190  							Namespaces: &gatewayv1.RouteNamespaces{
   191  								From: model.AddressOf(gatewayv1.NamespacesFromAll),
   192  							},
   193  						},
   194  					},
   195  				},
   196  			},
   197  			Status: gatewayv1.GatewayStatus{},
   198  		},
   199  		// Service for valid HTTPRoute
   200  		&corev1.Service{
   201  			ObjectMeta: metav1.ObjectMeta{
   202  				Name:      "dummy-backend",
   203  				Namespace: "default",
   204  			},
   205  		},
   206  
   207  		// Service in another namespace
   208  		&corev1.Service{
   209  			ObjectMeta: metav1.ObjectMeta{
   210  				Name:      "dummy-backend",
   211  				Namespace: "another-namespace",
   212  			},
   213  		},
   214  
   215  		// Service for reference grant in another namespace
   216  		&corev1.Service{
   217  			ObjectMeta: metav1.ObjectMeta{
   218  				Name:      "dummy-backend-grant",
   219  				Namespace: "another-namespace",
   220  			},
   221  		},
   222  
   223  		// Deleting HTTPRoute
   224  		&gatewayv1.HTTPRoute{
   225  			ObjectMeta: metav1.ObjectMeta{
   226  				Name:              "deleting-http-route",
   227  				Namespace:         "default",
   228  				Finalizers:        []string{httpRFFinalizer},
   229  				DeletionTimestamp: &metav1.Time{Time: time.Now()},
   230  			},
   231  			Spec: gatewayv1.HTTPRouteSpec{},
   232  		},
   233  
   234  		// Valid HTTPRoute
   235  		&gatewayv1.HTTPRoute{
   236  			ObjectMeta: metav1.ObjectMeta{
   237  				Name:      "valid-http-route-service",
   238  				Namespace: "default",
   239  			},
   240  			Spec: gatewayv1.HTTPRouteSpec{
   241  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   242  					ParentRefs: []gatewayv1.ParentReference{
   243  						{
   244  							Name: "dummy-gateway",
   245  						},
   246  					},
   247  				},
   248  				Rules: []gatewayv1.HTTPRouteRule{
   249  					{
   250  						BackendRefs: []gatewayv1.HTTPBackendRef{
   251  							{
   252  								BackendRef: gatewayv1.BackendRef{
   253  									BackendObjectReference: gatewayv1.BackendObjectReference{
   254  										Name: "dummy-backend",
   255  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   256  									},
   257  								},
   258  							},
   259  						},
   260  					},
   261  				},
   262  			},
   263  		},
   264  
   265  		// Valid HTTPRoute with Hostnames
   266  		&gatewayv1.HTTPRoute{
   267  			ObjectMeta: metav1.ObjectMeta{
   268  				Name:      "valid-http-route-hostname-service",
   269  				Namespace: "default",
   270  			},
   271  			Spec: gatewayv1.HTTPRouteSpec{
   272  				Hostnames: []gatewayv1.Hostname{
   273  					"bar.foo.com",
   274  				},
   275  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   276  					ParentRefs: []gatewayv1.ParentReference{
   277  						{
   278  							Name: "dummy-gateway-hostnames",
   279  						},
   280  					},
   281  				},
   282  				Rules: []gatewayv1.HTTPRouteRule{
   283  					{
   284  						BackendRefs: []gatewayv1.HTTPBackendRef{
   285  							{
   286  								BackendRef: gatewayv1.BackendRef{
   287  									BackendObjectReference: gatewayv1.BackendObjectReference{
   288  										Name: "dummy-backend",
   289  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   290  									},
   291  								},
   292  							},
   293  						},
   294  					},
   295  				},
   296  			},
   297  		},
   298  
   299  		// Valid HTTPRoute with Hostnames
   300  		&gatewayv1.HTTPRoute{
   301  			ObjectMeta: metav1.ObjectMeta{
   302  				Name:      "valid-http-route-hostname-serviceimport",
   303  				Namespace: "default",
   304  			},
   305  			Spec: gatewayv1.HTTPRouteSpec{
   306  				Hostnames: []gatewayv1.Hostname{
   307  					"bar.foo.com",
   308  				},
   309  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   310  					ParentRefs: []gatewayv1.ParentReference{
   311  						{
   312  							Name: "dummy-gateway-hostnames",
   313  						},
   314  					},
   315  				},
   316  				Rules: []gatewayv1.HTTPRouteRule{
   317  					{
   318  						BackendRefs: []gatewayv1.HTTPBackendRef{
   319  							{
   320  								BackendRef: gatewayv1.BackendRef{
   321  									BackendObjectReference: gatewayv1.BackendObjectReference{
   322  										Name: "dummy-backend",
   323  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   324  									},
   325  								},
   326  							},
   327  							{
   328  								BackendRef: gatewayv1.BackendRef{
   329  									BackendObjectReference: gatewayv1.BackendObjectReference{
   330  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   331  										Kind:  KindPtr("ServiceImport"),
   332  										Name:  "dummy-backend",
   333  										Port:  model.AddressOf[gatewayv1.PortNumber](8080),
   334  									},
   335  								},
   336  							},
   337  						},
   338  					},
   339  				},
   340  			},
   341  		},
   342  
   343  		&gatewayv1.HTTPRoute{
   344  			ObjectMeta: metav1.ObjectMeta{
   345  				Name:      "valid-http-route-serviceimport",
   346  				Namespace: "default",
   347  			},
   348  			Spec: gatewayv1.HTTPRouteSpec{
   349  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   350  					ParentRefs: []gatewayv1.ParentReference{
   351  						{
   352  							Name: "dummy-gateway",
   353  						},
   354  					},
   355  				},
   356  				Rules: []gatewayv1.HTTPRouteRule{
   357  					{
   358  						BackendRefs: []gatewayv1.HTTPBackendRef{
   359  							{
   360  								BackendRef: gatewayv1.BackendRef{
   361  									BackendObjectReference: gatewayv1.BackendObjectReference{
   362  										Name: "dummy-backend",
   363  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   364  									},
   365  								},
   366  							},
   367  							{
   368  								BackendRef: gatewayv1.BackendRef{
   369  									BackendObjectReference: gatewayv1.BackendObjectReference{
   370  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   371  										Kind:  KindPtr("ServiceImport"),
   372  										Name:  "dummy-backend",
   373  										Port:  model.AddressOf[gatewayv1.PortNumber](8080),
   374  									},
   375  								},
   376  							},
   377  						},
   378  					},
   379  				},
   380  			},
   381  		},
   382  
   383  		// HTTPRoute with nonexistent backend
   384  		&gatewayv1.HTTPRoute{
   385  			ObjectMeta: metav1.ObjectMeta{
   386  				Name:      "http-route-with-nonexistent-svc",
   387  				Namespace: "default",
   388  			},
   389  			Spec: gatewayv1.HTTPRouteSpec{
   390  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   391  					ParentRefs: []gatewayv1.ParentReference{
   392  						{
   393  							Name: "dummy-gateway",
   394  						},
   395  					},
   396  				},
   397  				Rules: []gatewayv1.HTTPRouteRule{
   398  					{
   399  						BackendRefs: []gatewayv1.HTTPBackendRef{
   400  							{
   401  								BackendRef: gatewayv1.BackendRef{
   402  									BackendObjectReference: gatewayv1.BackendObjectReference{
   403  										Name: "nonexistent-backend",
   404  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   405  									},
   406  								},
   407  							},
   408  						},
   409  					},
   410  				},
   411  			},
   412  		},
   413  		&gatewayv1.HTTPRoute{
   414  			ObjectMeta: metav1.ObjectMeta{
   415  				Name:      "http-route-with-nonexistent-svcimport",
   416  				Namespace: "default",
   417  			},
   418  			Spec: gatewayv1.HTTPRouteSpec{
   419  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   420  					ParentRefs: []gatewayv1.ParentReference{
   421  						{
   422  							Name: "dummy-gateway",
   423  						},
   424  					},
   425  				},
   426  				Rules: []gatewayv1.HTTPRouteRule{
   427  					{
   428  						BackendRefs: []gatewayv1.HTTPBackendRef{
   429  							{
   430  								BackendRef: gatewayv1.BackendRef{
   431  									BackendObjectReference: gatewayv1.BackendObjectReference{
   432  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   433  										Kind:  KindPtr("ServiceImport"),
   434  										Name:  "nonexistent-backend",
   435  										Port:  model.AddressOf[gatewayv1.PortNumber](8080),
   436  									},
   437  								},
   438  							},
   439  						},
   440  					},
   441  				},
   442  			},
   443  		},
   444  		&gatewayv1.HTTPRoute{
   445  			ObjectMeta: metav1.ObjectMeta{
   446  				Name:      "http-route-with-nonexistent-svcimport-svc",
   447  				Namespace: "default",
   448  			},
   449  			Spec: gatewayv1.HTTPRouteSpec{
   450  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   451  					ParentRefs: []gatewayv1.ParentReference{
   452  						{
   453  							Name: "dummy-gateway",
   454  						},
   455  					},
   456  				},
   457  				Rules: []gatewayv1.HTTPRouteRule{
   458  					{
   459  						BackendRefs: []gatewayv1.HTTPBackendRef{
   460  							{
   461  								BackendRef: gatewayv1.BackendRef{
   462  									BackendObjectReference: gatewayv1.BackendObjectReference{
   463  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   464  										Kind:  KindPtr("ServiceImport"),
   465  										Name:  "dummy-backend-no-svc",
   466  										Port:  model.AddressOf[gatewayv1.PortNumber](8080),
   467  									},
   468  								},
   469  							},
   470  						},
   471  					},
   472  				},
   473  			},
   474  		},
   475  		&gatewayv1.HTTPRoute{
   476  			ObjectMeta: metav1.ObjectMeta{
   477  				Name:      "http-route-with-nonexistent-svcimport-svc-annotation",
   478  				Namespace: "default",
   479  			},
   480  			Spec: gatewayv1.HTTPRouteSpec{
   481  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   482  					ParentRefs: []gatewayv1.ParentReference{
   483  						{
   484  							Name: "dummy-gateway",
   485  						},
   486  					},
   487  				},
   488  				Rules: []gatewayv1.HTTPRouteRule{
   489  					{
   490  						BackendRefs: []gatewayv1.HTTPBackendRef{
   491  							{
   492  								BackendRef: gatewayv1.BackendRef{
   493  									BackendObjectReference: gatewayv1.BackendObjectReference{
   494  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   495  										Kind:  KindPtr("ServiceImport"),
   496  										Name:  "dummy-backend-no-svc-annotation",
   497  										Port:  model.AddressOf[gatewayv1.PortNumber](8080),
   498  									},
   499  								},
   500  							},
   501  						},
   502  					},
   503  				},
   504  			},
   505  		},
   506  
   507  		// HTTPRoute with cross namespace backend
   508  		&gatewayv1.HTTPRoute{
   509  			ObjectMeta: metav1.ObjectMeta{
   510  				Name:      "http-route-with-cross-namespace-service",
   511  				Namespace: "default",
   512  			},
   513  			Spec: gatewayv1.HTTPRouteSpec{
   514  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   515  					ParentRefs: []gatewayv1.ParentReference{
   516  						{
   517  							Name: "dummy-gateway",
   518  						},
   519  					},
   520  				},
   521  				Rules: []gatewayv1.HTTPRouteRule{
   522  					{
   523  						BackendRefs: []gatewayv1.HTTPBackendRef{
   524  							{
   525  								BackendRef: gatewayv1.BackendRef{
   526  									BackendObjectReference: gatewayv1.BackendObjectReference{
   527  										Name:      "dummy-backend",
   528  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   529  									},
   530  								},
   531  							},
   532  						},
   533  					},
   534  				},
   535  			},
   536  		},
   537  		&gatewayv1.HTTPRoute{
   538  			ObjectMeta: metav1.ObjectMeta{
   539  				Name:      "http-route-with-cross-namespace-serviceimport",
   540  				Namespace: "default",
   541  			},
   542  			Spec: gatewayv1.HTTPRouteSpec{
   543  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   544  					ParentRefs: []gatewayv1.ParentReference{
   545  						{
   546  							Name: "dummy-gateway",
   547  						},
   548  					},
   549  				},
   550  				Rules: []gatewayv1.HTTPRouteRule{
   551  					{
   552  						BackendRefs: []gatewayv1.HTTPBackendRef{
   553  							{
   554  								BackendRef: gatewayv1.BackendRef{
   555  									BackendObjectReference: gatewayv1.BackendObjectReference{
   556  										Group:     GroupPtr(mcsapiv1alpha1.GroupName),
   557  										Kind:      KindPtr("ServiceImport"),
   558  										Name:      "dummy-backend",
   559  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   560  										Port:      model.AddressOf[gatewayv1.PortNumber](8080),
   561  									},
   562  								},
   563  							},
   564  						},
   565  					},
   566  				},
   567  			},
   568  		},
   569  		// HTTPRoute with cross namespace listener
   570  		&gatewayv1.HTTPRoute{
   571  			ObjectMeta: metav1.ObjectMeta{
   572  				Name:      "http-route-with-cross-namespace-listener",
   573  				Namespace: "another-namespace",
   574  			},
   575  			Spec: gatewayv1.HTTPRouteSpec{
   576  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   577  					ParentRefs: []gatewayv1.ParentReference{
   578  						{
   579  							Name:      "dummy-gateway-two-listeners",
   580  							Namespace: model.AddressOf[gatewayv1.Namespace]("default"),
   581  						},
   582  					},
   583  				},
   584  				Rules: []gatewayv1.HTTPRouteRule{
   585  					{
   586  						BackendRefs: []gatewayv1.HTTPBackendRef{
   587  							{
   588  								BackendRef: gatewayv1.BackendRef{
   589  									BackendObjectReference: gatewayv1.BackendObjectReference{
   590  										Name:      "dummy-backend",
   591  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   592  										Port:      model.AddressOf(gatewayv1.PortNumber(8080)),
   593  									},
   594  								},
   595  							},
   596  						},
   597  					},
   598  				},
   599  			},
   600  		},
   601  		// HTTPRoute with hostnames and cross namespace listener
   602  		&gatewayv1.HTTPRoute{
   603  			ObjectMeta: metav1.ObjectMeta{
   604  				Name:      "http-route-with-hostnames-and-cross-namespace-listener",
   605  				Namespace: "another-namespace",
   606  			},
   607  			Spec: gatewayv1.HTTPRouteSpec{
   608  				Hostnames: []gatewayv1.Hostname{
   609  					"bar.foo.com",
   610  				},
   611  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   612  					ParentRefs: []gatewayv1.ParentReference{
   613  						{
   614  							Name:      "dummy-gateway-two-listeners",
   615  							Namespace: model.AddressOf[gatewayv1.Namespace]("default"),
   616  						},
   617  					},
   618  				},
   619  				Rules: []gatewayv1.HTTPRouteRule{
   620  					{
   621  						BackendRefs: []gatewayv1.HTTPBackendRef{
   622  							{
   623  								BackendRef: gatewayv1.BackendRef{
   624  									BackendObjectReference: gatewayv1.BackendObjectReference{
   625  										Name:      "dummy-backend",
   626  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   627  										Port:      model.AddressOf(gatewayv1.PortNumber(8080)),
   628  									},
   629  								},
   630  							},
   631  						},
   632  					},
   633  				},
   634  			},
   635  		},
   636  		// HTTPRoute with cross namespace backend
   637  		&gatewayv1.HTTPRoute{
   638  			ObjectMeta: metav1.ObjectMeta{
   639  				Name:      "http-route-with-cross-namespace-backend-with-grant",
   640  				Namespace: "default",
   641  			},
   642  			Spec: gatewayv1.HTTPRouteSpec{
   643  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   644  					ParentRefs: []gatewayv1.ParentReference{
   645  						{
   646  							Name: "dummy-gateway",
   647  						},
   648  					},
   649  				},
   650  				Rules: []gatewayv1.HTTPRouteRule{
   651  					{
   652  						BackendRefs: []gatewayv1.HTTPBackendRef{
   653  							{
   654  								BackendRef: gatewayv1.BackendRef{
   655  									BackendObjectReference: gatewayv1.BackendObjectReference{
   656  										Name:      "dummy-backend-grant",
   657  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   658  										Port:      model.AddressOf[gatewayv1.PortNumber](8080),
   659  									},
   660  								},
   661  							},
   662  							{
   663  								BackendRef: gatewayv1.BackendRef{
   664  									BackendObjectReference: gatewayv1.BackendObjectReference{
   665  										Group:     GroupPtr(mcsapiv1alpha1.GroupName),
   666  										Kind:      KindPtr("ServiceImport"),
   667  										Name:      "dummy-backend-grant",
   668  										Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   669  										Port:      model.AddressOf[gatewayv1.PortNumber](8080),
   670  									},
   671  								},
   672  							},
   673  						},
   674  					},
   675  				},
   676  			},
   677  		},
   678  
   679  		// ReferenceGrant to allow "http-route-with-cross-namespace-backend-with-grant
   680  		&gatewayv1beta1.ReferenceGrant{
   681  			ObjectMeta: metav1.ObjectMeta{
   682  				Name:      "allow-service-from-default",
   683  				Namespace: "another-namespace",
   684  			},
   685  			Spec: gatewayv1beta1.ReferenceGrantSpec{
   686  				From: []gatewayv1beta1.ReferenceGrantFrom{
   687  					{
   688  						Group:     "gateway.networking.k8s.io",
   689  						Kind:      "HTTPRoute",
   690  						Namespace: "default",
   691  					},
   692  				},
   693  				To: []gatewayv1beta1.ReferenceGrantTo{
   694  					{
   695  						Group: "",
   696  						Kind:  "Service",
   697  						Name:  ObjectNamePtr("dummy-backend-grant"),
   698  					},
   699  				},
   700  			},
   701  		},
   702  		&gatewayv1beta1.ReferenceGrant{
   703  			ObjectMeta: metav1.ObjectMeta{
   704  				Name:      "allow-service-import-from-default",
   705  				Namespace: "another-namespace",
   706  			},
   707  			Spec: gatewayv1beta1.ReferenceGrantSpec{
   708  				From: []gatewayv1beta1.ReferenceGrantFrom{
   709  					{
   710  						Group:     "gateway.networking.k8s.io",
   711  						Kind:      "HTTPRoute",
   712  						Namespace: "default",
   713  					},
   714  				},
   715  				To: []gatewayv1beta1.ReferenceGrantTo{
   716  					{
   717  						Group: mcsapiv1alpha1.GroupName,
   718  						Kind:  "ServiceImport",
   719  						Name:  ObjectNamePtr("dummy-backend-grant"),
   720  					},
   721  				},
   722  			},
   723  		},
   724  
   725  		// HTTPRoute with unsupported backend
   726  		&gatewayv1.HTTPRoute{
   727  			ObjectMeta: metav1.ObjectMeta{
   728  				Name:      "http-route-with-unsupported-backend",
   729  				Namespace: "default",
   730  			},
   731  			Spec: gatewayv1.HTTPRouteSpec{
   732  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   733  					ParentRefs: []gatewayv1.ParentReference{
   734  						{
   735  							Name: "dummy-gateway",
   736  						},
   737  					},
   738  				},
   739  				Rules: []gatewayv1.HTTPRouteRule{
   740  					{
   741  						BackendRefs: []gatewayv1.HTTPBackendRef{
   742  							{
   743  								BackendRef: gatewayv1.BackendRef{
   744  									BackendObjectReference: gatewayv1.BackendObjectReference{
   745  										Name:  "unsupported-backend",
   746  										Group: GroupPtr("unsupported-group"),
   747  										Kind:  KindPtr("UnsupportedKind"),
   748  									},
   749  								},
   750  							},
   751  						},
   752  					},
   753  				},
   754  			},
   755  		},
   756  		// HTTPRoute missing port for Service and ServiceImport
   757  		&gatewayv1.HTTPRoute{
   758  			ObjectMeta: metav1.ObjectMeta{
   759  				Name:      "http-route-missing-port-for-backend-service",
   760  				Namespace: "default",
   761  			},
   762  			Spec: gatewayv1.HTTPRouteSpec{
   763  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   764  					ParentRefs: []gatewayv1.ParentReference{
   765  						{
   766  							Name: "dummy-gateway",
   767  						},
   768  					},
   769  				},
   770  				Rules: []gatewayv1.HTTPRouteRule{
   771  					{
   772  						BackendRefs: []gatewayv1.HTTPBackendRef{
   773  							{
   774  								BackendRef: gatewayv1.BackendRef{
   775  									BackendObjectReference: gatewayv1.BackendObjectReference{
   776  										Name:  "missing-port-service-backend",
   777  										Group: GroupPtr(""),
   778  										Kind:  KindPtr("Service"),
   779  									},
   780  								},
   781  							},
   782  						},
   783  					},
   784  				},
   785  			},
   786  		},
   787  		&gatewayv1.HTTPRoute{
   788  			ObjectMeta: metav1.ObjectMeta{
   789  				Name:      "http-route-missing-port-for-backend-serviceimport",
   790  				Namespace: "default",
   791  			},
   792  			Spec: gatewayv1.HTTPRouteSpec{
   793  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   794  					ParentRefs: []gatewayv1.ParentReference{
   795  						{
   796  							Name: "dummy-gateway",
   797  						},
   798  					},
   799  				},
   800  				Rules: []gatewayv1.HTTPRouteRule{
   801  					{
   802  						BackendRefs: []gatewayv1.HTTPBackendRef{
   803  							{
   804  								BackendRef: gatewayv1.BackendRef{
   805  									BackendObjectReference: gatewayv1.BackendObjectReference{
   806  										Group: GroupPtr(mcsapiv1alpha1.GroupName),
   807  										Kind:  KindPtr("ServiceImport"),
   808  										Name:  "missing-port-service-backend",
   809  									},
   810  								},
   811  							},
   812  						},
   813  					},
   814  				},
   815  			},
   816  		},
   817  
   818  		// HTTPRoute with non-existent gateway
   819  		&gatewayv1.HTTPRoute{
   820  			ObjectMeta: metav1.ObjectMeta{
   821  				Name:      "http-route-with-nonexistent-gateway",
   822  				Namespace: "default",
   823  			},
   824  			Spec: gatewayv1.HTTPRouteSpec{
   825  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   826  					ParentRefs: []gatewayv1.ParentReference{
   827  						{
   828  							Name: "non-existent-gateway",
   829  						},
   830  					},
   831  				},
   832  				Rules: []gatewayv1.HTTPRouteRule{
   833  					{
   834  						BackendRefs: []gatewayv1.HTTPBackendRef{
   835  							{
   836  								BackendRef: gatewayv1.BackendRef{
   837  									BackendObjectReference: gatewayv1.BackendObjectReference{
   838  										Name: "dummy-backend",
   839  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   840  									},
   841  								},
   842  							},
   843  						},
   844  					},
   845  				},
   846  			},
   847  		},
   848  
   849  		// HTTPRoute with valid but not allowed gateway
   850  		&gatewayv1.HTTPRoute{
   851  			ObjectMeta: metav1.ObjectMeta{
   852  				Name:      "http-route-with-not-allowed-gateway",
   853  				Namespace: "default",
   854  			},
   855  			Spec: gatewayv1.HTTPRouteSpec{
   856  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   857  					ParentRefs: []gatewayv1.ParentReference{
   858  						{
   859  							Name:      "dummy-gateway",
   860  							Namespace: model.AddressOf[gatewayv1.Namespace]("another-namespace"),
   861  						},
   862  					},
   863  				},
   864  				Rules: []gatewayv1.HTTPRouteRule{
   865  					{
   866  						BackendRefs: []gatewayv1.HTTPBackendRef{
   867  							{
   868  								BackendRef: gatewayv1.BackendRef{
   869  									BackendObjectReference: gatewayv1.BackendObjectReference{
   870  										Name: "dummy-backend",
   871  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   872  									},
   873  								},
   874  							},
   875  						},
   876  					},
   877  				},
   878  			},
   879  		},
   880  
   881  		// HTTPRoute with non-matching hostname with gateway listener
   882  		&gatewayv1.HTTPRoute{
   883  			ObjectMeta: metav1.ObjectMeta{
   884  				Name:      "http-route-with-non-matching-hostname",
   885  				Namespace: "default",
   886  			},
   887  			Spec: gatewayv1.HTTPRouteSpec{
   888  				CommonRouteSpec: gatewayv1.CommonRouteSpec{
   889  					ParentRefs: []gatewayv1.ParentReference{
   890  						{
   891  							Name: "dummy-gateway",
   892  						},
   893  					},
   894  				},
   895  				Hostnames: []gatewayv1.Hostname{
   896  					"non-matching-hostname",
   897  				},
   898  				Rules: []gatewayv1.HTTPRouteRule{
   899  					{
   900  						BackendRefs: []gatewayv1.HTTPBackendRef{
   901  							{
   902  								BackendRef: gatewayv1.BackendRef{
   903  									BackendObjectReference: gatewayv1.BackendObjectReference{
   904  										Name: "dummy-backend",
   905  										Port: model.AddressOf[gatewayv1.PortNumber](8080),
   906  									},
   907  								},
   908  							},
   909  						},
   910  					},
   911  				},
   912  			},
   913  		},
   914  	}
   915  )
   916  
   917  func Test_httpRouteReconciler_Reconcile(t *testing.T) {
   918  	scheme := testScheme()
   919  	mcsapiv1alpha1.AddToScheme(scheme)
   920  
   921  	c := fake.NewClientBuilder().
   922  		WithScheme(scheme).
   923  		WithObjects(crdsFixture...).
   924  		WithObjects(httpRouteFixture...).
   925  		WithObjects(httpRouteServiceImportFixture...).
   926  		WithStatusSubresource(&gatewayv1.HTTPRoute{}).
   927  		Build()
   928  
   929  	r := &httpRouteReconciler{Client: c}
   930  
   931  	t.Run("no http route", func(t *testing.T) {
   932  		result, err := r.Reconcile(context.Background(), ctrl.Request{
   933  			NamespacedName: types.NamespacedName{
   934  				Name:      "non-existing-http-route",
   935  				Namespace: "default",
   936  			},
   937  		})
   938  		require.NoError(t, err)
   939  		require.Equal(t, ctrl.Result{}, result)
   940  	})
   941  
   942  	t.Run("http route exists but being deleted", func(t *testing.T) {
   943  		result, err := r.Reconcile(context.Background(), ctrl.Request{
   944  			NamespacedName: types.NamespacedName{
   945  				Name:      "deleting-http-route",
   946  				Namespace: "default",
   947  			},
   948  		})
   949  
   950  		require.NoError(t, err, "Error reconciling httpRoute")
   951  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
   952  	})
   953  
   954  	t.Run("valid http route", func(t *testing.T) {
   955  		for _, name := range []string{"service", "serviceimport"} {
   956  			key := types.NamespacedName{
   957  				Name:      "valid-http-route-" + name,
   958  				Namespace: "default",
   959  			}
   960  			result, err := r.Reconcile(context.Background(), ctrl.Request{
   961  				NamespacedName: key,
   962  			})
   963  
   964  			require.NoError(t, err, "Error reconciling httpRoute")
   965  			require.Equal(t, ctrl.Result{}, result, "Result should be empty")
   966  
   967  			route := &gatewayv1.HTTPRoute{}
   968  			err = c.Get(context.Background(), key, route)
   969  
   970  			require.NoError(t, err)
   971  			require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
   972  			require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
   973  
   974  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
   975  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
   976  
   977  			require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
   978  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
   979  		}
   980  	})
   981  
   982  	t.Run("valid http route and hostname matches", func(t *testing.T) {
   983  		for _, name := range []string{"service", "serviceimport"} {
   984  			key := types.NamespacedName{
   985  				Name:      "valid-http-route-hostname-" + name,
   986  				Namespace: "default",
   987  			}
   988  			result, err := r.Reconcile(context.Background(), ctrl.Request{
   989  				NamespacedName: key,
   990  			})
   991  
   992  			require.NoError(t, err, "Error reconciling httpRoute")
   993  			require.Equal(t, ctrl.Result{}, result, "Result should be empty")
   994  
   995  			route := &gatewayv1.HTTPRoute{}
   996  			err = c.Get(context.Background(), key, route)
   997  
   998  			require.NoError(t, err)
   999  			require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1000  			require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1001  
  1002  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1003  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1004  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Reason)
  1005  			require.Equal(t, "Accepted HTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Message)
  1006  
  1007  			require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1008  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1009  		}
  1010  	})
  1011  
  1012  	t.Run("valid http route and hostname matches cross-namespace", func(t *testing.T) {
  1013  		key := types.NamespacedName{
  1014  			Name:      "http-route-with-hostnames-and-cross-namespace-listener",
  1015  			Namespace: "another-namespace",
  1016  		}
  1017  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1018  			NamespacedName: key,
  1019  		})
  1020  
  1021  		require.NoError(t, err, "Error reconciling httpRoute")
  1022  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1023  
  1024  		route := &gatewayv1.HTTPRoute{}
  1025  		err = c.Get(context.Background(), key, route)
  1026  
  1027  		require.NoError(t, err)
  1028  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1029  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1030  
  1031  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1032  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1033  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Reason)
  1034  		require.Equal(t, "Accepted HTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Message)
  1035  
  1036  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1037  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1038  	})
  1039  
  1040  	t.Run("http route with nonexistent backend", func(t *testing.T) {
  1041  		for _, name := range []string{"svc", "svcimport", "svcimport-svc", "svcimport-svc-annotation"} {
  1042  			key := types.NamespacedName{
  1043  				Name:      "http-route-with-nonexistent-" + name,
  1044  				Namespace: "default",
  1045  			}
  1046  			result, err := r.Reconcile(context.Background(), ctrl.Request{
  1047  				NamespacedName: key,
  1048  			})
  1049  
  1050  			require.NoError(t, err, "Error reconciling httpRoute")
  1051  			require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1052  
  1053  			route := &gatewayv1.HTTPRoute{}
  1054  			err = c.Get(context.Background(), key, route)
  1055  
  1056  			require.NoError(t, err)
  1057  			require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1058  			require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1059  
  1060  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1061  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1062  
  1063  			require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1064  			require.Equal(t, "BackendNotFound", route.Status.RouteStatus.Parents[0].Conditions[1].Reason)
  1065  		}
  1066  	})
  1067  
  1068  	t.Run("http route with nonexistent gateway", func(t *testing.T) {
  1069  		key := types.NamespacedName{
  1070  			Name:      "http-route-with-nonexistent-gateway",
  1071  			Namespace: "default",
  1072  		}
  1073  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1074  			NamespacedName: key,
  1075  		})
  1076  
  1077  		require.NoError(t, err, "Error reconciling httpRoute")
  1078  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1079  
  1080  		route := &gatewayv1.HTTPRoute{}
  1081  		err = c.Get(context.Background(), key, route)
  1082  
  1083  		require.NoError(t, err)
  1084  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1085  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1086  
  1087  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1088  		require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1089  		require.Equal(t, "InvalidHTTPRoute", route.Status.RouteStatus.Parents[0].Conditions[0].Reason)
  1090  
  1091  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1092  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1093  	})
  1094  
  1095  	t.Run("http route with valid but not allowed gateway", func(t *testing.T) {
  1096  		key := types.NamespacedName{
  1097  			Name:      "http-route-with-not-allowed-gateway",
  1098  			Namespace: "default",
  1099  		}
  1100  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1101  			NamespacedName: key,
  1102  		})
  1103  
  1104  		require.NoError(t, err, "Error reconciling httpRoute")
  1105  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1106  
  1107  		route := &gatewayv1.HTTPRoute{}
  1108  		err = c.Get(context.Background(), key, route)
  1109  
  1110  		require.NoError(t, err)
  1111  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1112  
  1113  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1114  		require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1115  		require.Equal(t, "NotAllowedByListeners", route.Status.RouteStatus.Parents[0].Conditions[0].Reason)
  1116  		require.Equal(t, "HTTPRoute is not allowed to attach to this Gateway due to namespace restrictions", route.Status.RouteStatus.Parents[0].Conditions[0].Message)
  1117  
  1118  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1119  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1120  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1121  	})
  1122  
  1123  	t.Run("http route with non-matching hostname", func(t *testing.T) {
  1124  		key := types.NamespacedName{
  1125  			Name:      "http-route-with-non-matching-hostname",
  1126  			Namespace: "default",
  1127  		}
  1128  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1129  			NamespacedName: key,
  1130  		})
  1131  
  1132  		require.NoError(t, err, "Error reconciling httpRoute")
  1133  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1134  
  1135  		route := &gatewayv1.HTTPRoute{}
  1136  		err = c.Get(context.Background(), key, route)
  1137  
  1138  		require.NoError(t, err)
  1139  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1140  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1141  
  1142  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1143  		require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1144  		require.Equal(t, "NoMatchingListenerHostname", route.Status.RouteStatus.Parents[0].Conditions[0].Reason)
  1145  		require.Equal(t, "No matching listener hostname", route.Status.RouteStatus.Parents[0].Conditions[0].Message)
  1146  
  1147  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1148  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1149  	})
  1150  
  1151  	t.Run("http route with cross namespace backend", func(t *testing.T) {
  1152  		for _, name := range []string{"service", "serviceimport"} {
  1153  			key := types.NamespacedName{
  1154  				Name:      "http-route-with-cross-namespace-" + name,
  1155  				Namespace: "default",
  1156  			}
  1157  			result, err := r.Reconcile(context.Background(), ctrl.Request{
  1158  				NamespacedName: key,
  1159  			})
  1160  
  1161  			require.NoError(t, err, "Error reconciling httpRoute")
  1162  			require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1163  
  1164  			route := &gatewayv1.HTTPRoute{}
  1165  			err = c.Get(context.Background(), key, route)
  1166  
  1167  			require.NoError(t, err)
  1168  			require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1169  			require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1170  
  1171  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1172  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1173  
  1174  			require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1175  			require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1176  			require.Equal(t, "RefNotPermitted", route.Status.RouteStatus.Parents[0].Conditions[1].Reason)
  1177  			require.Equal(t, "Cross namespace references are not allowed", route.Status.RouteStatus.Parents[0].Conditions[1].Message)
  1178  		}
  1179  	})
  1180  
  1181  	t.Run("http route with cross namespace listener", func(t *testing.T) {
  1182  		key := types.NamespacedName{
  1183  			Name:      "http-route-with-cross-namespace-listener",
  1184  			Namespace: "another-namespace",
  1185  		}
  1186  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1187  			NamespacedName: key,
  1188  		})
  1189  
  1190  		require.NoError(t, err, "Error reconciling httpRoute")
  1191  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1192  
  1193  		route := &gatewayv1.HTTPRoute{}
  1194  		err = c.Get(context.Background(), key, route)
  1195  
  1196  		require.NoError(t, err)
  1197  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1198  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1199  
  1200  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1201  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1202  
  1203  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1204  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1205  	})
  1206  
  1207  	t.Run("http route with cross namespace backend with reference grant", func(t *testing.T) {
  1208  		key := types.NamespacedName{
  1209  			Name:      "http-route-with-cross-namespace-backend-with-grant",
  1210  			Namespace: "default",
  1211  		}
  1212  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1213  			NamespacedName: key,
  1214  		})
  1215  
  1216  		require.NoError(t, err, "Error reconciling httpRoute")
  1217  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1218  
  1219  		route := &gatewayv1.HTTPRoute{}
  1220  		err = c.Get(context.Background(), key, route)
  1221  
  1222  		require.NoError(t, err)
  1223  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1224  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1225  
  1226  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1227  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1228  
  1229  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1230  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1231  	})
  1232  
  1233  	t.Run("http route with un-supported backend", func(t *testing.T) {
  1234  		key := types.NamespacedName{
  1235  			Name:      "http-route-with-unsupported-backend",
  1236  			Namespace: "default",
  1237  		}
  1238  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1239  			NamespacedName: key,
  1240  		})
  1241  
  1242  		require.NoError(t, err, "Error reconciling httpRoute")
  1243  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1244  
  1245  		route := &gatewayv1.HTTPRoute{}
  1246  		err = c.Get(context.Background(), key, route)
  1247  
  1248  		require.NoError(t, err)
  1249  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1250  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1251  
  1252  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1253  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1254  
  1255  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1256  		require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1257  		require.Equal(t, "InvalidKind", route.Status.RouteStatus.Parents[0].Conditions[1].Reason)
  1258  		require.Equal(t, "Unsupported backend kind UnsupportedKind", route.Status.RouteStatus.Parents[0].Conditions[1].Message)
  1259  	})
  1260  
  1261  	t.Run("http route missing port of Service backend", func(t *testing.T) {
  1262  		for _, name := range []string{"service", "serviceimport"} {
  1263  			key := types.NamespacedName{
  1264  				Name:      "http-route-missing-port-for-backend-" + name,
  1265  				Namespace: "default",
  1266  			}
  1267  			result, err := r.Reconcile(context.Background(), ctrl.Request{
  1268  				NamespacedName: key,
  1269  			})
  1270  
  1271  			require.NoError(t, err, "Error reconciling httpRoute")
  1272  			require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1273  
  1274  			route := &gatewayv1.HTTPRoute{}
  1275  			err = c.Get(context.Background(), key, route)
  1276  
  1277  			require.NoError(t, err)
  1278  			require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1279  			require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1280  
  1281  			require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1282  			require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1283  
  1284  			require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1285  			require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1286  			require.Equal(t, "InvalidKind", route.Status.RouteStatus.Parents[0].Conditions[1].Reason)
  1287  			require.Equal(t, "Must have port for backend object reference", route.Status.RouteStatus.Parents[0].Conditions[1].Message)
  1288  		}
  1289  	})
  1290  }
  1291  
  1292  func Test_httpRouteReconciler_Reconcile_NoServiceImportCRD(t *testing.T) {
  1293  	c := fake.NewClientBuilder().
  1294  		WithScheme(testScheme()).
  1295  		WithObjects(httpRouteFixture...).
  1296  		WithStatusSubresource(&gatewayv1.HTTPRoute{}).
  1297  		Build()
  1298  
  1299  	r := &httpRouteReconciler{Client: c}
  1300  
  1301  	t.Run("valid http route with Service", func(t *testing.T) {
  1302  		key := types.NamespacedName{
  1303  			Name:      "valid-http-route-service",
  1304  			Namespace: "default",
  1305  		}
  1306  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1307  			NamespacedName: key,
  1308  		})
  1309  
  1310  		require.NoError(t, err, "Error reconciling httpRoute")
  1311  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1312  
  1313  		route := &gatewayv1.HTTPRoute{}
  1314  		err = c.Get(context.Background(), key, route)
  1315  
  1316  		require.NoError(t, err)
  1317  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1318  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1319  
  1320  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1321  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1322  
  1323  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1324  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1325  	})
  1326  
  1327  	t.Run("valid http route with ServiceImport", func(t *testing.T) {
  1328  		key := types.NamespacedName{
  1329  			Name:      "valid-http-route-serviceimport",
  1330  			Namespace: "default",
  1331  		}
  1332  		result, err := r.Reconcile(context.Background(), ctrl.Request{
  1333  			NamespacedName: key,
  1334  		})
  1335  
  1336  		require.NoError(t, err, "Error reconciling httpRoute")
  1337  		require.Equal(t, ctrl.Result{}, result, "Result should be empty")
  1338  
  1339  		route := &gatewayv1.HTTPRoute{}
  1340  		err = c.Get(context.Background(), key, route)
  1341  
  1342  		require.NoError(t, err)
  1343  		require.Len(t, route.Status.RouteStatus.Parents, 1, "Should have 1 parent")
  1344  		require.Len(t, route.Status.RouteStatus.Parents[0].Conditions, 2)
  1345  
  1346  		require.Equal(t, "Accepted", route.Status.RouteStatus.Parents[0].Conditions[0].Type)
  1347  		require.Equal(t, metav1.ConditionStatus("True"), route.Status.RouteStatus.Parents[0].Conditions[0].Status)
  1348  
  1349  		require.Equal(t, "ResolvedRefs", route.Status.RouteStatus.Parents[0].Conditions[1].Type)
  1350  		require.Equal(t, metav1.ConditionStatus("False"), route.Status.RouteStatus.Parents[0].Conditions[1].Status)
  1351  		require.Equal(t, "BackendNotFound", route.Status.RouteStatus.Parents[0].Conditions[1].Reason)
  1352  		require.Equal(t, "Attempt to reference a ServiceImport backend while "+
  1353  			"the corresponding CRD is not installed, "+
  1354  			"please restart the cilium-operator if the CRD is already installed",
  1355  			route.Status.RouteStatus.Parents[0].Conditions[1].Message)
  1356  	})
  1357  }