mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-29 22:48:15 +02:00
add tests/benchmarks for http1/http2 tcp tunnels and http1 websockets (#5471)
* add tests/benchmarks for http1/http2 tcp tunnels and http1 websockets testenv: - add new TCP upstream - add websocket functions to HTTP upstream - add https support to mock idp (default on) - add new debug flags -env.bind-address and -env.use-trace-environ to allow changing the default bind address, and enabling otel environment based trace config, respectively * linter pass --------- Co-authored-by: Denis Mishin <dmishin@pomerium.com>
This commit is contained in:
parent
d6b02441b3
commit
08623ef346
12 changed files with 1104 additions and 182 deletions
|
@ -1,11 +1,17 @@
|
|||
package envoyconfig_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
@ -28,9 +34,9 @@ func TestH2C(t *testing.T) {
|
|||
|
||||
http := up.Route().
|
||||
From(env.SubdomainURL("grpc-http")).
|
||||
To(values.Bind(up.Port(), func(port int) string {
|
||||
To(values.Bind(up.Addr(), func(addr string) string {
|
||||
// override the target protocol to use http://
|
||||
return fmt.Sprintf("http://127.0.0.1:%d", port)
|
||||
return fmt.Sprintf("http://%s", addr)
|
||||
})).
|
||||
Policy(func(p *config.Policy) { p.AllowPublicUnauthenticatedAccess = true })
|
||||
|
||||
|
@ -118,6 +124,234 @@ func TestHTTP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestTCPTunnel(t *testing.T) {
|
||||
env := testenv.New(t, testenv.Debug())
|
||||
|
||||
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
|
||||
up := upstreams.TCP()
|
||||
routeH1 := up.Route().
|
||||
From(env.SubdomainURL("h1")).
|
||||
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
|
||||
routeH2 := up.Route().
|
||||
From(env.SubdomainURL("h2")).
|
||||
Policy(func(p *config.Policy) {
|
||||
p.AllowWebsockets = true
|
||||
}).
|
||||
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
|
||||
|
||||
up.Handle(func(_ context.Context, c net.Conn) error {
|
||||
c.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
buf := make([]byte, 8)
|
||||
n, err := c.Read(buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(buf[:n]), "hello")
|
||||
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err = c.Write([]byte("world"))
|
||||
require.NoError(t, err)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
env.AddUpstream(up)
|
||||
env.Start()
|
||||
snippets.WaitStartupComplete(env)
|
||||
|
||||
t.Run("http1", func(t *testing.T) {
|
||||
assert.NoError(t, up.Dial(routeH1, func(_ context.Context, c net.Conn) error {
|
||||
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err := c.Write([]byte("hello"))
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
c.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, err := c.Read(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(buf[:n]), "world")
|
||||
return nil
|
||||
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(upstreams.DialHTTP1)))
|
||||
})
|
||||
|
||||
t.Run("http2", func(t *testing.T) {
|
||||
assert.NoError(t, up.Dial(routeH2, func(_ context.Context, c net.Conn) error {
|
||||
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err := c.Write([]byte("hello"))
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
c.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, err := c.Read(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(buf[:n]), "world")
|
||||
return nil
|
||||
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(upstreams.DialHTTP2)))
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHTTP1TCPTunnel(b *testing.B) {
|
||||
env := testenv.New(b, testenv.Silent())
|
||||
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
|
||||
up := upstreams.TCP()
|
||||
h1 := up.Route().
|
||||
From(env.SubdomainURL("bench-h1")).
|
||||
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
|
||||
|
||||
env.AddUpstream(up)
|
||||
env.Start()
|
||||
snippets.WaitStartupComplete(env)
|
||||
|
||||
b.Run("http1", func(b *testing.B) {
|
||||
benchmarkTCP(b, up, h1, tcpBenchmarkParams{
|
||||
msgLen: 512,
|
||||
protocol: upstreams.DialHTTP1,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHTTP2TCPTunnel(b *testing.B) {
|
||||
env := testenv.New(b, testenv.Silent())
|
||||
env.Add(scenarios.NewIDP([]*scenarios.User{{Email: "test@example.com"}}))
|
||||
up := upstreams.TCP()
|
||||
|
||||
h2 := up.Route().
|
||||
From(env.SubdomainURL("bench-h2")).
|
||||
Policy(func(p *config.Policy) {
|
||||
p.AllowWebsockets = true
|
||||
}).
|
||||
PPL(`{"allow":{"and":["email":{"is":"test@example.com"}]}}`)
|
||||
|
||||
env.AddUpstream(up)
|
||||
env.Start()
|
||||
snippets.WaitStartupComplete(env)
|
||||
|
||||
b.Run("http2", func(b *testing.B) {
|
||||
benchmarkTCP(b, up, h2, tcpBenchmarkParams{
|
||||
msgLen: 512,
|
||||
protocol: upstreams.DialHTTP2,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type tcpBenchmarkParams struct {
|
||||
msgLen int
|
||||
protocol upstreams.Protocol
|
||||
}
|
||||
|
||||
func benchmarkTCP(b *testing.B, up upstreams.TCPUpstream, route testenv.Route, params tcpBenchmarkParams) {
|
||||
sendMsg := func(c net.Conn, buf []byte) error {
|
||||
c.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err := c.Write(buf)
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
recvMsg := func(c net.Conn, buf []byte) error {
|
||||
c.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
for read := 0; read != len(buf); {
|
||||
n, err := c.Read(buf)
|
||||
read += n
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
up.Handle(func(_ context.Context, c net.Conn) error {
|
||||
for {
|
||||
buf := make([]byte, params.msgLen)
|
||||
if err := recvMsg(c, buf[:]); err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := sendMsg(c, buf[:]); err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
})
|
||||
var threads atomic.Int32
|
||||
var requests atomic.Int32
|
||||
var bytes atomic.Int64
|
||||
start := time.Now()
|
||||
b.RunParallel(func(p *testing.PB) {
|
||||
threads.Add(1)
|
||||
require.NoError(b, up.Dial(route, func(_ context.Context, c net.Conn) error {
|
||||
buf := make([]byte, params.msgLen)
|
||||
for p.Next() {
|
||||
requests.Add(1)
|
||||
bytes.Add(int64(params.msgLen))
|
||||
require.NoError(b, sendMsg(c, buf[:]))
|
||||
require.NoError(b, recvMsg(c, buf[:]))
|
||||
}
|
||||
return nil
|
||||
}, upstreams.AuthenticateAs("test@example.com"), upstreams.DialProtocol(params.protocol)))
|
||||
})
|
||||
duration := time.Since(start)
|
||||
b.Logf("sent %d requests over %d parallel connections in %s", requests.Load(), threads.Load(), duration)
|
||||
b.Logf("throughput: %f bytes/s", float64(bytes.Load())/duration.Seconds())
|
||||
}
|
||||
|
||||
func TestHttp1Websocket(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
|
||||
up := upstreams.HTTP(nil)
|
||||
up.HandleWS("/ws", websocket.Upgrader{}, func(conn *websocket.Conn) error {
|
||||
for {
|
||||
mt, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// echo the message back
|
||||
err = conn.WriteMessage(mt, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
route := up.Route().
|
||||
From(env.SubdomainURL("ws-test")).
|
||||
Policy(func(p *config.Policy) {
|
||||
p.AllowPublicUnauthenticatedAccess = true
|
||||
p.AllowWebsockets = true
|
||||
})
|
||||
|
||||
env.AddUpstream(up)
|
||||
env.Start()
|
||||
snippets.WaitStartupComplete(env)
|
||||
|
||||
assert.NoError(t, up.DialWS(route, func(conn *websocket.Conn) error {
|
||||
if err := conn.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte("hello world")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
|
||||
return err
|
||||
}
|
||||
mt, bytes, err := conn.ReadMessage()
|
||||
if err := err; err != nil {
|
||||
return err
|
||||
}
|
||||
assert.Equal(t, websocket.TextMessage, mt)
|
||||
assert.Equal(t, "hello world", string(bytes))
|
||||
return nil
|
||||
}, upstreams.Path("/ws")))
|
||||
}
|
||||
|
||||
func TestClientCert(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
env.Add(scenarios.DownstreamMTLS(config.MTLSEnforcementRejectConnection))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue