blob: abbaad72171cf321075ed86746fa20df14818ea1 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package route_test
import (
"os"
"reflect"
"testing"
"time"
)
import (
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoyroute "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/onsi/gomega"
"google.golang.org/protobuf/types/known/durationpb"
wrappers "google.golang.org/protobuf/types/known/wrapperspb"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/features"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/core/v1alpha3"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/core/v1alpha3/route"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
"github.com/apache/dubbo-go-pixiu/pilot/test/xdstest"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
)
func TestBuildHTTPRoutes(t *testing.T) {
serviceRegistry := map[host.Name]*model.Service{
"*.example.org": {
Hostname: "*.example.org",
DefaultAddress: "1.1.1.1",
Ports: model.PortList{
&model.Port{
Name: "default",
Port: 8080,
Protocol: protocol.HTTP,
},
},
},
}
node := func(cg *v1alpha3.ConfigGenTest) *model.Proxy {
return cg.SetupProxy(&model.Proxy{
Type: model.SidecarProxy,
IPAddresses: []string{"1.1.1.1"},
ID: "someID",
DNSDomain: "foo.com",
})
}
gatewayNames := map[string]bool{"some-gateway": true}
t.Run("for virtual service", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
os.Setenv("ISTIO_DEFAULT_REQUEST_TIMEOUT", "0ms")
defer os.Unsetenv("ISTIO_DEFAULT_REQUEST_TIMEOUT")
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServicePlain, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
// Validate that when timeout is not specified, we disable it based on default value of flag.
g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(0)))
// nolint: staticcheck
g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(0)))
})
t.Run("for virtual service with HTTP/3 discovery enabled", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServicePlain, serviceRegistry, nil, 8080, gatewayNames, true, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(routes[0].GetResponseHeadersToAdd()).To(gomega.Equal([]*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: util.AltSvcHeader,
Value: `h3=":8080"; ma=86400`,
},
Append: &wrappers.BoolValue{Value: true},
},
}))
})
t.Run("for virtual service with changed default timeout", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
dt := features.DefaultRequestTimeout
features.DefaultRequestTimeout = durationpb.New(1 * time.Second)
defer func() { features.DefaultRequestTimeout = dt }()
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServicePlain, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
// Validate that when timeout is not specified, we send what is set in the timeout flag.
g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(1)))
// nolint: staticcheck
g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(1)))
})
t.Run("for virtual service with timeout", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithTimeout, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
// Validate that when timeout specified, we send the configured timeout to Envoys.
g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(10)))
// nolint: staticcheck
g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(10)))
})
t.Run("for virtual service with disabled timeout", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithTimeoutDisabled, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetRoute().Timeout.Seconds).To(gomega.Equal(int64(0)))
// nolint: staticcheck
g.Expect(routes[0].GetRoute().MaxGrpcTimeout.Seconds).To(gomega.Equal(int64(0)))
})
t.Run("for virtual service with catch all route", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithCatchAllRoute,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(2))
g.Expect(routes[0].Name).To(gomega.Equal("route.non-catch-all"))
g.Expect(routes[1].Name).To(gomega.Equal("route.catch-all"))
})
t.Run("for virtual service with catch all routeļ¼šport match", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithCatchAllPort,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].Name).To(gomega.Equal("route 1.catch-all for 8080"))
})
t.Run("for internally generated virtual service with ingress semantics (istio version<1.14)", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
vs := virtualServiceWithCatchAllRoute
if vs.Annotations == nil {
vs.Annotations = make(map[string]string)
}
vs.Annotations[constants.InternalRouteSemantics] = constants.RouteSemanticsIngress
proxy := node(cg)
proxy.IstioVersion = &model.IstioVersion{
Major: 1,
Minor: 13,
}
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, vs,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(routes[0].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: util.RegexEngine,
Regex: `/route/v1((\/).*)?`,
},
}))
g.Expect(routes[1].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_Prefix{
Prefix: "/",
}))
})
t.Run("for internally generated virtual service with gateway semantics (istio version<1.14)", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
vs := virtualServiceWithCatchAllRoute
if vs.Annotations == nil {
vs.Annotations = make(map[string]string)
}
vs.Annotations[constants.InternalRouteSemantics] = constants.RouteSemanticsGateway
proxy := node(cg)
proxy.IstioVersion = &model.IstioVersion{
Major: 1,
Minor: 13,
}
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, vs,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(routes[0].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: util.RegexEngine,
Regex: `/route/v1((\/).*)?`,
},
}))
g.Expect(routes[1].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_Prefix{
Prefix: "/",
}))
})
t.Run("for internally generated virtual service with ingress semantics (istio version>=1.14)", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
vs := virtualServiceWithCatchAllRoute
if vs.Annotations == nil {
vs.Annotations = make(map[string]string)
}
vs.Annotations[constants.InternalRouteSemantics] = constants.RouteSemanticsIngress
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), vs,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(routes[0].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_PathSeparatedPrefix{
PathSeparatedPrefix: "/route/v1",
}))
g.Expect(routes[1].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_Prefix{
Prefix: "/",
}))
})
t.Run("for internally generated virtual service with gateway semantics (istio version>=1.14)", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
vs := virtualServiceWithCatchAllRoute
if vs.Annotations == nil {
vs.Annotations = make(map[string]string)
}
vs.Annotations[constants.InternalRouteSemantics] = constants.RouteSemanticsGateway
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), vs,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(routes[0].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_PathSeparatedPrefix{
PathSeparatedPrefix: "/route/v1",
}))
g.Expect(routes[1].Match.PathSpecifier).To(gomega.Equal(&envoyroute.RouteMatch_Prefix{
Prefix: "/",
}))
})
t.Run("for virtual service with top level catch all route", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithCatchAllRouteWeightedDestination,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
})
t.Run("for virtual service with multi prefix catch all route", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithCatchAllMultiPrefixRoute,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
})
t.Run("for virtual service with regex matching on URI", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithRegexMatchingOnURI,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetSafeRegex().GetRegex()).To(gomega.Equal("\\/(.?)\\/status"))
})
t.Run("for virtual service with exact matching on JWT claims", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithExactMatchingOnHeaderForJWTClaims,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(len(routes[0].GetMatch().GetHeaders())).To(gomega.Equal(0))
g.Expect(routes[0].GetMatch().GetDynamicMetadata()[0].GetFilter()).To(gomega.Equal("istio_authn"))
g.Expect(routes[0].GetMatch().GetDynamicMetadata()[0].GetInvert()).To(gomega.BeFalse())
g.Expect(routes[0].GetMatch().GetDynamicMetadata()[1].GetFilter()).To(gomega.Equal("istio_authn"))
g.Expect(routes[0].GetMatch().GetDynamicMetadata()[1].GetInvert()).To(gomega.BeTrue())
})
t.Run("for virtual service with regex matching on header", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithRegexMatchingOnHeader,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetStringMatch().GetSafeRegex().GetRegex()).To(gomega.Equal("Bearer .+?\\..+?\\..+?"))
})
t.Run("for virtual service with regex matching on without_header", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithRegexMatchingOnWithoutHeader,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetStringMatch().GetSafeRegex().GetRegex()).To(gomega.Equal("BAR .+?\\..+?\\..+?"))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(true))
})
t.Run("for virtual service with presence matching on header", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithPresentMatchingOnHeader,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
g.Expect(err).NotTo(gomega.HaveOccurred())
xdstest.ValidateRoutes(t, routes)
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(false))
})
t.Run("for virtual service with presence matching on header and without_header", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithPresentMatchingOnWithoutHeader,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
g.Expect(err).NotTo(gomega.HaveOccurred())
xdstest.ValidateRoutes(t, routes)
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(true))
})
t.Run("for virtual service with regex matching for all cases on header", func(t *testing.T) {
cset := createVirtualServiceWithRegexMatchingForAllCasesOnHeader()
for _, c := range cset {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), *c, serviceRegistry, nil,
8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetName()).To(gomega.Equal("FOO-HEADER"))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetPresentMatch()).To(gomega.Equal(true))
g.Expect(routes[0].GetMatch().GetHeaders()[0].GetInvertMatch()).To(gomega.Equal(false))
}
})
t.Run("for virtual service with source namespace matching", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
fooNode := cg.SetupProxy(&model.Proxy{
ConfigNamespace: "foo",
})
routes, err := route.BuildHTTPRoutesForVirtualService(fooNode, virtualServiceMatchingOnSourceNamespace,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetName()).To(gomega.Equal("foo"))
barNode := cg.SetupProxy(&model.Proxy{
ConfigNamespace: "bar",
})
routes, err = route.BuildHTTPRoutesForVirtualService(barNode, virtualServiceMatchingOnSourceNamespace,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
g.Expect(routes[0].GetName()).To(gomega.Equal("bar"))
})
t.Run("for virtual service with ring hash", func(t *testing.T) {
g := gomega.NewWithT(t)
ttl := durationpb.Duration{Nanos: 100}
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: "*.example.org",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
Name: "hash-cookie",
Ttl: &ttl,
},
},
},
},
},
},
},
},
},
})
proxy := node(cg)
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualServicePlain, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualServicePlain, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "hash-cookie",
Ttl: &ttl,
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("for virtual service with query param based ring hash", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: "*.example.org",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpQueryParameterName{
HttpQueryParameterName: "query",
},
},
},
},
},
},
},
},
})
proxy := node(cg)
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualServicePlain, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualServicePlain, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_QueryParameter_{
QueryParameter: &envoyroute.RouteAction_HashPolicy_QueryParameter{
Name: "query",
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("for virtual service with subsets with ring hash", func(t *testing.T) {
g := gomega.NewWithT(t)
virtualService := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: virtualServiceWithSubset,
}
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{
virtualService,
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: "*.example.org",
Subsets: []*networking.Subset{networkingSubset},
},
},
},
})
proxy := node(cg)
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualService, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualService, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "other-cookie",
Ttl: nil,
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("for virtual service with subsets with port level settings with ring hash", func(t *testing.T) {
g := gomega.NewWithT(t)
virtualService := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: virtualServiceWithSubsetWithPortLevelSettings,
}
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{
virtualService,
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: portLevelDestinationRuleWithSubsetPolicy,
},
},
})
proxy := node(cg)
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualService, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualService, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "port-level-settings-cookie",
Ttl: nil,
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("for virtual service with subsets and top level traffic policy with ring hash", func(t *testing.T) {
g := gomega.NewWithT(t)
virtualService := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: virtualServiceWithSubset,
}
cnfg := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
}
rule := networkingDestinationRule
rule.Subsets = []*networking.Subset{networkingSubset}
cnfg.Spec = networkingDestinationRule
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{cnfg, virtualService},
})
proxy := node(cg)
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualService, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualService, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "other-cookie",
Ttl: nil,
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("port selector based traffic policy", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Services: exampleService,
Configs: []config.Config{{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: portLevelDestinationRule,
}},
})
proxy := node(cg)
gatewayNames := map[string]bool{"some-gateway": true}
hashByDestination := route.GetConsistentHashForVirtualService(cg.PushContext(), proxy, virtualServicePlain, serviceRegistry)
routes, err := route.BuildHTTPRoutesForVirtualService(proxy, virtualServicePlain, serviceRegistry,
hashByDestination, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "hash-cookie",
Ttl: nil,
},
},
}
g.Expect(routes[0].GetRoute().GetHashPolicy()).To(gomega.ConsistOf(hashPolicy))
})
t.Run("for header operations for single cluster", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithHeaderOperationsForSingleCluster,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
r := routes[0]
g.Expect(len(r.RequestHeadersToAdd)).To(gomega.Equal(4))
g.Expect(len(r.ResponseHeadersToAdd)).To(gomega.Equal(4))
g.Expect(len(r.RequestHeadersToRemove)).To(gomega.Equal(2))
g.Expect(len(r.ResponseHeadersToRemove)).To(gomega.Equal(2))
g.Expect(r.RequestHeadersToAdd).To(gomega.Equal([]*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-req-set",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-req-add",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
{
Header: &core.HeaderValue{
Key: "x-route-req-set",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-req-add",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
}))
g.Expect(r.RequestHeadersToRemove).To(gomega.Equal([]string{"x-req-remove", "x-route-req-remove"}))
g.Expect(r.ResponseHeadersToAdd).To(gomega.Equal([]*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-resp-set",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-resp-add",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
{
Header: &core.HeaderValue{
Key: "x-route-resp-set",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-resp-add",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
}))
g.Expect(r.ResponseHeadersToRemove).To(gomega.Equal([]string{"x-resp-remove", "x-route-resp-remove"}))
routeAction, ok := r.GetAction().(*envoyroute.Route_Route)
g.Expect(ok).NotTo(gomega.BeFalse())
g.Expect(routeAction.Route.GetHostRewriteLiteral()).To(gomega.Equal("foo.extsvc.com"))
})
t.Run("for header operations for weighted cluster", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithHeaderOperationsForWeightedCluster,
serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
r := routes[0]
routeAction, ok := r.GetAction().(*envoyroute.Route_Route)
g.Expect(ok).NotTo(gomega.BeFalse())
weightedCluster := routeAction.Route.GetWeightedClusters()
g.Expect(weightedCluster).NotTo(gomega.BeNil())
g.Expect(len(weightedCluster.GetClusters())).To(gomega.Equal(2))
expectResults := []struct {
reqAdd []*core.HeaderValueOption
reqRemove []string
respAdd []*core.HeaderValueOption
respRemove []string
authority string
}{
{
reqAdd: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-route-req-set-blue",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-req-add-blue",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
},
reqRemove: []string{"x-route-req-remove-blue"},
respAdd: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-route-resp-set-blue",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-resp-add-blue",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
},
respRemove: []string{"x-route-resp-remove-blue"},
authority: "blue.foo.extsvc.com",
},
{
reqAdd: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-route-req-set-green",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-req-add-green",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
},
reqRemove: []string{"x-route-req-remove-green"},
respAdd: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "x-route-resp-set-green",
Value: "v1",
},
Append: &wrappers.BoolValue{Value: false},
},
{
Header: &core.HeaderValue{
Key: "x-route-resp-add-green",
Value: "v2",
},
Append: &wrappers.BoolValue{Value: true},
},
},
respRemove: []string{"x-route-resp-remove-green"},
authority: "green.foo.extsvc.com",
},
}
for i, expectResult := range expectResults {
cluster := weightedCluster.GetClusters()[i]
g.Expect(cluster.RequestHeadersToAdd).To(gomega.Equal(expectResult.reqAdd))
g.Expect(cluster.RequestHeadersToRemove).To(gomega.Equal(expectResult.reqRemove))
g.Expect(cluster.ResponseHeadersToAdd).To(gomega.Equal(expectResult.respAdd))
g.Expect(cluster.RequestHeadersToRemove).To(gomega.Equal(expectResult.reqRemove))
g.Expect(cluster.GetHostRewriteLiteral()).To(gomega.Equal(expectResult.authority))
}
})
t.Run("for redirect code", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithRedirect, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
redirectAction, ok := routes[0].Action.(*envoyroute.Route_Redirect)
g.Expect(ok).NotTo(gomega.BeFalse())
g.Expect(redirectAction.Redirect.ResponseCode).To(gomega.Equal(envoyroute.RedirectAction_PERMANENT_REDIRECT))
})
t.Run("for redirect and header manipulation", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{})
routes, err := route.BuildHTTPRoutesForVirtualService(node(cg), virtualServiceWithRedirectAndSetHeader, serviceRegistry, nil, 8080, gatewayNames, false, nil)
xdstest.ValidateRoutes(t, routes)
g.Expect(err).NotTo(gomega.HaveOccurred())
g.Expect(len(routes)).To(gomega.Equal(1))
redirectAction, ok := routes[0].Action.(*envoyroute.Route_Redirect)
g.Expect(ok).NotTo(gomega.BeFalse())
g.Expect(redirectAction.Redirect.ResponseCode).To(gomega.Equal(envoyroute.RedirectAction_PERMANENT_REDIRECT))
g.Expect(len(routes[0].ResponseHeadersToAdd)).To(gomega.Equal(1))
g.Expect(routes[0].ResponseHeadersToAdd[0].Append.Value).To(gomega.BeFalse())
g.Expect(routes[0].ResponseHeadersToAdd[0].Header.Key).To(gomega.Equal("Strict-Transport-Security"))
g.Expect(routes[0].ResponseHeadersToAdd[0].Header.Value).To(gomega.Equal("max-age=31536000; includeSubDomains; preload"))
})
t.Run("for no virtualservice but has destinationrule with consistentHash loadbalancer", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Configs: []config.Config{
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: networkingDestinationRule,
},
},
Services: exampleService,
})
vhosts := route.BuildSidecarVirtualHostWrapper(nil, node(cg), cg.PushContext(), serviceRegistry, []config.Config{}, 8080)
g.Expect(vhosts[0].Routes[0].Action.(*envoyroute.Route_Route).Route.HashPolicy).NotTo(gomega.BeNil())
})
t.Run("for no virtualservice but has destinationrule with portLevel consistentHash loadbalancer", func(t *testing.T) {
g := gomega.NewWithT(t)
cg := v1alpha3.NewConfigGenTest(t, v1alpha3.TestOptions{
Configs: []config.Config{
{
Meta: config.Meta{
GroupVersionKind: gvk.DestinationRule,
Name: "acme",
Namespace: "dubbo-system",
},
Spec: networkingDestinationRuleWithPortLevelTrafficPolicy,
},
},
Services: exampleService,
})
vhosts := route.BuildSidecarVirtualHostWrapper(nil, node(cg), cg.PushContext(), serviceRegistry, []config.Config{}, 8080)
hashPolicy := &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "hash-cookie-1",
},
},
}
g.Expect(vhosts[0].Routes[0].Action.(*envoyroute.Route_Route).Route.HashPolicy).To(gomega.ConsistOf(hashPolicy))
})
}
func loadBalancerPolicy(name string) *networking.LoadBalancerSettings_ConsistentHash {
return &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
Name: name,
},
},
},
}
}
var virtualServiceWithSubset = &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Subset: "some-subset",
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 65000,
},
},
Weight: 100,
},
},
},
},
}
var virtualServiceWithSubsetWithPortLevelSettings = &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Subset: "port-level-settings-subset",
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
},
}
var virtualServicePlain = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
},
},
}
var virtualServiceWithTimeout = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
Timeout: &durationpb.Duration{
Seconds: 10,
},
},
},
},
}
var virtualServiceWithTimeoutDisabled = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
Timeout: &durationpb.Duration{
Seconds: 0,
},
},
},
},
}
var virtualServiceWithCatchAllRoute = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Name: "route",
Match: []*networking.HTTPMatchRequest{
{
Name: "non-catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/route/v1",
},
},
},
{
Name: "catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/",
},
},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
},
},
}
var virtualServiceWithCatchAllPort = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Name: "route 1",
Match: []*networking.HTTPMatchRequest{
{
Name: "catch-all for 8080",
Port: 8080,
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "example1.default.svc.cluster.local",
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
},
{
Name: "route 2",
Match: []*networking.HTTPMatchRequest{
{
Name: "header match",
Headers: map[string]*networking.StringMatch{
"cookie": {
MatchType: &networking.StringMatch_Exact{Exact: "canary"},
},
},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "example2.default.svc.cluster.local",
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
},
{
Name: "route 3",
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "example1.default.svc.cluster.local",
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
},
},
},
}
var virtualServiceWithCatchAllMultiPrefixRoute = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "catch-all",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/",
},
},
SourceLabels: map[string]string{
"matchingNoSrc": "xxx",
},
},
{
Name: "specific match",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/a",
},
},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "*.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
},
},
}
var virtualServiceWithCatchAllRouteWeightedDestination = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{"headers.test.istio.io"},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "headers-only",
Headers: map[string]*networking.StringMatch{
"version": {
MatchType: &networking.StringMatch_Exact{
Exact: "v2",
},
},
},
SourceLabels: map[string]string{
"version": "v1",
},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "c-weighted.extsvc.com",
Subset: "v2",
},
Weight: 100,
},
},
},
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "c-weighted.extsvc.com",
Subset: "v1",
},
Weight: 100,
},
},
},
},
},
}
var virtualServiceWithHeaderOperationsForSingleCluster = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{"headers.test.istio.io"},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "c-weighted.extsvc.com",
Subset: "v1",
},
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-req-set": "v1", ":authority": "internal.foo.extsvc.com"},
Add: map[string]string{"x-route-req-add": "v2", ":authority": "internal.bar.extsvc.com"},
Remove: []string{"x-route-req-remove"},
},
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-resp-set": "v1"},
Add: map[string]string{"x-route-resp-add": "v2"},
Remove: []string{"x-route-resp-remove"},
},
},
Weight: 100,
},
},
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-req-set": "v1", ":authority": "foo.extsvc.com"},
Add: map[string]string{"x-req-add": "v2", ":authority": "bar.extsvc.com"},
Remove: []string{"x-req-remove"},
},
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-resp-set": "v1"},
Add: map[string]string{"x-resp-add": "v2"},
Remove: []string{"x-resp-remove"},
},
},
},
},
},
}
var virtualServiceWithHeaderOperationsForWeightedCluster = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{"headers.test.istio.io"},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "c-weighted.extsvc.com",
Subset: "blue",
},
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-req-set-blue": "v1", ":authority": "blue.foo.extsvc.com"},
Add: map[string]string{"x-route-req-add-blue": "v2", ":authority": "blue.bar.extsvc.com"},
Remove: []string{"x-route-req-remove-blue"},
},
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-resp-set-blue": "v1"},
Add: map[string]string{"x-route-resp-add-blue": "v2"},
Remove: []string{"x-route-resp-remove-blue"},
},
},
Weight: 90,
},
{
Destination: &networking.Destination{
Host: "c-weighted.extsvc.com",
Subset: "green",
},
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-req-set-green": "v1", ":authority": "green.foo.extsvc.com"},
Add: map[string]string{"x-route-req-add-green": "v2", ":authority": "green.bar.extsvc.com"},
Remove: []string{"x-route-req-remove-green"},
},
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-route-resp-set-green": "v1"},
Add: map[string]string{"x-route-resp-add-green": "v2"},
Remove: []string{"x-route-resp-remove-green"},
},
},
Weight: 10,
},
},
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-req-set": "v1", ":authority": "foo.extsvc.com"},
Add: map[string]string{"x-req-add": "v2", ":authority": "bar.extsvc.com"},
Remove: []string{"x-req-remove"},
},
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{"x-resp-set": "v1"},
Add: map[string]string{"x-resp-add": "v2"},
Remove: []string{"x-resp-remove"},
},
},
},
},
},
}
var virtualServiceWithRedirect = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceWithRedirectAndSetHeader = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
Headers: &networking.Headers{
Response: &networking.Headers_HeaderOperations{
Set: map[string]string{
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
},
},
},
},
},
},
}
var virtualServiceWithRegexMatchingOnURI = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "status",
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "\\/(.?)\\/status",
},
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceWithExactMatchingOnHeaderForJWTClaims = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "auth",
Headers: map[string]*networking.StringMatch{
"@request.auth.claims.Foo": {
MatchType: &networking.StringMatch_Exact{
Exact: "Bar",
},
},
},
WithoutHeaders: map[string]*networking.StringMatch{
"@request.auth.claims.Bla": {
MatchType: &networking.StringMatch_Exact{
Exact: "Bar",
},
},
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceWithRegexMatchingOnHeader = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "auth",
Headers: map[string]*networking.StringMatch{
"Authentication": {
MatchType: &networking.StringMatch_Regex{
Regex: "Bearer .+?\\..+?\\..+?",
},
},
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
func createVirtualServiceWithRegexMatchingForAllCasesOnHeader() []*config.Config {
ret := []*config.Config{}
regex := "*"
ret = append(ret, &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "presence",
Headers: map[string]*networking.StringMatch{
"FOO-HEADER": {
MatchType: &networking.StringMatch_Regex{
Regex: regex,
},
},
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
})
return ret
}
var virtualServiceWithRegexMatchingOnWithoutHeader = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "without-test",
WithoutHeaders: map[string]*networking.StringMatch{
"FOO-HEADER": {
MatchType: &networking.StringMatch_Regex{
Regex: "BAR .+?\\..+?\\..+?",
},
},
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceWithPresentMatchingOnHeader = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "presence",
Headers: map[string]*networking.StringMatch{
"FOO-HEADER": nil,
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceWithPresentMatchingOnWithoutHeader = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{"some-gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Name: "presence",
WithoutHeaders: map[string]*networking.StringMatch{
"FOO-HEADER": nil,
},
},
},
Redirect: &networking.HTTPRedirect{
Uri: "example.org",
Authority: "some-authority.default.svc.cluster.local",
RedirectCode: 308,
},
},
},
},
}
var virtualServiceMatchingOnSourceNamespace = config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Http: []*networking.HTTPRoute{
{
Name: "foo",
Match: []*networking.HTTPMatchRequest{
{
SourceNamespace: "foo",
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "foo.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
{
Name: "bar",
Match: []*networking.HTTPMatchRequest{
{
SourceNamespace: "bar",
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "bar.example.org",
Port: &networking.PortSelector{
Number: 8484,
},
},
Weight: 100,
},
},
},
},
},
}
var portLevelDestinationRule = &networking.DestinationRule{
Host: "*.example.org",
Subsets: []*networking.Subset{},
TrafficPolicy: &networking.TrafficPolicy{
PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("hash-cookie"),
},
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
}
var portLevelDestinationRuleWithSubsetPolicy = &networking.DestinationRule{
Host: "*.example.org",
Subsets: []*networking.Subset{networkingSubsetWithPortLevelSettings},
TrafficPolicy: &networking.TrafficPolicy{
PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("hash-cookie"),
},
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
}
var networkingDestinationRule = &networking.DestinationRule{
Host: "*.example.org",
Subsets: []*networking.Subset{},
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("hash-cookie"),
},
},
}
var exampleService = []*model.Service{{Hostname: "*.example.org", Attributes: model.ServiceAttributes{Namespace: "dubbo-system"}}}
var networkingDestinationRuleWithPortLevelTrafficPolicy = &networking.DestinationRule{
Host: "*.example.org",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("hash-cookie"),
},
PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("hash-cookie-1"),
},
Port: &networking.PortSelector{
Number: 8080,
},
},
},
},
}
var networkingSubset = &networking.Subset{
Name: "some-subset",
Labels: map[string]string{},
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
Name: "other-cookie",
},
},
},
},
},
},
}
var networkingSubsetWithPortLevelSettings = &networking.Subset{
Name: "port-level-settings-subset",
Labels: map[string]string{},
TrafficPolicy: &networking.TrafficPolicy{
PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: loadBalancerPolicy("port-level-settings-cookie"),
},
Port: &networking.PortSelector{
Number: 8484,
},
},
},
},
}
func TestCombineVHostRoutes(t *testing.T) {
regexEngine := &matcher.RegexMatcher_GoogleRe2{GoogleRe2: &matcher.RegexMatcher_GoogleRE2{
MaxProgramSize: &wrappers.UInt32Value{
Value: uint32(10),
},
}}
first := []*envoyroute.Route{
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path1"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix1"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: ".*?regex1",
},
}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/"}}},
}
second := []*envoyroute.Route{
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path12"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix12"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: ".*?regex12",
},
}}},
{Match: &envoyroute.RouteMatch{
PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: "*",
},
},
Headers: []*envoyroute.HeaderMatcher{
{
Name: "foo",
HeaderMatchSpecifier: &envoyroute.HeaderMatcher_StringMatch{
StringMatch: &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Exact{Exact: "bar"}},
},
InvertMatch: false,
},
},
}},
}
want := []*envoyroute.Route{
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path1"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix1"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: ".*?regex1",
},
}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Path{Path: "/path12"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/prefix12"}}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: ".*?regex12",
},
}}},
{Match: &envoyroute.RouteMatch{
PathSpecifier: &envoyroute.RouteMatch_SafeRegex{
SafeRegex: &matcher.RegexMatcher{
EngineType: regexEngine,
Regex: "*",
},
},
Headers: []*envoyroute.HeaderMatcher{
{
Name: "foo",
HeaderMatchSpecifier: &envoyroute.HeaderMatcher_StringMatch{
StringMatch: &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Exact{Exact: "bar"}},
},
InvertMatch: false,
},
},
}},
{Match: &envoyroute.RouteMatch{PathSpecifier: &envoyroute.RouteMatch_Prefix{Prefix: "/"}}},
}
got := route.CombineVHostRoutes(first, second)
if !reflect.DeepEqual(want, got) {
t.Errorf("CombineVHostRoutes: \n")
t.Errorf("got: \n")
for _, g := range got {
t.Errorf("%v\n", g.Match.PathSpecifier)
}
t.Errorf("want: \n")
for _, g := range want {
t.Errorf("%v\n", g.Match.PathSpecifier)
}
}
}