istio

Форк
0
/
conversion_test.go 
1459 строк · 34.9 Кб
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
	k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
31
	k8s "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
	}
418
	for _, tt := range cases {
419
		t.Run(tt.name, func(t *testing.T) {
420
			input := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt.name), validator, nil)
421
			// Setup a few preconfigured services
422
			instances := []*model.ServiceInstance{}
423
			for _, svc := range services {
424
				instances = append(instances, &model.ServiceInstance{
425
					Service:     svc,
426
					ServicePort: ports[0],
427
					Endpoint:    &model.IstioEndpoint{EndpointPort: 8080},
428
				}, &model.ServiceInstance{
429
					Service:     svc,
430
					ServicePort: ports[1],
431
					Endpoint:    &model.IstioEndpoint{},
432
				}, &model.ServiceInstance{
433
					Service:     svc,
434
					ServicePort: ports[2],
435
					Endpoint:    &model.IstioEndpoint{},
436
				})
437
			}
438
			cg := core.NewConfigGenTest(t, core.TestOptions{
439
				Services:  services,
440
				Instances: instances,
441
			})
442
			kr := splitInput(t, input)
443
			kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
444
			output := convertResources(kr)
445
			output.AllowedReferences = AllowedReferences{} // Not tested here
446
			output.ReferencedNamespaceKeys = nil           // Not tested here
447
			output.ResourceReferences = nil                // Not tested here
448

449
			// sort virtual services to make the order deterministic
450
			sort.Slice(output.VirtualService, func(i, j int) bool {
451
				return output.VirtualService[i].Namespace+"/"+output.VirtualService[i].Name < output.VirtualService[j].Namespace+"/"+output.VirtualService[j].Name
452
			})
453
			goldenFile := fmt.Sprintf("testdata/%s.yaml.golden", tt.name)
454
			res := append(output.Gateway, output.VirtualService...)
455
			util.CompareContent(t, marshalYaml(t, res), goldenFile)
456
			golden := splitOutput(readConfig(t, goldenFile, validator, tt.validationIgnorer))
457

458
			// sort virtual services to make the order deterministic
459
			sort.Slice(golden.VirtualService, func(i, j int) bool {
460
				return golden.VirtualService[i].Namespace+"/"+golden.VirtualService[i].Name < golden.VirtualService[j].Namespace+"/"+golden.VirtualService[j].Name
461
			})
462

463
			assert.Equal(t, golden, output)
464

465
			outputStatus := getStatus(t, kr.GatewayClass, kr.Gateway, kr.HTTPRoute, kr.GRPCRoute, kr.TLSRoute, kr.TCPRoute)
466
			goldenStatusFile := fmt.Sprintf("testdata/%s.status.yaml.golden", tt.name)
467
			if util.Refresh() {
468
				if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
469
					t.Fatal(err)
470
				}
471
			}
472
			goldenStatus, err := os.ReadFile(goldenStatusFile)
473
			if err != nil {
474
				t.Fatal(err)
475
			}
476
			if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
477
				t.Fatalf("Diff:\n%s", diff)
478
			}
479
		})
480
	}
481
}
482

483
func TestSortHTTPRoutes(t *testing.T) {
484
	cases := []struct {
485
		name string
486
		in   []*istio.HTTPRoute
487
		out  []*istio.HTTPRoute
488
	}{
489
		{
490
			"match is preferred over no match",
491
			[]*istio.HTTPRoute{
492
				{
493
					Match: []*istio.HTTPMatchRequest{},
494
				},
495
				{
496
					Match: []*istio.HTTPMatchRequest{
497
						{
498
							Uri: &istio.StringMatch{
499
								MatchType: &istio.StringMatch_Exact{
500
									Exact: "/foo",
501
								},
502
							},
503
						},
504
					},
505
				},
506
			},
507
			[]*istio.HTTPRoute{
508
				{
509
					Match: []*istio.HTTPMatchRequest{
510
						{
511
							Uri: &istio.StringMatch{
512
								MatchType: &istio.StringMatch_Exact{
513
									Exact: "/foo",
514
								},
515
							},
516
						},
517
					},
518
				},
519
				{
520
					Match: []*istio.HTTPMatchRequest{},
521
				},
522
			},
523
		},
524
		{
525
			"path matching exact > prefix  > regex",
526
			[]*istio.HTTPRoute{
527
				{
528
					Match: []*istio.HTTPMatchRequest{
529
						{
530
							Uri: &istio.StringMatch{
531
								MatchType: &istio.StringMatch_Prefix{
532
									Prefix: "/",
533
								},
534
							},
535
						},
536
					},
537
				},
538
				{
539
					Match: []*istio.HTTPMatchRequest{
540
						{
541
							Uri: &istio.StringMatch{
542
								MatchType: &istio.StringMatch_Regex{
543
									Regex: ".*foo",
544
								},
545
							},
546
						},
547
					},
548
				},
549
				{
550
					Match: []*istio.HTTPMatchRequest{
551
						{
552
							Uri: &istio.StringMatch{
553
								MatchType: &istio.StringMatch_Exact{
554
									Exact: "/foo",
555
								},
556
							},
557
						},
558
					},
559
				},
560
			},
561
			[]*istio.HTTPRoute{
562
				{
563
					Match: []*istio.HTTPMatchRequest{
564
						{
565
							Uri: &istio.StringMatch{
566
								MatchType: &istio.StringMatch_Exact{
567
									Exact: "/foo",
568
								},
569
							},
570
						},
571
					},
572
				},
573
				{
574
					Match: []*istio.HTTPMatchRequest{
575
						{
576
							Uri: &istio.StringMatch{
577
								MatchType: &istio.StringMatch_Prefix{
578
									Prefix: "/",
579
								},
580
							},
581
						},
582
					},
583
				},
584
				{
585
					Match: []*istio.HTTPMatchRequest{
586
						{
587
							Uri: &istio.StringMatch{
588
								MatchType: &istio.StringMatch_Regex{
589
									Regex: ".*foo",
590
								},
591
							},
592
						},
593
					},
594
				},
595
			},
596
		},
597
		{
598
			"path prefix matching with largest characters",
599
			[]*istio.HTTPRoute{
600
				{
601
					Match: []*istio.HTTPMatchRequest{
602
						{
603
							Uri: &istio.StringMatch{
604
								MatchType: &istio.StringMatch_Prefix{
605
									Prefix: "/foo",
606
								},
607
							},
608
						},
609
					},
610
				},
611
				{
612
					Match: []*istio.HTTPMatchRequest{
613
						{
614
							Uri: &istio.StringMatch{
615
								MatchType: &istio.StringMatch_Prefix{
616
									Prefix: "/",
617
								},
618
							},
619
						},
620
					},
621
				},
622
				{
623
					Match: []*istio.HTTPMatchRequest{
624
						{
625
							Uri: &istio.StringMatch{
626
								MatchType: &istio.StringMatch_Prefix{
627
									Prefix: "/foobar",
628
								},
629
							},
630
						},
631
					},
632
				},
633
			},
634
			[]*istio.HTTPRoute{
635
				{
636
					Match: []*istio.HTTPMatchRequest{
637
						{
638
							Uri: &istio.StringMatch{
639
								MatchType: &istio.StringMatch_Prefix{
640
									Prefix: "/foobar",
641
								},
642
							},
643
						},
644
					},
645
				},
646
				{
647
					Match: []*istio.HTTPMatchRequest{
648
						{
649
							Uri: &istio.StringMatch{
650
								MatchType: &istio.StringMatch_Prefix{
651
									Prefix: "/foo",
652
								},
653
							},
654
						},
655
					},
656
				},
657
				{
658
					Match: []*istio.HTTPMatchRequest{
659
						{
660
							Uri: &istio.StringMatch{
661
								MatchType: &istio.StringMatch_Prefix{
662
									Prefix: "/",
663
								},
664
							},
665
						},
666
					},
667
				},
668
			},
669
		},
670
		{
671
			"path match is preferred over method match",
672
			[]*istio.HTTPRoute{
673
				{
674
					Match: []*istio.HTTPMatchRequest{
675
						{
676
							Method: &istio.StringMatch{
677
								MatchType: &istio.StringMatch_Exact{
678
									Exact: "GET",
679
								},
680
							},
681
						},
682
					},
683
				},
684
				{
685
					Match: []*istio.HTTPMatchRequest{
686
						{
687
							Uri: &istio.StringMatch{
688
								MatchType: &istio.StringMatch_Prefix{
689
									Prefix: "/foobar",
690
								},
691
							},
692
						},
693
					},
694
				},
695
			},
696
			[]*istio.HTTPRoute{
697
				{
698
					Match: []*istio.HTTPMatchRequest{
699
						{
700
							Uri: &istio.StringMatch{
701
								MatchType: &istio.StringMatch_Prefix{
702
									Prefix: "/foobar",
703
								},
704
							},
705
						},
706
					},
707
				},
708
				{
709
					Match: []*istio.HTTPMatchRequest{
710
						{
711
							Method: &istio.StringMatch{
712
								MatchType: &istio.StringMatch_Exact{
713
									Exact: "GET",
714
								},
715
							},
716
						},
717
					},
718
				},
719
			},
720
		},
721
		{
722
			"largest number of header matches is preferred",
723
			[]*istio.HTTPRoute{
724
				{
725
					Match: []*istio.HTTPMatchRequest{
726
						{
727
							Headers: map[string]*istio.StringMatch{
728
								"header1": {
729
									MatchType: &istio.StringMatch_Exact{
730
										Exact: "value1",
731
									},
732
								},
733
							},
734
						},
735
					},
736
				},
737
				{
738
					Match: []*istio.HTTPMatchRequest{
739
						{
740
							Headers: map[string]*istio.StringMatch{
741
								"header1": {
742
									MatchType: &istio.StringMatch_Exact{
743
										Exact: "value1",
744
									},
745
								},
746
								"header2": {
747
									MatchType: &istio.StringMatch_Exact{
748
										Exact: "value2",
749
									},
750
								},
751
							},
752
						},
753
					},
754
				},
755
			},
756
			[]*istio.HTTPRoute{
757
				{
758
					Match: []*istio.HTTPMatchRequest{
759
						{
760
							Headers: map[string]*istio.StringMatch{
761
								"header1": {
762
									MatchType: &istio.StringMatch_Exact{
763
										Exact: "value1",
764
									},
765
								},
766
								"header2": {
767
									MatchType: &istio.StringMatch_Exact{
768
										Exact: "value2",
769
									},
770
								},
771
							},
772
						},
773
					},
774
				},
775
				{
776
					Match: []*istio.HTTPMatchRequest{
777
						{
778
							Headers: map[string]*istio.StringMatch{
779
								"header1": {
780
									MatchType: &istio.StringMatch_Exact{
781
										Exact: "value1",
782
									},
783
								},
784
							},
785
						},
786
					},
787
				},
788
			},
789
		},
790
		{
791
			"largest number of query params is preferred",
792
			[]*istio.HTTPRoute{
793
				{
794
					Match: []*istio.HTTPMatchRequest{
795
						{
796
							QueryParams: map[string]*istio.StringMatch{
797
								"param1": {
798
									MatchType: &istio.StringMatch_Exact{
799
										Exact: "value1",
800
									},
801
								},
802
							},
803
						},
804
					},
805
				},
806
				{
807
					Match: []*istio.HTTPMatchRequest{
808
						{
809
							QueryParams: map[string]*istio.StringMatch{
810
								"param1": {
811
									MatchType: &istio.StringMatch_Exact{
812
										Exact: "value1",
813
									},
814
								},
815
								"param2": {
816
									MatchType: &istio.StringMatch_Exact{
817
										Exact: "value2",
818
									},
819
								},
820
							},
821
						},
822
					},
823
				},
824
			},
825
			[]*istio.HTTPRoute{
826
				{
827
					Match: []*istio.HTTPMatchRequest{
828
						{
829
							QueryParams: map[string]*istio.StringMatch{
830
								"param1": {
831
									MatchType: &istio.StringMatch_Exact{
832
										Exact: "value1",
833
									},
834
								},
835
								"param2": {
836
									MatchType: &istio.StringMatch_Exact{
837
										Exact: "value2",
838
									},
839
								},
840
							},
841
						},
842
					},
843
				},
844
				{
845
					Match: []*istio.HTTPMatchRequest{
846
						{
847
							QueryParams: map[string]*istio.StringMatch{
848
								"param1": {
849
									MatchType: &istio.StringMatch_Exact{
850
										Exact: "value1",
851
									},
852
								},
853
							},
854
						},
855
					},
856
				},
857
			},
858
		},
859
		{
860
			"path > method > header > query params",
861
			[]*istio.HTTPRoute{
862
				{
863
					Match: []*istio.HTTPMatchRequest{
864
						{
865
							Uri: &istio.StringMatch{
866
								MatchType: &istio.StringMatch_Prefix{
867
									Prefix: "/",
868
								},
869
							},
870
						},
871
					},
872
				},
873
				{
874
					Match: []*istio.HTTPMatchRequest{
875
						{
876
							QueryParams: map[string]*istio.StringMatch{
877
								"param1": {
878
									MatchType: &istio.StringMatch_Exact{
879
										Exact: "value1",
880
									},
881
								},
882
							},
883
						},
884
					},
885
				},
886
				{
887
					Match: []*istio.HTTPMatchRequest{
888
						{
889
							Method: &istio.StringMatch{
890
								MatchType: &istio.StringMatch_Exact{Exact: "GET"},
891
							},
892
						},
893
					},
894
				},
895
				{
896
					Match: []*istio.HTTPMatchRequest{
897
						{
898
							Headers: map[string]*istio.StringMatch{
899
								"param1": {
900
									MatchType: &istio.StringMatch_Exact{
901
										Exact: "value1",
902
									},
903
								},
904
							},
905
						},
906
					},
907
				},
908
			},
909
			[]*istio.HTTPRoute{
910
				{
911
					Match: []*istio.HTTPMatchRequest{
912
						{
913
							Uri: &istio.StringMatch{
914
								MatchType: &istio.StringMatch_Prefix{
915
									Prefix: "/",
916
								},
917
							},
918
						},
919
					},
920
				},
921
				{
922
					Match: []*istio.HTTPMatchRequest{
923
						{
924
							Method: &istio.StringMatch{
925
								MatchType: &istio.StringMatch_Exact{Exact: "GET"},
926
							},
927
						},
928
					},
929
				},
930
				{
931
					Match: []*istio.HTTPMatchRequest{
932
						{
933
							Headers: map[string]*istio.StringMatch{
934
								"param1": {
935
									MatchType: &istio.StringMatch_Exact{
936
										Exact: "value1",
937
									},
938
								},
939
							},
940
						},
941
					},
942
				},
943
				{
944
					Match: []*istio.HTTPMatchRequest{
945
						{
946
							QueryParams: map[string]*istio.StringMatch{
947
								"param1": {
948
									MatchType: &istio.StringMatch_Exact{
949
										Exact: "value1",
950
									},
951
								},
952
							},
953
						},
954
					},
955
				},
956
			},
957
		},
958
	}
959

960
	for _, tt := range cases {
961
		t.Run(tt.name, func(t *testing.T) {
962
			sortHTTPRoutes(tt.in)
963
			if !reflect.DeepEqual(tt.in, tt.out) {
964
				t.Fatalf("expected %v, got %v", tt.out, tt.in)
965
			}
966
		})
967
	}
968
}
969

970
func TestReferencePolicy(t *testing.T) {
971
	validator := crdvalidation.NewIstioValidator(t)
972
	type res struct {
973
		name, namespace string
974
		allowed         bool
975
	}
976
	cases := []struct {
977
		name         string
978
		config       string
979
		expectations []res
980
	}{
981
		{
982
			name: "simple",
983
			config: `apiVersion: gateway.networking.k8s.io/v1beta1
984
kind: ReferenceGrant
985
metadata:
986
  name: allow-gateways-to-ref-secrets
987
  namespace: default
988
spec:
989
  from:
990
  - group: gateway.networking.k8s.io
991
    kind: Gateway
992
    namespace: istio-system
993
  to:
994
  - group: ""
995
    kind: Secret
996
`,
997
			expectations: []res{
998
				// allow cross namespace
999
				{"kubernetes-gateway://default/wildcard-example-com-cert", "istio-system", true},
1000
				// denied same namespace. We do not implicitly allow (in this code - higher level code does)
1001
				{"kubernetes-gateway://default/wildcard-example-com-cert", "default", false},
1002
				// denied namespace
1003
				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
1004
			},
1005
		},
1006
		{
1007
			name: "multiple in one",
1008
			config: `apiVersion: gateway.networking.k8s.io/v1beta1
1009
kind: ReferenceGrant
1010
metadata:
1011
  name: allow-gateways-to-ref-secrets
1012
  namespace: default
1013
spec:
1014
  from:
1015
  - group: gateway.networking.k8s.io
1016
    kind: Gateway
1017
    namespace: ns-1
1018
  - group: gateway.networking.k8s.io
1019
    kind: Gateway
1020
    namespace: ns-2
1021
  to:
1022
  - group: ""
1023
    kind: Secret
1024
`,
1025
			expectations: []res{
1026
				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
1027
				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
1028
				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
1029
			},
1030
		},
1031
		{
1032
			name: "multiple",
1033
			config: `apiVersion: gateway.networking.k8s.io/v1beta1
1034
kind: ReferenceGrant
1035
metadata:
1036
  name: ns1
1037
  namespace: default
1038
spec:
1039
  from:
1040
  - group: gateway.networking.k8s.io
1041
    kind: Gateway
1042
    namespace: ns-1
1043
  to:
1044
  - group: ""
1045
    kind: Secret
1046
---
1047
apiVersion: gateway.networking.k8s.io/v1beta1
1048
kind: ReferenceGrant
1049
metadata:
1050
  name: ns2
1051
  namespace: default
1052
spec:
1053
  from:
1054
  - group: gateway.networking.k8s.io
1055
    kind: Gateway
1056
    namespace: ns-2
1057
  to:
1058
  - group: ""
1059
    kind: Secret
1060
`,
1061
			expectations: []res{
1062
				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-1", true},
1063
				{"kubernetes-gateway://default/wildcard-example-com-cert", "ns-2", true},
1064
				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
1065
			},
1066
		},
1067
		{
1068
			name: "same namespace",
1069
			config: `apiVersion: gateway.networking.k8s.io/v1beta1
1070
kind: ReferenceGrant
1071
metadata:
1072
  name: allow-gateways-to-ref-secrets
1073
  namespace: default
1074
spec:
1075
  from:
1076
  - group: gateway.networking.k8s.io
1077
    kind: Gateway
1078
    namespace: default
1079
  to:
1080
  - group: ""
1081
    kind: Secret
1082
`,
1083
			expectations: []res{
1084
				{"kubernetes-gateway://default/wildcard-example-com-cert", "istio-system", false},
1085
				{"kubernetes-gateway://default/wildcard-example-com-cert", "default", true},
1086
				{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
1087
			},
1088
		},
1089
		{
1090
			name: "same name",
1091
			config: `apiVersion: gateway.networking.k8s.io/v1beta1
1092
kind: ReferenceGrant
1093
metadata:
1094
  name: allow-gateways-to-ref-secrets
1095
  namespace: default
1096
spec:
1097
  from:
1098
  - group: gateway.networking.k8s.io
1099
    kind: Gateway
1100
    namespace: default
1101
  to:
1102
  - group: ""
1103
    kind: Secret
1104
    name: public
1105
`,
1106
			expectations: []res{
1107
				{"kubernetes-gateway://default/public", "istio-system", false},
1108
				{"kubernetes-gateway://default/public", "default", true},
1109
				{"kubernetes-gateway://default/private", "default", false},
1110
			},
1111
		},
1112
	}
1113
	for _, tt := range cases {
1114
		t.Run(tt.name, func(t *testing.T) {
1115
			input := readConfigString(t, tt.config, validator, nil)
1116
			cg := core.NewConfigGenTest(t, core.TestOptions{})
1117
			kr := splitInput(t, input)
1118
			kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
1119
			output := convertResources(kr)
1120
			c := &Controller{
1121
				state: output,
1122
			}
1123
			for _, sc := range tt.expectations {
1124
				t.Run(fmt.Sprintf("%v/%v", sc.name, sc.namespace), func(t *testing.T) {
1125
					got := c.SecretAllowed(sc.name, sc.namespace)
1126
					if got != sc.allowed {
1127
						t.Fatalf("expected allowed=%v, got allowed=%v", sc.allowed, got)
1128
					}
1129
				})
1130
			}
1131
		})
1132
	}
1133
}
1134

1135
func getStatus(t test.Failer, acfgs ...[]config.Config) []byte {
1136
	cfgs := []config.Config{}
1137
	for _, cl := range acfgs {
1138
		cfgs = append(cfgs, cl...)
1139
	}
1140
	for i, c := range cfgs {
1141
		if c.Status.(*kstatus.WrappedStatus) != nil && c.GroupVersionKind == gvk.GatewayClass {
1142
			// 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
1143
			c.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
1144
				gcs := s.(*k8sv1.GatewayClassStatus)
1145
				gcs.SupportedFeatures = []k8sv1.SupportedFeature{"HTTPRouteFeatureA", "HTTPRouteFeatureB"}
1146
				return gcs
1147
			})
1148
		}
1149
		c = c.DeepCopy()
1150
		c.Spec = nil
1151
		c.Labels = nil
1152
		c.Annotations = nil
1153
		if c.Status.(*kstatus.WrappedStatus) != nil {
1154
			c.Status = c.Status.(*kstatus.WrappedStatus).Status
1155
		}
1156
		cfgs[i] = c
1157
	}
1158
	return timestampRegex.ReplaceAll(marshalYaml(t, cfgs), []byte("lastTransitionTime: fake"))
1159
}
1160

1161
var timestampRegex = regexp.MustCompile(`lastTransitionTime:.*`)
1162

1163
func splitOutput(configs []config.Config) IstioResources {
1164
	out := IstioResources{
1165
		Gateway:        []config.Config{},
1166
		VirtualService: []config.Config{},
1167
	}
1168
	for _, c := range configs {
1169
		c.Domain = "domain.suffix"
1170
		switch c.GroupVersionKind {
1171
		case gvk.Gateway:
1172
			out.Gateway = append(out.Gateway, c)
1173
		case gvk.VirtualService:
1174
			out.VirtualService = append(out.VirtualService, c)
1175
		}
1176
	}
1177
	return out
1178
}
1179

1180
func splitInput(t test.Failer, configs []config.Config) GatewayResources {
1181
	out := GatewayResources{}
1182
	namespaces := sets.New[string]()
1183
	for _, c := range configs {
1184
		namespaces.Insert(c.Namespace)
1185
		switch c.GroupVersionKind {
1186
		case gvk.GatewayClass:
1187
			out.GatewayClass = append(out.GatewayClass, c)
1188
		case gvk.KubernetesGateway:
1189
			out.Gateway = append(out.Gateway, c)
1190
		case gvk.HTTPRoute:
1191
			out.HTTPRoute = append(out.HTTPRoute, c)
1192
		case gvk.GRPCRoute:
1193
			out.GRPCRoute = append(out.GRPCRoute, c)
1194
		case gvk.TCPRoute:
1195
			out.TCPRoute = append(out.TCPRoute, c)
1196
		case gvk.TLSRoute:
1197
			out.TLSRoute = append(out.TLSRoute, c)
1198
		case gvk.ReferenceGrant:
1199
			out.ReferenceGrant = append(out.ReferenceGrant, c)
1200
		case gvk.ServiceEntry:
1201
			out.ServiceEntry = append(out.ServiceEntry, c)
1202
		}
1203
	}
1204
	out.Namespaces = map[string]*corev1.Namespace{}
1205
	for ns := range namespaces {
1206
		out.Namespaces[ns] = &corev1.Namespace{
1207
			ObjectMeta: metav1.ObjectMeta{
1208
				Name: ns,
1209
				Labels: map[string]string{
1210
					"istio.io/test-name-part": strings.Split(ns, "-")[0],
1211
				},
1212
			},
1213
		}
1214
	}
1215

1216
	client := kube.NewFakeClient(secrets...)
1217
	out.Credentials = credentials.NewCredentialsController(client, nil)
1218
	client.RunAndWait(test.NewStop(t))
1219

1220
	out.Domain = "domain.suffix"
1221
	return out
1222
}
1223

1224
func readConfig(t testing.TB, filename string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer) []config.Config {
1225
	t.Helper()
1226

1227
	data, err := os.ReadFile(filename)
1228
	if err != nil {
1229
		t.Fatalf("failed to read input yaml file: %v", err)
1230
	}
1231
	return readConfigString(t, string(data), validator, ignorer)
1232
}
1233

1234
func readConfigString(t testing.TB, data string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer,
1235
) []config.Config {
1236
	if err := validator.ValidateCustomResourceYAML(data, ignorer); err != nil {
1237
		t.Error(err)
1238
	}
1239
	c, _, err := crd.ParseInputs(data)
1240
	if err != nil {
1241
		t.Fatalf("failed to parse CRD: %v", err)
1242
	}
1243
	return insertDefaults(c)
1244
}
1245

1246
// insertDefaults sets default values that would be present when reading from Kubernetes but not from
1247
// files
1248
func insertDefaults(cfgs []config.Config) []config.Config {
1249
	res := make([]config.Config, 0, len(cfgs))
1250
	for _, c := range cfgs {
1251
		switch c.GroupVersionKind {
1252
		case gvk.GatewayClass:
1253
			c.Status = kstatus.Wrap(&k8sv1.GatewayClassStatus{})
1254
		case gvk.KubernetesGateway:
1255
			c.Status = kstatus.Wrap(&k8sv1.GatewayStatus{})
1256
		case gvk.HTTPRoute:
1257
			c.Status = kstatus.Wrap(&k8sv1.HTTPRouteStatus{})
1258
		case gvk.GRPCRoute:
1259
			c.Status = kstatus.Wrap(&k8s.GRPCRouteStatus{})
1260
		case gvk.TCPRoute:
1261
			c.Status = kstatus.Wrap(&k8s.TCPRouteStatus{})
1262
		case gvk.TLSRoute:
1263
			c.Status = kstatus.Wrap(&k8s.TLSRouteStatus{})
1264
		}
1265
		res = append(res, c)
1266
	}
1267
	return res
1268
}
1269

1270
// Print as YAML
1271
func marshalYaml(t test.Failer, cl []config.Config) []byte {
1272
	t.Helper()
1273
	result := []byte{}
1274
	separator := []byte("---\n")
1275
	for _, config := range cl {
1276
		obj, err := crd.ConvertConfig(config)
1277
		if err != nil {
1278
			t.Fatalf("Could not decode %v: %v", config.Name, err)
1279
		}
1280
		bytes, err := yaml.Marshal(obj)
1281
		if err != nil {
1282
			t.Fatalf("Could not convert %v to YAML: %v", config, err)
1283
		}
1284
		result = append(result, bytes...)
1285
		result = append(result, separator...)
1286
	}
1287
	return result
1288
}
1289

1290
func TestHumanReadableJoin(t *testing.T) {
1291
	tests := []struct {
1292
		input []string
1293
		want  string
1294
	}{
1295
		{[]string{"a"}, "a"},
1296
		{[]string{"a", "b"}, "a and b"},
1297
		{[]string{"a", "b", "c"}, "a, b, and c"},
1298
	}
1299
	for _, tt := range tests {
1300
		t.Run(strings.Join(tt.input, "_"), func(t *testing.T) {
1301
			if got := humanReadableJoin(tt.input); !reflect.DeepEqual(got, tt.want) {
1302
				t.Errorf("got %v, want %v", got, tt.want)
1303
			}
1304
		})
1305
	}
1306
}
1307

1308
func BenchmarkBuildHTTPVirtualServices(b *testing.B) {
1309
	ports := []*model.Port{
1310
		{
1311
			Name:     "http",
1312
			Port:     80,
1313
			Protocol: "HTTP",
1314
		},
1315
		{
1316
			Name:     "tcp",
1317
			Port:     34000,
1318
			Protocol: "TCP",
1319
		},
1320
	}
1321
	ingressSvc := &model.Service{
1322
		Attributes: model.ServiceAttributes{
1323
			Name:      "istio-ingressgateway",
1324
			Namespace: "istio-system",
1325
			ClusterExternalAddresses: &model.AddressMap{
1326
				Addresses: map[cluster.ID][]string{
1327
					constants.DefaultClusterName: {"1.2.3.4"},
1328
				},
1329
			},
1330
		},
1331
		Ports:    ports,
1332
		Hostname: "istio-ingressgateway.istio-system.svc.domain.suffix",
1333
	}
1334
	altIngressSvc := &model.Service{
1335
		Attributes: model.ServiceAttributes{
1336
			Namespace: "istio-system",
1337
		},
1338
		Ports:    ports,
1339
		Hostname: "example.com",
1340
	}
1341
	cg := core.NewConfigGenTest(b, core.TestOptions{
1342
		Services: []*model.Service{ingressSvc, altIngressSvc},
1343
		Instances: []*model.ServiceInstance{
1344
			{Service: ingressSvc, ServicePort: ingressSvc.Ports[0], Endpoint: &model.IstioEndpoint{EndpointPort: 8080}},
1345
			{Service: ingressSvc, ServicePort: ingressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
1346
			{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[0], Endpoint: &model.IstioEndpoint{}},
1347
			{Service: altIngressSvc, ServicePort: altIngressSvc.Ports[1], Endpoint: &model.IstioEndpoint{}},
1348
		},
1349
	})
1350

1351
	validator := crdvalidation.NewIstioValidator(b)
1352
	input := readConfig(b, "testdata/benchmark-httproute.yaml", validator, nil)
1353
	kr := splitInput(b, input)
1354
	kr.Context = NewGatewayContext(cg.PushContext(), "Kubernetes")
1355
	ctx := configContext{
1356
		GatewayResources:  kr,
1357
		AllowedReferences: convertReferencePolicies(kr),
1358
	}
1359
	_, gwMap, _ := convertGateways(ctx)
1360
	ctx.GatewayReferences = gwMap
1361

1362
	b.ResetTimer()
1363
	for n := 0; n < b.N; n++ {
1364
		// for gateway routes, build one VS per gateway+host
1365
		gatewayRoutes := make(map[string]map[string]*config.Config)
1366
		// for mesh routes, build one VS per namespace+host
1367
		meshRoutes := make(map[string]map[string]*config.Config)
1368
		for _, obj := range kr.HTTPRoute {
1369
			buildHTTPVirtualServices(ctx, obj, gatewayRoutes, meshRoutes)
1370
		}
1371
	}
1372
}
1373

1374
func TestExtractGatewayServices(t *testing.T) {
1375
	tests := []struct {
1376
		name            string
1377
		r               GatewayResources
1378
		kgw             *k8sv1.GatewaySpec
1379
		obj             config.Config
1380
		gatewayServices []string
1381
		err             *ConfigError
1382
	}{
1383
		{
1384
			name: "managed gateway",
1385
			r:    GatewayResources{Domain: "cluster.local"},
1386
			kgw: &k8sv1.GatewaySpec{
1387
				GatewayClassName: "istio",
1388
			},
1389
			obj: config.Config{
1390
				Meta: config.Meta{
1391
					Name:      "foo",
1392
					Namespace: "default",
1393
				},
1394
			},
1395
			gatewayServices: []string{"foo-istio.default.svc.cluster.local"},
1396
		},
1397
		{
1398
			name: "managed gateway with name overridden",
1399
			r:    GatewayResources{Domain: "cluster.local"},
1400
			kgw: &k8sv1.GatewaySpec{
1401
				GatewayClassName: "istio",
1402
			},
1403
			obj: config.Config{
1404
				Meta: config.Meta{
1405
					Name:      "foo",
1406
					Namespace: "default",
1407
					Annotations: map[string]string{
1408
						gatewayNameOverride: "bar",
1409
					},
1410
				},
1411
			},
1412
			gatewayServices: []string{"bar.default.svc.cluster.local"},
1413
		},
1414
		{
1415
			name: "unmanaged gateway",
1416
			r:    GatewayResources{Domain: "domain"},
1417
			kgw: &k8sv1.GatewaySpec{
1418
				GatewayClassName: "istio",
1419
				Addresses: []k8sv1.GatewayAddress{
1420
					{
1421
						Value: "abc",
1422
					},
1423
					{
1424
						Type: func() *k8sv1.AddressType {
1425
							t := k8sv1.HostnameAddressType
1426
							return &t
1427
						}(),
1428
						Value: "example.com",
1429
					},
1430
					{
1431
						Type: func() *k8sv1.AddressType {
1432
							t := k8sv1.IPAddressType
1433
							return &t
1434
						}(),
1435
						Value: "1.2.3.4",
1436
					},
1437
				},
1438
			},
1439
			obj: config.Config{
1440
				Meta: config.Meta{
1441
					Name:      "foo",
1442
					Namespace: "default",
1443
				},
1444
			},
1445
			gatewayServices: []string{"abc.default.svc.domain", "example.com"},
1446
			err: &ConfigError{
1447
				Reason:  InvalidAddress,
1448
				Message: "only Hostname is supported, ignoring [1.2.3.4]",
1449
			},
1450
		},
1451
	}
1452
	for _, tt := range tests {
1453
		t.Run(tt.name, func(t *testing.T) {
1454
			gatewayServices, err := extractGatewayServices(tt.r, tt.kgw, tt.obj)
1455
			assert.Equal(t, gatewayServices, tt.gatewayServices)
1456
			assert.Equal(t, err, tt.err)
1457
		})
1458
	}
1459
}
1460

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.