github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/controller_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  	"errors"
     9  	"fmt"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	corev1 "k8s.io/api/core/v1"
    16  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    20  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    21  	ctrl "sigs.k8s.io/controller-runtime"
    22  	"sigs.k8s.io/controller-runtime/pkg/client"
    23  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    24  	"sigs.k8s.io/controller-runtime/pkg/event"
    25  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    26  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    27  	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
    28  
    29  	controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime"
    30  	"github.com/cilium/cilium/operator/pkg/model"
    31  	ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    32  )
    33  
    34  func testScheme() *runtime.Scheme {
    35  	scheme := runtime.NewScheme()
    36  
    37  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
    38  	utilruntime.Must(ciliumv2.AddToScheme(scheme))
    39  	utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
    40  
    41  	registerGatewayAPITypesToScheme(scheme)
    42  
    43  	return scheme
    44  }
    45  
    46  var controllerTestFixture = []client.Object{
    47  	// Cilium Gateway Class
    48  	&gatewayv1.GatewayClass{
    49  		ObjectMeta: metav1.ObjectMeta{
    50  			Name: "cilium",
    51  		},
    52  		Spec: gatewayv1.GatewayClassSpec{
    53  			ControllerName: "io.cilium/gateway-controller",
    54  		},
    55  	},
    56  
    57  	// Secret used in Gateway
    58  	&corev1.Secret{
    59  		ObjectMeta: metav1.ObjectMeta{
    60  			Name:      "tls-secret",
    61  			Namespace: "default",
    62  		},
    63  		StringData: map[string]string{
    64  			"tls.crt": "cert",
    65  			"tls.key": "key",
    66  		},
    67  		Type: corev1.SecretTypeTLS,
    68  	},
    69  
    70  	// Gateway with valid TLS secret
    71  	&gatewayv1.Gateway{
    72  		ObjectMeta: metav1.ObjectMeta{
    73  			Name:      "valid-gateway",
    74  			Namespace: "default",
    75  		},
    76  		Spec: gatewayv1.GatewaySpec{
    77  			GatewayClassName: "cilium",
    78  			Listeners: []gatewayv1.Listener{
    79  				{
    80  					Name:     "https",
    81  					Hostname: model.AddressOf[gatewayv1.Hostname]("example.com"),
    82  					Port:     443,
    83  					TLS: &gatewayv1.GatewayTLSConfig{
    84  						CertificateRefs: []gatewayv1.SecretObjectReference{
    85  							{
    86  								Name: "tls-secret",
    87  							},
    88  						},
    89  					},
    90  				},
    91  			},
    92  		},
    93  	},
    94  
    95  	&gatewayv1.Gateway{
    96  		ObjectMeta: metav1.ObjectMeta{
    97  			Name:      "valid-gateway-2",
    98  			Namespace: "default",
    99  		},
   100  		Spec: gatewayv1.GatewaySpec{
   101  			GatewayClassName: "cilium",
   102  			Listeners: []gatewayv1.Listener{
   103  				{
   104  					Name:     "https",
   105  					Hostname: model.AddressOf[gatewayv1.Hostname]("example2.com"),
   106  					Port:     443,
   107  					TLS: &gatewayv1.GatewayTLSConfig{
   108  						CertificateRefs: []gatewayv1.SecretObjectReference{},
   109  					},
   110  				},
   111  			},
   112  		},
   113  	},
   114  
   115  	// Gateway with no TLS listener
   116  	&gatewayv1.Gateway{
   117  		ObjectMeta: metav1.ObjectMeta{
   118  			Name:      "gateway-with-no-tls",
   119  			Namespace: "default",
   120  		},
   121  		Spec: gatewayv1.GatewaySpec{
   122  			GatewayClassName: "cilium",
   123  			Listeners: []gatewayv1.Listener{
   124  				{
   125  					Name: "https",
   126  					Port: 80,
   127  				},
   128  			},
   129  		},
   130  	},
   131  
   132  	// Gateway for TLSRoute
   133  	&gatewayv1.Gateway{
   134  		ObjectMeta: metav1.ObjectMeta{
   135  			Name:      "gateway-tlsroute",
   136  			Namespace: "default",
   137  		},
   138  		Spec: gatewayv1.GatewaySpec{
   139  			GatewayClassName: "cilium",
   140  			Listeners: []gatewayv1.Listener{
   141  				{
   142  					Name:     "tls",
   143  					Protocol: gatewayv1.TLSProtocolType,
   144  					Port:     443,
   145  				},
   146  			},
   147  		},
   148  	},
   149  
   150  	// Gateway with allowed route in same namespace only
   151  	&gatewayv1.Gateway{
   152  		ObjectMeta: metav1.ObjectMeta{
   153  			Name:      "gateway-from-same-namespace",
   154  			Namespace: "default",
   155  		},
   156  		Spec: gatewayv1.GatewaySpec{
   157  			GatewayClassName: "cilium",
   158  			Listeners: []gatewayv1.Listener{
   159  				{
   160  					Name: "https",
   161  					Port: 80,
   162  					AllowedRoutes: &gatewayv1.AllowedRoutes{
   163  						Namespaces: &gatewayv1.RouteNamespaces{
   164  							From: model.AddressOf(gatewayv1.NamespacesFromSame),
   165  						},
   166  					},
   167  				},
   168  			},
   169  		},
   170  	},
   171  
   172  	// Gateway with allowed routes from ALL namespace
   173  	&gatewayv1.Gateway{
   174  		ObjectMeta: metav1.ObjectMeta{
   175  			Name:      "gateway-from-all-namespaces",
   176  			Namespace: "default",
   177  		},
   178  		Spec: gatewayv1.GatewaySpec{
   179  			GatewayClassName: "cilium",
   180  			Listeners: []gatewayv1.Listener{
   181  				{
   182  					Name: "https",
   183  					Port: 80,
   184  					AllowedRoutes: &gatewayv1.AllowedRoutes{
   185  						Namespaces: &gatewayv1.RouteNamespaces{
   186  							From: model.AddressOf(gatewayv1.NamespacesFromAll),
   187  						},
   188  					},
   189  				},
   190  			},
   191  		},
   192  	},
   193  
   194  	// Gateway with allowed routes with selector
   195  	&gatewayv1.Gateway{
   196  		ObjectMeta: metav1.ObjectMeta{
   197  			Name:      "gateway-with-namespaces-selector",
   198  			Namespace: "default",
   199  		},
   200  		Spec: gatewayv1.GatewaySpec{
   201  			GatewayClassName: "cilium",
   202  			Listeners: []gatewayv1.Listener{
   203  				{
   204  					Name: "https",
   205  					Port: 80,
   206  					AllowedRoutes: &gatewayv1.AllowedRoutes{
   207  						Namespaces: &gatewayv1.RouteNamespaces{
   208  							From: model.AddressOf(gatewayv1.NamespacesFromSelector),
   209  							Selector: &metav1.LabelSelector{
   210  								MatchLabels: map[string]string{
   211  									"gateway": "allowed",
   212  								},
   213  							},
   214  						},
   215  					},
   216  				},
   217  			},
   218  		},
   219  	},
   220  }
   221  
   222  var namespaceFixtures = []client.Object{
   223  	&corev1.Namespace{
   224  		ObjectMeta: metav1.ObjectMeta{
   225  			Name: "default",
   226  		},
   227  	},
   228  	&corev1.Namespace{
   229  		ObjectMeta: metav1.ObjectMeta{
   230  			Name: "another-namespace",
   231  		},
   232  	},
   233  	&corev1.Namespace{
   234  		ObjectMeta: metav1.ObjectMeta{
   235  			Name: "namespace-with-allowed-gateway-selector",
   236  			Labels: map[string]string{
   237  				"gateway": "allowed",
   238  			},
   239  		},
   240  	},
   241  	&corev1.Namespace{
   242  		ObjectMeta: metav1.ObjectMeta{
   243  			Name: "namespace-with-disallowed-gateway-selector",
   244  			Labels: map[string]string{
   245  				"gateway": "disallowed",
   246  			},
   247  		},
   248  	},
   249  }
   250  
   251  func Test_hasMatchingController(t *testing.T) {
   252  	c := fake.NewClientBuilder().WithScheme(testScheme()).WithObjects(controllerTestFixture...).Build()
   253  	fn := hasMatchingController(context.Background(), c, "io.cilium/gateway-controller")
   254  
   255  	t.Run("invalid object", func(t *testing.T) {
   256  		res := fn(&corev1.Pod{})
   257  		require.False(t, res)
   258  	})
   259  
   260  	t.Run("gateway is matched by controller", func(t *testing.T) {
   261  		res := fn(&gatewayv1.Gateway{
   262  			Spec: gatewayv1.GatewaySpec{
   263  				GatewayClassName: "cilium",
   264  			},
   265  		})
   266  		require.True(t, res)
   267  	})
   268  
   269  	t.Run("gateway is linked to non-existent class", func(t *testing.T) {
   270  		res := fn(&gatewayv1.Gateway{
   271  			Spec: gatewayv1.GatewaySpec{
   272  				GatewayClassName: "non-existent",
   273  			},
   274  		})
   275  		require.False(t, res)
   276  	})
   277  }
   278  
   279  func Test_getGatewaysForSecret(t *testing.T) {
   280  	c := fake.NewClientBuilder().WithScheme(testScheme()).WithObjects(controllerTestFixture...).Build()
   281  
   282  	t.Run("secret is used in gateway", func(t *testing.T) {
   283  		gwList := getGatewaysForSecret(context.Background(), c, &corev1.Secret{
   284  			ObjectMeta: metav1.ObjectMeta{
   285  				Name:      "tls-secret",
   286  				Namespace: "default",
   287  			},
   288  		})
   289  
   290  		require.Len(t, gwList, 1)
   291  		require.Equal(t, "valid-gateway", gwList[0].Name)
   292  	})
   293  
   294  	t.Run("secret is not used in gateway", func(t *testing.T) {
   295  		gwList := getGatewaysForSecret(context.Background(), c, &corev1.Secret{
   296  			ObjectMeta: metav1.ObjectMeta{
   297  				Name:      "tls-secret-not-used",
   298  				Namespace: "default",
   299  			},
   300  		})
   301  
   302  		require.Len(t, gwList, 0)
   303  	})
   304  }
   305  
   306  func Test_getGatewaysForNamespace(t *testing.T) {
   307  	c := fake.NewClientBuilder().
   308  		WithScheme(testScheme()).
   309  		WithObjects(namespaceFixtures...).
   310  		WithObjects(controllerTestFixture...).
   311  		Build()
   312  
   313  	type args struct {
   314  		namespace string
   315  	}
   316  
   317  	tests := []struct {
   318  		name string
   319  		args args
   320  		want []string
   321  	}{
   322  		{
   323  			name: "with default namespace",
   324  			args: args{namespace: "default"},
   325  			want: []string{"gateway-from-all-namespaces", "gateway-from-same-namespace"},
   326  		},
   327  		{
   328  			name: "with another namespace",
   329  			args: args{namespace: "another-namespace"},
   330  			want: []string{"gateway-from-all-namespaces"},
   331  		},
   332  		{
   333  			name: "with namespace-with-allowed-gateway-selector",
   334  			args: args{namespace: "namespace-with-allowed-gateway-selector"},
   335  			want: []string{"gateway-from-all-namespaces", "gateway-with-namespaces-selector"},
   336  		},
   337  		{
   338  			name: "with namespace-with-disallowed-gateway-selector",
   339  			args: args{namespace: "namespace-with-disallowed-gateway-selector"},
   340  			want: []string{"gateway-from-all-namespaces"},
   341  		},
   342  	}
   343  	for _, tt := range tests {
   344  		t.Run(tt.name, func(t *testing.T) {
   345  			gwList := getGatewaysForNamespace(context.Background(), c, &corev1.Namespace{
   346  				ObjectMeta: metav1.ObjectMeta{
   347  					Name: tt.args.namespace,
   348  				},
   349  			})
   350  			names := make([]string, 0, len(gwList))
   351  			for _, gw := range gwList {
   352  				names = append(names, gw.Name)
   353  			}
   354  			require.ElementsMatch(t, tt.want, names)
   355  		})
   356  	}
   357  }
   358  
   359  func Test_success(t *testing.T) {
   360  	tests := []struct {
   361  		name    string
   362  		want    ctrl.Result
   363  		wantErr assert.ErrorAssertionFunc
   364  	}{
   365  		{
   366  			name:    "success",
   367  			want:    ctrl.Result{},
   368  			wantErr: assert.NoError,
   369  		},
   370  	}
   371  	for _, tt := range tests {
   372  		t.Run(tt.name, func(t *testing.T) {
   373  			got, err := controllerruntime.Success()
   374  			if !tt.wantErr(t, err, "success()") {
   375  				return
   376  			}
   377  			assert.Equalf(t, tt.want, got, "success()")
   378  		})
   379  	}
   380  }
   381  
   382  func Test_fail(t *testing.T) {
   383  	type args struct {
   384  		e error
   385  	}
   386  	tests := []struct {
   387  		name    string
   388  		args    args
   389  		want    ctrl.Result
   390  		wantErr assert.ErrorAssertionFunc
   391  	}{
   392  		{
   393  			name: "fail",
   394  			args: args{
   395  				e: errors.New("fail"),
   396  			},
   397  			want:    ctrl.Result{},
   398  			wantErr: assert.Error,
   399  		},
   400  	}
   401  	for _, tt := range tests {
   402  		t.Run(tt.name, func(t *testing.T) {
   403  			got, err := controllerruntime.Fail(tt.args.e)
   404  			if !tt.wantErr(t, err, fmt.Sprintf("fail(%v)", tt.args.e)) {
   405  				return
   406  			}
   407  			assert.Equalf(t, tt.want, got, "fail(%v)", tt.args.e)
   408  		})
   409  	}
   410  }
   411  
   412  func Test_onlyStatusChanged(t *testing.T) {
   413  	failingFuncs := predicate.Funcs{
   414  		CreateFunc: func(event.CreateEvent) bool {
   415  			t.Fail()
   416  			return false
   417  		},
   418  		DeleteFunc: func(event.DeleteEvent) bool {
   419  			t.Fail()
   420  			return false
   421  		},
   422  		UpdateFunc: func(event.UpdateEvent) bool {
   423  			t.Fail()
   424  			return false
   425  		},
   426  		GenericFunc: func(event.GenericEvent) bool {
   427  			t.Fail()
   428  			return false
   429  		},
   430  	}
   431  	f := failingFuncs
   432  	f.UpdateFunc = onlyStatusChanged().Update
   433  
   434  	type args struct {
   435  		evt event.UpdateEvent
   436  	}
   437  	tests := []struct {
   438  		name     string
   439  		args     args
   440  		expected bool
   441  	}{
   442  		{
   443  			name: "unsupported kind",
   444  			args: args{
   445  				evt: event.UpdateEvent{
   446  					ObjectOld: &corev1.Pod{},
   447  					ObjectNew: &corev1.Pod{},
   448  				},
   449  			},
   450  			expected: false,
   451  		},
   452  		{
   453  			name: "mismatch kind for GatewayClass",
   454  			args: args{
   455  				evt: event.UpdateEvent{
   456  					ObjectOld: &gatewayv1.GatewayClass{},
   457  					ObjectNew: &gatewayv1.Gateway{},
   458  				},
   459  			},
   460  			expected: false,
   461  		},
   462  		{
   463  			name: "mismatch kind for Gateway",
   464  			args: args{
   465  				evt: event.UpdateEvent{
   466  					ObjectOld: &gatewayv1.Gateway{},
   467  					ObjectNew: &gatewayv1.GatewayClass{},
   468  				},
   469  			},
   470  			expected: false,
   471  		},
   472  		{
   473  			name: "mismatch kind for HTTPRoute",
   474  			args: args{
   475  				evt: event.UpdateEvent{
   476  					ObjectOld: &gatewayv1.HTTPRoute{},
   477  					ObjectNew: &gatewayv1.GatewayClass{},
   478  				},
   479  			},
   480  			expected: false,
   481  		},
   482  		{
   483  			name: "no change in GatewayClass status",
   484  			args: args{
   485  				evt: event.UpdateEvent{
   486  					ObjectOld: &gatewayv1.GatewayClass{},
   487  					ObjectNew: &gatewayv1.GatewayClass{},
   488  				},
   489  			},
   490  			expected: false,
   491  		},
   492  		{
   493  			name: "change in GatewayClass status",
   494  			args: args{
   495  				evt: event.UpdateEvent{
   496  					ObjectOld: &gatewayv1.GatewayClass{},
   497  					ObjectNew: &gatewayv1.GatewayClass{
   498  						Status: gatewayv1.GatewayClassStatus{
   499  							Conditions: []metav1.Condition{
   500  								{
   501  									Type:               string(gatewayv1.GatewayConditionAccepted),
   502  									Status:             metav1.ConditionTrue,
   503  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   504  									LastTransitionTime: metav1.NewTime(time.Now()),
   505  								},
   506  							},
   507  						},
   508  					},
   509  				},
   510  			},
   511  			expected: true,
   512  		},
   513  		{
   514  			name: "only change LastTransitionTime in GatewayClass status",
   515  			args: args{
   516  				evt: event.UpdateEvent{
   517  					ObjectOld: &gatewayv1.Gateway{
   518  						Status: gatewayv1.GatewayStatus{
   519  							Conditions: []metav1.Condition{
   520  								{
   521  									Type:               string(gatewayv1.GatewayConditionAccepted),
   522  									Status:             metav1.ConditionTrue,
   523  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   524  									LastTransitionTime: metav1.NewTime(time.Now()),
   525  								},
   526  							},
   527  						},
   528  					},
   529  					ObjectNew: &gatewayv1.Gateway{
   530  						Status: gatewayv1.GatewayStatus{
   531  							Conditions: []metav1.Condition{
   532  								{
   533  									Type:               string(gatewayv1.GatewayConditionAccepted),
   534  									Status:             metav1.ConditionTrue,
   535  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   536  									LastTransitionTime: metav1.NewTime(time.Now()),
   537  								},
   538  							},
   539  						},
   540  					},
   541  				},
   542  			},
   543  			expected: false,
   544  		},
   545  
   546  		{
   547  			name: "no change in gateway status",
   548  			args: args{
   549  				evt: event.UpdateEvent{
   550  					ObjectOld: &gatewayv1.Gateway{},
   551  					ObjectNew: &gatewayv1.Gateway{},
   552  				},
   553  			},
   554  			expected: false,
   555  		},
   556  		{
   557  			name: "change in gateway status",
   558  			args: args{
   559  				evt: event.UpdateEvent{
   560  					ObjectOld: &gatewayv1.Gateway{},
   561  					ObjectNew: &gatewayv1.Gateway{
   562  						Status: gatewayv1.GatewayStatus{
   563  							Conditions: []metav1.Condition{
   564  								{
   565  									Type:               string(gatewayv1.GatewayConditionAccepted),
   566  									Status:             metav1.ConditionTrue,
   567  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   568  									LastTransitionTime: metav1.NewTime(time.Now()),
   569  								},
   570  							},
   571  						},
   572  					},
   573  				},
   574  			},
   575  			expected: true,
   576  		},
   577  		{
   578  			name: "only change LastTransitionTime in gateway status",
   579  			args: args{
   580  				evt: event.UpdateEvent{
   581  					ObjectOld: &gatewayv1.Gateway{
   582  						Status: gatewayv1.GatewayStatus{
   583  							Conditions: []metav1.Condition{
   584  								{
   585  									Type:               string(gatewayv1.GatewayConditionAccepted),
   586  									Status:             metav1.ConditionTrue,
   587  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   588  									LastTransitionTime: metav1.NewTime(time.Now()),
   589  								},
   590  							},
   591  						},
   592  					},
   593  					ObjectNew: &gatewayv1.Gateway{
   594  						Status: gatewayv1.GatewayStatus{
   595  							Conditions: []metav1.Condition{
   596  								{
   597  									Type:               string(gatewayv1.GatewayConditionAccepted),
   598  									Status:             metav1.ConditionTrue,
   599  									Reason:             string(gatewayv1.GatewayReasonAccepted),
   600  									LastTransitionTime: metav1.NewTime(time.Now()),
   601  								},
   602  							},
   603  						},
   604  					},
   605  				},
   606  			},
   607  			expected: false,
   608  		},
   609  		{
   610  			name: "no change in HTTPRoute status",
   611  			args: args{
   612  				evt: event.UpdateEvent{
   613  					ObjectOld: &gatewayv1.HTTPRoute{},
   614  					ObjectNew: &gatewayv1.HTTPRoute{},
   615  				},
   616  			},
   617  			expected: false,
   618  		},
   619  		{
   620  			name: "change in HTTP route status",
   621  			args: args{
   622  				evt: event.UpdateEvent{
   623  					ObjectOld: &gatewayv1.HTTPRoute{},
   624  					ObjectNew: &gatewayv1.HTTPRoute{
   625  						Status: gatewayv1.HTTPRouteStatus{
   626  							RouteStatus: gatewayv1.RouteStatus{
   627  								Parents: []gatewayv1.RouteParentStatus{
   628  									{
   629  										ParentRef: gatewayv1.ParentReference{
   630  											Name: "test-gateway",
   631  										},
   632  										ControllerName: "io.cilium/gateway-controller",
   633  										Conditions: []metav1.Condition{
   634  											{
   635  												Type:               "Accepted",
   636  												Status:             "True",
   637  												ObservedGeneration: 100,
   638  												Reason:             "Accepted",
   639  												Message:            "Valid HTTPRoute",
   640  											},
   641  										},
   642  									},
   643  								},
   644  							},
   645  						},
   646  					},
   647  				},
   648  			},
   649  			expected: true,
   650  		},
   651  		{
   652  			name: "only change LastTransitionTime in HTTPRoute status",
   653  			args: args{
   654  				evt: event.UpdateEvent{
   655  					ObjectOld: &gatewayv1.HTTPRoute{
   656  						Status: gatewayv1.HTTPRouteStatus{
   657  							RouteStatus: gatewayv1.RouteStatus{
   658  								Parents: []gatewayv1.RouteParentStatus{
   659  									{
   660  										ParentRef: gatewayv1.ParentReference{
   661  											Name: "test-gateway",
   662  										},
   663  										ControllerName: "io.cilium/gateway-controller",
   664  										Conditions: []metav1.Condition{
   665  											{
   666  												Type:               "Accepted",
   667  												Status:             "True",
   668  												ObservedGeneration: 100,
   669  												Reason:             "Accepted",
   670  												Message:            "Valid HTTPRoute",
   671  											},
   672  										},
   673  									},
   674  								},
   675  							},
   676  						},
   677  					},
   678  					ObjectNew: &gatewayv1.HTTPRoute{
   679  						Status: gatewayv1.HTTPRouteStatus{
   680  							RouteStatus: gatewayv1.RouteStatus{
   681  								Parents: []gatewayv1.RouteParentStatus{
   682  									{
   683  										ParentRef: gatewayv1.ParentReference{
   684  											Name: "test-gateway",
   685  										},
   686  										ControllerName: "io.cilium/gateway-controller",
   687  										Conditions: []metav1.Condition{
   688  											{
   689  												Type:               "Accepted",
   690  												Status:             "True",
   691  												ObservedGeneration: 100,
   692  												Reason:             "Accepted",
   693  												Message:            "Valid HTTPRoute",
   694  											},
   695  										},
   696  									},
   697  								},
   698  							},
   699  						},
   700  					},
   701  				},
   702  			},
   703  			expected: false,
   704  		},
   705  		{
   706  			name: "no change in TLSRoute status",
   707  			args: args{
   708  				evt: event.UpdateEvent{
   709  					ObjectOld: &gatewayv1alpha2.TLSRoute{},
   710  					ObjectNew: &gatewayv1alpha2.TLSRoute{},
   711  				},
   712  			},
   713  			expected: false,
   714  		},
   715  		{
   716  			name: "change in TLSRoute status",
   717  			args: args{
   718  				evt: event.UpdateEvent{
   719  					ObjectOld: &gatewayv1alpha2.TLSRoute{},
   720  					ObjectNew: &gatewayv1alpha2.TLSRoute{
   721  						Status: gatewayv1alpha2.TLSRouteStatus{
   722  							RouteStatus: gatewayv1.RouteStatus{
   723  								Parents: []gatewayv1.RouteParentStatus{
   724  									{
   725  										ParentRef: gatewayv1.ParentReference{
   726  											Name: "test-gateway",
   727  										},
   728  										ControllerName: "io.cilium/gateway-controller",
   729  										Conditions: []metav1.Condition{
   730  											{
   731  												Type:               "Accepted",
   732  												Status:             "True",
   733  												ObservedGeneration: 100,
   734  												Reason:             "Accepted",
   735  												Message:            "Valid TLSRoute",
   736  											},
   737  										},
   738  									},
   739  								},
   740  							},
   741  						},
   742  					},
   743  				},
   744  			},
   745  			expected: true,
   746  		},
   747  		{
   748  			name: "only change LastTransitionTime in TLSRoute status",
   749  			args: args{
   750  				evt: event.UpdateEvent{
   751  					ObjectOld: &gatewayv1alpha2.TLSRoute{
   752  						Status: gatewayv1alpha2.TLSRouteStatus{
   753  							RouteStatus: gatewayv1.RouteStatus{
   754  								Parents: []gatewayv1.RouteParentStatus{
   755  									{
   756  										ParentRef: gatewayv1.ParentReference{
   757  											Name: "test-gateway",
   758  										},
   759  										ControllerName: "io.cilium/gateway-controller",
   760  										Conditions: []metav1.Condition{
   761  											{
   762  												Type:               "Accepted",
   763  												Status:             "True",
   764  												ObservedGeneration: 100,
   765  												Reason:             "Accepted",
   766  												Message:            "Valid TLSRoute",
   767  											},
   768  										},
   769  									},
   770  								},
   771  							},
   772  						},
   773  					},
   774  					ObjectNew: &gatewayv1alpha2.TLSRoute{
   775  						Status: gatewayv1alpha2.TLSRouteStatus{
   776  							RouteStatus: gatewayv1.RouteStatus{
   777  								Parents: []gatewayv1.RouteParentStatus{
   778  									{
   779  										ParentRef: gatewayv1.ParentReference{
   780  											Name: "test-gateway",
   781  										},
   782  										ControllerName: "io.cilium/gateway-controller",
   783  										Conditions: []metav1.Condition{
   784  											{
   785  												Type:               "Accepted",
   786  												Status:             "True",
   787  												ObservedGeneration: 100,
   788  												Reason:             "Accepted",
   789  												Message:            "Valid TLSRoute",
   790  											},
   791  										},
   792  									},
   793  								},
   794  							},
   795  						},
   796  					},
   797  				},
   798  			},
   799  			expected: false,
   800  		},
   801  	}
   802  	for _, tt := range tests {
   803  		t.Run(tt.name, func(t *testing.T) {
   804  			res := f.Update(tt.args.evt)
   805  			assert.Equal(t, tt.expected, res)
   806  		})
   807  	}
   808  }