istio

Форк
0
/
deploymentcontroller_test.go 
539 строк · 16.5 Кб
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
	"bytes"
19
	"fmt"
20
	"path/filepath"
21
	"testing"
22
	"time"
23

24
	"go.uber.org/atomic"
25
	corev1 "k8s.io/api/core/v1"
26
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
	"k8s.io/apimachinery/pkg/runtime"
28
	"k8s.io/apimachinery/pkg/runtime/schema"
29
	kubeVersion "k8s.io/apimachinery/pkg/version"
30
	fakediscovery "k8s.io/client-go/discovery/fake"
31
	k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
32
	"sigs.k8s.io/gateway-api/apis/v1alpha2"
33
	"sigs.k8s.io/gateway-api/apis/v1beta1"
34
	"sigs.k8s.io/yaml"
35

36
	istioio_networking_v1beta1 "istio.io/api/networking/v1beta1"
37
	istio_type_v1beta1 "istio.io/api/type/v1beta1"
38
	"istio.io/istio/pilot/pkg/features"
39
	"istio.io/istio/pilot/pkg/model"
40
	"istio.io/istio/pilot/test/util"
41
	"istio.io/istio/pkg/cluster"
42
	"istio.io/istio/pkg/config"
43
	"istio.io/istio/pkg/config/constants"
44
	"istio.io/istio/pkg/config/mesh"
45
	"istio.io/istio/pkg/config/schema/gvk"
46
	"istio.io/istio/pkg/config/schema/gvr"
47
	"istio.io/istio/pkg/kube"
48
	"istio.io/istio/pkg/kube/controllers"
49
	"istio.io/istio/pkg/kube/inject"
50
	"istio.io/istio/pkg/kube/kclient"
51
	"istio.io/istio/pkg/kube/kclient/clienttest"
52
	"istio.io/istio/pkg/kube/kubetypes"
53
	istiolog "istio.io/istio/pkg/log"
54
	"istio.io/istio/pkg/revisions"
55
	"istio.io/istio/pkg/test"
56
	"istio.io/istio/pkg/test/env"
57
	"istio.io/istio/pkg/test/util/assert"
58
	"istio.io/istio/pkg/test/util/file"
59
	"istio.io/istio/pkg/test/util/retry"
60
)
61

62
func TestConfigureIstioGateway(t *testing.T) {
63
	discoveryNamespacesFilter := buildFilter("default")
64
	defaultNamespace := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}}
65
	customClass := &v1beta1.GatewayClass{
66
		ObjectMeta: metav1.ObjectMeta{
67
			Name: "custom",
68
		},
69
		Spec: v1beta1.GatewayClassSpec{
70
			ControllerName: k8sv1.GatewayController(features.ManagedGatewayController),
71
		},
72
	}
73
	defaultObjects := []runtime.Object{defaultNamespace}
74
	store := model.NewFakeStore()
75
	if _, err := store.Create(config.Config{
76
		Meta: config.Meta{
77
			GroupVersionKind: gvk.ProxyConfig,
78
			Name:             "test",
79
			Namespace:        "default",
80
		},
81
		Spec: &istioio_networking_v1beta1.ProxyConfig{
82
			Selector: &istio_type_v1beta1.WorkloadSelector{
83
				MatchLabels: map[string]string{
84
					"gateway.networking.k8s.io/gateway-name": "default",
85
				},
86
			},
87
			Image: &istioio_networking_v1beta1.ProxyImage{
88
				ImageType: "distroless",
89
			},
90
		},
91
	}); err != nil {
92
		t.Fatalf("failed to create ProxyConfigs: %s", err)
93
	}
94
	proxyConfig := model.GetProxyConfigs(store, mesh.DefaultMeshConfig())
95
	tests := []struct {
96
		name                     string
97
		gw                       v1beta1.Gateway
98
		objects                  []runtime.Object
99
		pcs                      *model.ProxyConfigs
100
		values                   string
101
		discoveryNamespaceFilter kubetypes.DynamicObjectFilter
102
		ignore                   bool
103
	}{
104
		{
105
			name: "simple",
106
			gw: v1beta1.Gateway{
107
				ObjectMeta: metav1.ObjectMeta{
108
					Name:        "default",
109
					Namespace:   "default",
110
					Labels:      map[string]string{"should": "see"},
111
					Annotations: map[string]string{"should": "see"},
112
				},
113
				Spec: v1alpha2.GatewaySpec{
114
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
115
				},
116
			},
117
			objects:                  defaultObjects,
118
			discoveryNamespaceFilter: discoveryNamespacesFilter,
119
		},
120
		{
121
			name: "simple",
122
			gw: v1beta1.Gateway{
123
				ObjectMeta: metav1.ObjectMeta{
124
					Name:      "default",
125
					Namespace: "default",
126
				},
127
				Spec: v1alpha2.GatewaySpec{
128
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
129
				},
130
			},
131
			objects:                  defaultObjects,
132
			discoveryNamespaceFilter: buildFilter("not-default"),
133
			ignore:                   true,
134
		},
135
		{
136
			name: "manual-sa",
137
			gw: v1beta1.Gateway{
138
				ObjectMeta: metav1.ObjectMeta{
139
					Name:        "default",
140
					Namespace:   "default",
141
					Annotations: map[string]string{gatewaySAOverride: "custom-sa"},
142
				},
143
				Spec: v1alpha2.GatewaySpec{
144
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
145
				},
146
			},
147
			objects:                  defaultObjects,
148
			discoveryNamespaceFilter: discoveryNamespacesFilter,
149
		},
150
		{
151
			name: "manual-ip",
152
			gw: v1beta1.Gateway{
153
				ObjectMeta: metav1.ObjectMeta{
154
					Name:        "default",
155
					Namespace:   "default",
156
					Annotations: map[string]string{gatewayNameOverride: "default"},
157
				},
158
				Spec: v1beta1.GatewaySpec{
159
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
160
					Addresses: []v1beta1.GatewayAddress{{
161
						Type:  func() *v1beta1.AddressType { x := v1beta1.IPAddressType; return &x }(),
162
						Value: "1.2.3.4",
163
					}},
164
				},
165
			},
166
			objects:                  defaultObjects,
167
			discoveryNamespaceFilter: discoveryNamespacesFilter,
168
		},
169
		{
170
			name: "cluster-ip",
171
			gw: v1beta1.Gateway{
172
				ObjectMeta: metav1.ObjectMeta{
173
					Name:      "default",
174
					Namespace: "default",
175
					Annotations: map[string]string{
176
						"networking.istio.io/service-type": string(corev1.ServiceTypeClusterIP),
177
						gatewayNameOverride:                "default",
178
					},
179
				},
180
				Spec: v1beta1.GatewaySpec{
181
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
182
					Listeners: []v1beta1.Listener{{
183
						Name:     "http",
184
						Port:     v1beta1.PortNumber(80),
185
						Protocol: k8sv1.HTTPProtocolType,
186
					}},
187
				},
188
			},
189
			objects:                  defaultObjects,
190
			discoveryNamespaceFilter: discoveryNamespacesFilter,
191
		},
192
		{
193
			name: "multinetwork",
194
			gw: v1beta1.Gateway{
195
				ObjectMeta: metav1.ObjectMeta{
196
					Name:        "default",
197
					Namespace:   "default",
198
					Labels:      map[string]string{"topology.istio.io/network": "network-1"},
199
					Annotations: map[string]string{gatewayNameOverride: "default"},
200
				},
201
				Spec: v1beta1.GatewaySpec{
202
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
203
					Listeners: []v1beta1.Listener{{
204
						Name:     "http",
205
						Port:     v1beta1.PortNumber(80),
206
						Protocol: k8sv1.HTTPProtocolType,
207
					}},
208
				},
209
			},
210
			objects:                  defaultObjects,
211
			discoveryNamespaceFilter: discoveryNamespacesFilter,
212
		},
213
		{
214
			name: "waypoint",
215
			gw: v1beta1.Gateway{
216
				ObjectMeta: metav1.ObjectMeta{
217
					Name:      "namespace",
218
					Namespace: "default",
219
					Labels: map[string]string{
220
						"topology.istio.io/network": "network-1", // explicitly set network won't be overwritten
221
					},
222
				},
223
				Spec: v1beta1.GatewaySpec{
224
					GatewayClassName: constants.WaypointGatewayClassName,
225
					Listeners: []v1beta1.Listener{{
226
						Name:     "mesh",
227
						Port:     v1beta1.PortNumber(15008),
228
						Protocol: "ALL",
229
					}},
230
				},
231
			},
232
			objects: defaultObjects,
233
			values: `global:
234
  hub: test
235
  tag: test
236
  network: network-2`,
237
		},
238
		{
239
			name: "waypoint-no-network-label",
240
			gw: v1beta1.Gateway{
241
				ObjectMeta: metav1.ObjectMeta{
242
					Name:      "namespace",
243
					Namespace: "default",
244
				},
245
				Spec: v1beta1.GatewaySpec{
246
					GatewayClassName: constants.WaypointGatewayClassName,
247
					Listeners: []v1beta1.Listener{{
248
						Name:     "mesh",
249
						Port:     v1beta1.PortNumber(15008),
250
						Protocol: "ALL",
251
					}},
252
				},
253
			},
254
			objects: defaultObjects,
255
			values: `global:
256
  hub: test
257
  tag: test
258
  network: network-1`,
259
		},
260
		{
261
			name: "proxy-config-crd",
262
			gw: v1beta1.Gateway{
263
				ObjectMeta: metav1.ObjectMeta{
264
					Name:      "default",
265
					Namespace: "default",
266
				},
267
				Spec: v1alpha2.GatewaySpec{
268
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
269
				},
270
			},
271
			objects: defaultObjects,
272
			pcs:     proxyConfig,
273
		},
274
		{
275
			name: "custom-class",
276
			gw: v1beta1.Gateway{
277
				ObjectMeta: metav1.ObjectMeta{
278
					Name:      "default",
279
					Namespace: "default",
280
				},
281
				Spec: v1beta1.GatewaySpec{
282
					GatewayClassName: v1beta1.ObjectName(customClass.Name),
283
				},
284
			},
285
			objects: defaultObjects,
286
		},
287
		{
288
			name: "infrastructure-labels-annotations",
289
			gw: v1beta1.Gateway{
290
				ObjectMeta: metav1.ObjectMeta{
291
					Name:        "default",
292
					Namespace:   "default",
293
					Labels:      map[string]string{"should-not": "see"},
294
					Annotations: map[string]string{"should-not": "see"},
295
				},
296
				Spec: v1alpha2.GatewaySpec{
297
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
298
					Infrastructure: &k8sv1.GatewayInfrastructure{
299
						Labels:      map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{"foo": "bar", "gateway.networking.k8s.io/ignore": "true"},
300
						Annotations: map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{"fizz": "buzz", "gateway.networking.k8s.io/ignore": "true"},
301
					},
302
				},
303
			},
304
			objects: defaultObjects,
305
		},
306
		{
307
			name: "kube-gateway-ambient-redirect",
308
			gw: v1beta1.Gateway{
309
				ObjectMeta: metav1.ObjectMeta{
310
					Name:      "default",
311
					Namespace: "default",
312
					Annotations: map[string]string{
313
						"ambient.istio.io/redirection": "enabled",
314
					},
315
				},
316
				Spec: v1alpha2.GatewaySpec{
317
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
318
				},
319
			},
320
			objects: defaultObjects,
321
		},
322
		{
323
			name: "kube-gateway-ambient-redirect-infra",
324
			gw: v1beta1.Gateway{
325
				ObjectMeta: metav1.ObjectMeta{
326
					Name:      "default",
327
					Namespace: "default",
328
				},
329
				Spec: v1alpha2.GatewaySpec{
330
					GatewayClassName: k8sv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
331
					Infrastructure: &k8sv1.GatewayInfrastructure{
332
						Annotations: map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{
333
							"ambient.istio.io/redirection": "enabled",
334
						},
335
					},
336
				},
337
			},
338
			objects: defaultObjects,
339
		},
340
	}
341
	for _, tt := range tests {
342
		t.Run(tt.name, func(t *testing.T) {
343
			buf := &bytes.Buffer{}
344
			client := kube.NewFakeClient(tt.objects...)
345
			kube.SetObjectFilter(client, tt.discoveryNamespaceFilter)
346
			client.Kube().Discovery().(*fakediscovery.FakeDiscovery).FakedServerVersion = &kubeVersion.Info{Major: "1", Minor: "28"}
347
			kclient.NewWriteClient[*v1beta1.GatewayClass](client).Create(customClass)
348
			kclient.NewWriteClient[*v1beta1.Gateway](client).Create(&tt.gw)
349
			stop := test.NewStop(t)
350
			env := model.NewEnvironment()
351
			env.PushContext().ProxyConfigs = tt.pcs
352
			tw := revisions.NewTagWatcher(client, "")
353
			go tw.Run(stop)
354
			d := NewDeploymentController(
355
				client, cluster.ID(features.ClusterName), env, testInjectionConfig(t, tt.values), func(fn func()) {
356
				}, tw, "")
357
			d.patcher = func(gvr schema.GroupVersionResource, name string, namespace string, data []byte, subresources ...string) error {
358
				b, err := yaml.JSONToYAML(data)
359
				if err != nil {
360
					return err
361
				}
362
				buf.Write(b)
363
				buf.WriteString("---\n")
364
				return nil
365
			}
366
			client.RunAndWait(stop)
367
			go d.Run(stop)
368
			kube.WaitForCacheSync("test", stop, d.queue.HasSynced)
369

370
			if tt.ignore {
371
				assert.Equal(t, buf.String(), "")
372
			} else {
373
				resp := timestampRegex.ReplaceAll(buf.Bytes(), []byte("lastTransitionTime: fake"))
374
				util.CompareContent(t, resp, filepath.Join("testdata", "deployment", tt.name+".yaml"))
375
			}
376
		})
377
	}
378
}
379

380
func buildFilter(allowedNamespace string) kubetypes.DynamicObjectFilter {
381
	return kubetypes.NewStaticObjectFilter(func(obj any) bool {
382
		if ns, ok := obj.(string); ok {
383
			return ns == allowedNamespace
384
		}
385
		object := controllers.ExtractObject(obj)
386
		if object == nil {
387
			return false
388
		}
389
		ns := object.GetNamespace()
390
		if _, ok := object.(*corev1.Namespace); ok {
391
			ns = object.GetName()
392
		}
393
		return ns == allowedNamespace
394
	})
395
}
396

397
func TestVersionManagement(t *testing.T) {
398
	log.SetOutputLevel(istiolog.DebugLevel)
399
	writes := make(chan string, 10)
400
	c := kube.NewFakeClient(&corev1.Namespace{
401
		ObjectMeta: metav1.ObjectMeta{
402
			Name: "default",
403
		},
404
	})
405
	tw := revisions.NewTagWatcher(c, "default")
406
	env := &model.Environment{}
407
	d := NewDeploymentController(c, "", env, testInjectionConfig(t, ""), func(fn func()) {}, tw, "")
408
	reconciles := atomic.NewInt32(0)
409
	wantReconcile := int32(0)
410
	expectReconciled := func() {
411
		t.Helper()
412
		wantReconcile++
413
		assert.EventuallyEqual(t, reconciles.Load, wantReconcile, retry.Timeout(time.Second*5), retry.Message("no reconciliation"))
414
	}
415

416
	d.patcher = func(g schema.GroupVersionResource, name string, namespace string, data []byte, subresources ...string) error {
417
		if g == gvr.Service {
418
			reconciles.Inc()
419
		}
420
		if g == gvr.KubernetesGateway {
421
			b, err := yaml.JSONToYAML(data)
422
			if err != nil {
423
				return err
424
			}
425
			writes <- string(b)
426
		}
427
		return nil
428
	}
429
	stop := test.NewStop(t)
430
	gws := clienttest.Wrap(t, d.gateways)
431
	go tw.Run(stop)
432
	go d.Run(stop)
433
	c.RunAndWait(stop)
434
	kube.WaitForCacheSync("test", stop, d.queue.HasSynced)
435
	// Create a gateway, we should mark our ownership
436
	defaultGateway := &v1beta1.Gateway{
437
		ObjectMeta: metav1.ObjectMeta{
438
			Name:      "gw",
439
			Namespace: "default",
440
		},
441
		Spec: v1beta1.GatewaySpec{
442
			GatewayClassName: v1beta1.ObjectName(features.GatewayAPIDefaultGatewayClass),
443
		},
444
	}
445
	gws.Create(defaultGateway)
446
	assert.Equal(t, assert.ChannelHasItem(t, writes), buildPatch(ControllerVersion))
447
	expectReconciled()
448
	assert.ChannelIsEmpty(t, writes)
449
	// Test fake doesn't actual do Apply, so manually do this
450
	defaultGateway.Annotations = map[string]string{ControllerVersionAnnotation: fmt.Sprint(ControllerVersion)}
451
	gws.Update(defaultGateway)
452
	expectReconciled()
453
	// We shouldn't write in response to our write.
454
	assert.ChannelIsEmpty(t, writes)
455

456
	defaultGateway.Annotations["foo"] = "bar"
457
	gws.Update(defaultGateway)
458
	expectReconciled()
459
	// We should not be updating the version, its already set. Setting it introduces a possible race condition
460
	// since we use SSA so there is no conflict checks.
461
	assert.ChannelIsEmpty(t, writes)
462

463
	// Somehow the annotation is removed - it should be added back
464
	defaultGateway.Annotations = map[string]string{}
465
	gws.Update(defaultGateway)
466
	expectReconciled()
467
	assert.Equal(t, assert.ChannelHasItem(t, writes), buildPatch(ControllerVersion))
468
	assert.ChannelIsEmpty(t, writes)
469
	// Test fake doesn't actual do Apply, so manually do this
470
	defaultGateway.Annotations = map[string]string{ControllerVersionAnnotation: fmt.Sprint(ControllerVersion)}
471
	gws.Update(defaultGateway)
472
	expectReconciled()
473
	// We shouldn't write in response to our write.
474
	assert.ChannelIsEmpty(t, writes)
475

476
	// Somehow the annotation is set to an older version - it should be added back
477
	defaultGateway.Annotations = map[string]string{ControllerVersionAnnotation: fmt.Sprint(1)}
478
	gws.Update(defaultGateway)
479
	expectReconciled()
480
	assert.Equal(t, assert.ChannelHasItem(t, writes), buildPatch(ControllerVersion))
481
	assert.ChannelIsEmpty(t, writes)
482
	// Test fake doesn't actual do Apply, so manually do this
483
	defaultGateway.Annotations = map[string]string{ControllerVersionAnnotation: fmt.Sprint(ControllerVersion)}
484
	gws.Update(defaultGateway)
485
	expectReconciled()
486
	// We shouldn't write in response to our write.
487
	assert.ChannelIsEmpty(t, writes)
488

489
	// Somehow the annotation is set to an new version - we should do nothing
490
	defaultGateway.Annotations = map[string]string{ControllerVersionAnnotation: fmt.Sprint(10)}
491
	gws.Update(defaultGateway)
492
	assert.ChannelIsEmpty(t, writes)
493
	// Do not expect reconcile
494
	assert.Equal(t, reconciles.Load(), wantReconcile)
495
}
496

497
func testInjectionConfig(t test.Failer, values string) func() inject.WebhookConfig {
498
	var vc inject.ValuesConfig
499
	var err error
500
	if values != "" {
501
		vc, err = inject.NewValuesConfig(values)
502
		if err != nil {
503
			t.Fatal(err)
504
		}
505
	} else {
506
		vc, err = inject.NewValuesConfig(`
507
global:
508
  hub: test
509
  tag: test`)
510
		if err != nil {
511
			t.Fatal(err)
512
		}
513

514
	}
515
	tmpl, err := inject.ParseTemplates(map[string]string{
516
		"kube-gateway": file.AsStringOrFail(t, filepath.Join(env.IstioSrc, "manifests/charts/istio-control/istio-discovery/files/kube-gateway.yaml")),
517
		"waypoint":     file.AsStringOrFail(t, filepath.Join(env.IstioSrc, "manifests/charts/istio-control/istio-discovery/files/waypoint.yaml")),
518
	})
519
	if err != nil {
520
		t.Fatal(err)
521
	}
522
	injConfig := func() inject.WebhookConfig {
523
		return inject.WebhookConfig{
524
			Templates:  tmpl,
525
			Values:     vc,
526
			MeshConfig: mesh.DefaultMeshConfig(),
527
		}
528
	}
529
	return injConfig
530
}
531

532
func buildPatch(version int) string {
533
	return fmt.Sprintf(`apiVersion: gateway.networking.k8s.io/v1beta1
534
kind: Gateway
535
metadata:
536
  annotations:
537
    gateway.istio.io/controller-version: "%d"
538
`, version)
539
}
540

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

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

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

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