1
// Copyright Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
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"
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"
53
var ports = []*model.Port{
71
var services = []*model.Service{
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"},
83
Hostname: "istio-ingressgateway.istio-system.svc.domain.suffix",
86
Attributes: model.ServiceAttributes{
87
Namespace: "istio-system",
90
Hostname: "example.com",
93
Attributes: model.ServiceAttributes{
97
Hostname: "httpbin.default.svc.domain.suffix",
100
Attributes: model.ServiceAttributes{
104
Hostname: "httpbin-apple.apple.svc.domain.suffix",
107
Attributes: model.ServiceAttributes{
111
Hostname: "httpbin-banana.banana.svc.domain.suffix",
114
Attributes: model.ServiceAttributes{
115
Namespace: "default",
118
Hostname: "httpbin-second.default.svc.domain.suffix",
121
Attributes: model.ServiceAttributes{
122
Namespace: "default",
125
Hostname: "httpbin-wildcard.default.svc.domain.suffix",
128
Attributes: model.ServiceAttributes{
129
Namespace: "default",
132
Hostname: "foo-svc.default.svc.domain.suffix",
135
Attributes: model.ServiceAttributes{
136
Namespace: "default",
139
Hostname: "httpbin-other.default.svc.domain.suffix",
142
Attributes: model.ServiceAttributes{
143
Namespace: "default",
146
Hostname: "example.default.svc.domain.suffix",
149
Attributes: model.ServiceAttributes{
150
Namespace: "default",
153
Hostname: "echo.default.svc.domain.suffix",
156
Attributes: model.ServiceAttributes{
157
Namespace: "default",
160
Hostname: "echo.default.svc.domain.suffix",
163
Attributes: model.ServiceAttributes{
167
Hostname: "httpbin.cert.svc.domain.suffix",
170
Attributes: model.ServiceAttributes{
171
Namespace: "service",
174
Hostname: "my-svc.service.svc.domain.suffix",
177
Attributes: model.ServiceAttributes{
178
Namespace: "default",
181
Hostname: "google.com",
184
Attributes: model.ServiceAttributes{
185
Namespace: "allowed-1",
188
Hostname: "svc2.allowed-1.svc.domain.suffix",
191
Attributes: model.ServiceAttributes{
192
Namespace: "allowed-2",
195
Hostname: "svc2.allowed-2.svc.domain.suffix",
198
Attributes: model.ServiceAttributes{
199
Namespace: "allowed-1",
202
Hostname: "svc1.allowed-1.svc.domain.suffix",
205
Attributes: model.ServiceAttributes{
206
Namespace: "allowed-2",
209
Hostname: "svc3.allowed-2.svc.domain.suffix",
212
Attributes: model.ServiceAttributes{
213
Namespace: "default",
216
Hostname: "svc4.default.svc.domain.suffix",
219
Attributes: model.ServiceAttributes{
220
Namespace: "group-namespace1",
223
Hostname: "httpbin.group-namespace1.svc.domain.suffix",
226
Attributes: model.ServiceAttributes{
227
Namespace: "group-namespace2",
230
Hostname: "httpbin.group-namespace2.svc.domain.suffix",
233
Attributes: model.ServiceAttributes{
234
Namespace: "default",
237
Hostname: "httpbin-zero.default.svc.domain.suffix",
240
Attributes: model.ServiceAttributes{
241
Namespace: "istio-system",
244
Hostname: "httpbin.istio-system.svc.domain.suffix",
247
Attributes: model.ServiceAttributes{
248
Namespace: "default",
251
Hostname: "httpbin-mirror.default.svc.domain.suffix",
254
Attributes: model.ServiceAttributes{
255
Namespace: "default",
258
Hostname: "httpbin-foo.default.svc.domain.suffix",
261
Attributes: model.ServiceAttributes{
262
Namespace: "default",
265
Hostname: "httpbin-alt.default.svc.domain.suffix",
268
Attributes: model.ServiceAttributes{
269
Namespace: "istio-system",
272
Hostname: "istiod.istio-system.svc.domain.suffix",
275
Attributes: model.ServiceAttributes{
276
Namespace: "istio-system",
279
Hostname: "istiod.istio-system.svc.domain.suffix",
282
Attributes: model.ServiceAttributes{
283
Namespace: "istio-system",
286
Hostname: "echo.istio-system.svc.domain.suffix",
289
Attributes: model.ServiceAttributes{
290
Namespace: "default",
293
Hostname: "httpbin-bad.default.svc.domain.suffix",
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-----
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-----
323
secrets = []runtime.Object{
325
ObjectMeta: metav1.ObjectMeta{
326
Name: "my-cert-http",
327
Namespace: "istio-system",
329
Data: map[string][]byte{
330
"tls.crt": []byte(rsaCertPEM),
331
"tls.key": []byte(rsaKeyPEM),
335
ObjectMeta: metav1.ObjectMeta{
339
Data: map[string][]byte{
340
"tls.crt": []byte(rsaCertPEM),
341
"tls.key": []byte(rsaKeyPEM),
345
ObjectMeta: metav1.ObjectMeta{
347
Namespace: "istio-system",
349
Data: map[string][]byte{
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"),
362
features.EnableAlphaGatewayAPI = true
363
features.EnableAmbientWaypoints = true
364
// Recompute with ambient enabled
365
classInfos = getClassInfos()
366
builtinClasses = getBuiltinClasses()
369
func TestConvertResources(t *testing.T) {
370
validator := crdvalidation.NewIstioValidator(t)
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
387
validationIgnorer: crdvalidation.NewValidationIgnorer(
388
"default/^invalid-backendRef-kind-",
389
"default/^invalid-backendRef-mixed-",
392
{name: "multi-gateway"},
394
{name: "route-binding"},
395
{name: "reference-policy-tls"},
397
name: "reference-policy-service",
398
validationIgnorer: crdvalidation.NewValidationIgnorer(
399
"istio-system/^backend-not-allowed-",
403
name: "reference-policy-tcp",
404
validationIgnorer: crdvalidation.NewValidationIgnorer(
405
"istio-system/^not-allowed-echo-",
408
{name: "serviceentry"},
410
{name: "eastwest-tlsoption"},
411
{name: "eastwest-labelport"},
412
{name: "eastwest-remote"},
415
{name: "route-precedence"},
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{
426
ServicePort: ports[0],
427
Endpoint: &model.IstioEndpoint{EndpointPort: 8080},
428
}, &model.ServiceInstance{
430
ServicePort: ports[1],
431
Endpoint: &model.IstioEndpoint{},
432
}, &model.ServiceInstance{
434
ServicePort: ports[2],
435
Endpoint: &model.IstioEndpoint{},
438
cg := core.NewConfigGenTest(t, core.TestOptions{
440
Instances: instances,
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
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
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))
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
463
assert.Equal(t, golden, output)
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)
468
if err := os.WriteFile(goldenStatusFile, outputStatus, 0o644); err != nil {
472
goldenStatus, err := os.ReadFile(goldenStatusFile)
476
if diff := cmp.Diff(string(goldenStatus), string(outputStatus)); diff != "" {
477
t.Fatalf("Diff:\n%s", diff)
483
func TestSortHTTPRoutes(t *testing.T) {
486
in []*istio.HTTPRoute
487
out []*istio.HTTPRoute
490
"match is preferred over no match",
493
Match: []*istio.HTTPMatchRequest{},
496
Match: []*istio.HTTPMatchRequest{
498
Uri: &istio.StringMatch{
499
MatchType: &istio.StringMatch_Exact{
509
Match: []*istio.HTTPMatchRequest{
511
Uri: &istio.StringMatch{
512
MatchType: &istio.StringMatch_Exact{
520
Match: []*istio.HTTPMatchRequest{},
525
"path matching exact > prefix > regex",
528
Match: []*istio.HTTPMatchRequest{
530
Uri: &istio.StringMatch{
531
MatchType: &istio.StringMatch_Prefix{
539
Match: []*istio.HTTPMatchRequest{
541
Uri: &istio.StringMatch{
542
MatchType: &istio.StringMatch_Regex{
550
Match: []*istio.HTTPMatchRequest{
552
Uri: &istio.StringMatch{
553
MatchType: &istio.StringMatch_Exact{
563
Match: []*istio.HTTPMatchRequest{
565
Uri: &istio.StringMatch{
566
MatchType: &istio.StringMatch_Exact{
574
Match: []*istio.HTTPMatchRequest{
576
Uri: &istio.StringMatch{
577
MatchType: &istio.StringMatch_Prefix{
585
Match: []*istio.HTTPMatchRequest{
587
Uri: &istio.StringMatch{
588
MatchType: &istio.StringMatch_Regex{
598
"path prefix matching with largest characters",
601
Match: []*istio.HTTPMatchRequest{
603
Uri: &istio.StringMatch{
604
MatchType: &istio.StringMatch_Prefix{
612
Match: []*istio.HTTPMatchRequest{
614
Uri: &istio.StringMatch{
615
MatchType: &istio.StringMatch_Prefix{
623
Match: []*istio.HTTPMatchRequest{
625
Uri: &istio.StringMatch{
626
MatchType: &istio.StringMatch_Prefix{
636
Match: []*istio.HTTPMatchRequest{
638
Uri: &istio.StringMatch{
639
MatchType: &istio.StringMatch_Prefix{
647
Match: []*istio.HTTPMatchRequest{
649
Uri: &istio.StringMatch{
650
MatchType: &istio.StringMatch_Prefix{
658
Match: []*istio.HTTPMatchRequest{
660
Uri: &istio.StringMatch{
661
MatchType: &istio.StringMatch_Prefix{
671
"path match is preferred over method match",
674
Match: []*istio.HTTPMatchRequest{
676
Method: &istio.StringMatch{
677
MatchType: &istio.StringMatch_Exact{
685
Match: []*istio.HTTPMatchRequest{
687
Uri: &istio.StringMatch{
688
MatchType: &istio.StringMatch_Prefix{
698
Match: []*istio.HTTPMatchRequest{
700
Uri: &istio.StringMatch{
701
MatchType: &istio.StringMatch_Prefix{
709
Match: []*istio.HTTPMatchRequest{
711
Method: &istio.StringMatch{
712
MatchType: &istio.StringMatch_Exact{
722
"largest number of header matches is preferred",
725
Match: []*istio.HTTPMatchRequest{
727
Headers: map[string]*istio.StringMatch{
729
MatchType: &istio.StringMatch_Exact{
738
Match: []*istio.HTTPMatchRequest{
740
Headers: map[string]*istio.StringMatch{
742
MatchType: &istio.StringMatch_Exact{
747
MatchType: &istio.StringMatch_Exact{
758
Match: []*istio.HTTPMatchRequest{
760
Headers: map[string]*istio.StringMatch{
762
MatchType: &istio.StringMatch_Exact{
767
MatchType: &istio.StringMatch_Exact{
776
Match: []*istio.HTTPMatchRequest{
778
Headers: map[string]*istio.StringMatch{
780
MatchType: &istio.StringMatch_Exact{
791
"largest number of query params is preferred",
794
Match: []*istio.HTTPMatchRequest{
796
QueryParams: map[string]*istio.StringMatch{
798
MatchType: &istio.StringMatch_Exact{
807
Match: []*istio.HTTPMatchRequest{
809
QueryParams: map[string]*istio.StringMatch{
811
MatchType: &istio.StringMatch_Exact{
816
MatchType: &istio.StringMatch_Exact{
827
Match: []*istio.HTTPMatchRequest{
829
QueryParams: map[string]*istio.StringMatch{
831
MatchType: &istio.StringMatch_Exact{
836
MatchType: &istio.StringMatch_Exact{
845
Match: []*istio.HTTPMatchRequest{
847
QueryParams: map[string]*istio.StringMatch{
849
MatchType: &istio.StringMatch_Exact{
860
"path > method > header > query params",
863
Match: []*istio.HTTPMatchRequest{
865
Uri: &istio.StringMatch{
866
MatchType: &istio.StringMatch_Prefix{
874
Match: []*istio.HTTPMatchRequest{
876
QueryParams: map[string]*istio.StringMatch{
878
MatchType: &istio.StringMatch_Exact{
887
Match: []*istio.HTTPMatchRequest{
889
Method: &istio.StringMatch{
890
MatchType: &istio.StringMatch_Exact{Exact: "GET"},
896
Match: []*istio.HTTPMatchRequest{
898
Headers: map[string]*istio.StringMatch{
900
MatchType: &istio.StringMatch_Exact{
911
Match: []*istio.HTTPMatchRequest{
913
Uri: &istio.StringMatch{
914
MatchType: &istio.StringMatch_Prefix{
922
Match: []*istio.HTTPMatchRequest{
924
Method: &istio.StringMatch{
925
MatchType: &istio.StringMatch_Exact{Exact: "GET"},
931
Match: []*istio.HTTPMatchRequest{
933
Headers: map[string]*istio.StringMatch{
935
MatchType: &istio.StringMatch_Exact{
944
Match: []*istio.HTTPMatchRequest{
946
QueryParams: map[string]*istio.StringMatch{
948
MatchType: &istio.StringMatch_Exact{
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)
970
func TestReferencePolicy(t *testing.T) {
971
validator := crdvalidation.NewIstioValidator(t)
973
name, namespace string
983
config: `apiVersion: gateway.networking.k8s.io/v1beta1
986
name: allow-gateways-to-ref-secrets
990
- group: gateway.networking.k8s.io
992
namespace: istio-system
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},
1003
{"kubernetes-gateway://default/wildcard-example-com-cert", "bad", false},
1007
name: "multiple in one",
1008
config: `apiVersion: gateway.networking.k8s.io/v1beta1
1011
name: allow-gateways-to-ref-secrets
1015
- group: gateway.networking.k8s.io
1018
- group: gateway.networking.k8s.io
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},
1033
config: `apiVersion: gateway.networking.k8s.io/v1beta1
1040
- group: gateway.networking.k8s.io
1047
apiVersion: gateway.networking.k8s.io/v1beta1
1054
- group: gateway.networking.k8s.io
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},
1068
name: "same namespace",
1069
config: `apiVersion: gateway.networking.k8s.io/v1beta1
1072
name: allow-gateways-to-ref-secrets
1076
- group: gateway.networking.k8s.io
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},
1091
config: `apiVersion: gateway.networking.k8s.io/v1beta1
1094
name: allow-gateways-to-ref-secrets
1098
- group: gateway.networking.k8s.io
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},
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)
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)
1135
func getStatus(t test.Failer, acfgs ...[]config.Config) []byte {
1136
cfgs := []config.Config{}
1137
for _, cl := range acfgs {
1138
cfgs = append(cfgs, cl...)
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"}
1153
if c.Status.(*kstatus.WrappedStatus) != nil {
1154
c.Status = c.Status.(*kstatus.WrappedStatus).Status
1158
return timestampRegex.ReplaceAll(marshalYaml(t, cfgs), []byte("lastTransitionTime: fake"))
1161
var timestampRegex = regexp.MustCompile(`lastTransitionTime:.*`)
1163
func splitOutput(configs []config.Config) IstioResources {
1164
out := IstioResources{
1165
Gateway: []config.Config{},
1166
VirtualService: []config.Config{},
1168
for _, c := range configs {
1169
c.Domain = "domain.suffix"
1170
switch c.GroupVersionKind {
1172
out.Gateway = append(out.Gateway, c)
1173
case gvk.VirtualService:
1174
out.VirtualService = append(out.VirtualService, c)
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)
1191
out.HTTPRoute = append(out.HTTPRoute, c)
1193
out.GRPCRoute = append(out.GRPCRoute, c)
1195
out.TCPRoute = append(out.TCPRoute, c)
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)
1204
out.Namespaces = map[string]*corev1.Namespace{}
1205
for ns := range namespaces {
1206
out.Namespaces[ns] = &corev1.Namespace{
1207
ObjectMeta: metav1.ObjectMeta{
1209
Labels: map[string]string{
1210
"istio.io/test-name-part": strings.Split(ns, "-")[0],
1216
client := kube.NewFakeClient(secrets...)
1217
out.Credentials = credentials.NewCredentialsController(client, nil)
1218
client.RunAndWait(test.NewStop(t))
1220
out.Domain = "domain.suffix"
1224
func readConfig(t testing.TB, filename string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer) []config.Config {
1227
data, err := os.ReadFile(filename)
1229
t.Fatalf("failed to read input yaml file: %v", err)
1231
return readConfigString(t, string(data), validator, ignorer)
1234
func readConfigString(t testing.TB, data string, validator *crdvalidation.Validator, ignorer *crdvalidation.ValidationIgnorer,
1236
if err := validator.ValidateCustomResourceYAML(data, ignorer); err != nil {
1239
c, _, err := crd.ParseInputs(data)
1241
t.Fatalf("failed to parse CRD: %v", err)
1243
return insertDefaults(c)
1246
// insertDefaults sets default values that would be present when reading from Kubernetes but not from
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{})
1257
c.Status = kstatus.Wrap(&k8sv1.HTTPRouteStatus{})
1259
c.Status = kstatus.Wrap(&k8s.GRPCRouteStatus{})
1261
c.Status = kstatus.Wrap(&k8s.TCPRouteStatus{})
1263
c.Status = kstatus.Wrap(&k8s.TLSRouteStatus{})
1265
res = append(res, c)
1271
func marshalYaml(t test.Failer, cl []config.Config) []byte {
1274
separator := []byte("---\n")
1275
for _, config := range cl {
1276
obj, err := crd.ConvertConfig(config)
1278
t.Fatalf("Could not decode %v: %v", config.Name, err)
1280
bytes, err := yaml.Marshal(obj)
1282
t.Fatalf("Could not convert %v to YAML: %v", config, err)
1284
result = append(result, bytes...)
1285
result = append(result, separator...)
1290
func TestHumanReadableJoin(t *testing.T) {
1295
{[]string{"a"}, "a"},
1296
{[]string{"a", "b"}, "a and b"},
1297
{[]string{"a", "b", "c"}, "a, b, and c"},
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)
1308
func BenchmarkBuildHTTPVirtualServices(b *testing.B) {
1309
ports := []*model.Port{
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"},
1332
Hostname: "istio-ingressgateway.istio-system.svc.domain.suffix",
1334
altIngressSvc := &model.Service{
1335
Attributes: model.ServiceAttributes{
1336
Namespace: "istio-system",
1339
Hostname: "example.com",
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{}},
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),
1359
_, gwMap, _ := convertGateways(ctx)
1360
ctx.GatewayReferences = gwMap
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)
1374
func TestExtractGatewayServices(t *testing.T) {
1378
kgw *k8sv1.GatewaySpec
1380
gatewayServices []string
1384
name: "managed gateway",
1385
r: GatewayResources{Domain: "cluster.local"},
1386
kgw: &k8sv1.GatewaySpec{
1387
GatewayClassName: "istio",
1392
Namespace: "default",
1395
gatewayServices: []string{"foo-istio.default.svc.cluster.local"},
1398
name: "managed gateway with name overridden",
1399
r: GatewayResources{Domain: "cluster.local"},
1400
kgw: &k8sv1.GatewaySpec{
1401
GatewayClassName: "istio",
1406
Namespace: "default",
1407
Annotations: map[string]string{
1408
gatewayNameOverride: "bar",
1412
gatewayServices: []string{"bar.default.svc.cluster.local"},
1415
name: "unmanaged gateway",
1416
r: GatewayResources{Domain: "domain"},
1417
kgw: &k8sv1.GatewaySpec{
1418
GatewayClassName: "istio",
1419
Addresses: []k8sv1.GatewayAddress{
1424
Type: func() *k8sv1.AddressType {
1425
t := k8sv1.HostnameAddressType
1428
Value: "example.com",
1431
Type: func() *k8sv1.AddressType {
1432
t := k8sv1.IPAddressType
1442
Namespace: "default",
1445
gatewayServices: []string{"abc.default.svc.domain", "example.com"},
1447
Reason: InvalidAddress,
1448
Message: "only Hostname is supported, ignoring [1.2.3.4]",
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)