istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/gateway/conversion_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gateway
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"reflect"
    21  	"regexp"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	k8s "sigs.k8s.io/gateway-api/apis/v1"
    31  	k8salpha "sigs.k8s.io/gateway-api/apis/v1alpha2"
    32  	"sigs.k8s.io/yaml"
    33  
    34  	istio "istio.io/api/networking/v1alpha3"
    35  	"istio.io/istio/pilot/pkg/config/kube/crd"
    36  	credentials "istio.io/istio/pilot/pkg/credentials/kube"
    37  	"istio.io/istio/pilot/pkg/features"
    38  	"istio.io/istio/pilot/pkg/model"
    39  	"istio.io/istio/pilot/pkg/model/kstatus"
    40  	"istio.io/istio/pilot/pkg/networking/core"
    41  	"istio.io/istio/pilot/test/util"
    42  	"istio.io/istio/pkg/cluster"
    43  	"istio.io/istio/pkg/config"
    44  	"istio.io/istio/pkg/config/constants"
    45  	crdvalidation "istio.io/istio/pkg/config/crd"
    46  	"istio.io/istio/pkg/config/schema/gvk"
    47  	"istio.io/istio/pkg/kube"
    48  	"istio.io/istio/pkg/test"
    49  	"istio.io/istio/pkg/test/util/assert"
    50  	"istio.io/istio/pkg/util/sets"
    51  )
    52  
    53  var ports = []*model.Port{
    54  	{
    55  		Name:     "http",
    56  		Port:     80,
    57  		Protocol: "HTTP",
    58  	},
    59  	{
    60  		Name:     "tcp",
    61  		Port:     34000,
    62  		Protocol: "TCP",
    63  	},
    64  	{
    65  		Name:     "tcp-other",
    66  		Port:     34001,
    67  		Protocol: "TCP",
    68  	},
    69  }
    70  
    71  var services = []*model.Service{
    72  	{
    73  		Attributes: model.ServiceAttributes{
    74  			Name:      "istio-ingressgateway",
    75  			Namespace: "istio-system",
    76  			ClusterExternalAddresses: &model.AddressMap{
    77  				Addresses: map[cluster.ID][]string{
    78  					constants.DefaultClusterName: {"1.2.3.4"},
    79  				},
    80  			},
    81  		},
    82  		Ports:    ports,
    83  		Hostname: "istio-ingressgateway.istio-system.svc.domain.suffix",
    84  	},
    85  	{
    86  		Attributes: model.ServiceAttributes{
    87  			Namespace: "istio-system",
    88  		},
    89  		Ports:    ports,
    90  		Hostname: "example.com",
    91  	},
    92  	{
    93  		Attributes: model.ServiceAttributes{
    94  			Namespace: "default",
    95  		},
    96  		Ports:    ports,
    97  		Hostname: "httpbin.default.svc.domain.suffix",
    98  	},
    99  	{
   100  		Attributes: model.ServiceAttributes{
   101  			Namespace: "apple",
   102  		},
   103  		Ports:    ports,
   104  		Hostname: "httpbin-apple.apple.svc.domain.suffix",
   105  	},
   106  	{
   107  		Attributes: model.ServiceAttributes{
   108  			Namespace: "banana",
   109  		},
   110  		Ports:    ports,
   111  		Hostname: "httpbin-banana.banana.svc.domain.suffix",
   112  	},
   113  	{
   114  		Attributes: model.ServiceAttributes{
   115  			Namespace: "default",
   116  		},
   117  		Ports:    ports,
   118  		Hostname: "httpbin-second.default.svc.domain.suffix",
   119  	},
   120  	{
   121  		Attributes: model.ServiceAttributes{
   122  			Namespace: "default",
   123  		},
   124  		Ports:    ports,
   125  		Hostname: "httpbin-wildcard.default.svc.domain.suffix",
   126  	},
   127  	{
   128  		Attributes: model.ServiceAttributes{
   129  			Namespace: "default",
   130  		},
   131  		Ports:    ports,
   132  		Hostname: "foo-svc.default.svc.domain.suffix",
   133  	},
   134  	{
   135  		Attributes: model.ServiceAttributes{
   136  			Namespace: "default",
   137  		},
   138  		Ports:    ports,
   139  		Hostname: "httpbin-other.default.svc.domain.suffix",
   140  	},
   141  	{
   142  		Attributes: model.ServiceAttributes{
   143  			Namespace: "default",
   144  		},
   145  		Ports:    ports,
   146  		Hostname: "example.default.svc.domain.suffix",
   147  	},
   148  	{
   149  		Attributes: model.ServiceAttributes{
   150  			Namespace: "default",
   151  		},
   152  		Ports:    ports,
   153  		Hostname: "echo.default.svc.domain.suffix",
   154  	},
   155  	{
   156  		Attributes: model.ServiceAttributes{
   157  			Namespace: "default",
   158  		},
   159  		Ports:    ports,
   160  		Hostname: "echo.default.svc.domain.suffix",
   161  	},
   162  	{
   163  		Attributes: model.ServiceAttributes{
   164  			Namespace: "cert",
   165  		},
   166  		Ports:    ports,
   167  		Hostname: "httpbin.cert.svc.domain.suffix",
   168  	},
   169  	{
   170  		Attributes: model.ServiceAttributes{
   171  			Namespace: "service",
   172  		},
   173  		Ports:    ports,
   174  		Hostname: "my-svc.service.svc.domain.suffix",
   175  	},
   176  	{
   177  		Attributes: model.ServiceAttributes{
   178  			Namespace: "default",
   179  		},
   180  		Ports:    ports,
   181  		Hostname: "google.com",
   182  	},
   183  	{
   184  		Attributes: model.ServiceAttributes{
   185  			Namespace: "allowed-1",
   186  		},
   187  		Ports:    ports,
   188  		Hostname: "svc2.allowed-1.svc.domain.suffix",
   189  	},
   190  	{
   191  		Attributes: model.ServiceAttributes{
   192  			Namespace: "allowed-2",
   193  		},
   194  		Ports:    ports,
   195  		Hostname: "svc2.allowed-2.svc.domain.suffix",
   196  	},
   197  	{
   198  		Attributes: model.ServiceAttributes{
   199  			Namespace: "allowed-1",
   200  		},
   201  		Ports:    ports,
   202  		Hostname: "svc1.allowed-1.svc.domain.suffix",
   203  	},
   204  	{
   205  		Attributes: model.ServiceAttributes{
   206  			Namespace: "allowed-2",
   207  		},
   208  		Ports:    ports,
   209  		Hostname: "svc3.allowed-2.svc.domain.suffix",
   210  	},
   211  	{
   212  		Attributes: model.ServiceAttributes{
   213  			Namespace: "default",
   214  		},
   215  		Ports:    ports,
   216  		Hostname: "svc4.default.svc.domain.suffix",
   217  	},
   218  	{
   219  		Attributes: model.ServiceAttributes{
   220  			Namespace: "group-namespace1",
   221  		},
   222  		Ports:    ports,
   223  		Hostname: "httpbin.group-namespace1.svc.domain.suffix",
   224  	},
   225  	{
   226  		Attributes: model.ServiceAttributes{
   227  			Namespace: "group-namespace2",
   228  		},
   229  		Ports:    ports,
   230  		Hostname: "httpbin.group-namespace2.svc.domain.suffix",
   231  	},
   232  	{
   233  		Attributes: model.ServiceAttributes{
   234  			Namespace: "default",
   235  		},
   236  		Ports:    ports,
   237  		Hostname: "httpbin-zero.default.svc.domain.suffix",
   238  	},
   239  	{
   240  		Attributes: model.ServiceAttributes{
   241  			Namespace: "istio-system",
   242  		},
   243  		Ports:    ports,
   244  		Hostname: "httpbin.istio-system.svc.domain.suffix",
   245  	},
   246  	{
   247  		Attributes: model.ServiceAttributes{
   248  			Namespace: "default",
   249  		},
   250  		Ports:    ports,
   251  		Hostname: "httpbin-mirror.default.svc.domain.suffix",
   252  	},
   253  	{
   254  		Attributes: model.ServiceAttributes{
   255  			Namespace: "default",
   256  		},
   257  		Ports:    ports,
   258  		Hostname: "httpbin-foo.default.svc.domain.suffix",
   259  	},
   260  	{
   261  		Attributes: model.ServiceAttributes{
   262  			Namespace: "default",
   263  		},
   264  		Ports:    ports,
   265  		Hostname: "httpbin-alt.default.svc.domain.suffix",
   266  	},
   267  	{
   268  		Attributes: model.ServiceAttributes{
   269  			Namespace: "istio-system",
   270  		},
   271  		Ports:    ports,
   272  		Hostname: "istiod.istio-system.svc.domain.suffix",
   273  	},
   274  	{
   275  		Attributes: model.ServiceAttributes{
   276  			Namespace: "istio-system",
   277  		},
   278  		Ports:    ports,
   279  		Hostname: "istiod.istio-system.svc.domain.suffix",
   280  	},
   281  	{
   282  		Attributes: model.ServiceAttributes{
   283  			Namespace: "istio-system",
   284  		},
   285  		Ports:    ports,
   286  		Hostname: "echo.istio-system.svc.domain.suffix",
   287  	},
   288  	{
   289  		Attributes: model.ServiceAttributes{
   290  			Namespace: "default",
   291  		},
   292  		Ports:    ports,
   293  		Hostname: "httpbin-bad.default.svc.domain.suffix",
   294  	},
   295  }
   296  
   297  var (
   298  	// https://github.com/kubernetes/kubernetes/blob/v1.25.4/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls_test.go#L31
   299  	rsaCertPEM = `-----BEGIN CERTIFICATE-----
   300  MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
   301  BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
   302  aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
   303  MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
   304  ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
   305  hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
   306  rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
   307  zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
   308  MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
   309  r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
   310  -----END CERTIFICATE-----
   311  `
   312  	rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
   313  MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
   314  k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
   315  6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
   316  MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
   317  SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
   318  xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
   319  D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
   320  -----END RSA PRIVATE KEY-----
   321  `
   322  
   323  	secrets = []runtime.Object{
   324  		&corev1.Secret{
   325  			ObjectMeta: metav1.ObjectMeta{
   326  				Name:      "my-cert-http",
   327  				Namespace: "istio-system",
   328  			},
   329  			Data: map[string][]byte{
   330  				"tls.crt": []byte(rsaCertPEM),
   331  				"tls.key": []byte(rsaKeyPEM),
   332  			},
   333  		},
   334  		&corev1.Secret{
   335  			ObjectMeta: metav1.ObjectMeta{
   336  				Name:      "cert",
   337  				Namespace: "cert",
   338  			},
   339  			Data: map[string][]byte{
   340  				"tls.crt": []byte(rsaCertPEM),
   341  				"tls.key": []byte(rsaKeyPEM),
   342  			},
   343  		},
   344  		&corev1.Secret{
   345  			ObjectMeta: metav1.ObjectMeta{
   346  				Name:      "malformed",
   347  				Namespace: "istio-system",
   348  			},
   349  			Data: map[string][]byte{
   350  				// nolint: lll
   351  				// https://github.com/kubernetes-sigs/gateway-api/blob/d7f71d6b7df7e929ae299948973a693980afc183/conformance/tests/gateway-invalid-tls-certificateref.yaml#L87-L90
   352  				// this certificate is invalid because contains an invalid pem (base64 of "Hello world"),
   353  				// and the certificate and the key are identical
   354  				"tls.crt": []byte("SGVsbG8gd29ybGQK"),
   355  				"tls.key": []byte("SGVsbG8gd29ybGQK"),
   356  			},
   357  		},
   358  	}
   359  )
   360  
   361  func init() {
   362  	features.EnableAlphaGatewayAPI = true
   363  	features.EnableAmbientWaypoints = true
   364  	// Recompute with ambient enabled
   365  	classInfos = getClassInfos()
   366  	builtinClasses = getBuiltinClasses()
   367  }
   368  
   369  func TestConvertResources(t *testing.T) {
   370  	validator := crdvalidation.NewIstioValidator(t)
   371  	cases := []struct {
   372  		name string
   373  		// Some configs are intended to be generated with invalid configs, and since they will be validated
   374  		// by the validator, we need to ignore the validation errors to prevent the test from failing.
   375  		validationIgnorer *crdvalidation.ValidationIgnorer
   376  	}{
   377  		{name: "http"},
   378  		{name: "tcp"},
   379  		{name: "tls"},
   380  		{name: "grpc"},
   381  		{name: "mismatch"},
   382  		{name: "weighted"},
   383  		{name: "zero"},
   384  		{name: "mesh"},
   385  		{
   386  			name: "invalid",
   387  			validationIgnorer: crdvalidation.NewValidationIgnorer(
   388  				"default/^invalid-backendRef-kind-",
   389  				"default/^invalid-backendRef-mixed-",
   390  			),
   391  		},
   392  		{name: "multi-gateway"},
   393  		{name: "delegated"},
   394  		{name: "route-binding"},
   395  		{name: "reference-policy-tls"},
   396  		{
   397  			name: "reference-policy-service",
   398  			validationIgnorer: crdvalidation.NewValidationIgnorer(
   399  				"istio-system/^backend-not-allowed-",
   400  			),
   401  		},
   402  		{
   403  			name: "reference-policy-tcp",
   404  			validationIgnorer: crdvalidation.NewValidationIgnorer(
   405  				"istio-system/^not-allowed-echo-",
   406  			),
   407  		},
   408  		{name: "serviceentry"},
   409  		{name: "eastwest"},
   410  		{name: "eastwest-tlsoption"},
   411  		{name: "eastwest-labelport"},
   412  		{name: "eastwest-remote"},
   413  		{name: "alias"},
   414  		{name: "mcs"},
   415  		{name: "route-precedence"},
   416  		{name: "waypoint"},
   417  		{name: "isolation"},
   418  	}
   419  	for _, tt := range cases {
   420  		t.Run(tt.name, func(t *testing.T) {
   421  			input := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt.name), validator, nil)
   422  			// Setup a few preconfigured services
   423  			instances := []*model.ServiceInstance{}
   424  			for _, svc := range services {
   425  				instances = append(instances, &model.ServiceInstance{
   426  					Service:     svc,
   427  					ServicePort: ports[0],
   428  					Endpoint:    &model.IstioEndpoint{EndpointPort: 8080},
   429  				}, &model.ServiceInstance{
   430  					Service:     svc,
   431  					ServicePort: ports[1],
   432  					Endpoint:    &model.IstioEndpoint{},
   433  				}, &model.ServiceInstance{
   434  					Service:     svc,
   435  					ServicePort: ports[2],
   436  					Endpoint:    &model.IstioEndpoint{},
   437  				})
   438  			}
   439  			cg := core.NewConfigGenTest(t, core.TestOptions{
   440  				Services:  services,
   441  				Instances: instances,
   442  			})
   443  			kr := splitInput(t, input)
   444  			kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
   445  			output := convertResources(kr)
   446  			output.AllowedReferences = AllowedReferences{} // Not tested here
   447  			output.ReferencedNamespaceKeys = nil           // Not tested here
   448  			output.ResourceReferences = nil                // Not tested here
   449  
   450  			// sort virtual services to make the order deterministic
   451  			sort.Slice(output.VirtualService, func(i, j int) bool {
   452  				return output.VirtualService[i].Namespace+"/"+output.VirtualService[i].Name < output.VirtualService[j].Namespace+"/"+output.VirtualService[j].Name
   453  			})
   454  			goldenFile := fmt.Sprintf("testdata/%s.yaml.golden", tt.name)
   455  			res := append(output.Gateway, output.VirtualService...)
   456  			util.CompareContent(t, marshalYaml(t, res), goldenFile)
   457  			golden := splitOutput(readConfig(t, goldenFile, validator, tt.validationIgnorer))
   458  
   459  			// sort virtual services to make the order deterministic
   460  			sort.Slice(golden.VirtualService, func(i, j int) bool {
   461  				return golden.VirtualService[i].Namespace+"/"+golden.VirtualService[i].Name < golden.VirtualService[j].Namespace+"/"+golden.VirtualService[j].Name
   462  			})
   463  
   464  			assert.Equal(t, golden, output)
   465  
   466  			outputStatus := getStatus(t, kr.GatewayClass, kr.Gateway, kr.HTTPRoute, kr.GRPCRoute, kr.TLSRoute, kr.TCPRoute)
   467  			goldenStatusFile := fmt.Sprintf("testdata/%s.status.yaml.golden", tt.name)
   468  			if util.Refresh() {
   469  				if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
   470  					t.Fatal(err)
   471  				}
   472  			}
   473  			goldenStatus, err := os.ReadFile(goldenStatusFile)
   474  			if err != nil {
   475  				t.Fatal(err)
   476  			}
   477  			if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
   478  				t.Fatalf("Diff:\n%s", diff)
   479  			}
   480  		})
   481  	}
   482  }
   483  
   484  func TestSortHTTPRoutes(t *testing.T) {
   485  	cases := []struct {
   486  		name string
   487  		in   []*istio.HTTPRoute
   488  		out  []*istio.HTTPRoute
   489  	}{
   490  		{
   491  			"match is preferred over no match",
   492  			[]*istio.HTTPRoute{
   493  				{
   494  					Match: []*istio.HTTPMatchRequest{},
   495  				},
   496  				{
   497  					Match: []*istio.HTTPMatchRequest{
   498  						{
   499  							Uri: &istio.StringMatch{
   500  								MatchType: &istio.StringMatch_Exact{
   501  									Exact: "/foo",
   502  								},
   503  							},
   504  						},
   505  					},
   506  				},
   507  			},
   508  			[]*istio.HTTPRoute{
   509  				{
   510  					Match: []*istio.HTTPMatchRequest{
   511  						{
   512  							Uri: &istio.StringMatch{
   513  								MatchType: &istio.StringMatch_Exact{
   514  									Exact: "/foo",
   515  								},
   516  							},
   517  						},
   518  					},
   519  				},
   520  				{
   521  					Match: []*istio.HTTPMatchRequest{},
   522  				},
   523  			},
   524  		},
   525  		{
   526  			"path matching exact > prefix  > regex",
   527  			[]*istio.HTTPRoute{
   528  				{
   529  					Match: []*istio.HTTPMatchRequest{
   530  						{
   531  							Uri: &istio.StringMatch{
   532  								MatchType: &istio.StringMatch_Prefix{
   533  									Prefix: "/",
   534  								},
   535  							},
   536  						},
   537  					},
   538  				},
   539  				{
   540  					Match: []*istio.HTTPMatchRequest{
   541  						{
   542  							Uri: &istio.StringMatch{
   543  								MatchType: &istio.StringMatch_Regex{
   544  									Regex: ".*foo",
   545  								},
   546  							},
   547  						},
   548  					},
   549  				},
   550  				{
   551  					Match: []*istio.HTTPMatchRequest{
   552  						{
   553  							Uri: &istio.StringMatch{
   554  								MatchType: &istio.StringMatch_Exact{
   555  									Exact: "/foo",
   556  								},
   557  							},
   558  						},
   559  					},
   560  				},
   561  			},
   562  			[]*istio.HTTPRoute{
   563  				{
   564  					Match: []*istio.HTTPMatchRequest{
   565  						{
   566  							Uri: &istio.StringMatch{
   567  								MatchType: &istio.StringMatch_Exact{
   568  									Exact: "/foo",
   569  								},
   570  							},
   571  						},
   572  					},
   573  				},
   574  				{
   575  					Match: []*istio.HTTPMatchRequest{
   576  						{
   577  							Uri: &istio.StringMatch{
   578  								MatchType: &istio.StringMatch_Prefix{
   579  									Prefix: "/",
   580  								},
   581  							},
   582  						},
   583  					},
   584  				},
   585  				{
   586  					Match: []*istio.HTTPMatchRequest{
   587  						{
   588  							Uri: &istio.StringMatch{
   589  								MatchType: &istio.StringMatch_Regex{
   590  									Regex: ".*foo",
   591  								},
   592  							},
   593  						},
   594  					},
   595  				},
   596  			},
   597  		},
   598  		{
   599  			"path prefix matching with largest characters",
   600  			[]*istio.HTTPRoute{
   601  				{
   602  					Match: []*istio.HTTPMatchRequest{
   603  						{
   604  							Uri: &istio.StringMatch{
   605  								MatchType: &istio.StringMatch_Prefix{
   606  									Prefix: "/foo",
   607  								},
   608  							},
   609  						},
   610  					},
   611  				},
   612  				{
   613  					Match: []*istio.HTTPMatchRequest{
   614  						{
   615  							Uri: &istio.StringMatch{
   616  								MatchType: &istio.StringMatch_Prefix{
   617  									Prefix: "/",
   618  								},
   619  							},
   620  						},
   621  					},
   622  				},
   623  				{
   624  					Match: []*istio.HTTPMatchRequest{
   625  						{
   626  							Uri: &istio.StringMatch{
   627  								MatchType: &istio.StringMatch_Prefix{
   628  									Prefix: "/foobar",
   629  								},
   630  							},
   631  						},
   632  					},
   633  				},
   634  			},
   635  			[]*istio.HTTPRoute{
   636  				{
   637  					Match: []*istio.HTTPMatchRequest{
   638  						{
   639  							Uri: &istio.StringMatch{
   640  								MatchType: &istio.StringMatch_Prefix{
   641  									Prefix: "/foobar",
   642  								},
   643  							},
   644  						},
   645  					},
   646  				},
   647  				{
   648  					Match: []*istio.HTTPMatchRequest{
   649  						{
   650  							Uri: &istio.StringMatch{
   651  								MatchType: &istio.StringMatch_Prefix{
   652  									Prefix: "/foo",
   653  								},
   654  							},
   655  						},
   656  					},
   657  				},
   658  				{
   659  					Match: []*istio.HTTPMatchRequest{
   660  						{
   661  							Uri: &istio.StringMatch{
   662  								MatchType: &istio.StringMatch_Prefix{
   663  									Prefix: "/",
   664  								},
   665  							},
   666  						},
   667  					},
   668  				},
   669  			},
   670  		},
   671  		{
   672  			"path match is preferred over method match",
   673  			[]*istio.HTTPRoute{
   674  				{
   675  					Match: []*istio.HTTPMatchRequest{
   676  						{
   677  							Method: &istio.StringMatch{
   678  								MatchType: &istio.StringMatch_Exact{
   679  									Exact: "GET",
   680  								},
   681  							},
   682  						},
   683  					},
   684  				},
   685  				{
   686  					Match: []*istio.HTTPMatchRequest{
   687  						{
   688  							Uri: &istio.StringMatch{
   689  								MatchType: &istio.StringMatch_Prefix{
   690  									Prefix: "/foobar",
   691  								},
   692  							},
   693  						},
   694  					},
   695  				},
   696  			},
   697  			[]*istio.HTTPRoute{
   698  				{
   699  					Match: []*istio.HTTPMatchRequest{
   700  						{
   701  							Uri: &istio.StringMatch{
   702  								MatchType: &istio.StringMatch_Prefix{
   703  									Prefix: "/foobar",
   704  								},
   705  							},
   706  						},
   707  					},
   708  				},
   709  				{
   710  					Match: []*istio.HTTPMatchRequest{
   711  						{
   712  							Method: &istio.StringMatch{
   713  								MatchType: &istio.StringMatch_Exact{
   714  									Exact: "GET",
   715  								},
   716  							},
   717  						},
   718  					},
   719  				},
   720  			},
   721  		},
   722  		{
   723  			"largest number of header matches is preferred",
   724  			[]*istio.HTTPRoute{
   725  				{
   726  					Match: []*istio.HTTPMatchRequest{
   727  						{
   728  							Headers: map[string]*istio.StringMatch{
   729  								"header1": {
   730  									MatchType: &istio.StringMatch_Exact{
   731  										Exact: "value1",
   732  									},
   733  								},
   734  							},
   735  						},
   736  					},
   737  				},
   738  				{
   739  					Match: []*istio.HTTPMatchRequest{
   740  						{
   741  							Headers: map[string]*istio.StringMatch{
   742  								"header1": {
   743  									MatchType: &istio.StringMatch_Exact{
   744  										Exact: "value1",
   745  									},
   746  								},
   747  								"header2": {
   748  									MatchType: &istio.StringMatch_Exact{
   749  										Exact: "value2",
   750  									},
   751  								},
   752  							},
   753  						},
   754  					},
   755  				},
   756  			},
   757  			[]*istio.HTTPRoute{
   758  				{
   759  					Match: []*istio.HTTPMatchRequest{
   760  						{
   761  							Headers: map[string]*istio.StringMatch{
   762  								"header1": {
   763  									MatchType: &istio.StringMatch_Exact{
   764  										Exact: "value1",
   765  									},
   766  								},
   767  								"header2": {
   768  									MatchType: &istio.StringMatch_Exact{
   769  										Exact: "value2",
   770  									},
   771  								},
   772  							},
   773  						},
   774  					},
   775  				},
   776  				{
   777  					Match: []*istio.HTTPMatchRequest{
   778  						{
   779  							Headers: map[string]*istio.StringMatch{
   780  								"header1": {
   781  									MatchType: &istio.StringMatch_Exact{
   782  										Exact: "value1",
   783  									},
   784  								},
   785  							},
   786  						},
   787  					},
   788  				},
   789  			},
   790  		},
   791  		{
   792  			"largest number of query params is preferred",
   793  			[]*istio.HTTPRoute{
   794  				{
   795  					Match: []*istio.HTTPMatchRequest{
   796  						{
   797  							QueryParams: map[string]*istio.StringMatch{
   798  								"param1": {
   799  									MatchType: &istio.StringMatch_Exact{
   800  										Exact: "value1",
   801  									},
   802  								},
   803  							},
   804  						},
   805  					},
   806  				},
   807  				{
   808  					Match: []*istio.HTTPMatchRequest{
   809  						{
   810  							QueryParams: map[string]*istio.StringMatch{
   811  								"param1": {
   812  									MatchType: &istio.StringMatch_Exact{
   813  										Exact: "value1",
   814  									},
   815  								},
   816  								"param2": {
   817  									MatchType: &istio.StringMatch_Exact{
   818  										Exact: "value2",
   819  									},
   820  								},
   821  							},
   822  						},
   823  					},
   824  				},
   825  			},
   826  			[]*istio.HTTPRoute{
   827  				{
   828  					Match: []*istio.HTTPMatchRequest{
   829  						{
   830  							QueryParams: map[string]*istio.StringMatch{
   831  								"param1": {
   832  									MatchType: &istio.StringMatch_Exact{
   833  										Exact: "value1",
   834  									},
   835  								},
   836  								"param2": {
   837  									MatchType: &istio.StringMatch_Exact{
   838  										Exact: "value2",
   839  									},
   840  								},
   841  							},
   842  						},
   843  					},
   844  				},
   845  				{
   846  					Match: []*istio.HTTPMatchRequest{
   847  						{
   848  							QueryParams: map[string]*istio.StringMatch{
   849  								"param1": {
   850  									MatchType: &istio.StringMatch_Exact{
   851  										Exact: "value1",
   852  									},
   853  								},
   854  							},
   855  						},
   856  					},
   857  				},
   858  			},
   859  		},
   860  		{
   861  			"path > method > header > query params",
   862  			[]*istio.HTTPRoute{
   863  				{
   864  					Match: []*istio.HTTPMatchRequest{
   865  						{
   866  							Uri: &istio.StringMatch{
   867  								MatchType: &istio.StringMatch_Prefix{
   868  									Prefix: "/",
   869  								},
   870  							},
   871  						},
   872  					},
   873  				},
   874  				{
   875  					Match: []*istio.HTTPMatchRequest{
   876  						{
   877  							QueryParams: map[string]*istio.StringMatch{
   878  								"param1": {
   879  									MatchType: &istio.StringMatch_Exact{
   880  										Exact: "value1",
   881  									},
   882  								},
   883  							},
   884  						},
   885  					},
   886  				},
   887  				{
   888  					Match: []*istio.HTTPMatchRequest{
   889  						{
   890  							Method: &istio.StringMatch{
   891  								MatchType: &istio.StringMatch_Exact{Exact: "GET"},
   892  							},
   893  						},
   894  					},
   895  				},
   896  				{
   897  					Match: []*istio.HTTPMatchRequest{
   898  						{
   899  							Headers: map[string]*istio.StringMatch{
   900  								"param1": {
   901  									MatchType: &istio.StringMatch_Exact{
   902  										Exact: "value1",
   903  									},
   904  								},
   905  							},
   906  						},
   907  					},
   908  				},
   909  			},
   910  			[]*istio.HTTPRoute{
   911  				{
   912  					Match: []*istio.HTTPMatchRequest{
   913  						{
   914  							Uri: &istio.StringMatch{
   915  								MatchType: &istio.StringMatch_Prefix{
   916  									Prefix: "/",
   917  								},
   918  							},
   919  						},
   920  					},
   921  				},
   922  				{
   923  					Match: []*istio.HTTPMatchRequest{
   924  						{
   925  							Method: &istio.StringMatch{
   926  								MatchType: &istio.StringMatch_Exact{Exact: "GET"},
   927  							},
   928  						},
   929  					},
   930  				},
   931  				{
   932  					Match: []*istio.HTTPMatchRequest{
   933  						{
   934  							Headers: map[string]*istio.StringMatch{
   935  								"param1": {
   936  									MatchType: &istio.StringMatch_Exact{
   937  										Exact: "value1",
   938  									},
   939  								},
   940  							},
   941  						},
   942  					},
   943  				},
   944  				{
   945  					Match: []*istio.HTTPMatchRequest{
   946  						{
   947  							QueryParams: map[string]*istio.StringMatch{
   948  								"param1": {
   949  									MatchType: &istio.StringMatch_Exact{
   950  										Exact: "value1",
   951  									},
   952  								},
   953  							},
   954  						},
   955  					},
   956  				},
   957  			},
   958  		},
   959  	}
   960  
   961  	for _, tt := range cases {
   962  		t.Run(tt.name, func(t *testing.T) {
   963  			sortHTTPRoutes(tt.in)
   964  			if !reflect.DeepEqual(tt.in, tt.out) {
   965  				t.Fatalf("expected %v, got %v", tt.out, tt.in)
   966  			}
   967  		})
   968  	}
   969  }
   970  
   971  func TestReferencePolicy(t *testing.T) {
   972  	validator := crdvalidation.NewIstioValidator(t)
   973  	type res struct {
   974  		name, namespace string
   975  		allowed         bool
   976  	}
   977  	cases := []struct {
   978  		name         string
   979  		config       string
   980  		expectations []res
   981  	}{
   982  		{
   983  			name: "simple",
   984  			config: `apiVersion: gateway.networking.k8s.io/v1beta1
   985  kind: ReferenceGrant
   986  metadata:
   987    name: allow-gateways-to-ref-secrets
   988    namespace: default
   989  spec:
   990    from:
   991    - group: gateway.networking.k8s.io
   992      kind: Gateway
   993      namespace: istio-system
   994    to:
   995    - group: ""
   996      kind: Secret
   997  `,
   998  			expectations: []res{
   999  				// allow cross namespace
  1000  				{"kubernetes-gateway://default/wildcard-example-com-cert", "istio-system", true},
  1001  				// denied same namespace. We do not implicitly allow (in this code - higher level code does)
  1002  				{"kubernetes-gateway://default/wildcard-example-com-cert", "default", false},
  1003  				// denied namespace
  1004  				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
  1005  			},
  1006  		},
  1007  		{
  1008  			name: "multiple in one",
  1009  			config: `apiVersion: gateway.networking.k8s.io/v1beta1
  1010  kind: ReferenceGrant
  1011  metadata:
  1012    name: allow-gateways-to-ref-secrets
  1013    namespace: default
  1014  spec:
  1015    from:
  1016    - group: gateway.networking.k8s.io
  1017      kind: Gateway
  1018      namespace: ns-1
  1019    - group: gateway.networking.k8s.io
  1020      kind: Gateway
  1021      namespace: ns-2
  1022    to:
  1023    - group: ""
  1024      kind: Secret
  1025  `,
  1026  			expectations: []res{
  1027  				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
  1028  				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
  1029  				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
  1030  			},
  1031  		},
  1032  		{
  1033  			name: "multiple",
  1034  			config: `apiVersion: gateway.networking.k8s.io/v1beta1
  1035  kind: ReferenceGrant
  1036  metadata:
  1037    name: ns1
  1038    namespace: default
  1039  spec:
  1040    from:
  1041    - group: gateway.networking.k8s.io
  1042      kind: Gateway
  1043      namespace: ns-1
  1044    to:
  1045    - group: ""
  1046      kind: Secret
  1047  ---
  1048  apiVersion: gateway.networking.k8s.io/v1beta1
  1049  kind: ReferenceGrant
  1050  metadata:
  1051    name: ns2
  1052    namespace: default
  1053  spec:
  1054    from:
  1055    - group: gateway.networking.k8s.io
  1056      kind: Gateway
  1057      namespace: ns-2
  1058    to:
  1059    - group: ""
  1060      kind: Secret
  1061  `,
  1062  			expectations: []res{
  1063  				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
  1064  				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
  1065  				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
  1066  			},
  1067  		},
  1068  		{
  1069  			name: "same namespace",
  1070  			config: `apiVersion: gateway.networking.k8s.io/v1beta1
  1071  kind: ReferenceGrant
  1072  metadata:
  1073    name: allow-gateways-to-ref-secrets
  1074    namespace: default
  1075  spec:
  1076    from:
  1077    - group: gateway.networking.k8s.io
  1078      kind: Gateway
  1079      namespace: default
  1080    to:
  1081    - group: ""
  1082      kind: Secret
  1083  `,
  1084  			expectations: []res{
  1085  				{"kubernetes-gateway://default/wildcard-example-com-cert", "istio-system", false},
  1086  				{"kubernetes-gateway://default/wildcard-example-com-cert", "default", true},
  1087  				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
  1088  			},
  1089  		},
  1090  		{
  1091  			name: "same name",
  1092  			config: `apiVersion: gateway.networking.k8s.io/v1beta1
  1093  kind: ReferenceGrant
  1094  metadata:
  1095    name: allow-gateways-to-ref-secrets
  1096    namespace: default
  1097  spec:
  1098    from:
  1099    - group: gateway.networking.k8s.io
  1100      kind: Gateway
  1101      namespace: default
  1102    to:
  1103    - group: ""
  1104      kind: Secret
  1105      name: public
  1106  `,
  1107  			expectations: []res{
  1108  				{"kubernetes-gateway://default/public", "istio-system", false},
  1109  				{"kubernetes-gateway://default/public", "default", true},
  1110  				{"kubernetes-gateway://default/private", "default", false},
  1111  			},
  1112  		},
  1113  	}
  1114  	for _, tt := range cases {
  1115  		t.Run(tt.name, func(t *testing.T) {
  1116  			input := readConfigString(t, tt.config, validator, nil)
  1117  			cg := core.NewConfigGenTest(t, core.TestOptions{})
  1118  			kr := splitInput(t, input)
  1119  			kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
  1120  			output := convertResources(kr)
  1121  			c := &Controller{
  1122  				state: output,
  1123  			}
  1124  			for _, sc := range tt.expectations {
  1125  				t.Run(fmt.Sprintf("%v/%v", sc.name, sc.namespace), func(t *testing.T) {
  1126  					got := c.SecretAllowed(sc.name, sc.namespace)
  1127  					if got != sc.allowed {
  1128  						t.Fatalf("expected allowed=%v, got allowed=%v", sc.allowed, got)
  1129  					}
  1130  				})
  1131  			}
  1132  		})
  1133  	}
  1134  }
  1135  
  1136  func getStatus(t test.Failer, acfgs ...[]config.Config) []byte {
  1137  	cfgs := []config.Config{}
  1138  	for _, cl := range acfgs {
  1139  		cfgs = append(cfgs, cl...)
  1140  	}
  1141  	for i, c := range cfgs {
  1142  		if c.Status.(*kstatus.WrappedStatus) != nil && c.GroupVersionKind == gvk.GatewayClass {
  1143  			// Override GatewaySupportedFeatures for the test so we dont have huge golden files plus we wont need to update them every time we support a new feature
  1144  			c.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
  1145  				gcs := s.(*k8s.GatewayClassStatus)
  1146  				gcs.SupportedFeatures = []k8s.SupportedFeature{"HTTPRouteFeatureA", "HTTPRouteFeatureB"}
  1147  				return gcs
  1148  			})
  1149  		}
  1150  		c = c.DeepCopy()
  1151  		c.Spec = nil
  1152  		c.Labels = nil
  1153  		c.Annotations = nil
  1154  		if c.Status.(*kstatus.WrappedStatus) != nil {
  1155  			c.Status = c.Status.(*kstatus.WrappedStatus).Status
  1156  		}
  1157  		cfgs[i] = c
  1158  	}
  1159  	return timestampRegex.ReplaceAll(marshalYaml(t, cfgs), []byte("lastTransitionTime: fake"))
  1160  }
  1161  
  1162  var timestampRegex = regexp.MustCompile(`lastTransitionTime:.*`)
  1163  
  1164  func splitOutput(configs []config.Config) IstioResources {
  1165  	out := IstioResources{
  1166  		Gateway:        []config.Config{},
  1167  		VirtualService: []config.Config{},
  1168  	}
  1169  	for _, c := range configs {
  1170  		c.Domain = "domain.suffix"
  1171  		switch c.GroupVersionKind {
  1172  		case gvk.Gateway:
  1173  			out.Gateway = append(out.Gateway, c)
  1174  		case gvk.VirtualService:
  1175  			out.VirtualService = append(out.VirtualService, c)
  1176  		}
  1177  	}
  1178  	return out
  1179  }
  1180  
  1181  func splitInput(t test.Failer, configs []config.Config) GatewayResources {
  1182  	out := GatewayResources{}
  1183  	namespaces := sets.New[string]()
  1184  	for _, c := range configs {
  1185  		namespaces.Insert(c.Namespace)
  1186  		switch c.GroupVersionKind {
  1187  		case gvk.GatewayClass:
  1188  			out.GatewayClass = append(out.GatewayClass, c)
  1189  		case gvk.KubernetesGateway:
  1190  			out.Gateway = append(out.Gateway, c)
  1191  		case gvk.HTTPRoute:
  1192  			out.HTTPRoute = append(out.HTTPRoute, c)
  1193  		case gvk.GRPCRoute:
  1194  			out.GRPCRoute = append(out.GRPCRoute, c)
  1195  		case gvk.TCPRoute:
  1196  			out.TCPRoute = append(out.TCPRoute, c)
  1197  		case gvk.TLSRoute:
  1198  			out.TLSRoute = append(out.TLSRoute, c)
  1199  		case gvk.ReferenceGrant:
  1200  			out.ReferenceGrant = append(out.ReferenceGrant, c)
  1201  		case gvk.ServiceEntry:
  1202  			out.ServiceEntry = append(out.ServiceEntry, c)
  1203  		}
  1204  	}
  1205  	out.Namespaces = map[string]*corev1.Namespace{}
  1206  	for ns := range namespaces {
  1207  		out.Namespaces[ns] = &corev1.Namespace{
  1208  			ObjectMeta: metav1.ObjectMeta{
  1209  				Name: ns,
  1210  				Labels: map[string]string{
  1211  					"istio.io/test-name-part": strings.Split(ns, "-")[0],
  1212  				},
  1213  			},
  1214  		}
  1215  	}
  1216  
  1217  	client := kube.NewFakeClient(secrets...)
  1218  	out.Credentials = credentials.NewCredentialsController(client, nil)
  1219  	client.RunAndWait(test.NewStop(t))
  1220  
  1221  	out.Domain = "domain.suffix"
  1222  	return out
  1223  }
  1224  
  1225  func readConfig(t testing.TB, filename string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer) []config.Config {
  1226  	t.Helper()
  1227  
  1228  	data, err := os.ReadFile(filename)
  1229  	if err != nil {
  1230  		t.Fatalf("failed to read input yaml file: %v", err)
  1231  	}
  1232  	return readConfigString(t, string(data), validator, ignorer)
  1233  }
  1234  
  1235  func readConfigString(t testing.TB, data string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer,
  1236  ) []config.Config {
  1237  	if err := validator.ValidateCustomResourceYAML(data, ignorer); err != nil {
  1238  		t.Error(err)
  1239  	}
  1240  	c, _, err := crd.ParseInputs(data)
  1241  	if err != nil {
  1242  		t.Fatalf("failed to parse CRD: %v", err)
  1243  	}
  1244  	return insertDefaults(c)
  1245  }
  1246  
  1247  // insertDefaults sets default values that would be present when reading from Kubernetes but not from
  1248  // files
  1249  func insertDefaults(cfgs []config.Config) []config.Config {
  1250  	res := make([]config.Config, 0, len(cfgs))
  1251  	for _, c := range cfgs {
  1252  		switch c.GroupVersionKind {
  1253  		case gvk.GatewayClass:
  1254  			c.Status = kstatus.Wrap(&k8s.GatewayClassStatus{})
  1255  		case gvk.KubernetesGateway:
  1256  			c.Status = kstatus.Wrap(&k8s.GatewayStatus{})
  1257  		case gvk.HTTPRoute:
  1258  			c.Status = kstatus.Wrap(&k8s.HTTPRouteStatus{})
  1259  		case gvk.GRPCRoute:
  1260  			c.Status = kstatus.Wrap(&k8s.GRPCRouteStatus{})
  1261  		case gvk.TCPRoute:
  1262  			c.Status = kstatus.Wrap(&k8salpha.TCPRouteStatus{})
  1263  		case gvk.TLSRoute:
  1264  			c.Status = kstatus.Wrap(&k8salpha.TLSRouteStatus{})
  1265  		}
  1266  		res = append(res, c)
  1267  	}
  1268  	return res
  1269  }
  1270  
  1271  // Print as YAML
  1272  func marshalYaml(t test.Failer, cl []config.Config) []byte {
  1273  	t.Helper()
  1274  	result := []byte{}
  1275  	separator := []byte("---\n")
  1276  	for _, config := range cl {
  1277  		obj, err := crd.ConvertConfig(config)
  1278  		if err != nil {
  1279  			t.Fatalf("Could not decode %v: %v", config.Name, err)
  1280  		}
  1281  		bytes, err := yaml.Marshal(obj)
  1282  		if err != nil {
  1283  			t.Fatalf("Could not convert %v to YAML: %v", config, err)
  1284  		}
  1285  		result = append(result, bytes...)
  1286  		result = append(result, separator...)
  1287  	}
  1288  	return result
  1289  }
  1290  
  1291  func TestHumanReadableJoin(t *testing.T) {
  1292  	tests := []struct {
  1293  		input []string
  1294  		want  string
  1295  	}{
  1296  		{[]string{"a"}, "a"},
  1297  		{[]string{"a", "b"}, "a and b"},
  1298  		{[]string{"a", "b", "c"}, "a, b, and c"},
  1299  	}
  1300  	for _, tt := range tests {
  1301  		t.Run(strings.Join(tt.input, "_"), func(t *testing.T) {
  1302  			if got := humanReadableJoin(tt.input); !reflect.DeepEqual(got, tt.want) {
  1303  				t.Errorf("got %v, want %v", got, tt.want)
  1304  			}
  1305  		})
  1306  	}
  1307  }
  1308  
  1309  func BenchmarkBuildHTTPVirtualServices(b *testing.B) {
  1310  	ports := []*model.Port{
  1311  		{
  1312  			Name:     "http",
  1313  			Port:     80,
  1314  			Protocol: "HTTP",
  1315  		},
  1316  		{
  1317  			Name:     "tcp",
  1318  			Port:     34000,
  1319  			Protocol: "TCP",
  1320  		},
  1321  	}
  1322  	ingressSvc := &model.Service{
  1323  		Attributes: model.ServiceAttributes{
  1324  			Name:      "istio-ingressgateway",
  1325  			Namespace: "istio-system",
  1326  			ClusterExternalAddresses: &model.AddressMap{
  1327  				Addresses: map[cluster.ID][]string{
  1328  					constants.DefaultClusterName: {"1.2.3.4"},
  1329  				},
  1330  			},
  1331  		},
  1332  		Ports:    ports,
  1333  		Hostname: "istio-ingressgateway.istio-system.svc.domain.suffix",
  1334  	}
  1335  	altIngressSvc := &model.Service{
  1336  		Attributes: model.ServiceAttributes{
  1337  			Namespace: "istio-system",
  1338  		},
  1339  		Ports:    ports,
  1340  		Hostname: "example.com",
  1341  	}
  1342  	cg := core.NewConfigGenTest(b, core.TestOptions{
  1343  		Services: []*model.Service{ingressSvc, altIngressSvc},
  1344  		Instances: []*model.ServiceInstance{
  1345  			{Service: ingressSvc, ServicePort: ingressSvc.Ports[0], Endpoint: &model.IstioEndpoint{EndpointPort: 8080}},
  1346  			{Service: ingressSvc, ServicePort: ingressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
  1347  			{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[0], Endpoint: &model.IstioEndpoint{}},
  1348  			{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
  1349  		},
  1350  	})
  1351  
  1352  	validator := crdvalidation.NewIstioValidator(b)
  1353  	input := readConfig(b, "testdata/benchmark-httproute.yaml", validator, nil)
  1354  	kr := splitInput(b, input)
  1355  	kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
  1356  	ctx := configContext{
  1357  		GatewayResources:  kr,
  1358  		AllowedReferences: convertReferencePolicies(kr),
  1359  	}
  1360  	_, gwMap, _ := convertGateways(ctx)
  1361  	ctx.GatewayReferences = gwMap
  1362  
  1363  	b.ResetTimer()
  1364  	for n := 0; n < b.N; n++ {
  1365  		// for gateway routes, build one VS per gateway+host
  1366  		gatewayRoutes := make(map[string]map[string]*config.Config)
  1367  		// for mesh routes, build one VS per namespace+host
  1368  		meshRoutes := make(map[string]map[string]*config.Config)
  1369  		for _, obj := range kr.HTTPRoute {
  1370  			buildHTTPVirtualServices(ctx, obj, gatewayRoutes, meshRoutes)
  1371  		}
  1372  	}
  1373  }
  1374  
  1375  func TestExtractGatewayServices(t *testing.T) {
  1376  	tests := []struct {
  1377  		name            string
  1378  		r               GatewayResources
  1379  		kgw             *k8s.GatewaySpec
  1380  		obj             config.Config
  1381  		gatewayServices []string
  1382  		err             *ConfigError
  1383  	}{
  1384  		{
  1385  			name: "managed gateway",
  1386  			r:    GatewayResources{Domain: "cluster.local"},
  1387  			kgw: &k8s.GatewaySpec{
  1388  				GatewayClassName: "istio",
  1389  			},
  1390  			obj: config.Config{
  1391  				Meta: config.Meta{
  1392  					Name:      "foo",
  1393  					Namespace: "default",
  1394  				},
  1395  			},
  1396  			gatewayServices: []string{"foo-istio.default.svc.cluster.local"},
  1397  		},
  1398  		{
  1399  			name: "managed gateway with name overridden",
  1400  			r:    GatewayResources{Domain: "cluster.local"},
  1401  			kgw: &k8s.GatewaySpec{
  1402  				GatewayClassName: "istio",
  1403  			},
  1404  			obj: config.Config{
  1405  				Meta: config.Meta{
  1406  					Name:      "foo",
  1407  					Namespace: "default",
  1408  					Annotations: map[string]string{
  1409  						gatewayNameOverride: "bar",
  1410  					},
  1411  				},
  1412  			},
  1413  			gatewayServices: []string{"bar.default.svc.cluster.local"},
  1414  		},
  1415  		{
  1416  			name: "unmanaged gateway",
  1417  			r:    GatewayResources{Domain: "domain"},
  1418  			kgw: &k8s.GatewaySpec{
  1419  				GatewayClassName: "istio",
  1420  				Addresses: []k8s.GatewayAddress{
  1421  					{
  1422  						Value: "abc",
  1423  					},
  1424  					{
  1425  						Type: func() *k8s.AddressType {
  1426  							t := k8s.HostnameAddressType
  1427  							return &t
  1428  						}(),
  1429  						Value: "example.com",
  1430  					},
  1431  					{
  1432  						Type: func() *k8s.AddressType {
  1433  							t := k8s.IPAddressType
  1434  							return &t
  1435  						}(),
  1436  						Value: "1.2.3.4",
  1437  					},
  1438  				},
  1439  			},
  1440  			obj: config.Config{
  1441  				Meta: config.Meta{
  1442  					Name:      "foo",
  1443  					Namespace: "default",
  1444  				},
  1445  			},
  1446  			gatewayServices: []string{"abc.default.svc.domain", "example.com"},
  1447  			err: &ConfigError{
  1448  				Reason:  InvalidAddress,
  1449  				Message: "only Hostname is supported, ignoring [1.2.3.4]",
  1450  			},
  1451  		},
  1452  	}
  1453  	for _, tt := range tests {
  1454  		t.Run(tt.name, func(t *testing.T) {
  1455  			gatewayServices, err := extractGatewayServices(tt.r, tt.kgw, tt.obj, classInfo{})
  1456  			assert.Equal(t, gatewayServices, tt.gatewayServices)
  1457  			assert.Equal(t, err, tt.err)
  1458  		})
  1459  	}
  1460  }