1// Copyright 2018 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.
14package v2_test
15
16import (
17	"io/ioutil"
18	"os"
19	"testing"
20	"time"
21
22	"github.com/golang/protobuf/ptypes"
23
24	"istio.io/istio/pkg/config/mesh"
25
26	v2 "istio.io/istio/pilot/pkg/proxy/envoy/v2"
27	"istio.io/istio/pilot/pkg/serviceregistry"
28
29	xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2"
30	xdsapi_listener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
31	xdsapi_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
32
33	"istio.io/istio/pkg/config/labels"
34	"istio.io/istio/pkg/util/gogoprotomarshal"
35
36	testenv "istio.io/istio/mixer/test/client/env"
37	"istio.io/istio/pilot/pkg/bootstrap"
38	"istio.io/istio/pilot/pkg/model"
39	"istio.io/istio/pkg/adsc"
40	"istio.io/istio/pkg/test/env"
41	"istio.io/istio/tests/util"
42)
43
44// TestLDS using isolated namespaces
45func TestLDSIsolated(t *testing.T) {
46	_, tearDown := initLocalPilotTestEnv(t)
47	defer tearDown()
48
49	// Sidecar in 'none' mode
50	t.Run("sidecar_none", func(t *testing.T) {
51		// TODO: add a Service with EDS resolution in the none ns.
52		// The ServiceEntry only allows STATIC - both STATIC and EDS should generated TCP listeners on :port
53		// while DNS and NONE should generate old-style bind ports.
54		// Right now 'STATIC' and 'EDS' result in ClientSideLB in the internal object, so listener test is valid.
55
56		ldsr, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
57			Meta: model.NodeMetadata{
58				InterceptionMode: model.InterceptionNone,
59				HTTP10:           "1",
60			}.ToStruct(),
61			IP:        "10.11.0.1", // matches none.yaml s1tcp.none
62			Namespace: "none",
63		})
64		if err != nil {
65			t.Fatal(err)
66		}
67		defer ldsr.Close()
68
69		ldsr.Watch()
70
71		_, err = ldsr.Wait(5*time.Second, "lds")
72		if err != nil {
73			t.Fatal("Failed to receive LDS", err)
74			return
75		}
76
77		err = ldsr.Save(env.IstioOut + "/none")
78		if err != nil {
79			t.Fatal(err)
80		}
81
82		// 7071 (inbound), 2001 (service - also as http proxy), 18010 (fortio), 15006 (virtual inbound)
83		// We do not get mixer on 9091 because there are no services defined in istio-system namespace
84		// in the none.yaml setup
85		if len(ldsr.GetHTTPListeners()) != 4 {
86			// TODO: we are still debating if for HTTP services we have any use case to create a 127.0.0.1:port outbound
87			// for the service (the http proxy is already covering this)
88			t.Error("HTTP listeners, expecting 4 got ", len(ldsr.GetHTTPListeners()), ldsr.GetHTTPListeners())
89		}
90
91		// s1tcp:2000 outbound, bind=true (to reach other instances of the service)
92		// s1:5005 outbound, bind=true
93		// :443 - https external, bind=false
94		// 10.11.0.1_7070, bind=true -> inbound|2000|s1 - on port 7070, fwd to 37070
95		// virtual
96		if len(ldsr.GetTCPListeners()) == 0 {
97			t.Fatal("No response")
98		}
99
100		for _, s := range []string{"lds_tcp", "lds_http", "rds", "cds", "ecds"} {
101			want, err := ioutil.ReadFile(env.IstioOut + "/none_" + s + ".json")
102			if err != nil {
103				t.Fatal(err)
104			}
105			got, err := ioutil.ReadFile("testdata/none_" + s + ".json")
106			if err != nil {
107				t.Fatal(err)
108			}
109
110			if err = util.Compare(got, want); err != nil {
111				// Just log for now - golden changes every time there is a config generation update.
112				// It is mostly intended as a reference for what is generated - we need to add explicit checks
113				// for things we need, like the number of expected listeners.
114				// This is mainly using for debugging what changed from the snapshot in the golden files.
115				if os.Getenv("CONFIG_DIFF") == "1" {
116					t.Logf("error in golden file %s %v", s, err)
117				}
118			}
119		}
120
121		// TODO: check bind==true
122		// TODO: verify listeners for outbound are on 127.0.0.1 (not yet), port 2000, 2005, 2007
123		// TODO: verify virtual listeners for unsupported cases
124		// TODO: add and verify SNI listener on 127.0.0.1:443
125		// TODO: verify inbound service port is on 127.0.0.1, and containerPort on 0.0.0.0
126		// TODO: BUG, SE with empty endpoints is rejected - it is actually valid config (service may not have endpoints)
127	})
128
129	// Test for the examples in the ServiceEntry doc
130	t.Run("se_example", func(t *testing.T) {
131		// TODO: add a Service with EDS resolution in the none ns.
132		// The ServiceEntry only allows STATIC - both STATIC and EDS should generated TCP listeners on :port
133		// while DNS and NONE should generate old-style bind ports.
134		// Right now 'STATIC' and 'EDS' result in ClientSideLB in the internal object, so listener test is valid.
135
136		ldsr, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
137			Meta:      nil,
138			IP:        "10.12.0.1", // matches none.yaml s1tcp.none
139			Namespace: "seexamples",
140		})
141		if err != nil {
142			t.Fatal(err)
143		}
144		defer ldsr.Close()
145
146		ldsr.Watch()
147
148		if _, err := ldsr.Wait(5*time.Second, "lds"); err != nil {
149			t.Fatal("Failed to receive LDS", err)
150			return
151		}
152
153		err = ldsr.Save(env.IstioOut + "/seexample")
154		if err != nil {
155			t.Fatal(err)
156		}
157	})
158
159	// Test for the examples in the ServiceEntry doc
160	t.Run("se_examplegw", func(t *testing.T) {
161		// TODO: add a Service with EDS resolution in the none ns.
162		// The ServiceEntry only allows STATIC - both STATIC and EDS should generated TCP listeners on :port
163		// while DNS and NONE should generate old-style bind ports.
164		// Right now 'STATIC' and 'EDS' result in ClientSideLB in the internal object, so listener test is valid.
165
166		ldsr, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
167			Meta:      nil,
168			IP:        "10.13.0.1",
169			Namespace: "exampleegressgw",
170		})
171		if err != nil {
172			t.Fatal(err)
173		}
174		defer ldsr.Close()
175
176		ldsr.Watch()
177
178		if _, err = ldsr.Wait(5*time.Second, "lds"); err != nil {
179			t.Fatal("Failed to receive LDS", err)
180			return
181		}
182
183		err = ldsr.Save(env.IstioOut + "/seexample-eg")
184		if err != nil {
185			t.Fatal(err)
186		}
187	})
188
189}
190
191// TestLDS using default sidecar in root namespace
192func TestLDSWithDefaultSidecar(t *testing.T) {
193
194	server, tearDown := util.EnsureTestServer(func(args *bootstrap.PilotArgs) {
195		args.Plugins = bootstrap.DefaultPlugins
196		args.Config.FileDir = env.IstioSrc + "/tests/testdata/networking/sidecar-ns-scope"
197		args.Mesh.MixerAddress = ""
198		args.MeshConfig = nil
199		args.Mesh.ConfigFile = env.IstioSrc + "/tests/testdata/networking/sidecar-ns-scope/mesh.yaml"
200		args.Service.Registries = []string{}
201	})
202	testEnv = testenv.NewTestSetup(testenv.SidecarTest, t)
203	testEnv.Ports().PilotGrpcPort = uint16(util.MockPilotGrpcPort)
204	testEnv.Ports().PilotHTTPPort = uint16(util.MockPilotHTTPPort)
205	testEnv.IstioSrc = env.IstioSrc
206	testEnv.IstioOut = env.IstioOut
207
208	server.EnvoyXdsServer.ConfigUpdate(&model.PushRequest{Full: true})
209	defer tearDown()
210
211	adsResponse, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
212		Meta: model.NodeMetadata{
213			InstanceIPs:     []string{"100.1.1.2"},
214			ConfigNamespace: "ns1",
215			IstioVersion:    "1.3.0",
216		}.ToStruct(),
217		IP:        "100.1.1.2",
218		Namespace: "ns1",
219	})
220
221	if err != nil {
222		t.Fatal(err)
223	}
224	defer adsResponse.Close()
225
226	adsResponse.Watch()
227
228	upd, err := adsResponse.Wait(10*time.Second, "lds", "rds", "cds")
229	if err != nil {
230		t.Fatal("Failed to receive XDS response", err, upd)
231		return
232	}
233
234	// Expect 6 listeners : 2 orig_dst, 4 outbound (http, tcp1, istio-policy and istio-telemetry)
235	if (len(adsResponse.GetHTTPListeners()) + len(adsResponse.GetTCPListeners())) != 6 {
236		t.Fatalf("Expected 7 listeners, got %d\n", len(adsResponse.GetHTTPListeners())+len(adsResponse.GetTCPListeners()))
237	}
238
239	// Expect 11 CDS clusters:
240	// 2 inbound(http, inbound passthroughipv4) notes: no passthroughipv6
241	// 9 outbound (2 http services, 1 tcp service, 2 istio-system services,
242	//   and 2 subsets of http1, 1 blackhole, 1 passthrough)
243	if (len(adsResponse.GetClusters()) + len(adsResponse.GetEdsClusters())) != 11 {
244		t.Fatalf("Expected 12 clusters in CDS output. Got %d", len(adsResponse.GetClusters())+len(adsResponse.GetEdsClusters()))
245	}
246
247	// Expect two vhost blocks in RDS output for 8080 (one for http1, another for http2)
248	// plus one extra due to mem registry
249	if len(adsResponse.GetRoutes()["8080"].VirtualHosts) != 3 {
250		t.Fatalf("Expected 3 VirtualHosts in RDS output. Got %d", len(adsResponse.GetRoutes()["8080"].VirtualHosts))
251	}
252}
253
254// TestLDS using gateways
255func TestLDSWithIngressGateway(t *testing.T) {
256	server, tearDown := util.EnsureTestServer(func(args *bootstrap.PilotArgs) {
257		args.Plugins = bootstrap.DefaultPlugins
258		args.Config.FileDir = env.IstioSrc + "/tests/testdata/networking/ingress-gateway"
259		args.Mesh.MixerAddress = ""
260		args.Mesh.ConfigFile = env.IstioSrc + "/tests/testdata/networking/ingress-gateway/mesh.yaml"
261		args.Service.Registries = []string{}
262	})
263	testEnv = testenv.NewTestSetup(testenv.GatewayTest, t)
264	testEnv.Ports().PilotGrpcPort = uint16(util.MockPilotGrpcPort)
265	testEnv.Ports().PilotHTTPPort = uint16(util.MockPilotHTTPPort)
266	testEnv.IstioSrc = env.IstioSrc
267	testEnv.IstioOut = env.IstioOut
268
269	server.EnvoyXdsServer.ConfigUpdate(&model.PushRequest{Full: true})
270	defer tearDown()
271
272	adsResponse, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
273		Meta: model.NodeMetadata{
274			InstanceIPs:     []string{"99.1.1.1"}, // as service instance of ingress gateway
275			ConfigNamespace: "istio-system",
276			IstioVersion:    "1.3.0",
277		}.ToStruct(),
278		IP:        "99.1.1.1",
279		Namespace: "istio-system",
280		NodeType:  "router",
281	})
282
283	if err != nil {
284		t.Fatal(err)
285	}
286	defer adsResponse.Close()
287
288	adsResponse.Watch()
289
290	_, err = adsResponse.Wait(10*time.Second, "lds")
291	if err != nil {
292		t.Fatal("Failed to receive LDS response", err)
293		return
294	}
295
296	// Expect 2 listeners : 1 for 80, 1 for 443
297	// where 443 listener has 3 filter chains
298	if (len(adsResponse.GetHTTPListeners()) + len(adsResponse.GetTCPListeners())) != 2 {
299		t.Fatalf("Expected 2 listeners, got %d\n", len(adsResponse.GetHTTPListeners())+len(adsResponse.GetTCPListeners()))
300	}
301
302	// TODO: This is flimsy. The ADSC code treats any listener with http connection manager as a HTTP listener
303	// instead of looking at it as a listener with multiple filter chains
304	l := adsResponse.GetHTTPListeners()["0.0.0.0_443"]
305
306	if l != nil {
307		if len(l.FilterChains) != 3 {
308			t.Fatalf("Expected 3 filter chains, got %d\n", len(l.FilterChains))
309		}
310	}
311}
312
313// TestLDS is running LDSv2 tests.
314func TestLDS(t *testing.T) {
315	_, tearDown := initLocalPilotTestEnv(t)
316	defer tearDown()
317
318	t.Run("sidecar", func(t *testing.T) {
319		ldsr, cancel, err := connectADS(util.MockPilotGrpcAddr)
320		if err != nil {
321			t.Fatal(err)
322		}
323		defer cancel()
324		err = sendLDSReq(sidecarID(app3Ip, "app3"), ldsr)
325		if err != nil {
326			t.Fatal(err)
327		}
328
329		res, err := ldsr.Recv()
330		if err != nil {
331			t.Fatal("Failed to receive LDS", err)
332			return
333		}
334
335		strResponse, _ := gogoprotomarshal.ToJSONWithIndent(res, " ")
336		_ = ioutil.WriteFile(env.IstioOut+"/ldsv2_sidecar.json", []byte(strResponse), 0644)
337
338		if len(res.Resources) == 0 {
339			t.Fatal("No response")
340		}
341	})
342
343	// 'router' or 'gateway' type of listener
344	t.Run("gateway", func(t *testing.T) {
345		ldsr, cancel, err := connectADS(util.MockPilotGrpcAddr)
346		if err != nil {
347			t.Fatal(err)
348		}
349		defer cancel()
350		err = sendLDSReqWithLabels(gatewayID(gatewayIP), ldsr, map[string]string{"version": "v2", "app": "my-gateway-controller"})
351		if err != nil {
352			t.Fatal(err)
353		}
354
355		res, err := ldsr.Recv()
356		if err != nil {
357			t.Fatal("Failed to receive LDS", err)
358		}
359
360		strResponse, _ := gogoprotomarshal.ToJSONWithIndent(res, " ")
361
362		_ = ioutil.WriteFile(env.IstioOut+"/ldsv2_gateway.json", []byte(strResponse), 0644)
363
364		if len(res.Resources) == 0 {
365			t.Fatal("No response")
366		}
367	})
368
369	// TODO: compare with some golden once it's stable
370	// check that each mocked service and destination rule has a corresponding resource
371
372	// TODO: dynamic checks ( see EDS )
373}
374
375// TestLDS using sidecar scoped on workload without Service
376func TestLDSWithSidecarForWorkloadWithoutService(t *testing.T) {
377	server, tearDown := util.EnsureTestServer(func(args *bootstrap.PilotArgs) {
378		args.Plugins = bootstrap.DefaultPlugins
379		args.Config.FileDir = env.IstioSrc + "/tests/testdata/networking/sidecar-without-service"
380		args.Mesh.MixerAddress = ""
381		args.Mesh.ConfigFile = env.IstioSrc + "/tests/testdata/networking/sidecar-without-service/mesh.yaml"
382		args.Service.Registries = []string{}
383	})
384	registry := memServiceDiscovery(server, t)
385	registry.AddWorkload("98.1.1.1", labels.Instance{"app": "consumeronly"}) // These labels must match the sidecars workload selector
386
387	testEnv = testenv.NewTestSetup(testenv.SidecarConsumerOnlyTest, t)
388	testEnv.Ports().PilotGrpcPort = uint16(util.MockPilotGrpcPort)
389	testEnv.Ports().PilotHTTPPort = uint16(util.MockPilotHTTPPort)
390	testEnv.IstioSrc = env.IstioSrc
391	testEnv.IstioOut = env.IstioOut
392
393	server.EnvoyXdsServer.ConfigUpdate(&model.PushRequest{Full: true})
394	defer tearDown()
395
396	adsResponse, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
397		Meta: model.NodeMetadata{
398			InstanceIPs:     []string{"98.1.1.1"}, // as service instance of ingress gateway
399			ConfigNamespace: "consumerns",
400			IstioVersion:    "1.3.0",
401		}.ToStruct(),
402		IP:        "98.1.1.1",
403		Namespace: "consumerns", // namespace must match the namespace of the sidecar in the configs.yaml
404		NodeType:  "sidecar",
405	})
406
407	if err != nil {
408		t.Fatal(err)
409	}
410	defer adsResponse.Close()
411
412	adsResponse.Watch()
413
414	_, err = adsResponse.Wait(10*time.Second, "lds")
415	if err != nil {
416		t.Fatal("Failed to receive LDS response", err)
417		return
418	}
419
420	// Expect 2 HTTP listeners for outbound 8081 and one virtualInbound which has the same inbound 9080
421	// as a filter chain. Since the adsclient code treats any listener with a HTTP connection manager filter in ANY
422	// filter chain,  as a HTTP listener, we end up getting both 9080 and virtualInbound.
423	if len(adsResponse.GetHTTPListeners()) != 2 {
424		t.Fatalf("Expected 2 http listeners, got %d", len(adsResponse.GetHTTPListeners()))
425	}
426
427	// TODO: This is flimsy. The ADSC code treats any listener with http connection manager as a HTTP listener
428	// instead of looking at it as a listener with multiple filter chains
429	if l := adsResponse.GetHTTPListeners()["0.0.0.0_8081"]; l != nil {
430		expected := 2
431		if len(l.FilterChains) != expected {
432			t.Fatalf("Expected %d filter chains, got %d", expected, len(l.FilterChains))
433		}
434	} else {
435		t.Fatal("Expected listener for 0.0.0.0_8081")
436	}
437
438	if l := adsResponse.GetHTTPListeners()["virtualInbound"]; l == nil {
439		t.Fatal("Expected listener virtualInbound")
440	}
441
442	// Expect only one eds cluster for http1.ns1.svc.cluster.local
443	if len(adsResponse.GetEdsClusters()) != 1 {
444		t.Fatalf("Expected 1 eds cluster, got %d", len(adsResponse.GetEdsClusters()))
445	}
446	if cluster, ok := adsResponse.GetEdsClusters()["outbound|8081||http1.ns1.svc.cluster.local"]; !ok {
447		t.Fatalf("Expected eds cluster outbound|8081||http1.ns1.svc.cluster.local, got %v", cluster.Name)
448	}
449}
450
451// TestLDS using default sidecar in root namespace
452func TestLDSEnvoyFilterWithWorkloadSelector(t *testing.T) {
453	mesh.TestMode = true
454	server, tearDown := util.EnsureTestServer(func(args *bootstrap.PilotArgs) {
455		args.Plugins = bootstrap.DefaultPlugins
456		args.Config.FileDir = env.IstioSrc + "/tests/testdata/networking/envoyfilter-without-service"
457		args.Mesh.MixerAddress = ""
458		args.Mesh.ConfigFile = env.IstioSrc + "/tests/testdata/networking/envoyfilter-without-service/mesh.yaml"
459		args.Service.Registries = []string{}
460	})
461	registry := memServiceDiscovery(server, t)
462	// The labels of 98.1.1.1 must match the envoyfilter workload selector
463	registry.AddWorkload("98.1.1.1", labels.Instance{"app": "envoyfilter-test-app", "some": "otherlabel"})
464	registry.AddWorkload("98.1.1.2", labels.Instance{"app": "no-envoyfilter-test-app"})
465	registry.AddWorkload("98.1.1.3", labels.Instance{})
466
467	testEnv = testenv.NewTestSetup(testenv.SidecarConsumerOnlyTest, t)
468	testEnv.Ports().PilotGrpcPort = uint16(util.MockPilotGrpcPort)
469	testEnv.Ports().PilotHTTPPort = uint16(util.MockPilotHTTPPort)
470	testEnv.IstioSrc = env.IstioSrc
471	testEnv.IstioOut = env.IstioOut
472
473	server.EnvoyXdsServer.ConfigUpdate(&model.PushRequest{Full: true})
474	defer tearDown()
475
476	tests := []struct {
477		name            string
478		ip              string
479		expectLuaFilter bool
480	}{
481		{
482			name:            "Add filter with matching labels to sidecar",
483			ip:              "98.1.1.1",
484			expectLuaFilter: true,
485		},
486		{
487			name:            "Ignore filter with not matching labels to sidecar",
488			ip:              "98.1.1.2",
489			expectLuaFilter: false,
490		},
491		{
492			name:            "Ignore filter with empty labels to sidecar",
493			ip:              "98.1.1.3",
494			expectLuaFilter: false,
495		},
496	}
497
498	for _, test := range tests {
499		test := test
500		t.Run(test.name, func(t *testing.T) {
501			adsResponse, err := adsc.Dial(util.MockPilotGrpcAddr, "", &adsc.Config{
502				Meta: model.NodeMetadata{
503					InstanceIPs:     []string{test.ip}, // as service instance of ingress gateway
504					ConfigNamespace: "istio-system",
505					IstioVersion:    "1.4.0",
506				}.ToStruct(),
507				IP:        test.ip,
508				Namespace: "consumerns", // namespace must match the namespace of the sidecar in the configs.yaml
509				NodeType:  "sidecar",
510			})
511			if err != nil {
512				t.Fatal(err)
513			}
514			defer adsResponse.Close()
515
516			adsResponse.Watch()
517			_, err = adsResponse.Wait(10*time.Second, "lds")
518			if err != nil {
519				t.Fatal("Failed to receive LDS response", err)
520				return
521			}
522
523			// Expect 1 HTTP listeners for 8081, 1 hybrid listeners for 15006 (virtual inbound)
524			if len(adsResponse.GetHTTPListeners()) != 2 {
525				t.Fatalf("Expected 1 http listeners, got %d", len(adsResponse.GetHTTPListeners()))
526			}
527			// TODO: This is flimsy. The ADSC code treats any listener with http connection manager as a HTTP listener
528			// instead of looking at it as a listener with multiple filter chains
529			l := adsResponse.GetHTTPListeners()["0.0.0.0_8081"]
530
531			expectLuaFilter(t, l, test.expectLuaFilter)
532		})
533	}
534}
535
536func expectLuaFilter(t *testing.T, l *xdsapi.Listener, expected bool) {
537	if l != nil {
538		var chain *xdsapi_listener.FilterChain
539		for _, fc := range l.FilterChains {
540			if len(fc.Filters) == 1 && fc.Filters[0].Name == "envoy.http_connection_manager" {
541				chain = fc
542			}
543		}
544		if chain == nil {
545			t.Fatalf("Failed to find http_connection_manager")
546		}
547		if len(chain.Filters) != 1 {
548			t.Fatalf("Expected 1 filter in first filter chain, got %d", len(l.FilterChains))
549		}
550		filter := chain.Filters[0]
551		if filter.Name != "envoy.http_connection_manager" {
552			t.Fatalf("Expected HTTP connection, found %v", chain.Filters[0].Name)
553		}
554		httpCfg, ok := filter.ConfigType.(*xdsapi_listener.Filter_TypedConfig)
555		if !ok {
556			t.Fatalf("Expected Http Connection Manager Config Filter_TypedConfig, found %T", filter.ConfigType)
557		}
558		connectionManagerCfg := xdsapi_http_connection_manager.HttpConnectionManager{}
559		err := ptypes.UnmarshalAny(httpCfg.TypedConfig, &connectionManagerCfg)
560		if err != nil {
561			t.Fatalf("Could not deserialize http connection manager config: %v", err)
562		}
563		found := false
564		for _, filter := range connectionManagerCfg.HttpFilters {
565			if filter.Name == "envoy.lua" {
566				found = true
567			}
568		}
569		if expected != found {
570			t.Fatalf("Expected Lua filter: %v, found: %v", expected, found)
571		}
572	}
573}
574
575func memServiceDiscovery(server *bootstrap.Server, t *testing.T) *v2.MemServiceDiscovery {
576	index, found := server.ServiceController().GetRegistryIndex("v2-debug")
577	if !found {
578		t.Fatal("Could not find Mock ServiceRegistry")
579	}
580	registry, ok := server.ServiceController().GetRegistries()[index].(serviceregistry.Simple).ServiceDiscovery.(*v2.MemServiceDiscovery)
581	if !ok {
582		t.Fatal("Unexpected type of Mock ServiceRegistry")
583	}
584	return registry
585}
586
587// TODO: helper to test the http listener content
588// - file access log
589// - generate request id
590// - cors, fault, router filters
591// - tracing
592//
593