mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
11 KiB
467 lines
11 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package router_test |
|
|
|
import ( |
|
"fmt" |
|
"math/rand" |
|
"net" |
|
"strings" |
|
"testing" |
|
|
|
"github.com/hashicorp/consul/agent/metadata" |
|
"github.com/hashicorp/consul/agent/router" |
|
"github.com/hashicorp/consul/sdk/testutil" |
|
) |
|
|
|
type fauxAddr struct { |
|
addr string |
|
} |
|
|
|
func (a *fauxAddr) Network() string { |
|
return "faux" |
|
} |
|
|
|
func (a *fauxAddr) String() string { |
|
return a.addr |
|
} |
|
|
|
type fauxConnPool struct { |
|
// failPct between 0.0 and 1.0 == pct of time a Ping should fail. |
|
failPct float64 |
|
|
|
// failAddr fail whenever we see this address. |
|
failAddr net.Addr |
|
} |
|
|
|
func (cp *fauxConnPool) Ping(dc string, nodeName string, addr net.Addr) (bool, error) { |
|
var success bool |
|
|
|
successProb := rand.Float64() |
|
if successProb > cp.failPct { |
|
success = true |
|
} |
|
|
|
if cp.failAddr != nil && addr.String() == cp.failAddr.String() { |
|
success = false |
|
} |
|
|
|
return success, nil |
|
} |
|
|
|
type fauxSerf struct { |
|
} |
|
|
|
func (s *fauxSerf) NumNodes() int { |
|
return 16384 |
|
} |
|
|
|
func testManager(t testing.TB) (m *router.Manager) { |
|
logger := testutil.Logger(t) |
|
shutdownCh := make(chan struct{}) |
|
m = router.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{}, "", noopRebalancer) |
|
return m |
|
} |
|
|
|
func noopRebalancer() {} |
|
|
|
func testManagerFailProb(t testing.TB, failPct float64) (m *router.Manager) { |
|
logger := testutil.Logger(t) |
|
shutdownCh := make(chan struct{}) |
|
m = router.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{failPct: failPct}, "", noopRebalancer) |
|
return m |
|
} |
|
|
|
func testManagerFailAddr(t testing.TB, failAddr net.Addr) (m *router.Manager) { |
|
logger := testutil.Logger(t) |
|
shutdownCh := make(chan struct{}) |
|
m = router.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{failAddr: failAddr}, "", noopRebalancer) |
|
return m |
|
} |
|
|
|
// func (m *Manager) AddServer(server *metadata.Server) { |
|
func TestServers_AddServer(t *testing.T) { |
|
m := testManager(t) |
|
var num int |
|
num = m.NumServers() |
|
if num != 0 { |
|
t.Fatalf("Expected zero servers to start") |
|
} |
|
|
|
s1 := &metadata.Server{Name: "s1"} |
|
m.AddServer(s1) |
|
num = m.NumServers() |
|
if num != 1 { |
|
t.Fatalf("Expected one server") |
|
} |
|
|
|
m.AddServer(s1) |
|
num = m.NumServers() |
|
if num != 1 { |
|
t.Fatalf("Expected one server (still)") |
|
} |
|
|
|
s2 := &metadata.Server{Name: "s2"} |
|
m.AddServer(s2) |
|
num = m.NumServers() |
|
if num != 2 { |
|
t.Fatalf("Expected two servers") |
|
} |
|
} |
|
|
|
// func (m *Manager) IsOffline() bool { |
|
func TestServers_IsOffline(t *testing.T) { |
|
m := testManager(t) |
|
if !m.IsOffline() { |
|
t.Fatalf("bad") |
|
} |
|
|
|
s1 := &metadata.Server{Name: "s1"} |
|
m.AddServer(s1) |
|
if m.IsOffline() { |
|
t.Fatalf("bad") |
|
} |
|
m.RebalanceServers() |
|
if m.IsOffline() { |
|
t.Fatalf("bad") |
|
} |
|
m.RemoveServer(s1) |
|
m.RebalanceServers() |
|
if !m.IsOffline() { |
|
t.Fatalf("bad") |
|
} |
|
|
|
const failPct = 0.5 |
|
m = testManagerFailProb(t, failPct) |
|
m.AddServer(s1) |
|
var on, off int |
|
for i := 0; i < 100; i++ { |
|
m.RebalanceServers() |
|
if m.IsOffline() { |
|
off++ |
|
} else { |
|
on++ |
|
} |
|
} |
|
if on == 0 || off == 0 { |
|
t.Fatalf("bad: %d %d", on, off) |
|
} |
|
} |
|
|
|
// func (m *Manager) FindServer() (server *metadata.Server) { |
|
func TestServers_FindServer(t *testing.T) { |
|
m := testManager(t) |
|
|
|
if m.FindServer() != nil { |
|
t.Fatalf("Expected nil return") |
|
} |
|
|
|
m.AddServer(&metadata.Server{Name: "s1"}) |
|
if m.NumServers() != 1 { |
|
t.Fatalf("Expected one server") |
|
} |
|
|
|
s1 := m.FindServer() |
|
if s1 == nil { |
|
t.Fatalf("Expected non-nil server") |
|
} |
|
if s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server") |
|
} |
|
|
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server (still)") |
|
} |
|
|
|
m.AddServer(&metadata.Server{Name: "s2"}) |
|
if m.NumServers() != 2 { |
|
t.Fatalf("Expected two servers") |
|
} |
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server (still)") |
|
} |
|
|
|
m.NotifyFailedServer(s1) |
|
s2 := m.FindServer() |
|
if s2 == nil || s2.Name != "s2" { |
|
t.Fatalf("Expected s2 server") |
|
} |
|
|
|
m.NotifyFailedServer(s2) |
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server") |
|
} |
|
} |
|
|
|
func TestServers_New(t *testing.T) { |
|
logger := testutil.Logger(t) |
|
shutdownCh := make(chan struct{}) |
|
m := router.New(logger, shutdownCh, &fauxSerf{}, &fauxConnPool{}, "", noopRebalancer) |
|
if m == nil { |
|
t.Fatalf("Manager nil") |
|
} |
|
} |
|
|
|
// func (m *Manager) NotifyFailedServer(server *metadata.Server) { |
|
func TestServers_NotifyFailedServer(t *testing.T) { |
|
m := testManager(t) |
|
|
|
if m.NumServers() != 0 { |
|
t.Fatalf("Expected zero servers to start") |
|
} |
|
|
|
s1 := &metadata.Server{Name: "s1"} |
|
s2 := &metadata.Server{Name: "s2"} |
|
|
|
// Try notifying for a server that is not managed by Manager |
|
m.NotifyFailedServer(s1) |
|
if m.NumServers() != 0 { |
|
t.Fatalf("Expected zero servers to start") |
|
} |
|
m.AddServer(s1) |
|
|
|
// Test again w/ a server not in the list |
|
m.NotifyFailedServer(s2) |
|
if m.NumServers() != 1 { |
|
t.Fatalf("Expected one server") |
|
} |
|
|
|
m.AddServer(s2) |
|
if m.NumServers() != 2 { |
|
t.Fatalf("Expected two servers") |
|
} |
|
|
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server") |
|
} |
|
|
|
m.NotifyFailedServer(s2) |
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server (still)") |
|
} |
|
|
|
m.NotifyFailedServer(s1) |
|
s2 = m.FindServer() |
|
if s2 == nil || s2.Name != "s2" { |
|
t.Fatalf("Expected s2 server") |
|
} |
|
|
|
m.NotifyFailedServer(s2) |
|
s1 = m.FindServer() |
|
if s1 == nil || s1.Name != "s1" { |
|
t.Fatalf("Expected s1 server") |
|
} |
|
} |
|
|
|
// func (m *Manager) NumServers() (numServers int) { |
|
func TestServers_NumServers(t *testing.T) { |
|
m := testManager(t) |
|
var num int |
|
num = m.NumServers() |
|
if num != 0 { |
|
t.Fatalf("Expected zero servers to start") |
|
} |
|
|
|
s := &metadata.Server{} |
|
m.AddServer(s) |
|
num = m.NumServers() |
|
if num != 1 { |
|
t.Fatalf("Expected one server after AddServer") |
|
} |
|
} |
|
|
|
// func (m *Manager) RebalanceServers() { |
|
func TestServers_RebalanceServers(t *testing.T) { |
|
if testing.Short() { |
|
t.Skip("too slow for testing.Short") |
|
} |
|
|
|
const failPct = 0.5 |
|
m := testManagerFailProb(t, failPct) |
|
const maxServers = 100 |
|
const numShuffleTests = 100 |
|
const uniquePassRate = 0.5 |
|
|
|
// Make a huge list of nodes. |
|
for i := 0; i < maxServers; i++ { |
|
nodeName := fmt.Sprintf("s%02d", i) |
|
m.AddServer(&metadata.Server{Name: nodeName}) |
|
} |
|
|
|
// Keep track of how many unique shuffles we get. |
|
uniques := make(map[string]struct{}, maxServers) |
|
for i := 0; i < numShuffleTests; i++ { |
|
m.RebalanceServers() |
|
|
|
var names []string |
|
for j := 0; j < maxServers; j++ { |
|
server := m.FindServer() |
|
m.NotifyFailedServer(server) |
|
names = append(names, server.Name) |
|
} |
|
key := strings.Join(names, "|") |
|
uniques[key] = struct{}{} |
|
} |
|
|
|
// We have to allow for the fact that there won't always be a unique |
|
// shuffle each pass, so we just look for smell here without the test |
|
// being flaky. |
|
if len(uniques) < int(maxServers*uniquePassRate) { |
|
t.Fatalf("unique shuffle ratio too low: %d/%d", len(uniques), maxServers) |
|
} |
|
} |
|
|
|
func TestServers_RebalanceServers_AvoidFailed(t *testing.T) { |
|
// Do a large number of rebalances with one failed server in the |
|
// list and make sure we never have that one selected afterwards. |
|
// This was added when fixing #3463, when we were just doing the |
|
// shuffle and not actually cycling servers. We do a large number |
|
// of trials with a small number of servers to try to make sure |
|
// the shuffle alone won't give the right answer. |
|
servers := []*metadata.Server{ |
|
{Name: "s1", Addr: &fauxAddr{"s1"}}, |
|
{Name: "s2", Addr: &fauxAddr{"s2"}}, |
|
{Name: "s3", Addr: &fauxAddr{"s3"}}, |
|
} |
|
for i := 0; i < 100; i++ { |
|
m := testManagerFailAddr(t, &fauxAddr{"s2"}) |
|
for _, s := range servers { |
|
m.AddServer(s) |
|
} |
|
|
|
m.RebalanceServers() |
|
if front := m.FindServer().Name; front == "s2" { |
|
t.Fatalf("should have avoided the failed server") |
|
} |
|
} |
|
} |
|
|
|
// func (m *Manager) RemoveServer(server *metadata.Server) { |
|
func TestManager_RemoveServer(t *testing.T) { |
|
const nodeNameFmt = "s%02d" |
|
m := testManager(t) |
|
|
|
if m.NumServers() != 0 { |
|
t.Fatalf("Expected zero servers to start") |
|
} |
|
|
|
// Test removing server before its added |
|
nodeName := fmt.Sprintf(nodeNameFmt, 1) |
|
s1 := &metadata.Server{Name: nodeName} |
|
m.RemoveServer(s1) |
|
m.AddServer(s1) |
|
|
|
nodeName = fmt.Sprintf(nodeNameFmt, 2) |
|
s2 := &metadata.Server{Name: nodeName} |
|
m.RemoveServer(s2) |
|
m.AddServer(s2) |
|
|
|
const maxServers = 19 |
|
// Already added two servers above |
|
for i := maxServers; i > 2; i-- { |
|
nodeName := fmt.Sprintf(nodeNameFmt, i) |
|
server := &metadata.Server{Name: nodeName} |
|
m.AddServer(server) |
|
} |
|
if m.NumServers() != maxServers { |
|
t.Fatalf("Expected %d servers, received %d", maxServers, m.NumServers()) |
|
} |
|
|
|
m.RebalanceServers() |
|
|
|
if m.NumServers() != maxServers { |
|
t.Fatalf("Expected %d servers, received %d", maxServers, m.NumServers()) |
|
} |
|
|
|
findServer := func(server *metadata.Server) bool { |
|
for i := m.NumServers(); i > 0; i-- { |
|
s := m.FindServer() |
|
if s == server { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
expectedNumServers := maxServers |
|
removedServers := make([]*metadata.Server, 0, maxServers) |
|
|
|
// Remove servers from the front of the list |
|
for i := 3; i > 0; i-- { |
|
server := m.FindServer() |
|
if server == nil { |
|
t.Fatalf("FindServer returned nil") |
|
} |
|
m.RemoveServer(server) |
|
expectedNumServers-- |
|
if m.NumServers() != expectedNumServers { |
|
t.Fatalf("Expected %d servers (got %d)", expectedNumServers, m.NumServers()) |
|
} |
|
if findServer(server) == true { |
|
t.Fatalf("Did not expect to find server %s after removal from the front", server.Name) |
|
} |
|
removedServers = append(removedServers, server) |
|
} |
|
|
|
// Remove server from the end of the list |
|
for i := 3; i > 0; i-- { |
|
server := m.FindServer() |
|
m.NotifyFailedServer(server) |
|
m.RemoveServer(server) |
|
expectedNumServers-- |
|
if m.NumServers() != expectedNumServers { |
|
t.Fatalf("Expected %d servers (got %d)", expectedNumServers, m.NumServers()) |
|
} |
|
if findServer(server) == true { |
|
t.Fatalf("Did not expect to find server %s", server.Name) |
|
} |
|
removedServers = append(removedServers, server) |
|
} |
|
|
|
// Remove server from the middle of the list |
|
for i := 3; i > 0; i-- { |
|
server := m.FindServer() |
|
m.NotifyFailedServer(server) |
|
server2 := m.FindServer() |
|
m.NotifyFailedServer(server2) // server2 now at end of the list |
|
|
|
m.RemoveServer(server) |
|
expectedNumServers-- |
|
if m.NumServers() != expectedNumServers { |
|
t.Fatalf("Expected %d servers (got %d)", expectedNumServers, m.NumServers()) |
|
} |
|
if findServer(server) == true { |
|
t.Fatalf("Did not expect to find server %s", server.Name) |
|
} |
|
removedServers = append(removedServers, server) |
|
} |
|
|
|
if m.NumServers()+len(removedServers) != maxServers { |
|
t.Fatalf("Expected %d+%d=%d servers", m.NumServers(), len(removedServers), maxServers) |
|
} |
|
|
|
// Drain the remaining servers from the middle |
|
for i := m.NumServers(); i > 0; i-- { |
|
server := m.FindServer() |
|
m.NotifyFailedServer(server) |
|
server2 := m.FindServer() |
|
m.NotifyFailedServer(server2) // server2 now at end of the list |
|
m.RemoveServer(server) |
|
removedServers = append(removedServers, server) |
|
} |
|
|
|
if m.NumServers() != 0 { |
|
t.Fatalf("Expected an empty server list") |
|
} |
|
if len(removedServers) != maxServers { |
|
t.Fatalf("Expected all servers to be in removed server list") |
|
} |
|
} |
|
|
|
// func (m *Manager) Start() {
|
|
|