github.com/cilium/cilium@v1.16.2/operator/pkg/model/translation/cec_translator_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package translation
     5  
     6  import (
     7  	"fmt"
     8  	"slices"
     9  	"testing"
    10  
    11  	envoy_config_cluster_v3 "github.com/cilium/proxy/go/envoy/config/cluster/v3"
    12  	envoy_config_core_v3 "github.com/cilium/proxy/go/envoy/config/core/v3"
    13  	envoy_config_listener "github.com/cilium/proxy/go/envoy/config/listener/v3"
    14  	envoy_config_route_v3 "github.com/cilium/proxy/go/envoy/config/route/v3"
    15  	envoy_http_connection_manager_v3 "github.com/cilium/proxy/go/envoy/extensions/filters/network/http_connection_manager/v3"
    16  	envoy_transport_sockets_tls_v3 "github.com/cilium/proxy/go/envoy/extensions/transport_sockets/tls/v3"
    17  	matcherv3 "github.com/cilium/proxy/go/envoy/type/matcher/v3"
    18  	"github.com/google/go-cmp/cmp"
    19  	"github.com/stretchr/testify/require"
    20  	"google.golang.org/protobuf/proto"
    21  	"google.golang.org/protobuf/testing/protocmp"
    22  	"google.golang.org/protobuf/types/known/durationpb"
    23  
    24  	"github.com/cilium/cilium/operator/pkg/model"
    25  	ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    26  )
    27  
    28  func TestSharedIngressTranslator_getBackendServices(t *testing.T) {
    29  	type args struct {
    30  		m *model.Model
    31  	}
    32  	tests := []struct {
    33  		name string
    34  		args args
    35  		want []*ciliumv2.Service
    36  	}{
    37  		{
    38  			name: "default backend listener",
    39  			args: args{
    40  				m: defaultBackendModel,
    41  			},
    42  			want: []*ciliumv2.Service{
    43  				{
    44  					Name:      "default-backend",
    45  					Namespace: "random-namespace",
    46  					Ports: []string{
    47  						"8080",
    48  					},
    49  				},
    50  			},
    51  		},
    52  		{
    53  			name: "host rule listeners",
    54  			args: args{
    55  				m: hostRulesModel,
    56  			},
    57  			want: []*ciliumv2.Service{
    58  				{
    59  					Name:      "foo-bar-com",
    60  					Namespace: "random-namespace",
    61  					Ports: []string{
    62  						"http",
    63  					},
    64  				},
    65  				{
    66  					Name:      "wildcard-foo-com",
    67  					Namespace: "random-namespace",
    68  					Ports: []string{
    69  						"8080",
    70  					},
    71  				},
    72  			},
    73  		},
    74  		{
    75  			name: "path rule listeners",
    76  			args: args{
    77  				m: pathRulesModel,
    78  			},
    79  			want: []*ciliumv2.Service{
    80  				{
    81  					Name:      "aaa-prefix",
    82  					Namespace: "random-namespace",
    83  					Ports: []string{
    84  						"8080",
    85  					},
    86  				},
    87  				{
    88  					Name:      "aaa-slash-bbb-prefix",
    89  					Namespace: "random-namespace",
    90  					Ports: []string{
    91  						"8080",
    92  					},
    93  				},
    94  				{
    95  					Name:      "aaa-slash-bbb-slash-prefix",
    96  					Namespace: "random-namespace",
    97  					Ports: []string{
    98  						"8080",
    99  					},
   100  				},
   101  				{
   102  					Name:      "foo-exact",
   103  					Namespace: "random-namespace",
   104  					Ports: []string{
   105  						"8080",
   106  					},
   107  				},
   108  				{
   109  					Name:      "foo-prefix",
   110  					Namespace: "random-namespace",
   111  					Ports: []string{
   112  						"8080",
   113  					},
   114  				},
   115  				{
   116  					Name:      "foo-slash-exact",
   117  					Namespace: "random-namespace",
   118  					Ports: []string{
   119  						"8080",
   120  					},
   121  				},
   122  			},
   123  		},
   124  		{
   125  			name: "complex ingress",
   126  			args: args{
   127  				m: complexIngressModel,
   128  			},
   129  			want: []*ciliumv2.Service{
   130  				{
   131  					Name:      "another-dummy-backend",
   132  					Namespace: "dummy-namespace",
   133  					Ports:     []string{"8081"},
   134  				},
   135  				{
   136  					Name:      "default-backend",
   137  					Namespace: "dummy-namespace",
   138  					Ports:     []string{"8080"},
   139  				},
   140  				{
   141  					Name:      "dummy-backend",
   142  					Namespace: "dummy-namespace",
   143  					Ports:     []string{"8080"},
   144  				},
   145  			},
   146  		},
   147  	}
   148  	for _, tt := range tests {
   149  		t.Run(tt.name, func(t *testing.T) {
   150  			i := &cecTranslator{}
   151  			res := i.getBackendServices(tt.args.m)
   152  			require.Equal(t, tt.want, res)
   153  		})
   154  	}
   155  }
   156  
   157  func TestSharedIngressTranslator_getServices(t *testing.T) {
   158  	type fields struct {
   159  		name      string
   160  		namespace string
   161  	}
   162  	tests := []struct {
   163  		name   string
   164  		fields fields
   165  		model  *model.Model
   166  		want   []*ciliumv2.ServiceListener
   167  	}{
   168  		{
   169  			name: "default case",
   170  			fields: fields{
   171  				name:      "cilium-ingress",
   172  				namespace: "kube-system",
   173  			},
   174  			model: &model.Model{
   175  				HTTP: []model.HTTPListener{
   176  					{
   177  						Port: 80,
   178  					},
   179  					{
   180  						Port: 443,
   181  					},
   182  				},
   183  			},
   184  			want: []*ciliumv2.ServiceListener{
   185  				{
   186  					Name:      "cilium-ingress",
   187  					Namespace: "kube-system",
   188  					Ports: []uint16{
   189  						80,
   190  						443,
   191  					},
   192  				},
   193  			},
   194  		},
   195  		{
   196  			name: "only http port",
   197  			fields: fields{
   198  				name:      "someotherservice",
   199  				namespace: "default",
   200  			},
   201  			model: &model.Model{
   202  				HTTP: []model.HTTPListener{
   203  					{
   204  						Port: 80,
   205  					},
   206  				},
   207  			},
   208  			want: []*ciliumv2.ServiceListener{
   209  				{
   210  					Name:      "someotherservice",
   211  					Namespace: "default",
   212  					Ports: []uint16{
   213  						80,
   214  					},
   215  				},
   216  			},
   217  		},
   218  		{
   219  			name: "cleartext HTTP and TLS passthrough",
   220  			fields: fields{
   221  				name:      "cilium-ingress",
   222  				namespace: "kube-system",
   223  			},
   224  			model: &model.Model{
   225  				HTTP: []model.HTTPListener{
   226  					{
   227  						Port: 80,
   228  					},
   229  				},
   230  				TLSPassthrough: []model.TLSPassthroughListener{
   231  					{
   232  						Port: 443,
   233  					},
   234  				},
   235  			},
   236  			want: []*ciliumv2.ServiceListener{
   237  				{
   238  					Name:      "cilium-ingress",
   239  					Namespace: "kube-system",
   240  					Ports: []uint16{
   241  						80,
   242  						443,
   243  					},
   244  				},
   245  			},
   246  		},
   247  	}
   248  
   249  	for _, tt := range tests {
   250  		t.Run(tt.name, func(t *testing.T) {
   251  			i := &cecTranslator{}
   252  			got := i.getServicesWithPorts(tt.fields.namespace, tt.fields.name, tt.model)
   253  			require.Equal(t, tt.want, got)
   254  		})
   255  	}
   256  }
   257  
   258  func TestSharedIngressTranslator_getListenerProxy(t *testing.T) {
   259  	i := &cecTranslator{
   260  		secretsNamespace: "cilium-secrets",
   261  		useProxyProtocol: true,
   262  	}
   263  	res := i.getListener(&model.Model{
   264  		HTTP: []model.HTTPListener{
   265  			{
   266  				TLS: []model.TLSSecret{
   267  					{
   268  						Name:      "dummy-secret",
   269  						Namespace: "dummy-namespace",
   270  					},
   271  				},
   272  			},
   273  		},
   274  	})
   275  	require.Len(t, res, 1)
   276  	listener := &envoy_config_listener.Listener{}
   277  	err := proto.Unmarshal(res[0].GetValue(), listener)
   278  	require.NoError(t, err)
   279  
   280  	listenerNames := []string{}
   281  	for _, l := range listener.ListenerFilters {
   282  		listenerNames = append(listenerNames, l.Name)
   283  	}
   284  	slices.Sort(listenerNames)
   285  	require.Equal(t, []string{proxyProtocolType, tlsInspectorType}, listenerNames)
   286  }
   287  
   288  func TestSharedIngressTranslator_getListener(t *testing.T) {
   289  	i := &cecTranslator{
   290  		secretsNamespace: "cilium-secrets",
   291  	}
   292  
   293  	res := i.getListener(&model.Model{
   294  		HTTP: []model.HTTPListener{
   295  			{
   296  				TLS: []model.TLSSecret{
   297  					{
   298  						Name:      "dummy-secret",
   299  						Namespace: "dummy-namespace",
   300  					},
   301  				},
   302  			},
   303  		},
   304  	})
   305  	require.Len(t, res, 1)
   306  
   307  	listener := &envoy_config_listener.Listener{}
   308  	err := proto.Unmarshal(res[0].GetValue(), listener)
   309  	require.NoError(t, err)
   310  
   311  	require.Len(t, listener.ListenerFilters, 1)
   312  	require.Len(t, listener.FilterChains, 2)
   313  	require.Len(t, listener.FilterChains[0].Filters, 1)
   314  	require.Len(t, listener.SocketOptions, 4)
   315  	require.IsType(t, &envoy_config_listener.Filter_TypedConfig{}, listener.FilterChains[0].Filters[0].ConfigType)
   316  
   317  	// check for connection manager
   318  	insecureConnectionManager := &envoy_http_connection_manager_v3.HttpConnectionManager{}
   319  	err = proto.Unmarshal(listener.FilterChains[0].Filters[0].ConfigType.(*envoy_config_listener.Filter_TypedConfig).TypedConfig.Value, insecureConnectionManager)
   320  	require.NoError(t, err)
   321  
   322  	require.Equal(t, "listener-insecure", insecureConnectionManager.StatPrefix)
   323  	require.Equal(t, "listener-insecure", insecureConnectionManager.GetRds().RouteConfigName)
   324  
   325  	secureConnectionManager := &envoy_http_connection_manager_v3.HttpConnectionManager{}
   326  	err = proto.Unmarshal(listener.FilterChains[1].Filters[0].ConfigType.(*envoy_config_listener.Filter_TypedConfig).TypedConfig.Value, secureConnectionManager)
   327  	require.NoError(t, err)
   328  
   329  	require.Equal(t, "listener-secure", secureConnectionManager.StatPrefix)
   330  	require.Equal(t, "listener-secure", secureConnectionManager.GetRds().RouteConfigName)
   331  
   332  	// check TLS configuration
   333  	require.Equal(t, "envoy.transport_sockets.tls", listener.FilterChains[1].TransportSocket.Name)
   334  	require.IsType(t, &envoy_config_core_v3.TransportSocket_TypedConfig{}, listener.FilterChains[1].TransportSocket.ConfigType)
   335  
   336  	downStreamTLS := &envoy_transport_sockets_tls_v3.DownstreamTlsContext{}
   337  	err = proto.Unmarshal(listener.FilterChains[1].TransportSocket.ConfigType.(*envoy_config_core_v3.TransportSocket_TypedConfig).TypedConfig.Value, downStreamTLS)
   338  	require.NoError(t, err)
   339  
   340  	require.Len(t, downStreamTLS.CommonTlsContext.TlsCertificateSdsSecretConfigs, 1)
   341  	require.Equal(t, downStreamTLS.CommonTlsContext.TlsCertificateSdsSecretConfigs[0].GetName(), "cilium-secrets/dummy-namespace-dummy-secret")
   342  	require.Nil(t, downStreamTLS.CommonTlsContext.TlsCertificateSdsSecretConfigs[0].GetSdsConfig())
   343  }
   344  
   345  func TestSharedIngressTranslator_getClusters(t *testing.T) {
   346  	type args struct {
   347  		m *model.Model
   348  	}
   349  	tests := []struct {
   350  		name     string
   351  		args     args
   352  		expected []string
   353  	}{
   354  		{
   355  			name: "default backend listener",
   356  			args: args{
   357  				m: defaultBackendModel,
   358  			},
   359  			expected: []string{
   360  				"random-namespace:default-backend:8080",
   361  			},
   362  		},
   363  		{
   364  			name: "host rule listeners",
   365  			args: args{
   366  				m: hostRulesModel,
   367  			},
   368  			expected: []string{
   369  				"random-namespace:foo-bar-com:http",
   370  				"random-namespace:wildcard-foo-com:8080",
   371  			},
   372  		},
   373  		{
   374  			name: "path rule listeners",
   375  			args: args{
   376  				m: pathRulesModel,
   377  			},
   378  			expected: []string{
   379  				"random-namespace:aaa-prefix:8080",
   380  				"random-namespace:aaa-slash-bbb-prefix:8080",
   381  				"random-namespace:aaa-slash-bbb-slash-prefix:8080",
   382  				"random-namespace:foo-exact:8080",
   383  				"random-namespace:foo-prefix:8080",
   384  				"random-namespace:foo-slash-exact:8080",
   385  			},
   386  		},
   387  		{
   388  			name: "complex ingress",
   389  			args: args{
   390  				m: complexIngressModel,
   391  			},
   392  			expected: []string{
   393  				"dummy-namespace:another-dummy-backend:8081",
   394  				"dummy-namespace:default-backend:8080",
   395  				"dummy-namespace:dummy-backend:8080",
   396  			},
   397  		},
   398  	}
   399  
   400  	for _, tt := range tests {
   401  		i := &cecTranslator{}
   402  
   403  		t.Run(tt.name, func(t *testing.T) {
   404  			res := i.getClusters(tt.args.m)
   405  			require.Len(t, res, len(tt.expected))
   406  
   407  			for i := 0; i < len(tt.expected); i++ {
   408  				cluster := &envoy_config_cluster_v3.Cluster{}
   409  				err := proto.Unmarshal(res[i].GetValue(), cluster)
   410  				require.NoError(t, err)
   411  
   412  				require.Equal(t, tt.expected[i], cluster.Name)
   413  				require.Equal(t, &envoy_config_cluster_v3.Cluster_Type{Type: envoy_config_cluster_v3.Cluster_EDS}, cluster.ClusterDiscoveryType)
   414  			}
   415  		})
   416  	}
   417  }
   418  
   419  func TestGetEnvoyHTTPRouteConfiguration_VirtualHostSorted(t *testing.T) {
   420  	defT := &cecTranslator{}
   421  
   422  	routes := []model.HTTPRoute{
   423  		{
   424  			Backends: []model.Backend{
   425  				{
   426  					Name:      "default-backend",
   427  					Namespace: "random-namespace",
   428  					Port: &model.BackendPort{
   429  						Port: 8080,
   430  					},
   431  				},
   432  			},
   433  		},
   434  	}
   435  
   436  	l1 := []model.HTTPListener{
   437  		{
   438  			Port:                     443,
   439  			Hostname:                 "foo.bar",
   440  			ForceHTTPtoHTTPSRedirect: true,
   441  			Routes:                   routes,
   442  		},
   443  		{
   444  			Port:     443,
   445  			Hostname: "bar.foo",
   446  			Routes:   routes,
   447  		},
   448  	}
   449  
   450  	l2 := []model.HTTPListener{
   451  		{
   452  			Port:     443,
   453  			Hostname: "bar.foo",
   454  			Routes:   routes,
   455  		},
   456  		{
   457  			Port:                     443,
   458  			Hostname:                 "foo.bar",
   459  			ForceHTTPtoHTTPSRedirect: true,
   460  			Routes:                   routes,
   461  		},
   462  	}
   463  
   464  	res1 := defT.getEnvoyHTTPRouteConfiguration(&model.Model{HTTP: l1})
   465  	res2 := defT.getEnvoyHTTPRouteConfiguration(&model.Model{HTTP: l2})
   466  
   467  	diffOutput := cmp.Diff(res1, res2, protocmp.Transform())
   468  	if len(diffOutput) != 0 {
   469  		t.Errorf("CiliumEnvoyConfigs did not match:\n%s\n", diffOutput)
   470  	}
   471  
   472  	// assert.Equal(t, res1, res2)
   473  }
   474  
   475  func TestSharedIngressTranslator_getEnvoyHTTPRouteConfiguration(t *testing.T) {
   476  	type args struct {
   477  		m *model.Model
   478  	}
   479  
   480  	tests := []struct {
   481  		name                 string
   482  		args                 args
   483  		expectedRouteConfigs []*envoy_config_route_v3.RouteConfiguration
   484  	}{
   485  		{
   486  			name: "default backend",
   487  			args: args{
   488  				m: defaultBackendModel,
   489  			},
   490  			expectedRouteConfigs: defaultBackendExpectedConfig,
   491  		},
   492  		{
   493  			name: "host rule",
   494  			args: args{
   495  				m: hostRulesModel,
   496  			},
   497  			expectedRouteConfigs: hostRulesExpectedConfig,
   498  		},
   499  		{
   500  			name: "host rule with enforceHTTPS",
   501  			args: args{
   502  				m: hostRulesModelEnforcedHTTPS,
   503  			},
   504  			expectedRouteConfigs: hostRulesExpectedConfigEnforceHTTPS,
   505  		},
   506  		{
   507  			name: "path rules",
   508  			args: args{
   509  				m: pathRulesModel,
   510  			},
   511  			expectedRouteConfigs: pathRulesExpectedConfig,
   512  		},
   513  		{
   514  			name: "complex ingress",
   515  			args: args{
   516  				m: complexIngressModel,
   517  			},
   518  			expectedRouteConfigs: complexIngressExpectedConfig,
   519  		},
   520  		{
   521  			name: "complex ingress with enforceHTTPS",
   522  			args: args{
   523  				m: complexIngressModelwithRedirects,
   524  			},
   525  			expectedRouteConfigs: complexIngressExpectedConfigEnforceHTTPS,
   526  		},
   527  		{
   528  			name: "multiple path types in one listener",
   529  			args: args{
   530  				m: multiplePathTypesModel,
   531  			},
   532  			expectedRouteConfigs: multiplePathTypesExpectedConfig,
   533  		},
   534  		{
   535  			name: "routes with multiple hostnames in one listener",
   536  			args: args{
   537  				m: multipleRouteHostnamesModel,
   538  			},
   539  			expectedRouteConfigs: multipleRouteHostnamesExpectedConfig,
   540  		},
   541  	}
   542  
   543  	defT := &cecTranslator{}
   544  
   545  	for _, tt := range tests {
   546  		t.Run(tt.name, func(t *testing.T) {
   547  			res := defT.getEnvoyHTTPRouteConfiguration(tt.args.m)
   548  			require.Len(t, res, len(tt.expectedRouteConfigs), "Number of Listeners did not match")
   549  
   550  			for i, rawRoute := range res {
   551  				ttListener := tt.expectedRouteConfigs[i]
   552  				listener := &envoy_config_route_v3.RouteConfiguration{}
   553  				err := proto.Unmarshal(rawRoute.Value, listener)
   554  				require.NoError(t, err)
   555  
   556  				for j, vhost := range listener.VirtualHosts {
   557  					if j >= len(ttListener.VirtualHosts) {
   558  						t.Errorf("More VirtualHosts in the actual than the expected for actual Listener name %s", listener.Name)
   559  						continue
   560  					}
   561  
   562  					ttVhost := ttListener.VirtualHosts[j]
   563  
   564  					if len(ttVhost.Routes) > len(vhost.Routes) {
   565  						diffOutput := cmp.Diff(ttVhost.Routes, vhost.Routes, protocmp.Transform())
   566  						t.Errorf("More Routes in the actual than the expected for actual VirtualHost name %s in actual Listener %s\n%s\n", vhost.Name, listener.Name, diffOutput)
   567  					}
   568  
   569  					for k, route := range vhost.Routes {
   570  						if k >= len(ttVhost.Routes) {
   571  							continue
   572  						}
   573  
   574  						ttRoute := ttVhost.Routes[k]
   575  
   576  						diffOutput := cmp.Diff(ttRoute, route, protocmp.Transform())
   577  						if len(diffOutput) != 0 {
   578  							t.Errorf("Routes did not match for Listener %s and VirtualHost %s, route number %d:\n%s\n", ttListener.Name, ttVhost.Name, k, diffOutput)
   579  						}
   580  					}
   581  					// If there were no errors at the Route level, check for errors at the VirtualHost level
   582  					if !t.Failed() {
   583  						diffOutput := cmp.Diff(ttVhost, vhost, protocmp.Transform())
   584  						if len(diffOutput) != 0 {
   585  							t.Errorf("VirtualHosts did not match for Listener %s:\n %s\n", listener.Name, diffOutput)
   586  						}
   587  					}
   588  				}
   589  				// If there were no errors anywhere else, check for errors at the Listener level
   590  				if !t.Failed() {
   591  					diffOutput := cmp.Diff(ttListener, listener, protocmp.Transform())
   592  					if len(diffOutput) != 0 {
   593  						t.Errorf("VirtualHosts did not match:\n %s\n", diffOutput)
   594  					}
   595  				}
   596  
   597  			}
   598  		})
   599  	}
   600  }
   601  
   602  // The following helpers generate various types of path matches.
   603  // Most notably, we treat a match for the path "/" differently to other matches,
   604  // so it has its own helper.
   605  
   606  func envoyRouteMatchExactPath(path string) *envoy_config_route_v3.RouteMatch {
   607  	return &envoy_config_route_v3.RouteMatch{
   608  		PathSpecifier: &envoy_config_route_v3.RouteMatch_Path{
   609  			Path: path,
   610  		},
   611  	}
   612  }
   613  
   614  func envoyRouteMatchImplementationSpecific(path string) *envoy_config_route_v3.RouteMatch {
   615  	return &envoy_config_route_v3.RouteMatch{
   616  		PathSpecifier: &envoy_config_route_v3.RouteMatch_SafeRegex{
   617  			SafeRegex: &matcherv3.RegexMatcher{
   618  				Regex: path,
   619  			},
   620  		},
   621  	}
   622  }
   623  
   624  func envoyRouteMatchRootPath() *envoy_config_route_v3.RouteMatch {
   625  	return &envoy_config_route_v3.RouteMatch{
   626  		PathSpecifier: &envoy_config_route_v3.RouteMatch_Prefix{
   627  			Prefix: "/",
   628  		},
   629  	}
   630  }
   631  
   632  func envoyRouteMatchPrefixPath(path string) *envoy_config_route_v3.RouteMatch {
   633  	return &envoy_config_route_v3.RouteMatch{
   634  		PathSpecifier: &envoy_config_route_v3.RouteMatch_PathSeparatedPrefix{
   635  			PathSeparatedPrefix: path,
   636  		},
   637  	}
   638  }
   639  
   640  func envoyRouteAction(namespace, backend, port string) *envoy_config_route_v3.Route_Route {
   641  	return &envoy_config_route_v3.Route_Route{
   642  		Route: &envoy_config_route_v3.RouteAction{
   643  			ClusterSpecifier: &envoy_config_route_v3.RouteAction_Cluster{
   644  				Cluster: fmt.Sprintf("%s:%s:%s", namespace, backend, port),
   645  			},
   646  			MaxStreamDuration: &envoy_config_route_v3.RouteAction_MaxStreamDuration{
   647  				MaxStreamDuration: &durationpb.Duration{Seconds: 0},
   648  			},
   649  		},
   650  	}
   651  }
   652  
   653  func envoyHTTPSRouteRedirect() *envoy_config_route_v3.Route_Redirect {
   654  	return &envoy_config_route_v3.Route_Redirect{
   655  		Redirect: &envoy_config_route_v3.RedirectAction{
   656  			SchemeRewriteSpecifier: &envoy_config_route_v3.RedirectAction_HttpsRedirect{
   657  				HttpsRedirect: true,
   658  			},
   659  		},
   660  	}
   661  }
   662  
   663  func withAuthority(match *envoy_config_route_v3.RouteMatch, regex string) *envoy_config_route_v3.RouteMatch {
   664  	authorityHeader := &envoy_config_route_v3.HeaderMatcher{
   665  		Name: ":authority",
   666  		HeaderMatchSpecifier: &envoy_config_route_v3.HeaderMatcher_StringMatch{
   667  			StringMatch: &matcherv3.StringMatcher{
   668  				MatchPattern: &matcherv3.StringMatcher_SafeRegex{
   669  					SafeRegex: &matcherv3.RegexMatcher{
   670  						Regex: regex,
   671  					},
   672  				},
   673  			},
   674  		},
   675  	}
   676  
   677  	match.Headers = append(match.Headers, authorityHeader)
   678  
   679  	return match
   680  }
   681  
   682  func domainsHelper(domain string) []string {
   683  	if domain == "*" {
   684  		return []string{domain}
   685  	}
   686  
   687  	return []string{domain, fmt.Sprintf("%s:*", domain)}
   688  }
   689  
   690  func TestSharedIngressTranslator_getResources(t *testing.T) {
   691  	type args struct {
   692  		m *model.Model
   693  	}
   694  	tests := []struct {
   695  		name     string
   696  		args     args
   697  		expected int
   698  	}{
   699  		{
   700  			name: "default backend",
   701  			args: args{
   702  				m: defaultBackendModel,
   703  			},
   704  			expected: 3,
   705  		},
   706  		{
   707  			name: "host rules",
   708  			args: args{
   709  				m: hostRulesModel,
   710  			},
   711  			expected: 5,
   712  		},
   713  		{
   714  			name: "path rules",
   715  			args: args{
   716  				m: pathRulesModel,
   717  			},
   718  			expected: 8,
   719  		},
   720  		{
   721  			name: "complex ingress",
   722  			args: args{
   723  				m: complexIngressModel,
   724  			},
   725  			expected: 6,
   726  		},
   727  	}
   728  	for _, tt := range tests {
   729  		t.Run(tt.name, func(t *testing.T) {
   730  			i := &cecTranslator{}
   731  			got := i.getResources(tt.args.m)
   732  			require.Lenf(t, got, tt.expected, "expected %d resources, got %d", tt.expected, len(got))
   733  
   734  			// Log for debugging purpose
   735  			for _, e := range got {
   736  				b, _ := e.MarshalJSON()
   737  				t.Logf("%s\n", b)
   738  			}
   739  		})
   740  	}
   741  }