istio
342 строки · 10.7 Кб
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
15package controller
16
17import (
18"fmt"
19"sync"
20"testing"
21"time"
22
23corev1 "k8s.io/api/core/v1"
24metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25"k8s.io/apimachinery/pkg/runtime/schema"
26k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
27"sigs.k8s.io/gateway-api/apis/v1beta1"
28
29"istio.io/api/label"
30meshconfig "istio.io/api/mesh/v1alpha1"
31"istio.io/istio/pilot/pkg/features"
32"istio.io/istio/pilot/pkg/model"
33"istio.io/istio/pkg/config/constants"
34"istio.io/istio/pkg/config/mesh"
35"istio.io/istio/pkg/config/schema/gvr"
36"istio.io/istio/pkg/kube/controllers"
37"istio.io/istio/pkg/kube/kclient"
38"istio.io/istio/pkg/kube/kclient/clienttest"
39"istio.io/istio/pkg/test"
40"istio.io/istio/pkg/test/util/assert"
41"istio.io/istio/pkg/test/util/retry"
42"istio.io/istio/pkg/util/sets"
43)
44
45func TestNetworkUpdateTriggers(t *testing.T) {
46test.SetForTest(t, &features.MultiNetworkGatewayAPI, true)
47meshNetworks := mesh.NewFixedNetworksWatcher(nil)
48c, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{
49ClusterID: constants.DefaultClusterName,
50NetworksWatcher: meshNetworks,
51DomainSuffix: "cluster.local",
52CRDs: []schema.GroupVersionResource{gvr.KubernetesGateway},
53})
54
55if len(c.NetworkGateways()) != 0 {
56t.Fatal("did not expect any gateways yet")
57}
58
59notifyCh := make(chan struct{}, 1)
60var (
61gwMu sync.Mutex
62gws []model.NetworkGateway
63)
64setGws := func(v []model.NetworkGateway) {
65gwMu.Lock()
66defer gwMu.Unlock()
67gws = v
68}
69getGws := func() []model.NetworkGateway {
70gwMu.Lock()
71defer gwMu.Unlock()
72return gws
73}
74
75c.AppendNetworkGatewayHandler(func() {
76setGws(c.NetworkGateways())
77notifyCh <- struct{}{}
78})
79expectGateways := func(t *testing.T, expectedGws int) {
80// wait for a notification
81assert.ChannelHasItem(t, notifyCh)
82if n := len(getGws()); n != expectedGws {
83t.Errorf("expected %d gateways but got %d", expectedGws, n)
84}
85}
86
87t.Run("add meshnetworks", func(t *testing.T) {
88addMeshNetworksFromRegistryGateway(t, c, meshNetworks)
89expectGateways(t, 2)
90})
91t.Run("add labeled service", func(t *testing.T) {
92addLabeledServiceGateway(t, c, "nw0")
93expectGateways(t, 3)
94})
95t.Run("update labeled service network", func(t *testing.T) {
96addLabeledServiceGateway(t, c, "nw1")
97expectGateways(t, 3)
98})
99t.Run("add kubernetes gateway", func(t *testing.T) {
100addOrUpdateGatewayResource(t, c, 35443)
101expectGateways(t, 7)
102})
103t.Run("update kubernetes gateway", func(t *testing.T) {
104addOrUpdateGatewayResource(t, c, 45443)
105expectGateways(t, 7)
106})
107t.Run("remove kubernetes gateway", func(t *testing.T) {
108removeGatewayResource(t, c)
109expectGateways(t, 3)
110})
111t.Run("remove labeled service", func(t *testing.T) {
112removeLabeledServiceGateway(t, c)
113expectGateways(t, 2)
114})
115// gateways are created even with out service
116t.Run("add kubernetes gateway", func(t *testing.T) {
117addOrUpdateGatewayResource(t, c, 35443)
118expectGateways(t, 6)
119})
120t.Run("remove kubernetes gateway", func(t *testing.T) {
121removeGatewayResource(t, c)
122expectGateways(t, 2)
123})
124t.Run("remove meshnetworks", func(t *testing.T) {
125meshNetworks.SetNetworks(nil)
126expectGateways(t, 0)
127})
128}
129
130func addLabeledServiceGateway(t *testing.T, c *FakeController, nw string) {
131svc := &corev1.Service{
132ObjectMeta: metav1.ObjectMeta{Name: "istio-labeled-gw", Namespace: "arbitrary-ns", Labels: map[string]string{
133label.TopologyNetwork.Name: nw,
134}},
135Spec: corev1.ServiceSpec{
136Type: corev1.ServiceTypeLoadBalancer,
137Ports: []corev1.ServicePort{{Port: 15443, Protocol: corev1.ProtocolTCP}},
138},
139Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{
140IP: "2.3.4.6",
141Ports: []corev1.PortStatus{{Port: 15443, Protocol: corev1.ProtocolTCP}},
142}}}},
143}
144clienttest.Wrap(t, c.services).CreateOrUpdate(svc)
145}
146
147func removeLabeledServiceGateway(t *testing.T, c *FakeController) {
148clienttest.Wrap(t, c.services).Delete("istio-labeled-gw", "arbitrary-ns")
149}
150
151// creates a gateway that exposes 2 ports that are valid auto-passthrough ports
152// and it does so on an IP and a hostname
153func addOrUpdateGatewayResource(t *testing.T, c *FakeController, customPort int) {
154passthroughMode := k8sv1.TLSModePassthrough
155ipType := v1beta1.IPAddressType
156hostnameType := v1beta1.HostnameAddressType
157clienttest.Wrap(t, kclient.New[*v1beta1.Gateway](c.client)).CreateOrUpdate(&v1beta1.Gateway{
158ObjectMeta: metav1.ObjectMeta{
159Name: "eastwest-gwapi",
160Namespace: "istio-system",
161Labels: map[string]string{label.TopologyNetwork.Name: "nw2"},
162},
163Spec: v1beta1.GatewaySpec{
164GatewayClassName: "istio",
165Addresses: []v1beta1.GatewayAddress{
166{Type: &ipType, Value: "1.2.3.4"},
167{Type: &hostnameType, Value: "some hostname"},
168},
169Listeners: []v1beta1.Listener{
170{
171Name: "detected-by-options",
172TLS: &v1beta1.GatewayTLSConfig{
173Mode: &passthroughMode,
174Options: map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{
175constants.ListenerModeOption: constants.ListenerModeAutoPassthrough,
176},
177},
178Port: v1beta1.PortNumber(customPort),
179},
180{
181Name: "detected-by-number",
182TLS: &v1beta1.GatewayTLSConfig{Mode: &passthroughMode},
183Port: 15443,
184},
185},
186},
187Status: v1beta1.GatewayStatus{},
188})
189}
190
191func removeGatewayResource(t *testing.T, c *FakeController) {
192clienttest.Wrap(t, kclient.New[*v1beta1.Gateway](c.client)).Delete("eastwest-gwapi", "istio-system")
193}
194
195func addMeshNetworksFromRegistryGateway(t *testing.T, c *FakeController, watcher mesh.NetworksWatcher) {
196clienttest.Wrap(t, c.services).Create(&corev1.Service{
197ObjectMeta: metav1.ObjectMeta{Name: "istio-meshnetworks-gw", Namespace: "istio-system"},
198Spec: corev1.ServiceSpec{
199Type: corev1.ServiceTypeLoadBalancer,
200Ports: []corev1.ServicePort{{Port: 15443, Protocol: corev1.ProtocolTCP}},
201},
202Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{
203IP: "1.2.3.4",
204Ports: []corev1.PortStatus{{Port: 15443, Protocol: corev1.ProtocolTCP}},
205}}}},
206})
207watcher.SetNetworks(&meshconfig.MeshNetworks{Networks: map[string]*meshconfig.Network{
208"nw0": {
209Endpoints: []*meshconfig.Network_NetworkEndpoints{{
210Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{FromRegistry: "Kubernetes"},
211}},
212Gateways: []*meshconfig.Network_IstioNetworkGateway{{
213Port: 15443,
214Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{RegistryServiceName: "istio-meshnetworks-gw.istio-system.svc.cluster.local"},
215}},
216},
217"nw1": {
218Endpoints: []*meshconfig.Network_NetworkEndpoints{{
219Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{FromRegistry: "Kubernetes"},
220}},
221Gateways: []*meshconfig.Network_IstioNetworkGateway{{
222Port: 15443,
223Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{RegistryServiceName: "istio-meshnetworks-gw.istio-system.svc.cluster.local"},
224}},
225},
226}})
227}
228
229func TestAmbientSystemNamespaceNetworkChange(t *testing.T) {
230test.SetForTest(t, &features.EnableAmbientControllers, true)
231testNS := "test"
232systemNS := "istio-system"
233
234s, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{
235SystemNamespace: systemNS,
236NetworksWatcher: mesh.NewFixedNetworksWatcher(nil),
237})
238
239tracker := assert.NewTracker[string](t)
240
241s.namespaces.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) {
242tracker.Record(o.GetName())
243}))
244
245expectNetwork := func(t *testing.T, c *FakeController, network string) {
246t.Helper()
247retry.UntilSuccessOrFail(t, func() error {
248t.Helper()
249if c.networkFromSystemNamespace().String() != network {
250return fmt.Errorf("no network system notify")
251}
252podNames := sets.New[string]("pod1", "pod2")
253svcNames := sets.New[string]("svc1")
254addresses := c.ambientIndex.All()
255for _, addr := range addresses {
256wl := addr.GetWorkload()
257if wl != nil {
258if !podNames.Contains(wl.Name) {
259continue
260}
261if addr.GetWorkload().Network != network {
262return fmt.Errorf("no network workload notify")
263}
264}
265svc := addr.GetService()
266if svc != nil {
267if !svcNames.Contains(svc.Name) {
268continue
269}
270for _, saddr := range svc.GetAddresses() {
271if saddr.GetNetwork() != network {
272return fmt.Errorf("no network service notify")
273}
274}
275}
276}
277return nil
278}, retry.Timeout(time.Second*5))
279}
280
281pc := clienttest.NewWriter[*corev1.Pod](t, s.client)
282sc := clienttest.NewWriter[*corev1.Service](t, s.client)
283pod1 := generatePod("127.0.0.1", "pod1", testNS, "sa1", "node1", map[string]string{"app": "a"}, nil)
284pc.CreateOrUpdateStatus(pod1)
285fx.WaitOrFail(t, "xds")
286
287pod2 := generatePod("127.0.0.2", "pod2", testNS, "sa2", "node1", map[string]string{"app": "a"}, nil)
288pc.CreateOrUpdateStatus(pod2)
289fx.WaitOrFail(t, "xds")
290
291sc.CreateOrUpdate(generateService("svc1", testNS, map[string]string{}, // labels
292map[string]string{}, // annotations
293[]int32{80},
294map[string]string{"app": "a"}, // selector
295"10.0.0.1",
296))
297fx.WaitOrFail(t, "xds")
298
299createOrUpdateNamespace(t, s, testNS, "")
300createOrUpdateNamespace(t, s, systemNS, "")
301
302tracker.WaitOrdered(testNS, systemNS)
303
304t.Run("change namespace network to nw1", func(t *testing.T) {
305createOrUpdateNamespace(t, s, systemNS, "nw1")
306tracker.WaitOrdered(systemNS)
307expectNetwork(t, s, "nw1")
308})
309
310t.Run("change namespace network to nw2", func(t *testing.T) {
311createOrUpdateNamespace(t, s, systemNS, "nw2")
312tracker.WaitOrdered(systemNS)
313expectNetwork(t, s, "nw2")
314})
315
316t.Run("manually change namespace network to nw3, and update meshNetworks", func(t *testing.T) {
317s.setNetworkFromNamespace(&corev1.Namespace{
318ObjectMeta: metav1.ObjectMeta{
319Name: systemNS,
320Labels: map[string]string{
321label.TopologyNetwork.Name: "nw3",
322},
323},
324})
325createOrUpdateNamespace(t, s, systemNS, "nw3")
326tracker.WaitOrdered(systemNS)
327addMeshNetworksFromRegistryGateway(t, s, s.meshNetworksWatcher)
328expectNetwork(t, s, "nw3")
329})
330}
331
332func createOrUpdateNamespace(t *testing.T, c *FakeController, name, network string) {
333namespace := &corev1.Namespace{
334ObjectMeta: metav1.ObjectMeta{
335Name: name,
336Labels: map[string]string{
337label.TopologyNetwork.Name: network,
338},
339},
340}
341clienttest.Wrap(t, c.namespaces).CreateOrUpdate(namespace)
342}
343