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.
14
15package route_test
16
17import (
18	"os"
19	"reflect"
20	"testing"
21	"time"
22
23	envoyroute "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
24	"github.com/gogo/protobuf/types"
25	"github.com/golang/protobuf/ptypes"
26	"github.com/onsi/gomega"
27
28	"istio.io/istio/pkg/util/gogo"
29
30	networking "istio.io/api/networking/v1alpha3"
31
32	"istio.io/istio/pilot/pkg/features"
33	"istio.io/istio/pilot/pkg/model"
34	"istio.io/istio/pilot/pkg/networking/core/v1alpha3/route"
35	"istio.io/istio/pkg/config/host"
36	"istio.io/istio/pkg/config/mesh"
37	"istio.io/istio/pkg/config/protocol"
38	"istio.io/istio/pkg/config/schema/collections"
39)
40
41func TestBuildHTTPRoutes(t *testing.T) {
42	serviceRegistry := map[host.Name]*model.Service{
43		"*.example.org": {
44			Hostname:    "*.example.org",
45			Address:     "1.1.1.1",
46			ClusterVIPs: make(map[string]string),
47			Ports: model.PortList{
48				&model.Port{
49					Name:     "default",
50					Port:     8080,
51					Protocol: protocol.HTTP,
52				},
53			},
54		},
55	}
56
57	node := &model.Proxy{
58		Type:        model.SidecarProxy,
59		IPAddresses: []string{"1.1.1.1"},
60		ID:          "someID",
61		DNSDomain:   "foo.com",
62		Metadata:    &model.NodeMetadata{},
63	}
64
65	gatewayNames := map[string]bool{"some-gateway": true}
66
67	t.Run("for virtual service", func(t *testing.T) {
68		g := gomega.NewGomegaWithT(t)
69
70		os.Setenv("ISTIO_DEFAULT_REQUEST_TIMEOUT", "0ms")
71		defer os.Unsetenv("ISTIO_DEFAULT_REQUEST_TIMEOUT")
72
73		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServicePlain, serviceRegistry, 8080, gatewayNames)
74
75		// Valiate routes.
76		for _, r := range routes {
77			if err := r.Validate(); err != nil {
78				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
79			}
80		}
81		g.Expect(err).NotTo(gomega.HaveOccurred())
82		g.Expect(len(routes)).To(gomega.Equal(1))
83		// Validate that when timeout is not specified, we disable it based on default value of flag.
84		g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(0)))
85		g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(0)))
86	})
87
88	t.Run("for virtual service with changed default timeout", func(t *testing.T) {
89		g := gomega.NewGomegaWithT(t)
90
91		dt := features.DefaultRequestTimeout
92		features.DefaultRequestTimeout = ptypes.DurationProto(1 * time.Second)
93		defer func() { features.DefaultRequestTimeout = dt }()
94
95		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServicePlain, serviceRegistry, 8080, gatewayNames)
96		// Valiate routes.
97		for _, r := range routes {
98			if err := r.Validate(); err != nil {
99				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
100			}
101		}
102
103		g.Expect(err).NotTo(gomega.HaveOccurred())
104		g.Expect(len(routes)).To(gomega.Equal(1))
105		// Validate that when timeout is not specified, we send what is set in the timeout flag.
106		g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(1)))
107		g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(1)))
108	})
109
110	t.Run("for virtual service with timeout", func(t *testing.T) {
111		g := gomega.NewGomegaWithT(t)
112
113		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithTimeout, serviceRegistry, 8080, gatewayNames)
114		// Valiate routes.
115		for _, r := range routes {
116			if err := r.Validate(); err != nil {
117				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
118			}
119		}
120
121		g.Expect(err).NotTo(gomega.HaveOccurred())
122		g.Expect(len(routes)).To(gomega.Equal(1))
123		// Validate that when timeout specified, we send the configured timeout to Envoys.
124		g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(10)))
125		g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(10)))
126	})
127
128	t.Run("for virtual service with disabled timeout", func(t *testing.T) {
129		g := gomega.NewGomegaWithT(t)
130
131		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithTimeoutDisabled, serviceRegistry, 8080, gatewayNames)
132		// Valiate routes.
133		for _, r := range routes {
134			if err := r.Validate(); err != nil {
135				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
136			}
137		}
138
139		g.Expect(err).NotTo(gomega.HaveOccurred())
140		g.Expect(len(routes)).To(gomega.Equal(1))
141		g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(0)))
142		g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(0)))
143	})
144
145	t.Run("for virtual service with catch all route", func(t *testing.T) {
146		g := gomega.NewGomegaWithT(t)
147		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithCatchAllRoute, serviceRegistry, 8080, gatewayNames)
148		// Valiate routes.
149		for _, r := range routes {
150			if err := r.Validate(); err != nil {
151				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
152			}
153		}
154		g.Expect(err).NotTo(gomega.HaveOccurred())
155		g.Expect(len(routes)).To(gomega.Equal(1))
156	})
157
158	t.Run("for virtual service with top level catch all route", func(t *testing.T) {
159		g := gomega.NewGomegaWithT(t)
160
161		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithCatchAllRouteWeightedDestination, serviceRegistry, 8080, gatewayNames)
162		// Valiate routes.
163		for _, r := range routes {
164			if err := r.Validate(); err != nil {
165				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
166			}
167		}
168		g.Expect(err).NotTo(gomega.HaveOccurred())
169		g.Expect(len(routes)).To(gomega.Equal(1))
170	})
171
172	t.Run("for virtual service with multi prefix catch all route", func(t *testing.T) {
173		g := gomega.NewGomegaWithT(t)
174
175		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithCatchAllMultiPrefixRoute, serviceRegistry, 8080, gatewayNames)
176		// Valiate routes.
177		for _, r := range routes {
178			if err := r.Validate(); err != nil {
179				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
180			}
181		}
182		g.Expect(err).NotTo(gomega.HaveOccurred())
183		g.Expect(len(routes)).To(gomega.Equal(1))
184	})
185
186	t.Run("for virtual service with regex matching on URI", func(t *testing.T) {
187		g := gomega.NewGomegaWithT(t)
188
189		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithRegexMatchingOnURI, serviceRegistry, 8080, gatewayNames)
190		// Valiate routes.
191		for _, r := range routes {
192			if err := r.Validate(); err != nil {
193				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
194			}
195		}
196		g.Expect(err).NotTo(gomega.HaveOccurred())
197		g.Expect(len(routes)).To(gomega.Equal(1))
198		g.Expect(routes[0].GetMatch().GetSafeRegex().GetRegex()).To(gomega.Equal("\\/(.?)\\/status"))
199		g.Expect(routes[0].GetMatch().GetSafeRegex().GetGoogleRe2().GetMaxProgramSize().GetValue()).To(gomega.Equal(uint32(1024)))
200
201	})
202
203	t.Run("for virtual service with regex matching on header", func(t *testing.T) {
204		g := gomega.NewGomegaWithT(t)
205
206		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithRegexMatchingOnHeader, serviceRegistry, 8080, gatewayNames)
207		// Valiate routes.
208		for _, r := range routes {
209			if err := r.Validate(); err != nil {
210				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
211			}
212		}
213		g.Expect(err).NotTo(gomega.HaveOccurred())
214		g.Expect(len(routes)).To(gomega.Equal(1))
215		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetSafeRegexMatch().GetRegex()).To(gomega.Equal("Bearer .+?\\..+?\\..+?"))
216	})
217
218	t.Run("for virtual service with regex matching on without_header", func(t *testing.T) {
219		g := gomega.NewGomegaWithT(t)
220
221		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithRegexMatchingOnWithoutHeader, serviceRegistry, 8080, gatewayNames)
222		// Valiate routes.
223		for _, r := range routes {
224			if err := r.Validate(); err != nil {
225				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
226			}
227		}
228		g.Expect(err).NotTo(gomega.HaveOccurred())
229		g.Expect(len(routes)).To(gomega.Equal(1))
230		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetSafeRegexMatch().GetRegex()).To(gomega.Equal("BAR .+?\\..+?\\..+?"))
231		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(true))
232	})
233
234	t.Run("for virtual service with presence matching on header", func(t *testing.T) {
235		g := gomega.NewGomegaWithT(t)
236
237		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithPresentMatchingOnHeader, serviceRegistry, 8080, gatewayNames)
238		// Valiate routes.
239		for _, r := range routes {
240			if err := r.Validate(); err != nil {
241				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
242			}
243		}
244		g.Expect(err).NotTo(gomega.HaveOccurred())
245		g.Expect(len(routes)).To(gomega.Equal(1))
246		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
247		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
248		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(false))
249	})
250
251	t.Run("for virtual service with presence matching on header and without_header", func(t *testing.T) {
252		g := gomega.NewGomegaWithT(t)
253
254		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithPresentMatchingOnWithoutHeader, serviceRegistry, 8080, gatewayNames)
255		// Valiate routes.
256		for _, r := range routes {
257			if err := r.Validate(); err != nil {
258				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
259			}
260		}
261		g.Expect(err).NotTo(gomega.HaveOccurred())
262		g.Expect(len(routes)).To(gomega.Equal(1))
263		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
264		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
265		g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(true))
266	})
267
268	t.Run("for virtual service with regex matching for all cases on header", func(t *testing.T) {
269
270		cset := createVirtualServiceWithRegexMatchingForAllCasesOnHeader()
271
272		for _, c := range cset {
273			g := gomega.NewGomegaWithT(t)
274			routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, *c, serviceRegistry, 8080, gatewayNames)
275			// Valiate routes.
276			for _, r := range routes {
277				if err := r.Validate(); err != nil {
278					t.Fatalf("Route %s validation failed with error %v", r.Name, err)
279				}
280			}
281			g.Expect(err).NotTo(gomega.HaveOccurred())
282			g.Expect(len(routes)).To(gomega.Equal(1))
283			g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
284			g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
285			g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(false))
286		}
287	})
288
289	t.Run("for virtual service with source namespace matching", func(t *testing.T) {
290		g := gomega.NewGomegaWithT(t)
291
292		fooNode := *node
293		fooNode.Metadata = &model.NodeMetadata{
294			Namespace: "foo",
295		}
296		barNode := *node
297		barNode.Metadata = &model.NodeMetadata{
298			Namespace: "bar",
299		}
300
301		routes, err := route.BuildHTTPRoutesForVirtualService(&fooNode, nil, virtualServiceMatchingOnSourceNamespace, serviceRegistry, 8080, gatewayNames)
302		// Valiate routes.
303		for _, r := range routes {
304			if err := r.Validate(); err != nil {
305				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
306			}
307		}
308		g.Expect(err).NotTo(gomega.HaveOccurred())
309		g.Expect(len(routes)).To(gomega.Equal(1))
310		g.Expect(routes[0].GetName()).To(gomega.Equal("foo"))
311
312		routes, err = route.BuildHTTPRoutesForVirtualService(&barNode, nil, virtualServiceMatchingOnSourceNamespace, serviceRegistry, 8080, gatewayNames)
313		g.Expect(err).NotTo(gomega.HaveOccurred())
314		g.Expect(len(routes)).To(gomega.Equal(1))
315		g.Expect(routes[0].GetName()).To(gomega.Equal("bar"))
316	})
317
318	t.Run("for virtual service with ring hash", func(t *testing.T) {
319		g := gomega.NewGomegaWithT(t)
320
321		ttl := types.Duration{Nanos: 100}
322		meshConfig := mesh.DefaultMeshConfig()
323		push := &model.PushContext{
324			Mesh: &meshConfig,
325		}
326		push.SetDestinationRules([]model.Config{
327			{
328				ConfigMeta: model.ConfigMeta{
329					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
330					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
331					Name:    "acme",
332				},
333				Spec: &networking.DestinationRule{
334					Host: "*.example.org",
335					TrafficPolicy: &networking.TrafficPolicy{
336						LoadBalancer: &networking.LoadBalancerSettings{
337							LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
338								ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
339									HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
340										HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
341											Name: "hash-cookie",
342											Ttl:  &ttl,
343										},
344									},
345								},
346							},
347						},
348					},
349				},
350			},
351		})
352
353		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualServicePlain, serviceRegistry, 8080, gatewayNames)
354		// Valiate routes.
355		for _, r := range routes {
356			if err := r.Validate(); err != nil {
357				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
358			}
359		}
360		g.Expect(err).NotTo(gomega.HaveOccurred())
361		g.Expect(len(routes)).To(gomega.Equal(1))
362
363		hashPolicy := &envoyroute.RouteAction_HashPolicy{
364			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
365				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
366					Name: "hash-cookie",
367					Ttl:  gogo.DurationToProtoDuration(&ttl),
368				},
369			},
370		}
371		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
372	})
373
374	t.Run("for virtual service with query param based ring hash", func(t *testing.T) {
375		g := gomega.NewGomegaWithT(t)
376
377		meshConfig := mesh.DefaultMeshConfig()
378		push := &model.PushContext{
379			Mesh: &meshConfig,
380		}
381		push.SetDestinationRules([]model.Config{
382			{
383				ConfigMeta: model.ConfigMeta{
384					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
385					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
386					Name:    "acme",
387				},
388				Spec: &networking.DestinationRule{
389					Host: "*.example.org",
390					TrafficPolicy: &networking.TrafficPolicy{
391						LoadBalancer: &networking.LoadBalancerSettings{
392							LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
393								ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
394									HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpQueryParameterName{
395										HttpQueryParameterName: "query",
396									},
397								},
398							},
399						},
400					},
401				},
402			},
403		})
404
405		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualServicePlain, serviceRegistry, 8080, gatewayNames)
406		// Valiate routes.
407		for _, r := range routes {
408			if err := r.Validate(); err != nil {
409				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
410			}
411		}
412		g.Expect(err).NotTo(gomega.HaveOccurred())
413		g.Expect(len(routes)).To(gomega.Equal(1))
414
415		hashPolicy := &envoyroute.RouteAction_HashPolicy{
416			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_QueryParameter_{
417				QueryParameter: &envoyroute.RouteAction_HashPolicy_QueryParameter{
418					Name: "query",
419				},
420			},
421		}
422		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
423	})
424
425	t.Run("for virtual service with subsets with ring hash", func(t *testing.T) {
426		g := gomega.NewGomegaWithT(t)
427
428		virtualService := model.Config{
429			ConfigMeta: model.ConfigMeta{
430				Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
431				Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
432				Name:    "acme",
433			},
434			Spec: virtualServiceWithSubset,
435		}
436
437		meshConfig := mesh.DefaultMeshConfig()
438		push := &model.PushContext{
439			Mesh: &meshConfig,
440		}
441		push.SetDestinationRules([]model.Config{
442			{
443				ConfigMeta: model.ConfigMeta{
444					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
445					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
446					Name:    "acme",
447				},
448				Spec: &networking.DestinationRule{
449					Host:    "*.example.org",
450					Subsets: []*networking.Subset{networkingSubset},
451				},
452			},
453		})
454
455		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualService, serviceRegistry, 8080, gatewayNames)
456		// Valiate routes.
457		for _, r := range routes {
458			if err := r.Validate(); err != nil {
459				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
460			}
461		}
462		g.Expect(err).NotTo(gomega.HaveOccurred())
463		g.Expect(len(routes)).To(gomega.Equal(1))
464
465		hashPolicy := &envoyroute.RouteAction_HashPolicy{
466			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
467				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
468					Name: "other-cookie",
469					Ttl:  nil,
470				},
471			},
472		}
473		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
474	})
475
476	t.Run("for virtual service with subsets with port level settings with ring hash", func(t *testing.T) {
477		g := gomega.NewGomegaWithT(t)
478
479		virtualService := model.Config{ConfigMeta: model.ConfigMeta{Type: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
480			Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
481			Name:    "acme",
482		},
483			Spec: virtualServiceWithSubsetWithPortLevelSettings,
484		}
485
486		meshConfig := mesh.DefaultMeshConfig()
487		push := &model.PushContext{
488			Mesh: &meshConfig,
489		}
490
491		push.SetDestinationRules([]model.Config{
492			{
493
494				ConfigMeta: model.ConfigMeta{
495					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
496					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
497					Name:    "acme",
498				},
499				Spec: portLevelDestinationRuleWithSubsetPolicy,
500			}})
501
502		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualService, serviceRegistry, 8080, gatewayNames)
503		// Valiate routes.
504		for _, r := range routes {
505			if err := r.Validate(); err != nil {
506				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
507			}
508		}
509		g.Expect(err).NotTo(gomega.HaveOccurred())
510		g.Expect(len(routes)).To(gomega.Equal(1))
511
512		hashPolicy := &envoyroute.RouteAction_HashPolicy{
513			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
514				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
515					Name: "port-level-settings-cookie",
516					Ttl:  nil,
517				},
518			},
519		}
520		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
521	})
522
523	t.Run("for virtual service with subsets and top level traffic policy with ring hash", func(t *testing.T) {
524		g := gomega.NewGomegaWithT(t)
525
526		virtualService := model.Config{
527			ConfigMeta: model.ConfigMeta{
528				Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
529				Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
530				Name:    "acme",
531			},
532			Spec: virtualServiceWithSubset,
533		}
534
535		cnfg := model.Config{
536			ConfigMeta: model.ConfigMeta{
537				Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
538				Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
539				Name:    "acme",
540			},
541		}
542		rule := networkingDestinationRule
543		rule.Subsets = []*networking.Subset{networkingSubset}
544		cnfg.Spec = networkingDestinationRule
545
546		meshConfig := mesh.DefaultMeshConfig()
547		push := &model.PushContext{
548			Mesh: &meshConfig,
549		}
550
551		push.SetDestinationRules([]model.Config{
552			cnfg})
553
554		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualService, serviceRegistry, 8080, gatewayNames)
555		// Valiate routes.
556		for _, r := range routes {
557			if err := r.Validate(); err != nil {
558				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
559			}
560		}
561		g.Expect(err).NotTo(gomega.HaveOccurred())
562		g.Expect(len(routes)).To(gomega.Equal(1))
563
564		hashPolicy := &envoyroute.RouteAction_HashPolicy{
565			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
566				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
567					Name: "other-cookie",
568					Ttl:  nil,
569				},
570			},
571		}
572		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
573	})
574
575	t.Run("port selector based traffic policy", func(t *testing.T) {
576		g := gomega.NewGomegaWithT(t)
577
578		meshConfig := mesh.DefaultMeshConfig()
579		push := &model.PushContext{
580			Mesh: &meshConfig,
581		}
582
583		push.SetDestinationRules([]model.Config{
584			{
585				ConfigMeta: model.ConfigMeta{
586					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
587					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
588					Name:    "acme",
589				},
590				Spec: portLevelDestinationRule,
591			}})
592
593		gatewayNames := map[string]bool{"some-gateway": true}
594		routes, err := route.BuildHTTPRoutesForVirtualService(node, push, virtualServicePlain, serviceRegistry, 8080, gatewayNames)
595		// Valiate routes.
596		for _, r := range routes {
597			if err := r.Validate(); err != nil {
598				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
599			}
600		}
601		g.Expect(err).NotTo(gomega.HaveOccurred())
602		g.Expect(len(routes)).To(gomega.Equal(1))
603
604		hashPolicy := &envoyroute.RouteAction_HashPolicy{
605			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
606				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
607					Name: "hash-cookie",
608					Ttl:  nil,
609				},
610			},
611		}
612		g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
613	})
614
615	t.Run("for redirect code", func(t *testing.T) {
616		g := gomega.NewGomegaWithT(t)
617
618		routes, err := route.BuildHTTPRoutesForVirtualService(node, nil, virtualServiceWithRedirect,
619			serviceRegistry, 8080, gatewayNames)
620		// Valiate routes.
621		for _, r := range routes {
622			if err := r.Validate(); err != nil {
623				t.Fatalf("Route %s validation failed with error %v", r.Name, err)
624			}
625		}
626		g.Expect(err).NotTo(gomega.HaveOccurred())
627		g.Expect(len(routes)).To(gomega.Equal(1))
628
629		redirectAction, ok := routes[0].Action.(*envoyroute.Route_Redirect)
630		g.Expect(ok).NotTo(gomega.BeFalse())
631		g.Expect(redirectAction.Redirect.ResponseCode).To(gomega.Equal(envoyroute.RedirectAction_PERMANENT_REDIRECT))
632	})
633	t.Run("for no virtualservice but has destinationrule with consistentHash loadbalancer", func(t *testing.T) {
634		g := gomega.NewGomegaWithT(t)
635		meshConfig := mesh.DefaultMeshConfig()
636		push := &model.PushContext{
637			Mesh: &meshConfig,
638		}
639		push.SetDestinationRules([]model.Config{
640			{
641				ConfigMeta: model.ConfigMeta{
642					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
643					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
644					Name:    "acme",
645				},
646				Spec: networkingDestinationRule,
647			}})
648		vhosts := route.BuildSidecarVirtualHostsFromConfigAndRegistry(node, push, serviceRegistry, []model.Config{}, 8080)
649		g.Expect(vhosts[0].Routes[0].Action.(*envoyroute.Route_Route).Route.HashPolicy).NotTo(gomega.BeNil())
650	})
651	t.Run("for no virtualservice but has destinationrule with portLevel consistentHash loadbalancer", func(t *testing.T) {
652		g := gomega.NewGomegaWithT(t)
653		meshConfig := mesh.DefaultMeshConfig()
654		push := &model.PushContext{
655			Mesh: &meshConfig,
656		}
657		push.SetDestinationRules([]model.Config{
658			{
659				ConfigMeta: model.ConfigMeta{
660					Type:    collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Kind(),
661					Version: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().Version(),
662					Name:    "acme",
663				},
664				Spec: networkingDestinationRuleWithPortLevelTrafficPolicy,
665			}})
666
667		vhosts := route.BuildSidecarVirtualHostsFromConfigAndRegistry(node, push, serviceRegistry, []model.Config{}, 8080)
668
669		hashPolicy := &envoyroute.RouteAction_HashPolicy{
670			PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
671				Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
672					Name: "hash-cookie-1",
673				},
674			},
675		}
676		g.Expect(vhosts[0].Routes[0].Action.(*envoyroute.Route_Route).Route.HashPolicy).To(gomega.ConsistOf(hashPolicy))
677	})
678}
679
680func loadBalancerPolicy(name string) *networking.LoadBalancerSettings_ConsistentHash {
681	return &networking.LoadBalancerSettings_ConsistentHash{
682		ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
683			HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
684				HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
685					Name: name,
686				},
687			},
688		},
689	}
690}
691
692var virtualServiceWithSubset = &networking.VirtualService{
693	Hosts:    []string{},
694	Gateways: []string{"some-gateway"},
695	Http: []*networking.HTTPRoute{
696		{
697			Route: []*networking.HTTPRouteDestination{
698				{
699					Destination: &networking.Destination{
700						Subset: "some-subset",
701						Host:   "*.example.org",
702						Port: &networking.PortSelector{
703							Number: 65000,
704						},
705					},
706					Weight: 100,
707				},
708			},
709		},
710	},
711}
712
713var virtualServiceWithSubsetWithPortLevelSettings = &networking.VirtualService{
714	Hosts:    []string{},
715	Gateways: []string{"some-gateway"},
716	Http: []*networking.HTTPRoute{
717		{
718			Route: []*networking.HTTPRouteDestination{
719				{
720					Destination: &networking.Destination{
721						Subset: "port-level-settings-subset",
722						Host:   "*.example.org",
723						Port: &networking.PortSelector{
724							Number: 8484,
725						},
726					},
727					Weight: 100,
728				},
729			},
730		},
731	},
732}
733
734var virtualServicePlain = model.Config{
735	ConfigMeta: model.ConfigMeta{
736		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
737		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
738		Name:    "acme",
739	},
740	Spec: &networking.VirtualService{
741		Hosts:    []string{},
742		Gateways: []string{"some-gateway"},
743		Http: []*networking.HTTPRoute{
744			{
745				Route: []*networking.HTTPRouteDestination{
746					{
747						Destination: &networking.Destination{
748							Host: "*.example.org",
749							Port: &networking.PortSelector{
750								Number: 8484,
751							},
752						},
753						Weight: 100,
754					},
755				},
756			},
757		},
758	},
759}
760
761var virtualServiceWithTimeout = model.Config{
762	ConfigMeta: model.ConfigMeta{
763		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
764		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
765		Name:    "acme",
766	},
767	Spec: &networking.VirtualService{
768		Hosts:    []string{},
769		Gateways: []string{"some-gateway"},
770		Http: []*networking.HTTPRoute{
771			{
772				Route: []*networking.HTTPRouteDestination{
773					{
774						Destination: &networking.Destination{
775							Host: "*.example.org",
776							Port: &networking.PortSelector{
777								Number: 8484,
778							},
779						},
780						Weight: 100,
781					},
782				},
783				Timeout: &types.Duration{
784					Seconds: 10,
785				},
786			},
787		},
788	},
789}
790
791var virtualServiceWithTimeoutDisabled = model.Config{
792	ConfigMeta: model.ConfigMeta{
793		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
794		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
795		Name:    "acme",
796	},
797	Spec: &networking.VirtualService{
798		Hosts:    []string{},
799		Gateways: []string{"some-gateway"},
800		Http: []*networking.HTTPRoute{
801			{
802				Route: []*networking.HTTPRouteDestination{
803					{
804						Destination: &networking.Destination{
805							Host: "*.example.org",
806							Port: &networking.PortSelector{
807								Number: 8484,
808							},
809						},
810						Weight: 100,
811					},
812				},
813				Timeout: &types.Duration{
814					Seconds: 0,
815				},
816			},
817		},
818	},
819}
820
821var virtualServiceWithCatchAllRoute = model.Config{
822	ConfigMeta: model.ConfigMeta{
823		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
824		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
825		Name:    "acme",
826	},
827	Spec: &networking.VirtualService{
828		Hosts:    []string{},
829		Gateways: []string{"some-gateway"},
830		Http: []*networking.HTTPRoute{
831			{
832				Match: []*networking.HTTPMatchRequest{
833					{
834						Name: "non-catch-all",
835						Uri: &networking.StringMatch{
836							MatchType: &networking.StringMatch_Prefix{
837								Prefix: "/route/v1",
838							},
839						},
840					},
841					{
842						Name: "catch-all",
843						Uri: &networking.StringMatch{
844							MatchType: &networking.StringMatch_Prefix{
845								Prefix: "/",
846							},
847						},
848					},
849				},
850				Route: []*networking.HTTPRouteDestination{
851					{
852						Destination: &networking.Destination{
853							Host: "*.example.org",
854							Port: &networking.PortSelector{
855								Number: 8484,
856							},
857						},
858						Weight: 100,
859					},
860				},
861			},
862		},
863	},
864}
865
866var virtualServiceWithCatchAllMultiPrefixRoute = model.Config{
867	ConfigMeta: model.ConfigMeta{
868		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
869		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
870		Name:    "acme",
871	},
872	Spec: &networking.VirtualService{
873		Hosts:    []string{},
874		Gateways: []string{"some-gateway"},
875		Http: []*networking.HTTPRoute{
876			{
877				Match: []*networking.HTTPMatchRequest{
878					{
879						Name: "catch-all",
880						Uri: &networking.StringMatch{
881							MatchType: &networking.StringMatch_Prefix{
882								Prefix: "/",
883							},
884						},
885						SourceLabels: map[string]string{
886							"matchingNoSrc": "xxx",
887						},
888					},
889					{
890						Name: "specific match",
891						Uri: &networking.StringMatch{
892							MatchType: &networking.StringMatch_Prefix{
893								Prefix: "/a",
894							},
895						},
896					},
897				},
898				Route: []*networking.HTTPRouteDestination{
899					{
900						Destination: &networking.Destination{
901							Host: "*.example.org",
902							Port: &networking.PortSelector{
903								Number: 8484,
904							},
905						},
906						Weight: 100,
907					},
908				},
909			},
910		},
911	},
912}
913
914var virtualServiceWithCatchAllRouteWeightedDestination = model.Config{
915	ConfigMeta: model.ConfigMeta{
916		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
917		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
918		Name:    "acme",
919	},
920	Spec: &networking.VirtualService{
921		Hosts:    []string{"headers.test.istio.io"},
922		Gateways: []string{"some-gateway"},
923		Http: []*networking.HTTPRoute{
924			{
925				Match: []*networking.HTTPMatchRequest{
926					{
927						Name: "headers-only",
928						Headers: map[string]*networking.StringMatch{
929							"version": {
930								MatchType: &networking.StringMatch_Exact{
931									Exact: "v2",
932								},
933							},
934						},
935						SourceLabels: map[string]string{
936							"version": "v1",
937						},
938					},
939				},
940				Route: []*networking.HTTPRouteDestination{
941					{
942						Destination: &networking.Destination{
943							Host:   "c-weighted.extsvc.com",
944							Subset: "v2",
945						},
946						Weight: 100,
947					},
948				},
949			},
950			{
951				Route: []*networking.HTTPRouteDestination{
952					{
953						Destination: &networking.Destination{
954							Host:   "c-weighted.extsvc.com",
955							Subset: "v1",
956						},
957						Weight: 100,
958					},
959				},
960			},
961		},
962	},
963}
964
965var virtualServiceWithRedirect = model.Config{
966	ConfigMeta: model.ConfigMeta{
967		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
968		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
969		Name:    "acme",
970	},
971	Spec: &networking.VirtualService{
972		Hosts:    []string{},
973		Gateways: []string{"some-gateway"},
974		Http: []*networking.HTTPRoute{
975			{
976				Redirect: &networking.HTTPRedirect{
977					Uri:          "example.org",
978					Authority:    "some-authority.default.svc.cluster.local",
979					RedirectCode: 308,
980				},
981			},
982		},
983	},
984}
985
986var virtualServiceWithRegexMatchingOnURI = model.Config{
987	ConfigMeta: model.ConfigMeta{
988		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
989		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
990		Name:    "acme",
991	},
992	Spec: &networking.VirtualService{
993		Hosts:    []string{},
994		Gateways: []string{"some-gateway"},
995		Http: []*networking.HTTPRoute{
996			{
997				Match: []*networking.HTTPMatchRequest{
998					{
999						Name: "status",
1000						Uri: &networking.StringMatch{
1001							MatchType: &networking.StringMatch_Regex{
1002								Regex: "\\/(.?)\\/status",
1003							},
1004						},
1005					},
1006				},
1007				Redirect: &networking.HTTPRedirect{
1008					Uri:          "example.org",
1009					Authority:    "some-authority.default.svc.cluster.local",
1010					RedirectCode: 308,
1011				},
1012			},
1013		},
1014	},
1015}
1016
1017var virtualServiceWithRegexMatchingOnHeader = model.Config{
1018	ConfigMeta: model.ConfigMeta{
1019		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1020		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1021		Name:    "acme",
1022	},
1023	Spec: &networking.VirtualService{
1024		Hosts:    []string{},
1025		Gateways: []string{"some-gateway"},
1026		Http: []*networking.HTTPRoute{
1027			{
1028				Match: []*networking.HTTPMatchRequest{
1029					{
1030						Name: "auth",
1031						Headers: map[string]*networking.StringMatch{
1032							"Authentication": {
1033								MatchType: &networking.StringMatch_Regex{
1034									Regex: "Bearer .+?\\..+?\\..+?",
1035								},
1036							},
1037						},
1038					},
1039				},
1040				Redirect: &networking.HTTPRedirect{
1041					Uri:          "example.org",
1042					Authority:    "some-authority.default.svc.cluster.local",
1043					RedirectCode: 308,
1044				},
1045			},
1046		},
1047	},
1048}
1049
1050func createVirtualServiceWithRegexMatchingForAllCasesOnHeader() []*model.Config {
1051	ret := []*model.Config{}
1052	regex := "*"
1053	ret = append(ret, &model.Config{
1054		ConfigMeta: model.ConfigMeta{
1055			Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1056			Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1057			Name:    "acme",
1058		},
1059		Spec: &networking.VirtualService{
1060			Hosts:    []string{},
1061			Gateways: []string{"some-gateway"},
1062			Http: []*networking.HTTPRoute{
1063				{
1064					Match: []*networking.HTTPMatchRequest{
1065						{
1066							Name: "presence",
1067							Headers: map[string]*networking.StringMatch{
1068								"FOO-HEADER": {
1069									MatchType: &networking.StringMatch_Regex{
1070										Regex: regex,
1071									},
1072								},
1073							},
1074						},
1075					},
1076					Redirect: &networking.HTTPRedirect{
1077						Uri:          "example.org",
1078						Authority:    "some-authority.default.svc.cluster.local",
1079						RedirectCode: 308,
1080					},
1081				},
1082			},
1083		},
1084	})
1085
1086	return ret
1087}
1088
1089var virtualServiceWithRegexMatchingOnWithoutHeader = model.Config{
1090	ConfigMeta: model.ConfigMeta{
1091		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1092		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1093		Name:    "acme",
1094	},
1095	Spec: &networking.VirtualService{
1096		Hosts:    []string{},
1097		Gateways: []string{"some-gateway"},
1098		Http: []*networking.HTTPRoute{
1099			{
1100				Match: []*networking.HTTPMatchRequest{
1101					{
1102						Name: "without-test",
1103						WithoutHeaders: map[string]*networking.StringMatch{
1104							"FOO-HEADER": {
1105								MatchType: &networking.StringMatch_Regex{
1106									Regex: "BAR .+?\\..+?\\..+?",
1107								},
1108							},
1109						},
1110					},
1111				},
1112				Redirect: &networking.HTTPRedirect{
1113					Uri:          "example.org",
1114					Authority:    "some-authority.default.svc.cluster.local",
1115					RedirectCode: 308,
1116				},
1117			},
1118		},
1119	},
1120}
1121
1122var virtualServiceWithPresentMatchingOnHeader = model.Config{
1123	ConfigMeta: model.ConfigMeta{
1124		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1125		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1126		Name:    "acme",
1127	},
1128	Spec: &networking.VirtualService{
1129		Hosts:    []string{},
1130		Gateways: []string{"some-gateway"},
1131		Http: []*networking.HTTPRoute{
1132			{
1133				Match: []*networking.HTTPMatchRequest{
1134					{
1135						Name: "presence",
1136						Headers: map[string]*networking.StringMatch{
1137							"FOO-HEADER": nil,
1138						},
1139					},
1140				},
1141				Redirect: &networking.HTTPRedirect{
1142					Uri:          "example.org",
1143					Authority:    "some-authority.default.svc.cluster.local",
1144					RedirectCode: 308,
1145				},
1146			},
1147		},
1148	},
1149}
1150
1151var virtualServiceWithPresentMatchingOnWithoutHeader = model.Config{
1152	ConfigMeta: model.ConfigMeta{
1153		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1154		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1155		Name:    "acme",
1156	},
1157	Spec: &networking.VirtualService{
1158		Hosts:    []string{},
1159		Gateways: []string{"some-gateway"},
1160		Http: []*networking.HTTPRoute{
1161			{
1162				Match: []*networking.HTTPMatchRequest{
1163					{
1164						Name: "presence",
1165						WithoutHeaders: map[string]*networking.StringMatch{
1166							"FOO-HEADER": nil,
1167						},
1168					},
1169				},
1170				Redirect: &networking.HTTPRedirect{
1171					Uri:          "example.org",
1172					Authority:    "some-authority.default.svc.cluster.local",
1173					RedirectCode: 308,
1174				},
1175			},
1176		},
1177	},
1178}
1179
1180var virtualServiceMatchingOnSourceNamespace = model.Config{
1181	ConfigMeta: model.ConfigMeta{
1182		Type:    collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Kind(),
1183		Version: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().Version(),
1184		Name:    "acme",
1185	},
1186	Spec: &networking.VirtualService{
1187		Hosts: []string{},
1188		Http: []*networking.HTTPRoute{
1189			{
1190				Name: "foo",
1191				Match: []*networking.HTTPMatchRequest{
1192					{
1193						SourceNamespace: "foo",
1194					},
1195				},
1196				Route: []*networking.HTTPRouteDestination{
1197					{
1198						Destination: &networking.Destination{
1199							Host: "foo.example.org",
1200							Port: &networking.PortSelector{
1201								Number: 8484,
1202							},
1203						},
1204						Weight: 100,
1205					},
1206				},
1207			},
1208			{
1209				Name: "bar",
1210				Match: []*networking.HTTPMatchRequest{
1211					{
1212						SourceNamespace: "bar",
1213					},
1214				},
1215				Route: []*networking.HTTPRouteDestination{
1216					{
1217						Destination: &networking.Destination{
1218							Host: "bar.example.org",
1219							Port: &networking.PortSelector{
1220								Number: 8484,
1221							},
1222						},
1223						Weight: 100,
1224					},
1225				},
1226			},
1227		},
1228	},
1229}
1230
1231var portLevelDestinationRule = &networking.DestinationRule{
1232	Host:    "*.example.org",
1233	Subsets: []*networking.Subset{},
1234	TrafficPolicy: &networking.TrafficPolicy{
1235		PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
1236			{
1237				LoadBalancer: &networking.LoadBalancerSettings{
1238					LbPolicy: loadBalancerPolicy("hash-cookie"),
1239				},
1240				Port: &networking.PortSelector{
1241					Number: 8484,
1242				},
1243			},
1244		},
1245	},
1246}
1247
1248var portLevelDestinationRuleWithSubsetPolicy = &networking.DestinationRule{
1249	Host:    "*.example.org",
1250	Subsets: []*networking.Subset{networkingSubsetWithPortLevelSettings},
1251	TrafficPolicy: &networking.TrafficPolicy{
1252		PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
1253			{
1254				LoadBalancer: &networking.LoadBalancerSettings{
1255					LbPolicy: loadBalancerPolicy("hash-cookie"),
1256				},
1257				Port: &networking.PortSelector{
1258					Number: 8484,
1259				},
1260			},
1261		},
1262	},
1263}
1264
1265var networkingDestinationRule = &networking.DestinationRule{
1266	Host:    "*.example.org",
1267	Subsets: []*networking.Subset{},
1268	TrafficPolicy: &networking.TrafficPolicy{
1269		LoadBalancer: &networking.LoadBalancerSettings{
1270			LbPolicy: loadBalancerPolicy("hash-cookie"),
1271		},
1272	},
1273}
1274var networkingDestinationRuleWithPortLevelTrafficPolicy = &networking.DestinationRule{
1275	Host: "*.example.org",
1276	TrafficPolicy: &networking.TrafficPolicy{
1277		LoadBalancer: &networking.LoadBalancerSettings{
1278			LbPolicy: loadBalancerPolicy("hash-cookie"),
1279		},
1280		PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
1281			{
1282				LoadBalancer: &networking.LoadBalancerSettings{
1283					LbPolicy: loadBalancerPolicy("hash-cookie-1"),
1284				},
1285				Port: &networking.PortSelector{
1286					Number: 8080,
1287				},
1288			},
1289		},
1290	},
1291}
1292var networkingSubset = &networking.Subset{
1293	Name:   "some-subset",
1294	Labels: map[string]string{},
1295	TrafficPolicy: &networking.TrafficPolicy{
1296		LoadBalancer: &networking.LoadBalancerSettings{
1297			LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
1298				ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
1299					HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
1300						HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
1301							Name: "other-cookie",
1302						},
1303					},
1304				},
1305			},
1306		},
1307	},
1308}
1309
1310var networkingSubsetWithPortLevelSettings = &networking.Subset{
1311	Name:   "port-level-settings-subset",
1312	Labels: map[string]string{},
1313	TrafficPolicy: &networking.TrafficPolicy{
1314		PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
1315			{
1316				LoadBalancer: &networking.LoadBalancerSettings{
1317					LbPolicy: loadBalancerPolicy("port-level-settings-cookie"),
1318				},
1319				Port: &networking.PortSelector{
1320					Number: 8484,
1321				},
1322			},
1323		},
1324	},
1325}
1326
1327func TestCombineVHostRoutes(t *testing.T) {
1328	first := []*envoyroute.Route{
1329		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path1"}}},
1330		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix1"}}},
1331		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: ".*?regex1"}}},
1332		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/"}}},
1333	}
1334	second := []*envoyroute.Route{
1335		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path12"}}},
1336		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix12"}}},
1337		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: ".*?regex12"}}},
1338		{Match: &envoyroute.RouteMatch{
1339			PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: "*"},
1340			Headers: []*envoyroute.HeaderMatcher{
1341				{
1342					Name:                 "foo",
1343					HeaderMatchSpecifier: &envoyroute.HeaderMatcher_ExactMatch{ExactMatch: "bar"},
1344					InvertMatch:          false,
1345				},
1346			},
1347		}},
1348	}
1349
1350	want := []*envoyroute.Route{
1351		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path1"}}},
1352		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix1"}}},
1353		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: ".*?regex1"}}},
1354		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path12"}}},
1355		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix12"}}},
1356		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: ".*?regex12"}}},
1357		{Match: &envoyroute.RouteMatch{
1358			PathSpecifier: &envoyroute.RouteMatch_Regex{Regex: "*"},
1359			Headers: []*envoyroute.HeaderMatcher{
1360				{
1361					Name:                 "foo",
1362					HeaderMatchSpecifier: &envoyroute.HeaderMatcher_ExactMatch{ExactMatch: "bar"},
1363					InvertMatch:          false,
1364				},
1365			},
1366		}},
1367		{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/"}}},
1368	}
1369
1370	got := route.CombineVHostRoutes(first, second)
1371	if !reflect.DeepEqual(want, got) {
1372		t.Errorf("CombineVHostRoutes: \n")
1373		t.Errorf("got: \n")
1374		for _, g := range got {
1375			t.Errorf("%v\n", g.Match.PathSpecifier)
1376		}
1377		t.Errorf("want: \n")
1378		for _, g := range want {
1379			t.Errorf("%v\n", g.Match.PathSpecifier)
1380		}
1381	}
1382}
1383