istio

Форк
0
/
conversion_test.go 
525 строк · 14.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 ingress
16

17
import (
18
	"fmt"
19
	"os"
20
	"sort"
21
	"strings"
22
	"testing"
23

24
	"github.com/google/go-cmp/cmp"
25
	corev1 "k8s.io/api/core/v1"
26
	knetworking "k8s.io/api/networking/v1"
27
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
	"k8s.io/apimachinery/pkg/runtime"
29
	"k8s.io/apimachinery/pkg/util/intstr"
30
	"k8s.io/client-go/kubernetes/scheme"
31
	"sigs.k8s.io/yaml"
32

33
	meshconfig "istio.io/api/mesh/v1alpha1"
34
	networking "istio.io/api/networking/v1alpha3"
35
	"istio.io/istio/pilot/pkg/config/kube/crd"
36
	"istio.io/istio/pilot/test/util"
37
	"istio.io/istio/pkg/config"
38
	"istio.io/istio/pkg/config/mesh"
39
	"istio.io/istio/pkg/kube"
40
	"istio.io/istio/pkg/kube/kclient"
41
	"istio.io/istio/pkg/test"
42
)
43

44
func TestGoldenConversion(t *testing.T) {
45
	cases := []string{"simple", "tls", "overlay", "tls-no-secret"}
46
	for _, tt := range cases {
47
		t.Run(tt, func(t *testing.T) {
48
			input, err := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt))
49
			if err != nil {
50
				t.Fatal(err)
51
			}
52
			serviceLister := createFakeClient(t)
53
			cfgs := map[string]*config.Config{}
54
			for _, obj := range input {
55
				ingress := obj.(*knetworking.Ingress)
56
				ConvertIngressVirtualService(*ingress, "mydomain", cfgs, serviceLister)
57
			}
58
			ordered := []config.Config{}
59
			for _, v := range cfgs {
60
				ordered = append(ordered, *v)
61
			}
62
			for _, obj := range input {
63
				ingress := obj.(*knetworking.Ingress)
64
				m := mesh.DefaultMeshConfig()
65
				gws := ConvertIngressV1alpha3(*ingress, m, "mydomain")
66
				ordered = append(ordered, gws)
67
			}
68

69
			sort.Slice(ordered, func(i, j int) bool {
70
				return ordered[i].Name < ordered[j].Name
71
			})
72
			output := marshalYaml(t, ordered)
73
			goldenFile := fmt.Sprintf("testdata/%s.yaml.golden", tt)
74
			if util.Refresh() {
75
				if err := os.WriteFile(goldenFile, output, 0o644); err != nil {
76
					t.Fatal(err)
77
				}
78
			}
79
			expected, err := os.ReadFile(goldenFile)
80
			if err != nil {
81
				t.Fatal(err)
82
			}
83
			if diff := cmp.Diff(expected, output); diff != "" {
84
				t.Fatalf("Diff:\n%s", diff)
85
			}
86
		})
87
	}
88
}
89

90
// Print as YAML
91
func marshalYaml(t *testing.T, cl []config.Config) []byte {
92
	t.Helper()
93
	result := []byte{}
94
	separator := []byte("---\n")
95
	for _, config := range cl {
96
		obj, err := crd.ConvertConfig(config)
97
		if err != nil {
98
			t.Fatalf("Could not decode %v: %v", config.Name, err)
99
		}
100
		bytes, err := yaml.Marshal(obj)
101
		if err != nil {
102
			t.Fatalf("Could not convert %v to YAML: %v", config, err)
103
		}
104
		result = append(result, bytes...)
105
		result = append(result, separator...)
106
	}
107
	return result
108
}
109

110
func readConfig(t *testing.T, filename string) ([]runtime.Object, error) {
111
	t.Helper()
112

113
	data, err := os.ReadFile(filename)
114
	if err != nil {
115
		t.Fatalf("failed to read input yaml file: %v", err)
116
	}
117
	var varr []runtime.Object
118
	for _, yml := range strings.Split(string(data), "\n---") {
119
		obj, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(yml), nil, nil)
120
		if err != nil {
121
			return nil, err
122
		}
123
		varr = append(varr, obj)
124
	}
125

126
	return varr, nil
127
}
128

129
func TestConversion(t *testing.T) {
130
	prefix := knetworking.PathTypePrefix
131
	exact := knetworking.PathTypeExact
132

133
	ingress := knetworking.Ingress{
134
		ObjectMeta: metav1.ObjectMeta{
135
			Namespace: "mock", // goes into backend full name
136
		},
137
		Spec: knetworking.IngressSpec{
138
			Rules: []knetworking.IngressRule{
139
				{
140
					Host: "my.host.com",
141
					IngressRuleValue: knetworking.IngressRuleValue{
142
						HTTP: &knetworking.HTTPIngressRuleValue{
143
							Paths: []knetworking.HTTPIngressPath{
144
								{
145
									Path: "/test",
146
									Backend: knetworking.IngressBackend{
147
										Service: &knetworking.IngressServiceBackend{
148
											Name: "foo",
149
											Port: knetworking.ServiceBackendPort{Number: 8000},
150
										},
151
									},
152
								},
153
								{
154
									Path:     "/test/foo",
155
									PathType: &prefix,
156
									Backend: knetworking.IngressBackend{
157
										Service: &knetworking.IngressServiceBackend{
158
											Name: "foo",
159
											Port: knetworking.ServiceBackendPort{Number: 8000},
160
										},
161
									},
162
								},
163
							},
164
						},
165
					},
166
				},
167
				{
168
					Host: "my2.host.com",
169
					IngressRuleValue: knetworking.IngressRuleValue{
170
						HTTP: &knetworking.HTTPIngressRuleValue{
171
							Paths: []knetworking.HTTPIngressPath{
172
								{
173
									Path: "/test1.*",
174
									Backend: knetworking.IngressBackend{
175
										Service: &knetworking.IngressServiceBackend{
176
											Name: "bar",
177
											Port: knetworking.ServiceBackendPort{Number: 8000},
178
										},
179
									},
180
								},
181
							},
182
						},
183
					},
184
				},
185
				{
186
					Host: "my3.host.com",
187
					IngressRuleValue: knetworking.IngressRuleValue{
188
						HTTP: &knetworking.HTTPIngressRuleValue{
189
							Paths: []knetworking.HTTPIngressPath{
190
								{
191
									Path: "/test/*",
192
									Backend: knetworking.IngressBackend{
193
										Service: &knetworking.IngressServiceBackend{
194
											Name: "bar",
195
											Port: knetworking.ServiceBackendPort{Number: 8000},
196
										},
197
									},
198
								},
199
							},
200
						},
201
					},
202
				},
203
				{
204
					Host: "my4.host.com",
205
					IngressRuleValue: knetworking.IngressRuleValue{
206
						HTTP: &knetworking.HTTPIngressRuleValue{
207
							Paths: []knetworking.HTTPIngressPath{
208
								{
209
									Path: "/*",
210
									Backend: knetworking.IngressBackend{
211
										Service: &knetworking.IngressServiceBackend{
212
											Name: "bar",
213
											Port: knetworking.ServiceBackendPort{Number: 8000},
214
										},
215
									},
216
								},
217
							},
218
						},
219
					},
220
				},
221
			},
222
		},
223
	}
224
	ingress2 := knetworking.Ingress{
225
		ObjectMeta: metav1.ObjectMeta{
226
			Namespace: "mock",
227
		},
228
		Spec: knetworking.IngressSpec{
229
			Rules: []knetworking.IngressRule{
230
				{
231
					Host: "my.host.com",
232
					IngressRuleValue: knetworking.IngressRuleValue{
233
						HTTP: &knetworking.HTTPIngressRuleValue{
234
							Paths: []knetworking.HTTPIngressPath{
235
								{
236
									Path: "/test2",
237
									Backend: knetworking.IngressBackend{
238
										Service: &knetworking.IngressServiceBackend{
239
											Name: "foo",
240
											Port: knetworking.ServiceBackendPort{Number: 8000},
241
										},
242
									},
243
								},
244
								{
245
									Path:     "/test/foo/bar",
246
									PathType: &prefix,
247
									Backend: knetworking.IngressBackend{
248
										Service: &knetworking.IngressServiceBackend{
249
											Name: "foo",
250
											Port: knetworking.ServiceBackendPort{Number: 8000},
251
										},
252
									},
253
								},
254
								{
255
									Path:     "/test/foo/bar",
256
									PathType: &exact,
257
									Backend: knetworking.IngressBackend{
258
										Service: &knetworking.IngressServiceBackend{
259
											Name: "foo",
260
											Port: knetworking.ServiceBackendPort{Number: 8000},
261
										},
262
									},
263
								},
264
							},
265
						},
266
					},
267
				},
268
			},
269
		},
270
	}
271
	serviceLister := createFakeClient(t)
272
	cfgs := map[string]*config.Config{}
273
	ConvertIngressVirtualService(ingress, "mydomain", cfgs, serviceLister)
274
	ConvertIngressVirtualService(ingress2, "mydomain", cfgs, serviceLister)
275

276
	if len(cfgs) != 4 {
277
		t.Error("VirtualServices, expected 4 got ", len(cfgs))
278
	}
279

280
	expectedLength := [5]int{13, 13, 9, 6, 5}
281
	expectedExact := [5]bool{true, false, false, true, true}
282

283
	for n, cfg := range cfgs {
284
		vs := cfg.Spec.(*networking.VirtualService)
285

286
		if n == "my.host.com" {
287
			if vs.Hosts[0] != "my.host.com" {
288
				t.Error("Unexpected host", vs)
289
			}
290
			if len(vs.Http) != 5 {
291
				t.Error("Unexpected rules", vs.Http)
292
			}
293
			for i, route := range vs.Http {
294
				length, exact := getMatchURILength(route.Match[0])
295
				if length != expectedLength[i] || exact != expectedExact[i] {
296
					t.Errorf("Unexpected rule at idx:%d, want {length:%d, exact:%v}, got {length:%d, exact: %v}",
297
						i, expectedLength[i], expectedExact[i], length, exact)
298
				}
299
			}
300
		} else if n == "my4.host.com" {
301
			if vs.Hosts[0] != "my4.host.com" {
302
				t.Error("Unexpected host", vs)
303
			}
304
			if len(vs.Http) != 1 {
305
				t.Error("Unexpected rules", vs.Http)
306
			}
307
			if vs.Http[0].Match != nil {
308
				t.Error("Expected HTTPMatchRequest to be nil, got {}")
309
			}
310
		}
311
	}
312
}
313

314
func TestDecodeIngressRuleName(t *testing.T) {
315
	cases := []struct {
316
		ingressName string
317
		ruleNum     int
318
		pathNum     int
319
	}{
320
		{"myingress", 0, 0},
321
		{"myingress", 1, 2},
322
		{"my-ingress", 1, 2},
323
		{"my-cool-ingress", 1, 2},
324
	}
325

326
	for _, c := range cases {
327
		encoded := EncodeIngressRuleName(c.ingressName, c.ruleNum, c.pathNum)
328
		ingressName, ruleNum, pathNum, err := decodeIngressRuleName(encoded)
329
		if err != nil {
330
			t.Errorf("decodeIngressRuleName(%q) => error %v", encoded, err)
331
		}
332
		if ingressName != c.ingressName || ruleNum != c.ruleNum || pathNum != c.pathNum {
333
			t.Errorf("decodeIngressRuleName(%q) => (%q, %d, %d), want (%q, %d, %d)",
334
				encoded,
335
				ingressName, ruleNum, pathNum,
336
				c.ingressName, c.ruleNum, c.pathNum,
337
			)
338
		}
339
	}
340
}
341

342
func TestEncoding(t *testing.T) {
343
	if got := EncodeIngressRuleName("name", 3, 5); got != "name-3-5" {
344
		t.Errorf("unexpected ingress encoding %q", got)
345
	}
346

347
	cases := []string{
348
		"name",
349
		"name-path-5",
350
		"name-3-path",
351
	}
352
	for _, code := range cases {
353
		if _, _, _, err := decodeIngressRuleName(code); err == nil {
354
			t.Errorf("expected error on decoding %q", code)
355
		}
356
	}
357
}
358

359
func TestIngressClass(t *testing.T) {
360
	istio := mesh.DefaultMeshConfig().IngressClass
361
	ingressClassIstio := &knetworking.IngressClass{
362
		ObjectMeta: metav1.ObjectMeta{
363
			Name: "istio",
364
		},
365
		Spec: knetworking.IngressClassSpec{
366
			Controller: IstioIngressController,
367
		},
368
	}
369
	ingressClassOther := &knetworking.IngressClass{
370
		ObjectMeta: metav1.ObjectMeta{
371
			Name: "foo",
372
		},
373
		Spec: knetworking.IngressClassSpec{
374
			Controller: "foo",
375
		},
376
	}
377
	cases := []struct {
378
		annotation    string
379
		ingressClass  *knetworking.IngressClass
380
		ingressMode   meshconfig.MeshConfig_IngressControllerMode
381
		shouldProcess bool
382
	}{
383
		// Annotation
384
		{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: "nginx", shouldProcess: false},
385
		{ingressMode: meshconfig.MeshConfig_STRICT, annotation: "nginx", shouldProcess: false},
386
		{ingressMode: meshconfig.MeshConfig_OFF, annotation: istio, shouldProcess: false},
387
		{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: istio, shouldProcess: true},
388
		{ingressMode: meshconfig.MeshConfig_STRICT, annotation: istio, shouldProcess: true},
389
		{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: "", shouldProcess: true},
390
		{ingressMode: meshconfig.MeshConfig_STRICT, annotation: "", shouldProcess: false},
391

392
		// IngressClass
393
		{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: ingressClassOther, shouldProcess: false},
394
		{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassOther, shouldProcess: false},
395
		{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: ingressClassIstio, shouldProcess: true},
396
		{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassIstio, shouldProcess: true},
397
		{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: nil, shouldProcess: true},
398
		{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: nil, shouldProcess: false},
399

400
		// IngressClass and Annotation
401
		// note: k8s rejects Ingress resources configured with kubernetes.io/ingress.class annotation *and* ingressClassName field so this shouldn't happen
402
		// see https://github.com/kubernetes/kubernetes/blob/ededd08ba131b727e60f663bd7217fffaaccd448/pkg/apis/networking/validation/validation.go#L226
403
		{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassIstio, annotation: "nginx", shouldProcess: false},
404
		{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassOther, annotation: istio, shouldProcess: true},
405
		{ingressMode: -1, shouldProcess: false},
406
	}
407

408
	for i, c := range cases {
409
		className := ""
410
		if c.ingressClass != nil {
411
			className = c.ingressClass.Name
412
		}
413
		t.Run(fmt.Sprintf("%d %s %s %s", i, c.ingressMode, c.annotation, className), func(t *testing.T) {
414
			ing := knetworking.Ingress{
415
				ObjectMeta: metav1.ObjectMeta{
416
					Name:        "test-ingress",
417
					Namespace:   "default",
418
					Annotations: make(map[string]string),
419
				},
420
				Spec: knetworking.IngressSpec{
421
					DefaultBackend: &knetworking.IngressBackend{
422
						Service: &knetworking.IngressServiceBackend{
423
							Name: "default-http-backend",
424
							Port: knetworking.ServiceBackendPort{Number: 8000},
425
						},
426
					},
427
				},
428
			}
429

430
			mesh := mesh.DefaultMeshConfig()
431
			mesh.IngressControllerMode = c.ingressMode
432

433
			if c.annotation != "" {
434
				ing.Annotations["kubernetes.io/ingress.class"] = c.annotation
435
			}
436

437
			if c.shouldProcess != shouldProcessIngressWithClass(mesh, &ing, c.ingressClass) {
438
				t.Errorf("got %v, want %v",
439
					!c.shouldProcess, c.shouldProcess)
440
			}
441
		})
442
	}
443
}
444

445
func TestNamedPortIngressConversion(t *testing.T) {
446
	ingress := knetworking.Ingress{
447
		ObjectMeta: metav1.ObjectMeta{
448
			Namespace: "mock",
449
		},
450
		Spec: knetworking.IngressSpec{
451
			Rules: []knetworking.IngressRule{
452
				{
453
					Host: "host.com",
454
					IngressRuleValue: knetworking.IngressRuleValue{
455
						HTTP: &knetworking.HTTPIngressRuleValue{
456
							Paths: []knetworking.HTTPIngressPath{
457
								{
458
									Path: "/test/*",
459
									Backend: knetworking.IngressBackend{
460
										Service: &knetworking.IngressServiceBackend{
461
											Name: "foo",
462
											Port: knetworking.ServiceBackendPort{Name: "test-svc-port"},
463
										},
464
									},
465
								},
466
							},
467
						},
468
					},
469
				},
470
			},
471
		},
472
	}
473
	service := &corev1.Service{
474
		ObjectMeta: metav1.ObjectMeta{
475
			Name:      "foo",
476
			Namespace: "mock",
477
		},
478
		Spec: corev1.ServiceSpec{
479
			Ports: []corev1.ServicePort{
480
				{
481
					Name:     "test-svc-port",
482
					Protocol: "TCP",
483
					Port:     8888,
484
					TargetPort: intstr.IntOrString{
485
						Type:   intstr.String,
486
						StrVal: "test-port",
487
					},
488
				},
489
			},
490
			Selector: map[string]string{
491
				"app": "test-app",
492
			},
493
		},
494
	}
495
	serviceLister := createFakeClient(t, service)
496
	cfgs := map[string]*config.Config{}
497
	ConvertIngressVirtualService(ingress, "mydomain", cfgs, serviceLister)
498
	if len(cfgs) != 1 {
499
		t.Error("VirtualServices, expected 1 got ", len(cfgs))
500
	}
501
	if cfgs["host.com"] == nil {
502
		t.Error("Host, found nil")
503
	}
504
	vs := cfgs["host.com"].Spec.(*networking.VirtualService)
505
	if len(vs.Http) != 1 {
506
		t.Error("HttpSpec, expected 1 got ", len(vs.Http))
507
	}
508
	http := vs.Http[0]
509
	if len(http.Route) != 1 {
510
		t.Error("Route, expected 1 got ", len(http.Route))
511
	}
512
	route := http.Route[0]
513
	if route.Destination.Port.Number != 8888 {
514
		t.Error("PortNumer, expected 8888 got ", route.Destination.Port.Number)
515
	}
516
}
517

518
func createFakeClient(t test.Failer, objects ...runtime.Object) kclient.Client[*corev1.Service] {
519
	kc := kube.NewFakeClient(objects...)
520
	stop := test.NewStop(t)
521
	services := kclient.New[*corev1.Service](kc)
522
	kc.RunAndWait(stop)
523
	kube.WaitForCacheSync("test", stop, services.HasSynced)
524
	return services
525
}
526

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

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

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

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