blob: b905efd5d332543d615e091c6d5365429db74262 [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 v1alpha3_test
import (
"testing"
)
import (
"istio.io/pkg/env"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/simulation"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/xds"
"github.com/apache/dubbo-go-pixiu/pilot/test/xdstest"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/tmpl"
)
func TestDisablePortTranslation(t *testing.T) {
virtualServices := `
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "example.com"
gateways:
- gateway80
- gateway8081
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: b
spec:
hosts:
- "example.com"
gateways:
- gateway80
- gateway8081
http:
- match:
- uri:
prefix: /
route:
- destination:
host: b
port:
number: 8081`
runGatewayTest(t,
simulationTest{
name: "multiple target port, target port first",
config: createGateway("gateway80", "", `
port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
`) + createGateway("gateway8081", "", `
port:
number: 8081
name: http
protocol: HTTP
hosts:
- "example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
labels:
experimental.istio.io/disable-gateway-port-translation: "true"
spec:
hosts: ["a.example.com"]
ports:
- number: 80
targetPort: 8081
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
labels:
istio: ingressgateway
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance-2
namespace: dubbo-system
spec:
hosts: ["b.example.com"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
labels:
istio: ingressgateway
---
` + virtualServices,
calls: []simulation.Expect{
{
Name: "target port 1",
Call: simulation.Call{
Port: 8080,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "example.com:80",
},
},
{
Name: "target port 2",
Call: simulation.Call{
Port: 8081,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8081",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8081",
VirtualHostMatched: "example.com:8081",
},
},
},
},
simulationTest{
name: "multiple target port, service port first",
config: createGateway("gateway80", "", `
port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
`) + createGateway("gateway8081", "", `
port:
number: 8081
name: http
protocol: HTTP
hosts:
- "example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
labels:
experimental.istio.io/disable-gateway-port-translation: "true"
spec:
hosts: ["b.example.com"]
ports:
- number: 80
targetPort: 8081
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
labels:
istio: ingressgateway
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance-2
namespace: dubbo-system
spec:
hosts: ["a.example.com"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
labels:
istio: ingressgateway
---
` + virtualServices,
calls: []simulation.Expect{
{
Name: "target port 1",
Call: simulation.Call{
Port: 8080,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "example.com:80",
},
},
{
Name: "target port 2",
Call: simulation.Call{
Port: 8081,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8081",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8081",
VirtualHostMatched: "example.com:8081",
},
},
},
})
}
func TestHTTPGateway(t *testing.T) {
httpServer := `port:
number: 80
name: http
protocol: HTTP
hosts:
- "foo.bar"`
simpleRoute := `apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: vs
spec:
hosts:
- "example.com"
gateways:
- gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: b
---
`
runGatewayTest(t,
simulationTest{
name: "no virtual services",
config: createGateway("", "", httpServer),
calls: []simulation.Expect{
{
// Expect listener, but no routing
Name: "defined port",
Call: simulation.Call{
Port: 80,
HostHeader: "foo.bar",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrNoRoute,
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
VirtualHostMatched: "blackhole:80",
},
},
{
// There will be no listener
Name: "undefined port",
Call: simulation.Call{
Port: 81,
HostHeader: "foo.bar",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrNoListener,
},
},
},
},
simulationTest{
name: "simple http and virtual service",
config: createGateway("gateway", "", httpServer) + `
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- gateway
http:
- match:
- uri:
exact: /productpage
route:
- destination:
host: productpage
port:
number: 9080
`,
calls: []simulation.Expect{
{
Name: "uri mismatch",
Call: simulation.Call{
Port: 80,
HostHeader: "foo.bar",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
// We didn't match the URI
Error: simulation.ErrNoRoute,
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
VirtualHostMatched: "foo.bar:80",
},
},
{
Name: "host mismatch",
Call: simulation.Call{
Port: 80,
HostHeader: "bad.bar",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
// We didn't match the host
Error: simulation.ErrNoVirtualHost,
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
},
},
{
Name: "match",
Call: simulation.Call{
Port: 80,
HostHeader: "foo.bar",
Path: "/productpage",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
VirtualHostMatched: "foo.bar:80",
ClusterMatched: "outbound|9080||productpage.default",
},
},
},
},
simulationTest{
name: "virtual service merging",
config: createGateway("gateway", "", `port:
number: 80
name: http
protocol: HTTP
hosts:
- "*.example.com"`) + `
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "a.example.com"
gateways:
- gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: b
spec:
hosts:
- "b.example.com"
gateways:
- gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: b
port:
number: 80
`,
calls: []simulation.Expect{
{
Name: "a",
Call: simulation.Call{
Port: 80,
HostHeader: "a.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{ClusterMatched: "outbound|80||a.default"},
},
{
Name: "b",
Call: simulation.Call{
Port: 80,
HostHeader: "b.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{ClusterMatched: "outbound|80||b.default"},
},
{
Name: "undefined hostname",
Call: simulation.Call{
Port: 80,
HostHeader: "c.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{Error: simulation.ErrNoVirtualHost},
},
},
},
simulationTest{
name: "httpsRedirect without routes",
config: createGateway("gateway", "", `port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
tls:
httpsRedirect: true`),
calls: []simulation.Expect{
{
Name: "request",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrTLSRedirect,
ListenerMatched: "0.0.0.0_80",
VirtualHostMatched: "example.com:80",
RouteConfigMatched: "http.80",
StrictMatch: true,
},
},
},
},
simulationTest{
// This should be the same as without, we elide the routes anyways since they always
// redirect
name: "httpsRedirect with routes",
config: createGateway("gateway", "", `port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
tls:
httpsRedirect: true`) + simpleRoute,
calls: []simulation.Expect{
{
Name: "request",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrTLSRedirect,
ListenerMatched: "0.0.0.0_80",
VirtualHostMatched: "example.com:80",
RouteConfigMatched: "http.80",
StrictMatch: true,
},
},
},
},
simulationTest{
// A common example of when this would occur is when a user defines their Gateway with
// httpsRedirect, then cert-manager creates an Ingress which requires sending HTTP
// traffic Unfortunately, Envoy doesn't have a good way to do HTTPS redirect per-route.
name: "mixed httpsRedirect with routes",
config: createGateway("gateway", "", `port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
tls:
httpsRedirect: true`) + createGateway("gateway2", "", `port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"`) + simpleRoute,
calls: []simulation.Expect{
{
Name: "request",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrTLSRedirect,
ListenerMatched: "0.0.0.0_80",
VirtualHostMatched: "example.com:80",
RouteConfigMatched: "http.80",
StrictMatch: true,
},
},
},
},
simulationTest{
name: "httpsRedirect on https",
config: createGateway("gateway", "", `port:
number: 443
name: https
protocol: HTTPS
hosts:
- "example.com"
tls:
httpsRedirect: true
mode: SIMPLE
credentialName: test`) + simpleRoute,
calls: []simulation.Expect{
{
Name: "request",
Call: simulation.Call{
Port: 443,
HostHeader: "example.com",
Protocol: simulation.HTTP,
TLS: simulation.TLS,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_443",
VirtualHostMatched: "example.com:443",
RouteConfigMatched: "https.443.https.gateway.default",
ClusterMatched: "outbound|443||b.default",
StrictMatch: true,
},
},
},
},
)
}
func TestGatewayConflicts(t *testing.T) {
tcpServer := `port:
number: 80
name: tcp
protocol: TCP
hosts:
- "foo.bar"`
httpServer := `port:
number: 80
name: http
protocol: HTTP
hosts:
- "foo.bar"`
tlsServer := `hosts:
- ./*
port:
name: https-ingress
number: 443
protocol: HTTPS
tls:
credentialName: sds-credential
mode: SIMPLE`
gatewayCollision := `
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- dubbo-system/gateway
tcp:
- route:
- destination:
host: productpage
port:
number: 9080
`
runGatewayTest(t,
simulationTest{
name: "duplicate cross namespace gateway collision",
config: createGateway("gateway", "dubbo-system", tcpServer) +
createGateway("gateway", "alpha", tcpServer) + // namespace comes before dubbo-system
gatewayCollision,
calls: []simulation.Expect{
{
Name: "call",
Call: simulation.Call{Port: 80, Protocol: simulation.TCP},
// TODO(https://github.com/istio/istio/issues/21394) This is a bug!
// Should have identical result to the test below
Result: simulation.Result{Error: simulation.ErrNoListener},
},
},
},
simulationTest{
name: "duplicate cross namespace gateway collision - selected first",
config: createGateway("gateway", "dubbo-system", tcpServer) +
createGateway("gateway", "zeta", tcpServer) + // namespace comes after dubbo-system
gatewayCollision,
calls: []simulation.Expect{
{
Name: "call",
Call: simulation.Call{Port: 80, Protocol: simulation.TCP},
Result: simulation.Result{ListenerMatched: "0.0.0.0_80", ClusterMatched: "outbound|9080||productpage.default"},
},
},
},
simulationTest{
name: "duplicate tls gateway",
skipValidation: true,
// Create the same gateway in two namespaces
config: createGateway("", "dubbo-system", tlsServer) +
createGateway("", "default", tlsServer),
calls: []simulation.Expect{
{
// TODO(https://github.com/istio/istio/issues/24638) This is a bug!
// We should not have multiple matches, envoy will NACK this
Name: "call",
Call: simulation.Call{Port: 443, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "foo.bar"},
Result: simulation.Result{Error: simulation.ErrMultipleFilterChain},
},
},
},
simulationTest{
name: "duplicate tls virtual service",
// Create the same virtual service in two namespaces
config: `
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: ingressgateway
namespace: dubbo-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- '*.example.com'
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: vs1
namespace: default
spec:
gateways:
- dubbo-system/ingressgateway
hosts:
- mysite.example.com
tls:
- match:
- port: 443
sniHosts:
- mysite.example.com
route:
- destination:
host: mysite.default.svc.cluster.local
port:
number: 443
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: vs2
namespace: default
spec:
gateways:
- dubbo-system/ingressgateway
hosts:
- mysite.example.com
tls:
- match:
- port: 443
sniHosts:
- mysite.example.com
route:
- destination:
host: mysite.default.svc.cluster.local
port:
number: 443
`,
calls: []simulation.Expect{
{
Name: "call",
Call: simulation.Call{Port: 443, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "mysite.example.com"},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_443",
ClusterMatched: "outbound|443||mysite.default.svc.cluster.local",
},
},
},
},
simulationTest{
// TODO(https://github.com/istio/istio/issues/27481) this may be a bug. At very least, this should have indication to user
name: "multiple protocols on a port - tcp first",
config: createGateway("alpha", "", tcpServer) +
createGateway("beta", "", httpServer),
calls: []simulation.Expect{
{
Name: "call tcp",
// TCP takes precedence. Since we have no tcp routes, this will result in no listeners
Call: simulation.Call{
Port: 80,
Protocol: simulation.TCP,
},
Result: simulation.Result{
Error: simulation.ErrNoListener,
},
},
},
},
simulationTest{
// TODO(https://github.com/istio/istio/issues/27481) this may be a bug. At very least, this should have indication to user
name: "multiple protocols on a port - http first",
config: createGateway("beta", "", tcpServer) +
createGateway("alpha", "", httpServer),
calls: []simulation.Expect{
{
// Port define in gateway, but no virtual services
// Expect a 404
// HTTP protocol takes precedence
Name: "call http",
Call: simulation.Call{
Port: 80,
HostHeader: "foo.bar",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrNoRoute,
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
VirtualHostMatched: "blackhole:80",
},
},
},
},
simulationTest{
name: "multiple wildcards with virtual service disambiguator",
config: createGateway("alpha", "", `
hosts:
- ns-1/*.example.com
port:
name: http
number: 80
protocol: HTTP`) +
createGateway("beta", "", `
hosts:
- ns-2/*.example.com
port:
name: http
number: 80
protocol: HTTP`) + `
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: default
namespace: ns-1
spec:
hosts:
- "ns-1.example.com"
gateways:
- default/alpha
http:
- route:
- destination:
host: echo
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: default
namespace: ns-2
spec:
hosts:
- "ns-2.example.com"
gateways:
- default/beta
http:
- route:
- destination:
host: echo
`,
calls: []simulation.Expect{
{
Name: "ns-1",
Call: simulation.Call{
Port: 80,
HostHeader: "ns-1.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||echo.ns-1",
},
},
{
Name: "ns-2",
Call: simulation.Call{
Port: 80,
HostHeader: "ns-2.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||echo.ns-2",
},
},
},
},
)
}
func TestIngress(t *testing.T) {
cfg := `
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: {{.Name}}
namespace: default
creationTimestamp: "{{.Time}}"
annotations:
kubernetes.io/ingress.class: istio
spec:
rules:
- host: example.com
http:
paths:
- backend:
serviceName: {{.Name}}
servicePort: 80
path: /{{.Name}}
tls:
- hosts:
- example.com
secretName: ingressgateway-certs
---`
runGatewayTest(t, simulationTest{
name: "ingress shared TLS cert conflict - beta first",
// TODO(https://github.com/istio/istio/issues/24385) this is a bug
// "alpha" is created after "beta". This triggers a mismatch in the conflict resolution logic in Ingress and VirtualService, leading to unexpected results
kubeConfig: tmpl.MustEvaluate(cfg, map[string]string{"Name": "alpha", "Time": "2020-01-01T00:00:00Z"}) +
tmpl.MustEvaluate(cfg, map[string]string{"Name": "beta", "Time": "2010-01-01T00:00:00Z"}),
calls: []simulation.Expect{
{
Name: "http alpha",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Path: "/alpha",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||alpha.default.svc.cluster.local",
},
},
{
Name: "http beta",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Path: "/beta",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||beta.default.svc.cluster.local",
},
},
{
Name: "https alpha",
Call: simulation.Call{
Port: 443,
HostHeader: "example.com",
Path: "/alpha",
Protocol: simulation.HTTP,
TLS: simulation.TLS,
},
Result: simulation.Result{
Error: simulation.ErrNoRoute,
ListenerMatched: "0.0.0.0_443",
RouteConfigMatched: "https.443.https-443-ingress-alpha-default-0.alpha-istio-autogenerated-k8s-ingress-default.dubbo-system",
VirtualHostMatched: "blackhole:443",
},
},
{
Name: "https beta",
Call: simulation.Call{
Port: 443,
HostHeader: "example.com",
Path: "/beta",
Protocol: simulation.HTTP,
TLS: simulation.TLS,
},
Result: simulation.Result{
Error: simulation.ErrNoRoute,
ListenerMatched: "0.0.0.0_443",
RouteConfigMatched: "https.443.https-443-ingress-alpha-default-0.alpha-istio-autogenerated-k8s-ingress-default.dubbo-system",
VirtualHostMatched: "blackhole:443",
},
},
},
}, simulationTest{
name: "ingress shared TLS cert conflict - alpha first",
// "alpha" is created before "beta". This avoids the bug in the previous test
kubeConfig: tmpl.MustEvaluate(cfg, map[string]string{"Name": "alpha", "Time": "2010-01-01T00:00:00Z"}) +
tmpl.MustEvaluate(cfg, map[string]string{"Name": "beta", "Time": "2020-01-01T00:00:00Z"}),
calls: []simulation.Expect{
{
Name: "http alpha",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Path: "/alpha",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||alpha.default.svc.cluster.local",
},
},
{
Name: "http beta",
Call: simulation.Call{
Port: 80,
HostHeader: "example.com",
Path: "/beta",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_80",
RouteConfigMatched: "http.80",
ClusterMatched: "outbound|80||beta.default.svc.cluster.local",
},
},
{
Name: "https alpha",
Call: simulation.Call{
Port: 443,
HostHeader: "example.com",
Path: "/alpha",
Protocol: simulation.HTTP,
TLS: simulation.TLS,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_443",
RouteConfigMatched: "https.443.https-443-ingress-alpha-default-0.alpha-istio-autogenerated-k8s-ingress-default.dubbo-system",
ClusterMatched: "outbound|80||alpha.default.svc.cluster.local",
},
},
{
Name: "https beta",
Call: simulation.Call{
Port: 443,
HostHeader: "example.com",
Path: "/beta",
Protocol: simulation.HTTP,
TLS: simulation.TLS,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_443",
RouteConfigMatched: "https.443.https-443-ingress-alpha-default-0.alpha-istio-autogenerated-k8s-ingress-default.dubbo-system",
ClusterMatched: "outbound|80||beta.default.svc.cluster.local",
},
},
},
})
}
type simulationTest struct {
name string
config string
kubeConfig string
// skipValidation disables validation of XDS resources. Should be used only when we expect a failure (regression catching)
skipValidation bool
calls []simulation.Expect
}
var debugMode = env.RegisterBoolVar("SIMULATION_DEBUG", true, "if enabled, will dump verbose output").Get()
func runGatewayTest(t *testing.T, cases ...simulationTest) {
for _, tt := range cases {
proxy := &model.Proxy{
Metadata: &model.NodeMetadata{
Labels: map[string]string{"istio": "ingressgateway"},
Namespace: "dubbo-system",
},
Type: model.Router,
}
runSimulationTest(t, proxy, xds.FakeOptions{}, tt)
}
}
func runSimulationTest(t *testing.T, proxy *model.Proxy, o xds.FakeOptions, tt simulationTest) {
runTest := func(t *testing.T) {
o.ConfigString = tt.config
o.KubernetesObjectString = tt.kubeConfig
s := xds.NewFakeDiscoveryServer(t, o)
sim := simulation.NewSimulation(t, s, s.SetupProxy(proxy))
sim.RunExpectations(tt.calls)
if t.Failed() && debugMode {
t.Log(xdstest.MapKeys(xdstest.ExtractClusters(sim.Clusters)))
t.Log(xdstest.ExtractListenerNames(sim.Listeners))
t.Log(xdstest.DumpList(t, xdstest.InterfaceSlice(sim.Listeners)))
t.Log(xdstest.DumpList(t, xdstest.InterfaceSlice(sim.Routes)))
t.Log(tt.config)
t.Log(tt.kubeConfig)
}
t.Run("validate configs", func(t *testing.T) {
if tt.skipValidation {
t.Skip()
}
xdstest.ValidateClusters(t, sim.Clusters)
xdstest.ValidateListeners(t, sim.Listeners)
xdstest.ValidateRouteConfigurations(t, sim.Routes)
})
}
if tt.name != "" {
t.Run(tt.name, runTest)
} else {
runTest(t)
}
}
func createGateway(name, namespace string, servers ...string) string {
if name == "" {
name = "default"
}
if namespace == "" {
namespace = "default"
}
return tmpl.MustEvaluate(`apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: "{{.Name}}"
namespace: "{{.Namespace}}"
spec:
selector:
istio: ingressgateway
servers:
{{- range $i, $p := $.Servers }}
-
{{$p | trim | indent 4}}
{{- end }}
---
`, struct {
Name string
Namespace string
Servers []string
}{name, namespace, servers})
}
func createGatewayWithServiceSelector(name, service string, servers ...string) string {
if name == "" {
name = "default"
}
return tmpl.MustEvaluate(`apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: "{{.Name}}"
namespace: "dubbo-system"
annotations:
internal.istio.io/gateway-service: "{{.Service}}"
spec:
servers:
{{- range $i, $p := $.Servers }}
-
{{$p | trim | indent 4}}
{{- end }}
---
`, struct {
Name string
Service string
Servers []string
}{name, service, servers})
}
func TestTargetPort(t *testing.T) {
runGatewayTest(t,
simulationTest{
name: "basic",
config: createGatewayWithServiceSelector("gateway80", "istio-ingressgateway.dubbo-system.svc.cluster.local", `
port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
`) + createGatewayWithServiceSelector("gateway81", "istio-ingressgateway.dubbo-system.svc.cluster.local", `
port:
number: 8080
name: http
protocol: HTTP
hosts:
- "example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "example.com"
gateways:
- dubbo-system/gateway80
- dubbo-system/gateway81
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Call: simulation.Call{
Port: 8080,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "example.com:80",
},
},
},
},
simulationTest{
name: "multiple target port",
config: createGatewayWithServiceSelector("gateway80", "ingress1.dubbo-system.svc.cluster.local", `
port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
`) + createGatewayWithServiceSelector("gateway81", "ingress2.dubbo-system.svc.cluster.local", `
port:
number: 80
name: http
protocol: HTTP
hosts:
- "example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["ingress1.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance-2
namespace: dubbo-system
spec:
hosts: ["ingress2.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8081
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "example.com"
gateways:
- dubbo-system/gateway80
- dubbo-system/gateway81
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: b
spec:
hosts:
- "example.com"
gateways:
- dubbo-system/gateway80
- dubbo-system/gateway81
http:
- match:
- uri:
prefix: /
route:
- destination:
host: b
port:
number: 8081`,
calls: []simulation.Expect{
{
Name: "target port 1",
Call: simulation.Call{
Port: 8080,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "example.com:80",
},
},
{
Name: "target port 2",
Call: simulation.Call{
Port: 8081,
HostHeader: "example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8081",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8081",
VirtualHostMatched: "example.com:80",
},
},
},
})
}
func TestGatewayServices(t *testing.T) {
runGatewayTest(t,
simulationTest{
name: "single service",
config: createGatewayWithServiceSelector("gateway", "istio-ingressgateway.dubbo-system.svc.cluster.local", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`, `
port:
number: 82
name: http-82
protocol: HTTP
hosts:
- "82.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "*.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Name: "port 8080",
Call: simulation.Call{
Port: 8080,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "80.example.com:80",
},
},
{
Name: "no service match",
Call: simulation.Call{
Port: 82,
HostHeader: "81.example.com",
Protocol: simulation.HTTP,
},
// For gateway service reference, if there is no matching port we do not setup a listener
Result: simulation.Result{
Error: simulation.ErrNoListener,
},
},
},
},
simulationTest{
name: "wrong namespace",
config: createGatewayWithServiceSelector("gateway", "istio-ingressgateway.not-dubbo-system.svc.cluster.local", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: not-dubbo-system
spec:
hosts: ["istio-ingressgateway.not-dubbo-system.svc.cluster.local"]
ports:
- number: 80
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---`,
calls: []simulation.Expect{
{
Name: "port 80",
Call: simulation.Call{
Port: 80,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
// Service selected is in another namespace, we cannot cross namespace boundary
Error: simulation.ErrNoListener,
},
},
},
},
simulationTest{
name: "multiple services",
config: createGatewayWithServiceSelector("gateway", "istio-ingressgateway.dubbo-system.svc.cluster.local,ingress.com", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`, `
port:
number: 81
name: http-81
protocol: HTTP
hosts:
- "81.example.com"
`, `
port:
number: 82
name: http-82
protocol: HTTP
hosts:
- "82.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance2
namespace: dubbo-system
spec:
hosts: ["ingress.com"]
ports:
- number: 81
targetPort: 8081
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "*.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Name: "port 8080",
Call: simulation.Call{
Port: 8080,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "80.example.com:80",
},
},
{
Name: "port 8081",
Call: simulation.Call{
Port: 8081,
HostHeader: "81.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8081",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8081",
VirtualHostMatched: "81.example.com:81",
},
},
{
// For gateway service reference, if there is no matching port we do not setup a listener
Name: "no service match",
Call: simulation.Call{
Port: 82,
HostHeader: "81.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
Error: simulation.ErrNoListener,
},
},
},
},
simulationTest{
name: "multiple overlapping services",
config: createGatewayWithServiceSelector("gateway", "istio-ingressgateway.dubbo-system.svc.cluster.local,ingress.com", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`, `
port:
number: 81
name: http-81
protocol: HTTP
hosts:
- "81.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance2
namespace: dubbo-system
spec:
hosts: ["ingress.com"]
ports:
- number: 81
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a-80
spec:
hosts:
- "80.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a-81
spec:
hosts:
- "81.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Name: "port 8080 from 80",
Call: simulation.Call{
Port: 8080,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "80.example.com:80",
},
},
{
Name: "port 8080 from 81",
Call: simulation.Call{
Port: 8080,
HostHeader: "81.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "81.example.com:81",
},
},
},
},
simulationTest{
name: "multiple overlapping services wildcard",
config: createGatewayWithServiceSelector("gateway", "istio-ingressgateway.dubbo-system.svc.cluster.local,ingress.com", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`, `
port:
number: 81
name: http-81
protocol: HTTP
hosts:
- "81.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance2
namespace: dubbo-system
spec:
hosts: ["ingress.com"]
ports:
- number: 81
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a-80
spec:
hosts:
- "80.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a-81
spec:
hosts:
- "81.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Name: "port 8080 from 80",
Call: simulation.Call{
Port: 8080,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "80.example.com:80",
},
},
{
Name: "port 8080 from 81",
Call: simulation.Call{
Port: 8080,
HostHeader: "81.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "81.example.com:81",
},
},
},
},
simulationTest{
name: "no match selector",
config: createGateway("gateway", "dubbo-system", `
port:
number: 80
name: http-80
protocol: HTTP
hosts:
- "80.example.com"
`, `
port:
number: 82
name: http-82
protocol: HTTP
hosts:
- "82.example.com"
`) + `
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: service-instance
namespace: dubbo-system
spec:
hosts: ["istio-ingressgateway.dubbo-system.svc.cluster.local"]
ports:
- number: 80
targetPort: 8080
name: http
protocol: HTTP
resolution: STATIC
location: MESH_INTERNAL
endpoints:
- address: 1.1.1.1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: a
spec:
hosts:
- "*.example.com"
gateways:
- dubbo-system/gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: a
port:
number: 80`,
calls: []simulation.Expect{
{
Name: "port 8080",
Call: simulation.Call{
Port: 8080,
HostHeader: "80.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_8080",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.8080",
VirtualHostMatched: "80.example.com:80",
},
},
{
// For gateway selector more, if there is no matching service we use the port as is
Name: "no service match",
Call: simulation.Call{
Port: 82,
HostHeader: "82.example.com",
Protocol: simulation.HTTP,
},
Result: simulation.Result{
ListenerMatched: "0.0.0.0_82",
ClusterMatched: "outbound|80||a.default",
RouteConfigMatched: "http.82",
VirtualHostMatched: "82.example.com:82",
},
},
},
})
}