Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
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.

468 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() {