envoy: add hash policy and routing key for hash-based load balancers (#2791)

* envoy: add hash policy and routing key for hash-based load balancers

* fix integration test

* fix nginx
This commit is contained in:
Caleb Doxsey 2021-12-01 13:42:12 -07:00 committed by GitHub
parent bd0a5389bf
commit c97dcf7e0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 12935 additions and 182 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,12 +4,15 @@ import (
"context"
"crypto/tls"
"encoding/json"
"io"
"net/http"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/pomerium/pomerium/integration/flows"
)
func TestCORS(t *testing.T) {
@ -248,3 +251,72 @@ func TestGoogleCloudRun(t *testing.T) {
assert.NotEmpty(t, result.Headers["authorization"], "expected authorization header when cloudrun is enabled")
}
}
func TestLoadBalancer(t *testing.T) {
if ClusterType == "traefik" || ClusterType == "nginx" {
t.Skip()
return
}
ctx, clearTimeout := context.WithTimeout(context.Background(), time.Minute*10)
defer clearTimeout()
getDistribution := func(t *testing.T, path string) map[string]float64 {
client := getClient()
distribution := map[string]float64{}
res, err := flows.Authenticate(ctx, client,
mustParseURL("https://httpdetails.localhost.pomerium.io/"+path),
flows.WithEmail("user1@dogs.test"))
if !assert.NoError(t, err) {
return distribution
}
_, _ = io.ReadAll(res.Body)
_ = res.Body.Close()
for i := 0; i < 100; i++ {
req, err := http.NewRequestWithContext(ctx, "GET",
"https://httpdetails.localhost.pomerium.io/"+path, nil)
if !assert.NoError(t, err) {
return distribution
}
res, err = client.Do(req)
if !assert.NoError(t, err) {
return distribution
}
var result struct {
Hostname string `json:"hostname"`
}
err = json.NewDecoder(res.Body).Decode(&result)
_ = res.Body.Close()
assert.NoError(t, err)
distribution[result.Hostname]++
}
return distribution
}
t.Run("round robin", func(t *testing.T) {
distribution := getDistribution(t, "round-robin")
var xs []float64
for _, x := range distribution {
xs = append(xs, x)
}
assert.Lessf(t, standardDeviation(xs), 10.0, "should distribute requests evenly, got: %v",
distribution)
})
t.Run("ring hash", func(t *testing.T) {
distribution := getDistribution(t, "ring-hash")
assert.Lenf(t, distribution, 1, "should distribute requests to a single backend, got: %v",
distribution)
})
t.Run("maglev", func(t *testing.T) {
distribution := getDistribution(t, "maglev")
assert.Lenf(t, distribution, 1, "should distribute requests to a single backend, got: %v",
distribution)
})
}

26
integration/stats.go Normal file
View file

@ -0,0 +1,26 @@
package main
import "math"
func mean(xs []float64) float64 {
var sum float64
for _, x := range xs {
sum += x
}
return sum / float64(len(xs))
}
func variance(xs []float64) float64 {
m := mean(xs)
var sum float64
for _, x := range xs {
dx := x - m
sum += dx * dx
}
return sum / float64(len(xs))
}
func standardDeviation(xs []float64) float64 {
return math.Sqrt(variance(xs))
}

View file

@ -7,6 +7,21 @@ local Variations() =
cert: importstr '../files/trusted.pem',
key: importstr '../files/trusted-key.pem',
},
{
name: 'trusted-1',
cert: importstr '../files/trusted.pem',
key: importstr '../files/trusted-key.pem',
},
{
name: 'trusted-2',
cert: importstr '../files/trusted.pem',
key: importstr '../files/trusted-key.pem',
},
{
name: 'trusted-3',
cert: importstr '../files/trusted.pem',
key: importstr '../files/trusted-key.pem',
},
{
name: 'untrusted',
cert: importstr '../files/untrusted.pem',

View file

@ -88,6 +88,9 @@ local RouteLocationConfig(route) =
if std.objectHas(route, 'prefix') then '^~ ' + route.prefix
else if std.objectHas(route, 'path') then '= ' + route.path
else '/';
local to =
if std.isArray(route.to) then route.to[0]
else route.to;
|||
location %s {
proxy_pass %s;
@ -100,7 +103,7 @@ local RouteLocationConfig(route) =
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
}
||| % [rule, route.to];
||| % [rule, to];
local DomainServerConfig(domain, routes) =
local locations = std.join('\n', std.map(function(route) RouteLocationConfig(route), routes));

View file

@ -121,6 +121,9 @@ local ComposeService(name, definition, additionalAliases=[]) =
'mock-idp',
'redis',
'trusted-httpdetails',
'trusted-1-httpdetails',
'trusted-2-httpdetails',
'trusted-3-httpdetails',
'untrusted-httpdetails',
'verify',
'websocket-echo',

View file

@ -149,6 +149,42 @@ local Routes(mode, idp, dns_suffix) =
allowed_users: ['user1@dogs.test'],
pass_identity_headers: true,
},
// round robin load balancer
{
from: 'https://httpdetails.localhost.pomerium.io',
to: [
'http://trusted-1-httpdetails' + dns_suffix + ':8080',
'http://trusted-2-httpdetails' + dns_suffix + ':8080',
'http://trusted-3-httpdetails' + dns_suffix + ':8080',
],
prefix: '/round-robin',
allow_any_authenticated_user: true,
lb_policy: 'ROUND_ROBIN',
},
// ring hash load balancer
{
from: 'https://httpdetails.localhost.pomerium.io',
to: [
'http://trusted-1-httpdetails' + dns_suffix + ':8080',
'http://trusted-2-httpdetails' + dns_suffix + ':8080',
'http://trusted-3-httpdetails' + dns_suffix + ':8080',
],
prefix: '/ring-hash',
allow_any_authenticated_user: true,
lb_policy: 'RING_HASH',
},
// maglev load balancer
{
from: 'https://httpdetails.localhost.pomerium.io',
to: [
'http://trusted-1-httpdetails' + dns_suffix + ':8080',
'http://trusted-2-httpdetails' + dns_suffix + ':8080',
'http://trusted-3-httpdetails' + dns_suffix + ':8080',
],
prefix: '/maglev',
allow_any_authenticated_user: true,
lb_policy: 'MAGLEV',
},
// catch-all
{
from: 'https://httpdetails.localhost.pomerium.io',