istio
2362 строки · 91.2 Кб
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 serviceentry
16
17import (
18"fmt"
19"net"
20"reflect"
21"sort"
22"strings"
23"testing"
24"time"
25
26"istio.io/api/label"
27networking "istio.io/api/networking/v1alpha3"
28"istio.io/istio/pilot/pkg/config/memory"
29"istio.io/istio/pilot/pkg/model"
30"istio.io/istio/pilot/pkg/serviceregistry/util/xdsfake"
31"istio.io/istio/pkg/config"
32"istio.io/istio/pkg/config/constants"
33"istio.io/istio/pkg/config/host"
34"istio.io/istio/pkg/config/labels"
35"istio.io/istio/pkg/config/schema/collections"
36"istio.io/istio/pkg/config/schema/gvk"
37"istio.io/istio/pkg/maps"
38"istio.io/istio/pkg/ptr"
39"istio.io/istio/pkg/slices"
40"istio.io/istio/pkg/spiffe"
41"istio.io/istio/pkg/test"
42"istio.io/istio/pkg/test/util/assert"
43"istio.io/istio/pkg/test/util/retry"
44)
45
46func createConfigs(configs []*config.Config, store model.ConfigStore, t testing.TB) {
47t.Helper()
48for _, cfg := range configs {
49_, err := store.Create(*cfg)
50if err != nil && strings.Contains(err.Error(), "item already exists") {
51_, err := store.Update(*cfg)
52if err != nil {
53t.Fatalf("error occurred updating ServiceEntry config: %v", err)
54}
55} else if err != nil {
56t.Fatalf("error occurred creating ServiceEntry config: %v", err)
57}
58}
59}
60
61func callInstanceHandlers(instances []*model.WorkloadInstance, sd *Controller, ev model.Event, t testing.TB) {
62t.Helper()
63for _, instance := range instances {
64sd.WorkloadInstanceHandler(instance, ev)
65}
66}
67
68func deleteConfigs(configs []*config.Config, store model.ConfigStore, t testing.TB) {
69t.Helper()
70for _, cfg := range configs {
71err := store.Delete(cfg.GroupVersionKind, cfg.Name, cfg.Namespace, nil)
72if err != nil {
73t.Errorf("error occurred crearting ServiceEntry config: %v", err)
74}
75}
76}
77
78type Event = xdsfake.Event
79
80func initServiceDiscovery(t test.Failer) (model.ConfigStore, *Controller, *xdsfake.Updater) {
81return initServiceDiscoveryWithOpts(t, false)
82}
83
84// initServiceDiscoveryWithoutEvents initializes a test setup with no events. This avoids excessive attempts to push
85// EDS updates to a full queue
86func initServiceDiscoveryWithoutEvents(t test.Failer) (model.ConfigStore, *Controller) {
87store := memory.Make(collections.Pilot)
88configController := memory.NewController(store)
89
90stop := test.NewStop(t)
91go configController.Run(stop)
92fx := xdsfake.NewFakeXDS()
93go func() {
94for {
95select {
96case <-stop:
97return
98case <-fx.Events: // drain
99}
100}
101}()
102
103serviceController := NewController(configController, fx)
104return configController, serviceController
105}
106
107func initServiceDiscoveryWithOpts(t test.Failer, workloadOnly bool, opts ...Option) (model.ConfigStore, *Controller, *xdsfake.Updater) {
108store := memory.Make(collections.Pilot)
109configController := memory.NewSyncController(store)
110
111stop := test.NewStop(t)
112go configController.Run(stop)
113
114endpoints := model.NewEndpointIndex(model.DisabledCache{})
115delegate := model.NewEndpointIndexUpdater(endpoints)
116xdsUpdater := xdsfake.NewWithDelegate(delegate)
117
118istioStore := configController
119var controller *Controller
120if !workloadOnly {
121controller = NewController(configController, xdsUpdater, opts...)
122} else {
123controller = NewWorkloadEntryController(configController, xdsUpdater, opts...)
124}
125go controller.Run(stop)
126return istioStore, controller, xdsUpdater
127}
128
129func TestServiceDiscoveryServices(t *testing.T) {
130store, sd, fx := initServiceDiscovery(t)
131expectedServices := []*model.Service{
132makeService("*.istio.io", "httpDNSRR", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB),
133makeService("*.google.com", "httpDNS", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
134makeService("tcpstatic.com", "tcpStatic", "172.217.0.1", map[string]int{"tcp-444": 444}, true, model.ClientSideLB),
135}
136
137createConfigs([]*config.Config{httpDNS, httpDNSRR, tcpStatic}, store, t)
138
139expectEvents(t, fx,
140Event{Type: "xds full", ID: "*.google.com"},
141Event{Type: "xds full", ID: "*.istio.io"},
142Event{Type: "xds full", ID: "tcpstatic.com"},
143Event{Type: "service", ID: "*.google.com", Namespace: httpDNS.Namespace},
144Event{Type: "eds cache", ID: "*.google.com", Namespace: httpDNS.Namespace},
145Event{Type: "service", ID: "*.istio.io", Namespace: httpDNSRR.Namespace},
146Event{Type: "eds cache", ID: "*.istio.io", Namespace: httpDNSRR.Namespace},
147Event{Type: "service", ID: "tcpstatic.com", Namespace: tcpStatic.Namespace},
148Event{Type: "eds cache", ID: "tcpstatic.com", Namespace: tcpStatic.Namespace})
149services := sd.Services()
150sortServices(services)
151sortServices(expectedServices)
152if err := compare(t, services, expectedServices); err != nil {
153t.Error(err)
154}
155}
156
157func TestServiceDiscoveryGetService(t *testing.T) {
158hostname := "*.google.com"
159hostDNE := "does.not.exist.local"
160
161store, sd, fx := initServiceDiscovery(t)
162
163createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t)
164fx.WaitOrFail(t, "xds full")
165fx.WaitOrFail(t, "xds full")
166service := sd.GetService(host.Name(hostDNE))
167if service != nil {
168t.Errorf("GetService(%q) => should not exist, got %s", hostDNE, service.Hostname)
169}
170
171service = sd.GetService(host.Name(hostname))
172if service == nil {
173t.Fatalf("GetService(%q) => should exist", hostname)
174}
175if service.Hostname != host.Name(hostname) {
176t.Errorf("GetService(%q) => %q, want %q", hostname, service.Hostname, hostname)
177}
178}
179
180// TestServiceDiscoveryServiceUpdate test various add/update/delete events for ServiceEntry
181// nolint: lll
182func TestServiceDiscoveryServiceUpdate(t *testing.T) {
183store, sd, events := initServiceDiscovery(t)
184// httpStaticOverlayUpdated is the same as httpStaticOverlay but with an extra endpoint added to test updates
185httpStaticOverlayUpdated := func() *config.Config {
186c := httpStaticOverlay.DeepCopy()
187se := c.Spec.(*networking.ServiceEntry)
188se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{
189Address: "6.6.6.6",
190Labels: map[string]string{"other": "bar"},
191})
192return &c
193}()
194// httpStaticOverlayUpdatedInstance is the same as httpStaticOverlayUpdated but with an extra endpoint added that has the same address
195httpStaticOverlayUpdatedInstance := func() *config.Config {
196c := httpStaticOverlayUpdated.DeepCopy()
197se := c.Spec.(*networking.ServiceEntry)
198se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{
199Address: "6.6.6.6",
200Labels: map[string]string{"some-new-label": "bar"},
201})
202return &c
203}()
204
205// httpStaticOverlayUpdatedNop is the same as httpStaticOverlayUpdated but with a NOP change
206httpStaticOverlayUpdatedNop := func() *config.Config {
207return ptr.Of(httpStaticOverlayUpdated.DeepCopy())
208}()
209
210// httpStaticOverlayUpdatedNs is the same as httpStaticOverlay but with an extra endpoint and different namespace added to test updates
211httpStaticOverlayUpdatedNs := func() *config.Config {
212c := httpStaticOverlay.DeepCopy()
213c.Namespace = "other"
214se := c.Spec.(*networking.ServiceEntry)
215se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{
216Address: "7.7.7.7",
217Labels: map[string]string{"namespace": "bar"},
218})
219return &c
220}()
221
222// Setup the expected instances for `httpStatic`. This will be added/removed from as we add various configs
223baseInstances := []*model.ServiceInstance{
224makeInstance(httpStatic, "2.2.2.2", 7080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
225makeInstance(httpStatic, "2.2.2.2", 18080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS),
226makeInstance(httpStatic, "3.3.3.3", 1080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
227makeInstance(httpStatic, "3.3.3.3", 8080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS),
228makeInstance(httpStatic, "4.4.4.4", 1080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, PlainText),
229makeInstance(httpStatic, "4.4.4.4", 8080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"foo": "bar"}, PlainText),
230}
231
232t.Run("simple entry", func(t *testing.T) {
233// Create a SE, expect the base instances
234createConfigs([]*config.Config{httpStatic}, store, t)
235instances := baseInstances
236expectServiceInstances(t, sd, httpStatic, 0, instances)
237expectEvents(t, events,
238Event{Type: "service", ID: "*.google.com", Namespace: httpStatic.Namespace},
239Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStatic.Namespace},
240Event{Type: "xds full", ID: httpStatic.Spec.(*networking.ServiceEntry).Hosts[0]})
241})
242
243t.Run("add entry", func(t *testing.T) {
244// Create another SE for the same host, expect these instances to get added
245createConfigs([]*config.Config{httpStaticOverlay}, store, t)
246instances := append(baseInstances,
247makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText))
248expectServiceInstances(t, sd, httpStatic, 0, instances)
249expectEvents(t, events,
250Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
251Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
252Event{Type: "xds full", ID: httpStatic.Spec.(*networking.ServiceEntry).Hosts[0]})
253})
254
255t.Run("add endpoint", func(t *testing.T) {
256// Update the SE for the same host, expect these instances to get added
257createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
258instances := append(baseInstances,
259makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
260makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
261expectServiceInstances(t, sd, httpStatic, 0, instances)
262expectEvents(t, events, Event{Type: "eds", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace, EndpointCount: len(instances)})
263
264// Make a NOP change, expect that there are no changes
265createConfigs([]*config.Config{httpStaticOverlayUpdatedNop}, store, t)
266expectServiceInstances(t, sd, httpStaticOverlayUpdatedNop, 0, instances)
267// TODO this could trigger no changes
268expectEvents(t, events, Event{Type: "eds", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace, EndpointCount: len(instances)})
269})
270
271t.Run("overlapping address", func(t *testing.T) {
272// Add another SE with an additional endpoint with a matching address
273createConfigs([]*config.Config{httpStaticOverlayUpdatedInstance}, store, t)
274instances := append(baseInstances,
275makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
276makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText),
277makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"some-new-label": "bar"}, PlainText))
278expectServiceInstances(t, sd, httpStaticOverlayUpdatedInstance, 0, instances)
279proxyInstances := []model.ServiceTarget{
280makeTarget(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText),
281makeTarget(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"some-new-label": "bar"}, PlainText),
282}
283expectProxyTargets(t, sd, proxyInstances, "6.6.6.6")
284// TODO 45 is wrong
285expectEvents(t, events, Event{Type: "eds", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace, EndpointCount: len(instances)})
286
287// Remove the additional endpoint
288createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
289instances = append(baseInstances,
290makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
291makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
292expectServiceInstances(t, sd, httpStatic, 0, instances)
293proxyInstances = []model.ServiceTarget{
294makeTarget(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText),
295}
296expectProxyTargets(t, sd, proxyInstances, "6.6.6.6")
297expectEvents(t, events, Event{Type: "eds", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace, EndpointCount: len(instances)})
298})
299
300t.Run("update removes endpoint", func(t *testing.T) {
301// Update the SE for the same host to remove the endpoint
302createConfigs([]*config.Config{httpStaticOverlay}, store, t)
303instances := append(baseInstances,
304makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText))
305expectServiceInstances(t, sd, httpStaticOverlay, 0, instances)
306expectEvents(t, events,
307Event{Type: "eds", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace, EndpointCount: len(instances)})
308})
309
310t.Run("different namespace", func(t *testing.T) {
311// Update the SE for the same host in a different ns, expect these instances to get added
312createConfigs([]*config.Config{httpStaticOverlayUpdatedNs}, store, t)
313instances := []*model.ServiceInstance{
314makeInstance(httpStaticOverlayUpdatedNs, "5.5.5.5", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
315makeInstance(httpStaticOverlayUpdatedNs, "7.7.7.7", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"namespace": "bar"}, PlainText),
316}
317// This lookup is per-namespace, so we should only see the objects in the same namespace
318expectServiceInstances(t, sd, httpStaticOverlayUpdatedNs, 0, instances)
319// Expect a full push, as the Service has changed
320expectEvents(t, events,
321Event{Type: "service", ID: "*.google.com", Namespace: "other"},
322Event{Type: "eds cache", ID: "*.google.com", Namespace: "other"},
323Event{Type: "xds full", ID: httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Hosts[0]})
324})
325
326t.Run("delete entry", func(t *testing.T) {
327// Delete the additional SE in same namespace , expect it to get removed
328deleteConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
329expectServiceInstances(t, sd, httpStatic, 0, baseInstances)
330// Check the other namespace is untouched
331instances := []*model.ServiceInstance{
332makeInstance(httpStaticOverlayUpdatedNs, "5.5.5.5", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
333makeInstance(httpStaticOverlayUpdatedNs, "7.7.7.7", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"namespace": "bar"}, PlainText),
334}
335expectServiceInstances(t, sd, httpStaticOverlayUpdatedNs, 0, instances)
336// svcUpdate is not triggered since `httpStatic` is there and has instances, so we should
337// not delete the endpoints shards of "*.google.com". We xpect a full push as the service has changed.
338expectEvents(t, events,
339Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
340Event{Type: "xds full", ID: "*.google.com"},
341)
342
343// delete httpStatic, no "*.google.com" service exists now.
344deleteConfigs([]*config.Config{httpStatic}, store, t)
345// svcUpdate is triggered since "*.google.com" in same namespace is deleted and
346// we need to delete endpoint shards. We expect a full push as the service has changed.
347expectEvents(t, events,
348Event{Type: "service", ID: "*.google.com", Namespace: httpStatic.Namespace},
349Event{Type: "xds full", ID: "*.google.com"},
350)
351
352// add back httpStatic
353createConfigs([]*config.Config{httpStatic}, store, t)
354instances = baseInstances
355expectServiceInstances(t, sd, httpStatic, 0, instances)
356expectEvents(t, events,
357Event{Type: "service", ID: "*.google.com", Namespace: httpStatic.Namespace},
358Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStatic.Namespace},
359Event{Type: "xds full", ID: httpStatic.Spec.(*networking.ServiceEntry).Hosts[0]})
360
361// Add back the ServiceEntry, expect these instances to get added
362createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
363instances = append(baseInstances,
364makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
365makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
366expectServiceInstances(t, sd, httpStatic, 0, instances)
367// Service change, so we need a full push
368expectEvents(t, events,
369Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
370Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
371Event{Type: "xds full", ID: "*.google.com"})
372})
373
374t.Run("change target port", func(t *testing.T) {
375// Change the target port
376targetPortChanged := func() *config.Config {
377c := httpStaticOverlayUpdated.DeepCopy()
378c.Spec.(*networking.ServiceEntry).Ports[0].TargetPort = 33333
379return &c
380}()
381createConfigs([]*config.Config{targetPortChanged}, store, t)
382
383// Endpoint ports should be changed
384instances := append(baseInstances,
385makeInstance(httpStaticOverlay, "5.5.5.5", 33333, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
386makeInstance(httpStaticOverlay, "6.6.6.6", 33333, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
387expectServiceInstances(t, sd, targetPortChanged, 0, instances)
388
389// Expect a full push, as the target port has changed
390expectEvents(t, events,
391Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
392Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
393Event{Type: "xds full", ID: "*.google.com"})
394
395// Restore the target port
396createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
397
398// Endpoint ports should be changed
399instances = append(baseInstances,
400makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
401makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
402expectServiceInstances(t, sd, targetPortChanged, 0, instances)
403// Expect a full push, as the target port has changed
404expectEvents(t, events,
405Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
406Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
407Event{Type: "xds full", ID: "*.google.com"})
408})
409
410t.Run("change host", func(t *testing.T) {
411// same as httpStaticOverlayUpdated but with an additional host
412httpStaticHost := func() *config.Config {
413c := httpStaticOverlayUpdated.DeepCopy()
414se := c.Spec.(*networking.ServiceEntry)
415se.Hosts = append(se.Hosts, "other.com")
416return &c
417}()
418createConfigs([]*config.Config{httpStaticHost}, store, t)
419instances := append(baseInstances,
420makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
421makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText))
422// This is not applied, just to make makeInstance pick the right service.
423otherHost := func() *config.Config {
424c := httpStaticOverlayUpdated.DeepCopy()
425se := c.Spec.(*networking.ServiceEntry)
426se.Hosts = []string{"other.com"}
427return &c
428}()
429instances2 := []*model.ServiceInstance{
430makeInstance(otherHost, "5.5.5.5", 4567, httpStaticHost.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText),
431makeInstance(otherHost, "6.6.6.6", 4567, httpStaticHost.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText),
432}
433expectServiceInstances(t, sd, httpStaticHost, 0, instances, instances2)
434// Service change, so we need a full push
435expectEvents(t, events,
436Event{Type: "service", ID: "other.com", Namespace: httpStaticOverlayUpdated.Namespace},
437Event{Type: "eds cache", ID: "other.com", Namespace: httpStaticOverlayUpdated.Namespace},
438Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace},
439Event{Type: "xds full", ID: "other.com"}) // service added
440
441// restore this config and remove the added host.
442createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t)
443expectEvents(t, events,
444Event{Type: "service", ID: "other.com", Namespace: httpStatic.Namespace},
445Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStatic.Namespace},
446Event{Type: "xds full", ID: "other.com"}) // service deleted
447})
448
449t.Run("change dns endpoints", func(t *testing.T) {
450// Setup the expected instances for DNS. This will be added/removed from as we add various configs
451instances1 := []*model.ServiceInstance{
452makeInstance(tcpDNS, "lon.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0],
453nil, MTLS),
454makeInstance(tcpDNS, "in.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0],
455nil, MTLS),
456}
457
458// This is not applied, just to make makeInstance pick the right service.
459tcpDNSUpdated := func() *config.Config {
460c := tcpDNS.DeepCopy()
461se := c.Spec.(*networking.ServiceEntry)
462se.Endpoints = []*networking.WorkloadEntry{
463{
464Address: "lon.google.com",
465Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
466},
467}
468return &c
469}()
470
471instances2 := []*model.ServiceInstance{
472makeInstance(tcpDNS, "lon.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0],
473nil, MTLS),
474}
475
476createConfigs([]*config.Config{tcpDNS}, store, t)
477expectServiceInstances(t, sd, tcpDNS, 0, instances1)
478// Service change, so we need a full push
479expectEvents(t, events,
480Event{Type: "service", ID: "tcpdns.com", Namespace: tcpDNS.Namespace},
481Event{Type: "eds cache", ID: "tcpdns.com", Namespace: tcpDNS.Namespace},
482Event{Type: "xds full", ID: "tcpdns.com"}) // service added
483
484// now update the config
485createConfigs([]*config.Config{tcpDNSUpdated}, store, t)
486expectEvents(t, events,
487Event{Type: "xds full", ID: "tcpdns.com"},
488Event{Type: "eds cache", ID: "tcpdns.com"},
489) // service deleted
490expectServiceInstances(t, sd, tcpDNS, 0, instances2)
491})
492
493t.Run("change workload selector", func(t *testing.T) {
494// same as selector but with an additional host
495selector1 := func() *config.Config {
496c := httpStaticOverlay.DeepCopy()
497se := c.Spec.(*networking.ServiceEntry)
498se.Hosts = append(se.Hosts, "selector1.com")
499se.Endpoints = nil
500se.WorkloadSelector = &networking.WorkloadSelector{
501Labels: map[string]string{"app": "wle"},
502}
503return &c
504}()
505createConfigs([]*config.Config{selector1}, store, t)
506// Service change, so we need a full push
507expectEvents(t, events,
508Event{Type: "service", ID: "selector1.com", Namespace: httpStaticOverlay.Namespace},
509Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
510Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
511Event{Type: "xds full", ID: "*.google.com,selector1.com"}) // service added
512
513selector1Updated := func() *config.Config {
514c := selector1.DeepCopy()
515se := c.Spec.(*networking.ServiceEntry)
516se.WorkloadSelector = &networking.WorkloadSelector{
517Labels: map[string]string{"app": "wle1"},
518}
519return &c
520}()
521createConfigs([]*config.Config{selector1Updated}, store, t)
522expectEvents(t, events,
523Event{Type: "service", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
524Event{Type: "service", ID: "selector1.com", Namespace: httpStaticOverlay.Namespace},
525Event{Type: "eds cache", ID: "*.google.com", Namespace: httpStaticOverlay.Namespace},
526Event{Type: "xds full", ID: "*.google.com,selector1.com"}) // service updated
527})
528}
529
530func TestServiceDiscoveryWorkloadUpdate(t *testing.T) {
531store, sd, events := initServiceDiscovery(t)
532
533// Setup a couple workload entries for test. These will be selected by the `selector` SE
534wle := createWorkloadEntry("wl", selector.Name,
535&networking.WorkloadEntry{
536Address: "2.2.2.2",
537Labels: map[string]string{"app": "wle"},
538ServiceAccount: "default",
539})
540wle2 := createWorkloadEntry("wl2", selector.Name,
541&networking.WorkloadEntry{
542Address: "3.3.3.3",
543Labels: map[string]string{"app": "wle"},
544ServiceAccount: "default",
545})
546wle3 := createWorkloadEntry("wl3", selector.Name,
547&networking.WorkloadEntry{
548Address: "abc.def",
549Labels: map[string]string{"app": "wle"},
550ServiceAccount: "default",
551})
552dnsWle := createWorkloadEntry("dnswl", dnsSelector.Namespace,
553&networking.WorkloadEntry{
554Address: "4.4.4.4",
555Labels: map[string]string{"app": "dns-wle"},
556ServiceAccount: "default",
557})
558
559t.Run("service entry", func(t *testing.T) {
560// Add just the ServiceEntry with selector. We should see no instances
561createConfigs([]*config.Config{selector}, store, t)
562instances := []*model.ServiceInstance{}
563expectProxyInstances(t, sd, instances, "2.2.2.2")
564expectServiceInstances(t, sd, selector, 0, instances)
565expectEvents(t, events,
566Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
567Event{Type: "eds cache", ID: "selector.com", Namespace: selector.Namespace},
568Event{Type: "xds full", ID: "selector.com"})
569})
570
571t.Run("add workload", func(t *testing.T) {
572// Add a WLE, we expect this to update
573createConfigs([]*config.Config{wle}, store, t)
574
575instances := []*model.ServiceInstance{
576makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
577selector.Spec.(*networking.ServiceEntry).Ports[0],
578map[string]string{"app": "wle"}, "default"),
579makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
580selector.Spec.(*networking.ServiceEntry).Ports[1],
581map[string]string{"app": "wle"}, "default"),
582}
583for _, i := range instances {
584i.Endpoint.WorkloadName = "wl"
585i.Endpoint.Namespace = selector.Name
586}
587expectProxyInstances(t, sd, instances, "2.2.2.2")
588expectServiceInstances(t, sd, selector, 0, instances)
589expectEvents(t, events,
590Event{Type: "proxy", ID: "2.2.2.2"},
591Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2},
592)
593})
594
595t.Run("update service entry host", func(t *testing.T) {
596updated := func() *config.Config {
597d := selector.DeepCopy()
598se := d.Spec.(*networking.ServiceEntry)
599se.Hosts = []string{"updated.com"}
600return &d
601}()
602
603instances := []*model.ServiceInstance{
604makeInstanceWithServiceAccount(updated, "2.2.2.2", 444,
605updated.Spec.(*networking.ServiceEntry).Ports[0],
606map[string]string{"app": "wle"}, "default"),
607makeInstanceWithServiceAccount(updated, "2.2.2.2", 445,
608updated.Spec.(*networking.ServiceEntry).Ports[1],
609map[string]string{"app": "wle"}, "default"),
610}
611for _, i := range instances {
612i.Endpoint.WorkloadName = "wl"
613i.Endpoint.Namespace = updated.Name
614}
615
616createConfigs([]*config.Config{updated}, store, t)
617expectProxyInstances(t, sd, instances, "2.2.2.2")
618expectServiceInstances(t, sd, selector, 0, []*model.ServiceInstance{})
619expectServiceInstances(t, sd, updated, 0, instances)
620expectEvents(t, events,
621Event{Type: "service", ID: "updated.com", Namespace: selector.Namespace},
622Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
623Event{Type: "eds cache", ID: "updated.com", Namespace: selector.Namespace},
624Event{Type: "xds full", ID: "selector.com,updated.com"},
625)
626})
627
628t.Run("restore service entry host", func(t *testing.T) {
629instances := []*model.ServiceInstance{
630makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
631selector.Spec.(*networking.ServiceEntry).Ports[0],
632map[string]string{"app": "wle"}, "default"),
633makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
634selector.Spec.(*networking.ServiceEntry).Ports[1],
635map[string]string{"app": "wle"}, "default"),
636}
637for _, i := range instances {
638i.Endpoint.WorkloadName = "wl"
639i.Endpoint.Namespace = selector.Name
640}
641updated := func() *config.Config {
642d := selector.DeepCopy()
643se := d.Spec.(*networking.ServiceEntry)
644se.Hosts = []string{"updated.com"}
645return &d
646}()
647
648createConfigs([]*config.Config{selector}, store, t)
649expectProxyInstances(t, sd, instances, "2.2.2.2")
650expectServiceInstances(t, sd, selector, 0, instances)
651expectServiceInstances(t, sd, updated, 0, []*model.ServiceInstance{})
652expectEvents(t, events,
653Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
654Event{Type: "service", ID: "updated.com", Namespace: selector.Namespace},
655Event{Type: "eds cache", ID: "selector.com", Namespace: selector.Namespace},
656Event{Type: "xds full", ID: "selector.com,updated.com"},
657)
658})
659
660t.Run("add dns service entry", func(t *testing.T) {
661// Add just the ServiceEntry with selector. We should see no instances
662createConfigs([]*config.Config{dnsSelector}, store, t)
663instances := []*model.ServiceInstance{}
664expectProxyInstances(t, sd, instances, "4.4.4.4")
665expectServiceInstances(t, sd, dnsSelector, 0, instances)
666expectEvents(t, events,
667Event{Type: "service", ID: "dns.selector.com", Namespace: dnsSelector.Namespace},
668Event{Type: "eds cache", ID: "dns.selector.com", Namespace: dnsSelector.Namespace},
669Event{Type: "xds full", ID: "dns.selector.com"})
670})
671
672t.Run("add dns workload", func(t *testing.T) {
673// Add a WLE, we expect this to update
674createConfigs([]*config.Config{dnsWle}, store, t)
675instances := []*model.ServiceInstance{
676makeInstanceWithServiceAccount(dnsSelector, "4.4.4.4", 444,
677selector.Spec.(*networking.ServiceEntry).Ports[0],
678map[string]string{"app": "dns-wle"}, "default"),
679makeInstanceWithServiceAccount(dnsSelector, "4.4.4.4", 445,
680selector.Spec.(*networking.ServiceEntry).Ports[1],
681map[string]string{"app": "dns-wle"}, "default"),
682}
683for _, i := range instances {
684i.Endpoint.WorkloadName = "dnswl"
685i.Endpoint.Namespace = dnsSelector.Namespace
686}
687expectProxyInstances(t, sd, instances, "4.4.4.4")
688expectServiceInstances(t, sd, dnsSelector, 0, instances)
689expectEvents(t, events,
690Event{Type: "eds cache", ID: "dns.selector.com", Namespace: dnsSelector.Namespace},
691Event{Type: "xds full", ID: "dns.selector.com"})
692})
693
694t.Run("another workload", func(t *testing.T) {
695// Add a different WLE
696createConfigs([]*config.Config{wle2}, store, t)
697instances := []*model.ServiceInstance{
698makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
699selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
700makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
701selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
702}
703for _, i := range instances {
704i.Endpoint.WorkloadName = "wl"
705i.Endpoint.Namespace = selector.Name
706}
707expectProxyInstances(t, sd, instances, "2.2.2.2")
708instances = append(instances,
709makeInstanceWithServiceAccount(selector, "3.3.3.3", 444,
710selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
711makeInstanceWithServiceAccount(selector, "3.3.3.3", 445,
712selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"))
713for _, i := range instances[2:] {
714i.Endpoint.WorkloadName = "wl2"
715i.Endpoint.Namespace = selector.Name
716}
717expectServiceInstances(t, sd, selector, 0, instances)
718expectEvents(t, events,
719Event{Type: "proxy", ID: "3.3.3.3"},
720Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 4},
721)
722})
723
724t.Run("ignore host workload", func(t *testing.T) {
725// Add a WLE with host address. Should be ignored by static service entry.
726createConfigs([]*config.Config{wle3}, store, t)
727instances := []*model.ServiceInstance{
728makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
729selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
730makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
731selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
732}
733for _, i := range instances {
734i.Endpoint.WorkloadName = "wl"
735i.Endpoint.Namespace = selector.Name
736}
737expectProxyInstances(t, sd, instances, "2.2.2.2")
738instances = append(instances,
739makeInstanceWithServiceAccount(selector, "3.3.3.3", 444,
740selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
741makeInstanceWithServiceAccount(selector, "3.3.3.3", 445,
742selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"))
743for _, i := range instances[2:] {
744i.Endpoint.WorkloadName = "wl2"
745i.Endpoint.Namespace = selector.Name
746}
747expectServiceInstances(t, sd, selector, 0, instances)
748expectEvents(t, events,
749Event{Type: "proxy", ID: "abc.def"},
750)
751})
752
753t.Run("deletion", func(t *testing.T) {
754// Delete the configs, it should be gone
755deleteConfigs([]*config.Config{wle2}, store, t)
756instances := []*model.ServiceInstance{
757makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
758selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
759makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
760selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
761}
762for _, i := range instances {
763i.Endpoint.WorkloadName = "wl"
764i.Endpoint.Namespace = selector.Name
765}
766expectProxyInstances(t, sd, instances, "2.2.2.2")
767expectServiceInstances(t, sd, selector, 0, instances)
768expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2})
769
770// Delete the other config
771deleteConfigs([]*config.Config{wle}, store, t)
772instances = []*model.ServiceInstance{}
773expectServiceInstances(t, sd, selector, 0, instances)
774expectProxyInstances(t, sd, instances, "2.2.2.2")
775expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 0})
776
777// Add the config back
778createConfigs([]*config.Config{wle}, store, t)
779instances = []*model.ServiceInstance{
780makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
781selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
782makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
783selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
784}
785for _, i := range instances {
786i.Endpoint.WorkloadName = "wl"
787i.Endpoint.Namespace = selector.Name
788}
789expectProxyInstances(t, sd, instances, "2.2.2.2")
790expectServiceInstances(t, sd, selector, 0, instances)
791expectEvents(t, events,
792Event{Type: "proxy", ID: "2.2.2.2"},
793Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2},
794)
795})
796
797t.Run("update", func(t *testing.T) {
798updated := func() *config.Config {
799d := wle.DeepCopy()
800we := d.Spec.(*networking.WorkloadEntry)
801we.Address = "9.9.9.9"
802return &d
803}()
804// Update the configs
805createConfigs([]*config.Config{updated}, store, t)
806instances := []*model.ServiceInstance{
807makeInstanceWithServiceAccount(selector, "9.9.9.9", 444,
808selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
809makeInstanceWithServiceAccount(selector, "9.9.9.9", 445,
810selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
811}
812for _, i := range instances {
813i.Endpoint.WorkloadName = "wl"
814i.Endpoint.Namespace = selector.Name
815}
816// Old IP is gone
817expectProxyInstances(t, sd, nil, "2.2.2.2")
818expectProxyInstances(t, sd, instances, "9.9.9.9")
819expectServiceInstances(t, sd, selector, 0, instances)
820expectEvents(t, events,
821Event{Type: "proxy", ID: "9.9.9.9"},
822Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2},
823)
824})
825
826t.Run("cleanup", func(t *testing.T) {
827deleteConfigs([]*config.Config{wle, selector, dnsSelector, dnsWle, wle3}, store, t)
828assertControllerEmpty(t, sd)
829})
830}
831
832func assertControllerEmpty(t *testing.T, sd *Controller) {
833assert.Equal(t, len(sd.services.servicesBySE), 0)
834assert.Equal(t, len(sd.serviceInstances.ip2instance), 0)
835assert.Equal(t, len(sd.serviceInstances.instances), 0)
836assert.Equal(t, len(sd.serviceInstances.instancesBySE), 0)
837assert.Equal(t, len(sd.serviceInstances.instancesByHostAndPort), 0)
838assert.Equal(t, sd.workloadInstances.Empty(), true)
839}
840
841func TestServiceDiscoveryWorkloadChangeLabel(t *testing.T) {
842store, sd, events := initServiceDiscovery(t)
843
844wle := createWorkloadEntry("wl", selector.Name,
845&networking.WorkloadEntry{
846Address: "2.2.2.2",
847Labels: map[string]string{"app": "wle"},
848ServiceAccount: "default",
849})
850
851wle2 := createWorkloadEntry("wl", selector.Name,
852&networking.WorkloadEntry{
853Address: "2.2.2.2",
854Labels: map[string]string{"app": "wle2"},
855ServiceAccount: "default",
856})
857wle3 := createWorkloadEntry("wl3", selector.Name,
858&networking.WorkloadEntry{
859Address: "3.3.3.3",
860Labels: map[string]string{"app": "wle"},
861ServiceAccount: "default",
862})
863
864t.Run("service entry", func(t *testing.T) {
865// Add just the ServiceEntry with selector. We should see no instances
866createConfigs([]*config.Config{selector}, store, t)
867instances := []*model.ServiceInstance{}
868expectProxyInstances(t, sd, instances, "2.2.2.2")
869expectServiceInstances(t, sd, selector, 0, instances)
870expectEvents(t, events,
871Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
872Event{Type: "eds cache", ID: "selector.com", Namespace: selector.Namespace},
873Event{Type: "xds full", ID: "selector.com"})
874})
875
876t.Run("change label removing all", func(t *testing.T) {
877// Add a WLE, we expect this to update
878createConfigs([]*config.Config{wle}, store, t)
879instances := []*model.ServiceInstance{
880makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
881selector.Spec.(*networking.ServiceEntry).Ports[0],
882map[string]string{"app": "wle"}, "default"),
883makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
884selector.Spec.(*networking.ServiceEntry).Ports[1],
885map[string]string{"app": "wle"}, "default"),
886}
887for _, i := range instances {
888i.Endpoint.WorkloadName = "wl"
889i.Endpoint.Namespace = selector.Name
890}
891expectProxyInstances(t, sd, instances, "2.2.2.2")
892expectServiceInstances(t, sd, selector, 0, instances)
893expectEvents(t, events,
894Event{Type: "proxy", ID: "2.2.2.2"},
895Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2},
896)
897
898createConfigs([]*config.Config{wle2}, store, t)
899instances = []*model.ServiceInstance{}
900expectServiceInstances(t, sd, selector, 0, instances)
901expectProxyInstances(t, sd, instances, "2.2.2.2")
902expectEvents(t, events,
903Event{Type: "proxy", ID: "2.2.2.2"},
904Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 0})
905})
906
907t.Run("change label removing one", func(t *testing.T) {
908// Add a WLE, we expect this to update
909createConfigs([]*config.Config{wle}, store, t)
910expectEvents(t, events,
911Event{Type: "proxy", ID: "2.2.2.2"},
912Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2},
913)
914// add a wle, expect this to be an add
915createConfigs([]*config.Config{wle3}, store, t)
916instances := []*model.ServiceInstance{
917makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
918selector.Spec.(*networking.ServiceEntry).Ports[0],
919map[string]string{"app": "wle"}, "default"),
920makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
921selector.Spec.(*networking.ServiceEntry).Ports[1],
922map[string]string{"app": "wle"}, "default"),
923makeInstanceWithServiceAccount(selector, "3.3.3.3", 444,
924selector.Spec.(*networking.ServiceEntry).Ports[0],
925map[string]string{"app": "wle"}, "default"),
926makeInstanceWithServiceAccount(selector, "3.3.3.3", 445,
927selector.Spec.(*networking.ServiceEntry).Ports[1],
928map[string]string{"app": "wle"}, "default"),
929}
930for _, i := range instances[:2] {
931i.Endpoint.WorkloadName = "wl"
932i.Endpoint.Namespace = selector.Name
933}
934for _, i := range instances[2:] {
935i.Endpoint.WorkloadName = "wl3"
936i.Endpoint.Namespace = selector.Name
937}
938expectProxyInstances(t, sd, instances[:2], "2.2.2.2")
939expectProxyInstances(t, sd, instances[2:], "3.3.3.3")
940expectServiceInstances(t, sd, selector, 0, instances)
941expectEvents(t, events,
942Event{Type: "proxy", ID: "3.3.3.3"},
943Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 4},
944)
945
946createConfigs([]*config.Config{wle2}, store, t)
947instances = []*model.ServiceInstance{
948makeInstanceWithServiceAccount(selector, "3.3.3.3", 444,
949selector.Spec.(*networking.ServiceEntry).Ports[0],
950map[string]string{"app": "wle"}, "default"),
951makeInstanceWithServiceAccount(selector, "3.3.3.3", 445,
952selector.Spec.(*networking.ServiceEntry).Ports[1],
953map[string]string{"app": "wle"}, "default"),
954}
955for _, i := range instances {
956i.Endpoint.WorkloadName = "wl3"
957i.Endpoint.Namespace = selector.Name
958}
959expectServiceInstances(t, sd, selector, 0, instances)
960expectProxyInstances(t, sd, instances, "3.3.3.3")
961expectEvents(t, events,
962Event{Type: "proxy", ID: "2.2.2.2"},
963Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2})
964})
965}
966
967func TestWorkloadInstanceFullPush(t *testing.T) {
968store, sd, events := initServiceDiscovery(t)
969
970// Setup a WorkloadEntry with selector the same as ServiceEntry
971wle := createWorkloadEntry("wl", selectorDNS.Name,
972&networking.WorkloadEntry{
973Address: "postman-echo.com",
974Labels: map[string]string{"app": "wle"},
975ServiceAccount: "default",
976})
977
978fi1 := &model.WorkloadInstance{
979Name: "additional-name",
980Namespace: selectorDNS.Name,
981Endpoint: &model.IstioEndpoint{
982Address: "4.4.4.4",
983Labels: map[string]string{"app": "wle"},
984ServiceAccount: spiffe.MustGenSpiffeURI(selectorDNS.Name, "default"),
985TLSMode: model.IstioMutualTLSModeLabel,
986},
987}
988
989fi2 := &model.WorkloadInstance{
990Name: "another-name",
991Namespace: selectorDNS.Namespace,
992Endpoint: &model.IstioEndpoint{
993Address: "2.2.2.2",
994Labels: map[string]string{"app": "wle"},
995ServiceAccount: spiffe.MustGenSpiffeURI(selectorDNS.Name, "default"),
996TLSMode: model.IstioMutualTLSModeLabel,
997},
998}
999
1000t.Run("service entry", func(t *testing.T) {
1001// Add just the ServiceEntry with selector. We should see no instances
1002createConfigs([]*config.Config{selectorDNS}, store, t)
1003instances := []*model.ServiceInstance{}
1004expectProxyInstances(t, sd, instances, "4.4.4.4")
1005expectServiceInstances(t, sd, selectorDNS, 0, instances)
1006expectEvents(t, events,
1007Event{Type: "service", ID: "selector.com", Namespace: selectorDNS.Namespace},
1008Event{Type: "eds cache", ID: "selector.com", Namespace: selectorDNS.Namespace},
1009Event{Type: "xds full", ID: "selector.com"})
1010})
1011
1012t.Run("add workload", func(t *testing.T) {
1013// Add a WLE, we expect this to update
1014createConfigs([]*config.Config{wle}, store, t)
1015
1016instances := []*model.ServiceInstance{
1017makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 444,
1018selectorDNS.Spec.(*networking.ServiceEntry).Ports[0],
1019map[string]string{"app": "wle"}, "default"),
1020makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 445,
1021selectorDNS.Spec.(*networking.ServiceEntry).Ports[1],
1022map[string]string{"app": "wle"}, "default"),
1023}
1024for _, i := range instances {
1025i.Endpoint.WorkloadName = "wl"
1026i.Endpoint.Namespace = selectorDNS.Name
1027}
1028expectProxyInstances(t, sd, instances, "postman-echo.com")
1029expectServiceInstances(t, sd, selectorDNS, 0, instances)
1030expectEvents(t, events,
1031Event{Type: "eds cache", ID: "selector.com", Namespace: selectorDNS.Namespace},
1032Event{Type: "xds full", ID: "selector.com"},
1033)
1034})
1035
1036t.Run("full push for new instance", func(t *testing.T) {
1037callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventAdd, t)
1038instances := []*model.ServiceInstance{
1039makeInstanceWithServiceAccount(selectorDNS, "4.4.4.4", 444,
1040selectorDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1041makeInstanceWithServiceAccount(selectorDNS, "4.4.4.4", 445,
1042selectorDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1043makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 444,
1044selectorDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1045makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 445,
1046selectorDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1047}
1048
1049for _, i := range instances[2:] {
1050i.Endpoint.WorkloadName = "wl"
1051i.Endpoint.Namespace = selectorDNS.Name
1052}
1053
1054expectProxyInstances(t, sd, instances[:2], "4.4.4.4")
1055expectProxyInstances(t, sd, instances[2:], "postman-echo.com")
1056expectServiceInstances(t, sd, selectorDNS, 0, instances)
1057expectEvents(t, events,
1058Event{Type: "eds", ID: "selector.com", Namespace: selectorDNS.Namespace, EndpointCount: len(instances)},
1059Event{Type: "xds full", ID: "selector.com"})
1060})
1061
1062t.Run("full push for another new workload instance", func(t *testing.T) {
1063callInstanceHandlers([]*model.WorkloadInstance{fi2}, sd, model.EventAdd, t)
1064expectEvents(t, events,
1065Event{Type: "eds", ID: "selector.com", Namespace: selectorDNS.Namespace, EndpointCount: 6},
1066Event{Type: "xds full", ID: "selector.com"})
1067})
1068
1069t.Run("full push on delete workload instance", func(t *testing.T) {
1070callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventDelete, t)
1071instances := []*model.ServiceInstance{
1072makeInstanceWithServiceAccount(selectorDNS, "2.2.2.2", 444,
1073selectorDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1074makeInstanceWithServiceAccount(selectorDNS, "2.2.2.2", 445,
1075selectorDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1076makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 444,
1077selectorDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1078makeInstanceWithServiceAccount(selectorDNS, "postman-echo.com", 445,
1079selectorDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1080}
1081
1082for _, i := range instances[2:] {
1083i.Endpoint.WorkloadName = "wl"
1084i.Endpoint.Namespace = selectorDNS.Name
1085}
1086
1087expectProxyInstances(t, sd, instances[:2], "2.2.2.2")
1088expectProxyInstances(t, sd, instances[2:], "postman-echo.com")
1089expectServiceInstances(t, sd, selectorDNS, 0, instances)
1090
1091expectEvents(t, events,
1092Event{Type: "eds", ID: "selector.com", Namespace: selectorDNS.Namespace, EndpointCount: len(instances)},
1093Event{Type: "xds full", ID: "selector.com"})
1094})
1095}
1096
1097func TestServiceDiscoveryWorkloadInstance(t *testing.T) {
1098store, sd, events := initServiceDiscovery(t)
1099
1100// Setup a couple of workload instances for test. These will be selected by the `selector` SE
1101fi1 := &model.WorkloadInstance{
1102Name: selector.Name,
1103Namespace: selector.Namespace,
1104Endpoint: &model.IstioEndpoint{
1105Address: "2.2.2.2",
1106Labels: map[string]string{"app": "wle"},
1107ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"),
1108TLSMode: model.IstioMutualTLSModeLabel,
1109},
1110}
1111
1112fi2 := &model.WorkloadInstance{
1113Name: "some-other-name",
1114Namespace: selector.Namespace,
1115Endpoint: &model.IstioEndpoint{
1116Address: "3.3.3.3",
1117Labels: map[string]string{"app": "wle"},
1118ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"),
1119TLSMode: model.IstioMutualTLSModeLabel,
1120},
1121}
1122
1123fi3 := &model.WorkloadInstance{
1124Name: "another-name",
1125Namespace: dnsSelector.Namespace,
1126Endpoint: &model.IstioEndpoint{
1127Address: "2.2.2.2",
1128Labels: map[string]string{"app": "dns-wle"},
1129ServiceAccount: spiffe.MustGenSpiffeURI(dnsSelector.Name, "default"),
1130TLSMode: model.IstioMutualTLSModeLabel,
1131},
1132}
1133
1134t.Run("service entry", func(t *testing.T) {
1135// Add just the ServiceEntry with selector. We should see no instances
1136createConfigs([]*config.Config{selector}, store, t)
1137instances := []*model.ServiceInstance{}
1138expectProxyInstances(t, sd, instances, "2.2.2.2")
1139expectServiceInstances(t, sd, selector, 0, instances)
1140expectEvents(t, events,
1141Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
1142Event{Type: "eds cache", ID: "selector.com", Namespace: selector.Namespace},
1143Event{Type: "xds full", ID: "selector.com"})
1144})
1145
1146t.Run("add another service entry", func(t *testing.T) {
1147createConfigs([]*config.Config{dnsSelector}, store, t)
1148instances := []*model.ServiceInstance{}
1149expectProxyInstances(t, sd, instances, "2.2.2.2")
1150expectServiceInstances(t, sd, dnsSelector, 0, instances)
1151expectEvents(t, events,
1152Event{Type: "service", ID: "dns.selector.com", Namespace: dnsSelector.Namespace},
1153Event{Type: "eds cache", ID: "dns.selector.com", Namespace: dnsSelector.Namespace},
1154Event{Type: "xds full", ID: "dns.selector.com"})
1155})
1156
1157t.Run("add workload instance", func(t *testing.T) {
1158// Add a workload instance, we expect this to update
1159callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventAdd, t)
1160instances := []*model.ServiceInstance{
1161makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
1162selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1163makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
1164selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1165}
1166expectProxyInstances(t, sd, instances, "2.2.2.2")
1167expectServiceInstances(t, sd, selector, 0, instances)
1168expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2})
1169})
1170
1171t.Run("another workload instance", func(t *testing.T) {
1172// Add a different instance
1173callInstanceHandlers([]*model.WorkloadInstance{fi2}, sd, model.EventAdd, t)
1174instances := []*model.ServiceInstance{
1175makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
1176selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1177makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
1178selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1179}
1180expectProxyInstances(t, sd, instances, "2.2.2.2")
1181instances = append(instances,
1182makeInstanceWithServiceAccount(selector, "3.3.3.3", 444,
1183selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1184makeInstanceWithServiceAccount(selector, "3.3.3.3", 445,
1185selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"))
1186expectServiceInstances(t, sd, selector, 0, instances)
1187expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 4})
1188})
1189
1190t.Run("delete workload instance", func(t *testing.T) {
1191// Delete the instances, it should be gone
1192callInstanceHandlers([]*model.WorkloadInstance{fi2}, sd, model.EventDelete, t)
1193instances := []*model.ServiceInstance{
1194makeInstanceWithServiceAccount(selector, "2.2.2.2", 444,
1195selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1196makeInstanceWithServiceAccount(selector, "2.2.2.2", 445,
1197selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1198}
1199expectProxyInstances(t, sd, instances, "2.2.2.2")
1200expectServiceInstances(t, sd, selector, 0, instances)
1201expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 2})
1202
1203key := instancesKey{namespace: selector.Namespace, hostname: "selector.com"}
1204namespacedName := selector.NamespacedName()
1205if len(sd.serviceInstances.ip2instance) != 1 {
1206t.Fatalf("service instances store `ip2instance` memory leak, expect 1, got %d", len(sd.serviceInstances.ip2instance))
1207}
1208if len(sd.serviceInstances.instances[key]) != 1 {
1209t.Fatalf("service instances store `instances` memory leak, expect 1, got %d", len(sd.serviceInstances.instances[key]))
1210}
1211if len(sd.serviceInstances.instancesBySE[namespacedName]) != 1 {
1212t.Fatalf("service instances store `instancesBySE` memory leak, expect 1, got %d", len(sd.serviceInstances.instancesBySE[namespacedName]))
1213}
1214
1215// The following sections mimic this scenario:
1216// f1 starts terminating, f3 picks up the IP, f3 delete event (pod
1217// not ready yet) comes before f1
1218//
1219// Delete f3 event
1220callInstanceHandlers([]*model.WorkloadInstance{fi3}, sd, model.EventDelete, t)
1221expectProxyInstances(t, sd, instances, "2.2.2.2")
1222expectServiceInstances(t, sd, selector, 0, instances)
1223
1224// Delete f1 event
1225callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventDelete, t)
1226instances = []*model.ServiceInstance{}
1227expectProxyInstances(t, sd, instances, "2.2.2.2")
1228expectServiceInstances(t, sd, selector, 0, instances)
1229expectEvents(t, events, Event{Type: "eds", ID: "selector.com", Namespace: selector.Namespace, EndpointCount: 0})
1230
1231if len(sd.serviceInstances.ip2instance) != 0 {
1232t.Fatalf("service instances store `ip2instance` memory leak, expect 0, got %d", len(sd.serviceInstances.ip2instance))
1233}
1234if len(sd.serviceInstances.instances[key]) != 0 {
1235t.Fatalf("service instances store `instances` memory leak, expect 0, got %d", len(sd.serviceInstances.instances[key]))
1236}
1237if len(sd.serviceInstances.instancesBySE[namespacedName]) != 0 {
1238t.Fatalf("service instances store `instancesBySE` memory leak, expect 0, got %d", len(sd.serviceInstances.instancesBySE[namespacedName]))
1239}
1240
1241// Add f3 event
1242callInstanceHandlers([]*model.WorkloadInstance{fi3}, sd, model.EventAdd, t)
1243instances = []*model.ServiceInstance{
1244makeInstanceWithServiceAccount(dnsSelector, "2.2.2.2", 444,
1245dnsSelector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "dns-wle"}, "default"),
1246makeInstanceWithServiceAccount(dnsSelector, "2.2.2.2", 445,
1247dnsSelector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "dns-wle"}, "default"),
1248}
1249expectProxyInstances(t, sd, instances, "2.2.2.2")
1250expectServiceInstances(t, sd, dnsSelector, 0, instances)
1251expectEvents(t, events, Event{Type: "eds", ID: "dns.selector.com", Namespace: dnsSelector.Namespace, EndpointCount: 2})
1252})
1253}
1254
1255func TestServiceDiscoveryWorkloadInstanceChangeLabel(t *testing.T) {
1256store, sd, events := initServiceDiscovery(t)
1257
1258type expectedProxyInstances struct {
1259instancesWithSA []*model.ServiceInstance
1260address string
1261}
1262
1263type testWorkloadInstance struct {
1264name string
1265namespace string
1266address string
1267labels map[string]string
1268serviceAccount string
1269tlsmode string
1270expectedProxyInstances []expectedProxyInstances
1271}
1272
1273t.Run("service entry", func(t *testing.T) {
1274// Add just the ServiceEntry with selector. We should see no instances
1275createConfigs([]*config.Config{selector}, store, t)
1276instances := []*model.ServiceInstance{}
1277expectProxyInstances(t, sd, instances, "2.2.2.2")
1278expectServiceInstances(t, sd, selector, 0, instances)
1279expectEvents(t, events,
1280Event{Type: "service", ID: "selector.com", Namespace: selector.Namespace},
1281Event{Type: "eds cache", ID: "selector.com", Namespace: selector.Namespace},
1282Event{Type: "xds full"})
1283})
1284
1285cases := []struct {
1286name string
1287instances []testWorkloadInstance
1288}{
1289{
1290name: "change label removing all",
1291instances: []testWorkloadInstance{
1292{
1293name: selector.Name,
1294namespace: selector.Namespace,
1295address: "2.2.2.2",
1296labels: map[string]string{"app": "wle"},
1297serviceAccount: "default",
1298tlsmode: model.IstioMutualTLSModeLabel,
1299expectedProxyInstances: []expectedProxyInstances{
1300{
1301instancesWithSA: []*model.ServiceInstance{
1302makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1303makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1304},
1305address: "2.2.2.2",
1306},
1307},
1308},
1309{
1310name: selector.Name,
1311namespace: selector.Namespace,
1312address: "2.2.2.2",
1313labels: map[string]string{"app": "wle2"},
1314serviceAccount: "default",
1315tlsmode: model.IstioMutualTLSModeLabel,
1316expectedProxyInstances: []expectedProxyInstances{
1317{
1318instancesWithSA: []*model.ServiceInstance{}, // The instance labels don't match the se anymore, so adding this wi removes 2 instances
1319address: "2.2.2.2",
1320},
1321},
1322},
1323},
1324},
1325{
1326name: "change label removing all",
1327instances: []testWorkloadInstance{
1328{
1329name: selector.Name,
1330namespace: selector.Namespace,
1331address: "2.2.2.2",
1332labels: map[string]string{"app": "wle"},
1333serviceAccount: "default",
1334tlsmode: model.IstioMutualTLSModeLabel,
1335expectedProxyInstances: []expectedProxyInstances{
1336{
1337instancesWithSA: []*model.ServiceInstance{
1338makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1339makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1340},
1341address: "2.2.2.2",
1342},
1343},
1344},
1345{
1346name: "another-name",
1347namespace: selector.Namespace,
1348address: "3.3.3.3",
1349labels: map[string]string{"app": "wle"},
1350serviceAccount: "default",
1351tlsmode: model.IstioMutualTLSModeLabel,
1352expectedProxyInstances: []expectedProxyInstances{
1353{
1354instancesWithSA: []*model.ServiceInstance{
1355makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1356makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1357},
1358address: "2.2.2.2",
1359},
1360{
1361instancesWithSA: []*model.ServiceInstance{
1362makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1363makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1364},
1365address: "3.3.3.3",
1366},
1367},
1368},
1369{
1370name: selector.Name,
1371namespace: selector.Namespace,
1372address: "2.2.2.2",
1373labels: map[string]string{"app": "wle2"},
1374serviceAccount: "default",
1375tlsmode: model.IstioMutualTLSModeLabel,
1376expectedProxyInstances: []expectedProxyInstances{
1377{
1378instancesWithSA: []*model.ServiceInstance{
1379makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"),
1380makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"),
1381},
1382address: "3.3.3.3",
1383},
1384},
1385},
1386},
1387},
1388}
1389
1390for _, testCase := range cases {
1391t.Run(testCase.name, func(t *testing.T) {
1392for _, instance := range testCase.instances {
1393
1394wi := &model.WorkloadInstance{
1395Name: instance.name,
1396Namespace: instance.namespace,
1397Endpoint: &model.IstioEndpoint{
1398Address: instance.address,
1399Labels: instance.labels,
1400ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, instance.serviceAccount),
1401TLSMode: instance.tlsmode,
1402},
1403}
1404
1405callInstanceHandlers([]*model.WorkloadInstance{wi}, sd, model.EventAdd, t)
1406
1407totalInstances := []*model.ServiceInstance{}
1408for _, expectedProxyInstance := range instance.expectedProxyInstances {
1409expectProxyInstances(t, sd, expectedProxyInstance.instancesWithSA, expectedProxyInstance.address)
1410totalInstances = append(totalInstances, expectedProxyInstance.instancesWithSA...)
1411}
1412
1413expectServiceInstances(t, sd, selector, 0, totalInstances)
1414expectEvents(t, events,
1415Event{Type: "eds", ID: selector.Spec.(*networking.ServiceEntry).Hosts[0], Namespace: selector.Namespace, EndpointCount: len(totalInstances)})
1416}
1417})
1418}
1419}
1420
1421func expectProxyInstances(t testing.TB, sd *Controller, expected []*model.ServiceInstance, ip string) {
1422t.Helper()
1423expectProxyTargets(t, sd, slices.Map(expected, model.ServiceInstanceToTarget), ip)
1424}
1425
1426func expectProxyTargets(t testing.TB, sd *Controller, expected []model.ServiceTarget, ip string) {
1427t.Helper()
1428// The system is eventually consistent, so add some retries
1429retry.UntilSuccessOrFail(t, func() error {
1430instances := sd.GetProxyServiceTargets(&model.Proxy{IPAddresses: []string{ip}, Metadata: &model.NodeMetadata{}})
1431sortServiceTargets(instances)
1432sortServiceTargets(expected)
1433if err := compare(t, instances, expected); err != nil {
1434return err
1435}
1436return nil
1437}, retry.Converge(2), retry.Timeout(time.Second*5))
1438}
1439
1440func expectEvents(t testing.TB, ch *xdsfake.Updater, events ...Event) {
1441t.Helper()
1442ch.StrictMatchOrFail(t, events...)
1443}
1444
1445func expectServiceInstances(t testing.TB, sd *Controller, cfg *config.Config, port int, expected ...[]*model.ServiceInstance) {
1446t.Helper()
1447svcs := convertServices(*cfg)
1448if len(svcs) != len(expected) {
1449t.Fatalf("got more services than expected: %v vs %v", len(svcs), len(expected))
1450}
1451expe := [][]*model.IstioEndpoint{}
1452for _, o := range expected {
1453res := []*model.IstioEndpoint{}
1454for _, i := range o {
1455res = append(res, i.Endpoint)
1456}
1457expe = append(expe, res)
1458}
1459// The system is eventually consistent, so add some retries
1460retry.UntilSuccessOrFail(t, func() error {
1461for i, svc := range svcs {
1462endpoints := GetEndpointsForPort(svc, sd.XdsUpdater.(*xdsfake.Updater).Delegate.(*model.EndpointIndexUpdater).Index, port)
1463if endpoints == nil {
1464endpoints = []*model.IstioEndpoint{} // To simplify tests a bit
1465}
1466sortEndpoints(endpoints)
1467sortEndpoints(expe[i])
1468if err := compare(t, endpoints, expe[i]); err != nil {
1469return fmt.Errorf("%d: %v", i, err)
1470}
1471}
1472return nil
1473}, retry.Converge(2), retry.Timeout(time.Second*1))
1474}
1475
1476func TestServiceDiscoveryGetProxyServiceTargets(t *testing.T) {
1477store, sd, _ := initServiceDiscovery(t)
1478
1479createConfigs([]*config.Config{httpStatic, tcpStatic}, store, t)
1480
1481expectProxyInstances(t, sd, []*model.ServiceInstance{
1482makeInstance(httpStatic, "2.2.2.2", 7080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1483makeInstance(httpStatic, "2.2.2.2", 18080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS),
1484makeInstance(tcpStatic, "2.2.2.2", 444, tcpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1485}, "2.2.2.2")
1486}
1487
1488// Keeping this test for legacy - but it never happens in real life.
1489func TestServiceDiscoveryInstances(t *testing.T) {
1490store, sd, _ := initServiceDiscovery(t)
1491
1492createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t)
1493
1494expectServiceInstances(t, sd, httpDNS, 0, []*model.ServiceInstance{
1495makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1496makeInstance(httpDNS, "us.google.com", 18080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS),
1497makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1498makeInstance(httpDNS, "uk.google.com", 8080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS),
1499makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS),
1500makeInstance(httpDNS, "de.google.com", 8080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"foo": "bar"}, MTLS),
1501})
1502}
1503
1504// Keeping this test for legacy - but it never happens in real life.
1505func TestServiceDiscoveryInstances1Port(t *testing.T) {
1506store, sd, _ := initServiceDiscovery(t)
1507
1508createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t)
1509
1510expectServiceInstances(t, sd, httpDNS, 80, []*model.ServiceInstance{
1511makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1512makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1513makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS),
1514})
1515}
1516
1517func TestNonServiceConfig(t *testing.T) {
1518store, sd, _ := initServiceDiscovery(t)
1519
1520// Create a non-service configuration element. This should not affect the service registry at all.
1521cfg := config.Config{
1522Meta: config.Meta{
1523GroupVersionKind: gvk.DestinationRule,
1524Name: "fakeDestinationRule",
1525Namespace: "default",
1526Domain: "cluster.local",
1527CreationTimestamp: GlobalTime,
1528},
1529Spec: &networking.DestinationRule{
1530Host: "fakehost",
1531},
1532}
1533_, err := store.Create(cfg)
1534if err != nil {
1535t.Errorf("error occurred crearting ServiceEntry config: %v", err)
1536}
1537
1538// Now create some service entries and verify that it's added to the registry.
1539createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t)
1540expectServiceInstances(t, sd, httpDNS, 80, []*model.ServiceInstance{
1541makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1542makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS),
1543makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS),
1544})
1545}
1546
1547// nolint: lll
1548func TestServicesDiff(t *testing.T) {
1549updatedHTTPDNS := &config.Config{
1550Meta: config.Meta{
1551GroupVersionKind: gvk.ServiceEntry,
1552Name: "httpDNS",
1553Namespace: "httpDNS",
1554CreationTimestamp: GlobalTime,
1555Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
1556},
1557Spec: &networking.ServiceEntry{
1558Hosts: []string{"*.google.com", "*.mail.com"},
1559Ports: []*networking.ServicePort{
1560{Number: 80, Name: "http-port", Protocol: "http"},
1561{Number: 8080, Name: "http-alt-port", Protocol: "http"},
1562},
1563Endpoints: []*networking.WorkloadEntry{
1564{
1565Address: "us.google.com",
1566Ports: map[string]uint32{"http-port": 7080, "http-alt-port": 18080},
1567Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
1568},
1569{
1570Address: "uk.google.com",
1571Ports: map[string]uint32{"http-port": 1080},
1572Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
1573},
1574{
1575Address: "de.google.com",
1576Labels: map[string]string{"foo": "bar", label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
1577},
1578},
1579Location: networking.ServiceEntry_MESH_EXTERNAL,
1580Resolution: networking.ServiceEntry_DNS,
1581},
1582}
1583
1584updatedHTTPDNSPort := func() *config.Config {
1585c := updatedHTTPDNS.DeepCopy()
1586se := c.Spec.(*networking.ServiceEntry)
1587var ports []*networking.ServicePort
1588ports = append(ports, se.Ports...)
1589ports = append(ports, &networking.ServicePort{Number: 9090, Name: "http-new-port", Protocol: "http"})
1590se.Ports = ports
1591return &c
1592}()
1593
1594updatedEndpoint := func() *config.Config {
1595c := updatedHTTPDNS.DeepCopy()
1596se := c.Spec.(*networking.ServiceEntry)
1597var endpoints []*networking.WorkloadEntry
1598endpoints = append(endpoints, se.Endpoints...)
1599endpoints = append(endpoints, &networking.WorkloadEntry{
1600Address: "in.google.com",
1601Labels: map[string]string{"foo": "bar", label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel},
1602})
1603se.Endpoints = endpoints
1604return &c
1605}()
1606
1607stringsToHosts := func(hosts []string) []host.Name {
1608ret := make([]host.Name, len(hosts))
1609for i, hostname := range hosts {
1610ret[i] = host.Name(hostname)
1611}
1612return ret
1613}
1614
1615cases := []struct {
1616name string
1617current *config.Config
1618new *config.Config
1619
1620added []host.Name
1621deleted []host.Name
1622updated []host.Name
1623unchanged []host.Name
1624}{
1625{
1626name: "same config",
1627current: updatedHTTPDNS,
1628new: updatedHTTPDNS,
1629unchanged: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts),
1630},
1631{
1632name: "different resolution",
1633current: updatedHTTPDNS,
1634new: func() *config.Config {
1635c := updatedHTTPDNS.DeepCopy()
1636c.Spec.(*networking.ServiceEntry).Resolution = networking.ServiceEntry_NONE
1637return &c
1638}(),
1639updated: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts),
1640},
1641{
1642name: "config modified with added/deleted host",
1643current: updatedHTTPDNS,
1644new: func() *config.Config {
1645c := updatedHTTPDNS.DeepCopy()
1646se := c.Spec.(*networking.ServiceEntry)
1647se.Hosts = []string{"*.google.com", "host.com"}
1648return &c
1649}(),
1650added: []host.Name{"host.com"},
1651deleted: []host.Name{"*.mail.com"},
1652unchanged: []host.Name{"*.google.com"},
1653},
1654{
1655name: "config modified with additional port",
1656current: updatedHTTPDNS,
1657new: updatedHTTPDNSPort,
1658updated: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts),
1659},
1660{
1661name: "same config with additional endpoint",
1662current: updatedHTTPDNS,
1663new: updatedEndpoint,
1664unchanged: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts),
1665},
1666}
1667
1668servicesHostnames := func(services []*model.Service) []host.Name {
1669if len(services) == 0 {
1670return nil
1671}
1672ret := make([]host.Name, len(services))
1673for i, svc := range services {
1674ret[i] = svc.Hostname
1675}
1676return ret
1677}
1678
1679for _, tt := range cases {
1680t.Run(tt.name, func(t *testing.T) {
1681as := convertServices(*tt.current)
1682bs := convertServices(*tt.new)
1683added, deleted, updated, unchanged := servicesDiff(as, bs)
1684for i, item := range []struct {
1685hostnames []host.Name
1686services []*model.Service
1687}{
1688{tt.added, added},
1689{tt.deleted, deleted},
1690{tt.updated, updated},
1691{tt.unchanged, unchanged},
1692} {
1693if !reflect.DeepEqual(servicesHostnames(item.services), item.hostnames) {
1694t.Errorf("ServicesChanged %d got %v, want %v", i, servicesHostnames(item.services), item.hostnames)
1695}
1696}
1697})
1698}
1699}
1700
1701func sortServices(services []*model.Service) {
1702sort.Slice(services, func(i, j int) bool { return services[i].Hostname < services[j].Hostname })
1703for _, service := range services {
1704sortPorts(service.Ports)
1705}
1706}
1707
1708func sortServiceTargets(instances []model.ServiceTarget) {
1709sort.Slice(instances, func(i, j int) bool {
1710if instances[i].Service.Hostname == instances[j].Service.Hostname {
1711if instances[i].Port.TargetPort == instances[j].Port.TargetPort {
1712return instances[i].Port.TargetPort < instances[j].Port.TargetPort
1713}
1714}
1715return instances[i].Service.Hostname < instances[j].Service.Hostname
1716})
1717}
1718
1719func sortServiceInstances(instances []*model.ServiceInstance) {
1720labelsToSlice := func(labels labels.Instance) []string {
1721out := make([]string, 0, len(labels))
1722for k, v := range labels {
1723out = append(out, fmt.Sprintf("%s=%s", k, v))
1724}
1725sort.Strings(out)
1726return out
1727}
1728
1729sort.Slice(instances, func(i, j int) bool {
1730if instances[i].Service.Hostname == instances[j].Service.Hostname {
1731if instances[i].Endpoint.EndpointPort == instances[j].Endpoint.EndpointPort {
1732if instances[i].Endpoint.Address == instances[j].Endpoint.Address {
1733if len(instances[i].Endpoint.Labels) == len(instances[j].Endpoint.Labels) {
1734iLabels := labelsToSlice(instances[i].Endpoint.Labels)
1735jLabels := labelsToSlice(instances[j].Endpoint.Labels)
1736for k := range iLabels {
1737if iLabels[k] < jLabels[k] {
1738return true
1739}
1740}
1741}
1742return len(instances[i].Endpoint.Labels) < len(instances[j].Endpoint.Labels)
1743}
1744return instances[i].Endpoint.Address < instances[j].Endpoint.Address
1745}
1746return instances[i].Endpoint.EndpointPort < instances[j].Endpoint.EndpointPort
1747}
1748return instances[i].Service.Hostname < instances[j].Service.Hostname
1749})
1750}
1751
1752func sortEndpoints(endpoints []*model.IstioEndpoint) {
1753labelsToSlice := func(labels labels.Instance) []string {
1754out := make([]string, 0, len(labels))
1755for k, v := range labels {
1756out = append(out, fmt.Sprintf("%s=%s", k, v))
1757}
1758sort.Strings(out)
1759return out
1760}
1761
1762sort.Slice(endpoints, func(i, j int) bool {
1763if endpoints[i].EndpointPort == endpoints[j].EndpointPort {
1764if endpoints[i].Address == endpoints[j].Address {
1765if len(endpoints[i].Labels) == len(endpoints[j].Labels) {
1766iLabels := labelsToSlice(endpoints[i].Labels)
1767jLabels := labelsToSlice(endpoints[j].Labels)
1768for k := range iLabels {
1769if iLabels[k] < jLabels[k] {
1770return true
1771}
1772}
1773}
1774return len(endpoints[i].Labels) < len(endpoints[j].Labels)
1775}
1776return endpoints[i].Address < endpoints[j].Address
1777}
1778return endpoints[i].EndpointPort < endpoints[j].EndpointPort
1779})
1780}
1781
1782func sortPorts(ports []*model.Port) {
1783sort.Slice(ports, func(i, j int) bool {
1784if ports[i].Port == ports[j].Port {
1785if ports[i].Name == ports[j].Name {
1786return ports[i].Protocol < ports[j].Protocol
1787}
1788return ports[i].Name < ports[j].Name
1789}
1790return ports[i].Port < ports[j].Port
1791})
1792}
1793
1794func Test_autoAllocateIP_conditions(t *testing.T) {
1795tests := []struct {
1796name string
1797inServices []*model.Service
1798wantServices []*model.Service
1799}{
1800{
1801name: "no allocation for passthrough",
1802inServices: []*model.Service{
1803{
1804Hostname: "foo.com",
1805Resolution: model.Passthrough,
1806DefaultAddress: "0.0.0.0",
1807},
1808},
1809wantServices: []*model.Service{
1810{
1811Hostname: "foo.com",
1812Resolution: model.Passthrough,
1813DefaultAddress: "0.0.0.0",
1814},
1815},
1816},
1817{
1818name: "no allocation if address exists",
1819inServices: []*model.Service{
1820{
1821Hostname: "foo.com",
1822Resolution: model.ClientSideLB,
1823DefaultAddress: "1.1.1.1",
1824},
1825},
1826wantServices: []*model.Service{
1827{
1828Hostname: "foo.com",
1829Resolution: model.ClientSideLB,
1830DefaultAddress: "1.1.1.1",
1831},
1832},
1833},
1834{
1835name: "no allocation if hostname is wildcard",
1836inServices: []*model.Service{
1837{
1838Hostname: "*.foo.com",
1839Resolution: model.ClientSideLB,
1840DefaultAddress: "1.1.1.1",
1841},
1842},
1843wantServices: []*model.Service{
1844{
1845Hostname: "*.foo.com",
1846Resolution: model.ClientSideLB,
1847DefaultAddress: "1.1.1.1",
1848},
1849},
1850},
1851{
1852name: "allocate IP for clientside lb",
1853inServices: []*model.Service{
1854{
1855Hostname: "foo.com",
1856Resolution: model.ClientSideLB,
1857DefaultAddress: "0.0.0.0",
1858},
1859},
1860wantServices: []*model.Service{
1861{
1862Hostname: "foo.com",
1863Resolution: model.ClientSideLB,
1864DefaultAddress: "0.0.0.0",
1865AutoAllocatedIPv4Address: "240.240.227.81",
1866AutoAllocatedIPv6Address: "2001:2::f0f0:e351",
1867},
1868},
1869},
1870{
1871name: "allocate IP for dns lb",
1872inServices: []*model.Service{
1873{
1874Hostname: "foo.com",
1875Resolution: model.DNSLB,
1876DefaultAddress: "0.0.0.0",
1877},
1878},
1879wantServices: []*model.Service{
1880{
1881Hostname: "foo.com",
1882Resolution: model.DNSLB,
1883DefaultAddress: "0.0.0.0",
1884AutoAllocatedIPv4Address: "240.240.227.81",
1885AutoAllocatedIPv6Address: "2001:2::f0f0:e351",
1886},
1887},
1888},
1889{
1890name: "collision",
1891inServices: []*model.Service{
1892{
1893Hostname: "a17061.example.com",
1894Resolution: model.DNSLB,
1895DefaultAddress: "0.0.0.0",
1896},
1897{
1898// hashes to the same value as the hostname above,
1899// a new collision needs to be found if the hash algorithm changes
1900Hostname: "a44155.example.com",
1901Resolution: model.DNSLB,
1902DefaultAddress: "0.0.0.0",
1903},
1904},
1905wantServices: []*model.Service{
1906{
1907Hostname: "a17061.example.com",
1908Resolution: model.DNSLB,
1909DefaultAddress: "0.0.0.0",
1910AutoAllocatedIPv4Address: "240.240.25.11",
1911AutoAllocatedIPv6Address: "2001:2::f0f0:19b",
1912},
1913{
1914Hostname: "a44155.example.com",
1915Resolution: model.DNSLB,
1916DefaultAddress: "0.0.0.0",
1917AutoAllocatedIPv4Address: "240.240.31.17",
1918AutoAllocatedIPv6Address: "2001:2::f0f0:1f11",
1919},
1920},
1921},
1922{
1923name: "stable IP - baseline test",
1924inServices: []*model.Service{
1925{
1926Hostname: "a.example.com",
1927Resolution: model.DNSLB,
1928DefaultAddress: "0.0.0.0",
1929Attributes: model.ServiceAttributes{Namespace: "a"},
1930},
1931},
1932wantServices: []*model.Service{
1933{
1934Hostname: "a.example.com",
1935Resolution: model.DNSLB,
1936DefaultAddress: "0.0.0.0",
1937AutoAllocatedIPv4Address: "240.240.134.206",
1938AutoAllocatedIPv6Address: "2001:2::f0f0:86ce",
1939},
1940},
1941},
1942{
1943name: "stable IP - not affected by other namespace",
1944inServices: []*model.Service{
1945{
1946Hostname: "a.example.com",
1947Resolution: model.DNSLB,
1948DefaultAddress: "0.0.0.0",
1949Attributes: model.ServiceAttributes{Namespace: "a"},
1950},
1951{
1952Hostname: "a.example.com",
1953Resolution: model.DNSLB,
1954DefaultAddress: "0.0.0.0",
1955Attributes: model.ServiceAttributes{Namespace: "b"},
1956},
1957},
1958wantServices: []*model.Service{
1959{
1960Hostname: "a.example.com",
1961Resolution: model.DNSLB,
1962DefaultAddress: "0.0.0.0",
1963AutoAllocatedIPv4Address: "240.240.134.206",
1964AutoAllocatedIPv6Address: "2001:2::f0f0:86ce",
1965},
1966{
1967Hostname: "a.example.com",
1968Resolution: model.DNSLB,
1969DefaultAddress: "0.0.0.0",
1970AutoAllocatedIPv4Address: "240.240.41.100",
1971AutoAllocatedIPv6Address: "2001:2::f0f0:2964",
1972},
1973},
1974},
1975}
1976for _, tt := range tests {
1977t.Run(tt.name, func(t *testing.T) {
1978gotServices := autoAllocateIPs(tt.inServices)
1979for i, got := range gotServices {
1980if got.AutoAllocatedIPv4Address != tt.wantServices[i].AutoAllocatedIPv4Address {
1981t.Errorf("autoAllocateIPs() AutoAllocatedIPv4Address = %v, want %v",
1982got.AutoAllocatedIPv4Address, tt.wantServices[i].AutoAllocatedIPv4Address)
1983}
1984if got.AutoAllocatedIPv6Address != tt.wantServices[i].AutoAllocatedIPv6Address {
1985t.Errorf("autoAllocateIPs() AutoAllocatedIPv6Address = %v, want %v",
1986got.AutoAllocatedIPv6Address, tt.wantServices[i].AutoAllocatedIPv6Address)
1987}
1988}
1989})
1990}
1991}
1992
1993func Test_autoAllocateIP_values(t *testing.T) {
1994ips := maxIPs
1995inServices := make([]*model.Service, ips)
1996for i := 0; i < ips; i++ {
1997temp := model.Service{
1998Hostname: host.Name(fmt.Sprintf("foo%d.com", i)),
1999Resolution: model.ClientSideLB,
2000DefaultAddress: constants.UnspecifiedIP,
2001}
2002inServices[i] = &temp
2003}
2004gotServices := autoAllocateIPs(inServices)
2005
2006// We dont expect the following pattern of IPs.
2007// 240.240.0.0
2008// 240.240.0.255
2009// 240.240.1.0
2010// 240.240.1.255
2011// 240.240.2.0
2012// 240.240.2.255
2013// 240.240.3.0
2014// 240.240.3.255
2015// The last IP should be 240.240.202.167
2016doNotWant := map[string]bool{
2017"240.240.0.0": true,
2018"240.240.0.255": true,
2019"240.240.1.0": true,
2020"240.240.1.255": true,
2021"240.240.2.0": true,
2022"240.240.2.255": true,
2023"240.240.3.0": true,
2024"240.240.3.255": true,
2025}
2026expectedLastIP := "240.240.10.222"
2027if gotServices[len(gotServices)-1].AutoAllocatedIPv4Address != expectedLastIP {
2028t.Errorf("expected last IP address to be %s, got %s", expectedLastIP, gotServices[len(gotServices)-1].AutoAllocatedIPv4Address)
2029}
2030
2031gotIPMap := make(map[string]string)
2032for _, svc := range gotServices {
2033if svc.AutoAllocatedIPv4Address == "" || doNotWant[svc.AutoAllocatedIPv4Address] {
2034t.Errorf("unexpected value for auto allocated IP address %s for service %s", svc.AutoAllocatedIPv4Address, svc.Hostname.String())
2035}
2036if v, ok := gotIPMap[svc.AutoAllocatedIPv4Address]; ok && v != svc.Hostname.String() {
2037t.Errorf("multiple allocations of same IP address to different services with different hostname: %s", svc.AutoAllocatedIPv4Address)
2038}
2039gotIPMap[svc.AutoAllocatedIPv4Address] = svc.Hostname.String()
2040// Validate that IP address is valid.
2041ip := net.ParseIP(svc.AutoAllocatedIPv4Address)
2042if ip == nil {
2043t.Errorf("invalid IP address %s : %s", svc.AutoAllocatedIPv4Address, svc.Hostname.String())
2044}
2045// Validate that IP address is in the expected range.
2046_, subnet, _ := net.ParseCIDR("240.240.0.0/16")
2047if !subnet.Contains(ip) {
2048t.Errorf("IP address not in range %s : %s", svc.AutoAllocatedIPv4Address, svc.Hostname.String())
2049}
2050}
2051assert.Equal(t, maxIPs, len(gotIPMap))
2052}
2053
2054func BenchmarkAutoAllocateIPs(t *testing.B) {
2055inServices := make([]*model.Service, 255*255)
2056for i := 0; i < 255*255; i++ {
2057temp := model.Service{
2058Hostname: host.Name(fmt.Sprintf("foo%d.com", i)),
2059Resolution: model.ClientSideLB,
2060DefaultAddress: constants.UnspecifiedIP,
2061}
2062inServices[i] = &temp
2063}
2064t.ResetTimer()
2065for i := 0; i < t.N; i++ {
2066autoAllocateIPs(inServices)
2067}
2068}
2069
2070// Validate that ipaddress allocation is deterministic based on hash.
2071func Test_autoAllocateIP_deterministic(t *testing.T) {
2072inServices := make([]*model.Service, 0)
2073originalServices := map[string]string{
2074"a.com": "240.240.109.8",
2075"c.com": "240.240.234.51",
2076"e.com": "240.240.85.60",
2077"g.com": "240.240.23.172",
2078"i.com": "240.240.15.2",
2079"k.com": "240.240.160.161",
2080"l.com": "240.240.42.96",
2081"n.com": "240.240.121.61",
2082"o.com": "240.240.122.71",
2083}
2084
2085allocateAndValidate := func() {
2086gotServices := autoAllocateIPs(model.SortServicesByCreationTime(inServices))
2087gotIPMap := make(map[string]string)
2088serviceIPMap := make(map[string]string)
2089for _, svc := range gotServices {
2090if v, ok := gotIPMap[svc.AutoAllocatedIPv4Address]; ok && v != svc.Hostname.String() {
2091t.Errorf("multiple allocations of same IP address to different services with different hostname: %s", svc.AutoAllocatedIPv4Address)
2092}
2093gotIPMap[svc.AutoAllocatedIPv4Address] = svc.Hostname.String()
2094serviceIPMap[svc.Hostname.String()] = svc.AutoAllocatedIPv4Address
2095}
2096for k, v := range originalServices {
2097if gotIPMap[v] != k {
2098t.Errorf("ipaddress changed for service %s. expected: %s, got: %s", k, v, serviceIPMap[k])
2099}
2100}
2101for k, v := range gotIPMap {
2102if net.ParseIP(k) == nil {
2103t.Errorf("invalid ipaddress for service %s. got: %s", v, k)
2104}
2105}
2106}
2107
2108// Validate that IP addresses are allocated for original list of services.
2109for k := range originalServices {
2110inServices = append(inServices, &model.Service{
2111Hostname: host.Name(k),
2112Resolution: model.ClientSideLB,
2113DefaultAddress: constants.UnspecifiedIP,
2114})
2115}
2116allocateAndValidate()
2117
2118// Now add few services in between and validate that IPs are retained for original services.
2119addServices := map[string]bool{
2120"b.com": true,
2121"d.com": true,
2122"f.com": true,
2123"h.com": true,
2124"j.com": true,
2125"m.com": true,
2126"p.com": true,
2127"q.com": true,
2128"r.com": true,
2129}
2130
2131for k := range addServices {
2132inServices = append(inServices, &model.Service{
2133Hostname: host.Name(k),
2134Resolution: model.ClientSideLB,
2135DefaultAddress: constants.UnspecifiedIP,
2136})
2137}
2138allocateAndValidate()
2139
2140// Now delete few services and validate that IPs are retained for original services.
2141deleteServices := []*model.Service{}
2142for i, svc := range inServices {
2143if _, exists := originalServices[svc.Hostname.String()]; !exists {
2144if i%2 == 0 {
2145continue
2146}
2147}
2148deleteServices = append(deleteServices, svc)
2149}
2150inServices = deleteServices
2151allocateAndValidate()
2152}
2153
2154func Test_autoAllocateIP_with_duplicated_host(t *testing.T) {
2155inServices := make([]*model.Service, 0)
2156originalServices := map[string]string{
2157"a.com": "240.240.109.8",
2158"c.com": "240.240.234.51",
2159"e.com": "240.240.85.60",
2160"g.com": "240.240.23.172",
2161"i.com": "240.240.15.2",
2162"k.com": "240.240.160.161",
2163"l.com": "240.240.42.96",
2164"n.com": "240.240.121.61",
2165"o.com": "240.240.122.71",
2166}
2167
2168allocateAndValidate := func() {
2169gotServices := autoAllocateIPs(model.SortServicesByCreationTime(inServices))
2170gotIPMap := make(map[string]string)
2171serviceIPMap := make(map[string]string)
2172for _, svc := range gotServices {
2173if v, ok := gotIPMap[svc.AutoAllocatedIPv4Address]; ok && v != svc.Hostname.String() {
2174t.Errorf("multiple allocations of same IP address to different services with different hostname: %s", svc.AutoAllocatedIPv4Address)
2175}
2176gotIPMap[svc.AutoAllocatedIPv4Address] = svc.Hostname.String()
2177serviceIPMap[svc.Hostname.String()] = svc.AutoAllocatedIPv4Address
2178}
2179for k, v := range originalServices {
2180if gotIPMap[v] != k {
2181t.Errorf("ipaddress changed for service %s. expected: %s, got: %s", k, v, serviceIPMap[k])
2182}
2183}
2184for k, v := range gotIPMap {
2185if net.ParseIP(k) == nil {
2186t.Errorf("invalid ipaddress for service %s. got: %s", v, k)
2187}
2188}
2189}
2190
2191// Validate that IP addresses are allocated for original list of services.
2192for k := range originalServices {
2193inServices = append(inServices, &model.Service{
2194Hostname: host.Name(k),
2195Resolution: model.ClientSideLB,
2196DefaultAddress: constants.UnspecifiedIP,
2197})
2198}
2199allocateAndValidate()
2200
2201// Now add service with duplicated hostname validate that IPs are retained for original services and duplicated reuse the same IP
2202addServices := map[string]bool{
2203"i.com": true,
2204}
2205
2206for k := range addServices {
2207inServices = append(inServices, &model.Service{
2208Hostname: host.Name(k),
2209Resolution: model.ClientSideLB,
2210DefaultAddress: constants.UnspecifiedIP,
2211})
2212}
2213allocateAndValidate()
2214}
2215
2216func TestWorkloadEntryOnlyMode(t *testing.T) {
2217store, registry, _ := initServiceDiscoveryWithOpts(t, true)
2218createConfigs([]*config.Config{httpStatic}, store, t)
2219svcs := registry.Services()
2220if len(svcs) > 0 {
2221t.Fatalf("expected 0 services, got %d", len(svcs))
2222}
2223svc := registry.GetService("*.google.com")
2224if svc != nil {
2225t.Fatalf("expected nil, got %v", svc)
2226}
2227}
2228
2229func BenchmarkServiceEntryHandler(b *testing.B) {
2230_, sd := initServiceDiscoveryWithoutEvents(b)
2231stopCh := make(chan struct{})
2232go sd.Run(stopCh)
2233defer close(stopCh)
2234for i := 0; i < b.N; i++ {
2235sd.serviceEntryHandler(config.Config{}, *httpDNS, model.EventAdd)
2236sd.serviceEntryHandler(config.Config{}, *httpDNSRR, model.EventAdd)
2237sd.serviceEntryHandler(config.Config{}, *tcpDNS, model.EventAdd)
2238sd.serviceEntryHandler(config.Config{}, *tcpStatic, model.EventAdd)
2239
2240sd.serviceEntryHandler(config.Config{}, *httpDNS, model.EventDelete)
2241sd.serviceEntryHandler(config.Config{}, *httpDNSRR, model.EventDelete)
2242sd.serviceEntryHandler(config.Config{}, *tcpDNS, model.EventDelete)
2243sd.serviceEntryHandler(config.Config{}, *tcpStatic, model.EventDelete)
2244}
2245}
2246
2247func BenchmarkWorkloadInstanceHandler(b *testing.B) {
2248store, sd := initServiceDiscoveryWithoutEvents(b)
2249stopCh := make(chan struct{})
2250go sd.Run(stopCh)
2251defer close(stopCh)
2252// Add just the ServiceEntry with selector. We should see no instances
2253createConfigs([]*config.Config{selector, dnsSelector}, store, b)
2254
2255// Setup a couple of workload instances for test. These will be selected by the `selector` SE
2256fi1 := &model.WorkloadInstance{
2257Name: selector.Name,
2258Namespace: selector.Namespace,
2259Endpoint: &model.IstioEndpoint{
2260Address: "2.2.2.2",
2261Labels: map[string]string{"app": "wle"},
2262ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"),
2263TLSMode: model.IstioMutualTLSModeLabel,
2264},
2265}
2266
2267fi2 := &model.WorkloadInstance{
2268Name: "some-other-name",
2269Namespace: selector.Namespace,
2270Endpoint: &model.IstioEndpoint{
2271Address: "3.3.3.3",
2272Labels: map[string]string{"app": "wle"},
2273ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"),
2274TLSMode: model.IstioMutualTLSModeLabel,
2275},
2276}
2277
2278fi3 := &model.WorkloadInstance{
2279Name: "another-name",
2280Namespace: dnsSelector.Namespace,
2281Endpoint: &model.IstioEndpoint{
2282Address: "2.2.2.2",
2283Labels: map[string]string{"app": "dns-wle"},
2284ServiceAccount: spiffe.MustGenSpiffeURI(dnsSelector.Name, "default"),
2285TLSMode: model.IstioMutualTLSModeLabel,
2286},
2287}
2288for i := 0; i < b.N; i++ {
2289sd.WorkloadInstanceHandler(fi1, model.EventAdd)
2290sd.WorkloadInstanceHandler(fi2, model.EventAdd)
2291sd.WorkloadInstanceHandler(fi3, model.EventDelete)
2292
2293sd.WorkloadInstanceHandler(fi2, model.EventDelete)
2294sd.WorkloadInstanceHandler(fi1, model.EventDelete)
2295sd.WorkloadInstanceHandler(fi3, model.EventDelete)
2296}
2297}
2298
2299func BenchmarkWorkloadEntryHandler(b *testing.B) {
2300// Setup a couple workload entries for test. These will be selected by the `selector` SE
2301wle := createWorkloadEntry("wl", selector.Name,
2302&networking.WorkloadEntry{
2303Address: "2.2.2.2",
2304Labels: map[string]string{"app": "wle"},
2305ServiceAccount: "default",
2306})
2307wle2 := createWorkloadEntry("wl2", selector.Name,
2308&networking.WorkloadEntry{
2309Address: "3.3.3.3",
2310Labels: map[string]string{"app": "wle"},
2311ServiceAccount: "default",
2312})
2313dnsWle := createWorkloadEntry("dnswl", dnsSelector.Namespace,
2314&networking.WorkloadEntry{
2315Address: "4.4.4.4",
2316Labels: map[string]string{"app": "dns-wle"},
2317ServiceAccount: "default",
2318})
2319
2320store, sd := initServiceDiscoveryWithoutEvents(b)
2321stopCh := make(chan struct{})
2322go sd.Run(stopCh)
2323defer close(stopCh)
2324// Add just the ServiceEntry with selector. We should see no instances
2325createConfigs([]*config.Config{selector}, store, b)
2326
2327for i := 0; i < b.N; i++ {
2328sd.workloadEntryHandler(config.Config{}, *wle, model.EventAdd)
2329sd.workloadEntryHandler(config.Config{}, *dnsWle, model.EventAdd)
2330sd.workloadEntryHandler(config.Config{}, *wle2, model.EventAdd)
2331
2332sd.workloadEntryHandler(config.Config{}, *wle, model.EventDelete)
2333sd.workloadEntryHandler(config.Config{}, *dnsWle, model.EventDelete)
2334sd.workloadEntryHandler(config.Config{}, *wle2, model.EventDelete)
2335}
2336}
2337
2338func GetEndpoints(s *model.Service, endpoints *model.EndpointIndex) []*model.IstioEndpoint {
2339return GetEndpointsForPort(s, endpoints, 0)
2340}
2341
2342func GetEndpointsForPort(s *model.Service, endpoints *model.EndpointIndex, port int) []*model.IstioEndpoint {
2343shards, ok := endpoints.ShardsForService(string(s.Hostname), s.Attributes.Namespace)
2344if !ok {
2345return nil
2346}
2347var pn string
2348for _, p := range s.Ports {
2349if p.Port == port {
2350pn = p.Name
2351break
2352}
2353}
2354if pn == "" && port != 0 {
2355return nil
2356}
2357shards.RLock()
2358defer shards.RUnlock()
2359return slices.FilterInPlace(slices.Flatten(maps.Values(shards.Shards)), func(endpoint *model.IstioEndpoint) bool {
2360return pn == "" || endpoint.ServicePortName == pn
2361})
2362}
2363