github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/servers/manager_test.go (about) 1 package servers_test 2 3 import ( 4 "fmt" 5 "math/rand" 6 "net" 7 "strings" 8 "testing" 9 10 "github.com/hashicorp/nomad/ci" 11 "github.com/hashicorp/nomad/client/servers" 12 "github.com/hashicorp/nomad/helper/testlog" 13 "github.com/stretchr/testify/require" 14 ) 15 16 type fauxAddr struct { 17 Addr string 18 } 19 20 func (fa *fauxAddr) String() string { return fa.Addr } 21 func (fa *fauxAddr) Network() string { return fa.Addr } 22 23 type fauxConnPool struct { 24 // failPct between 0.0 and 1.0 == pct of time a Ping should fail 25 failPct float64 26 } 27 28 func (cp *fauxConnPool) Ping(net.Addr) error { 29 successProb := rand.Float64() 30 if successProb > cp.failPct { 31 return nil 32 } 33 return fmt.Errorf("bad server") 34 } 35 36 func testManager(t *testing.T) (m *servers.Manager) { 37 logger := testlog.HCLogger(t) 38 shutdownCh := make(chan struct{}) 39 m = servers.New(logger, shutdownCh, &fauxConnPool{}) 40 return m 41 } 42 43 func testManagerFailProb(t *testing.T, failPct float64) (m *servers.Manager) { 44 logger := testlog.HCLogger(t) 45 shutdownCh := make(chan struct{}) 46 m = servers.New(logger, shutdownCh, &fauxConnPool{failPct: failPct}) 47 return m 48 } 49 50 func TestServers_SetServers(t *testing.T) { 51 ci.Parallel(t) 52 53 require := require.New(t) 54 m := testManager(t) 55 var num int 56 num = m.NumServers() 57 if num != 0 { 58 t.Fatalf("Expected zero servers to start") 59 } 60 61 s1 := &servers.Server{Addr: &fauxAddr{"server1"}} 62 s2 := &servers.Server{Addr: &fauxAddr{"server2"}} 63 require.True(m.SetServers([]*servers.Server{s1, s2})) 64 require.False(m.SetServers([]*servers.Server{s1, s2})) 65 require.False(m.SetServers([]*servers.Server{s2, s1})) 66 require.Equal(2, m.NumServers()) 67 require.Len(m.GetServers(), 2) 68 69 require.True(m.SetServers([]*servers.Server{s1})) 70 require.Equal(1, m.NumServers()) 71 require.Len(m.GetServers(), 1) 72 73 // Test that the list of servers does not get shuffled 74 // as a side effect when incoming list is equal 75 require.True(m.SetServers([]*servers.Server{s1, s2})) 76 before := m.GetServers() 77 require.False(m.SetServers([]*servers.Server{s1, s2})) 78 after := m.GetServers() 79 require.Equal(before, after) 80 81 // Send a shuffled list, verify original order doesn't change 82 require.False(m.SetServers([]*servers.Server{s2, s1})) 83 afterShuffledInput := m.GetServers() 84 require.Equal(after, afterShuffledInput) 85 } 86 87 func TestServers_FindServer(t *testing.T) { 88 ci.Parallel(t) 89 90 m := testManager(t) 91 92 if m.FindServer() != nil { 93 t.Fatalf("Expected nil return") 94 } 95 96 var srvs []*servers.Server 97 srvs = append(srvs, &servers.Server{Addr: &fauxAddr{"s1"}}) 98 m.SetServers(srvs) 99 if m.NumServers() != 1 { 100 t.Fatalf("Expected one server") 101 } 102 103 s1 := m.FindServer() 104 if s1 == nil { 105 t.Fatalf("Expected non-nil server") 106 } 107 if s1.String() != "s1" { 108 t.Fatalf("Expected s1 server") 109 } 110 111 s1 = m.FindServer() 112 if s1 == nil || s1.String() != "s1" { 113 t.Fatalf("Expected s1 server (still)") 114 } 115 116 srvs = append(srvs, &servers.Server{Addr: &fauxAddr{"s2"}}) 117 m.SetServers(srvs) 118 if m.NumServers() != 2 { 119 t.Fatalf("Expected two servers") 120 } 121 s1 = m.FindServer() 122 123 for _, srv := range srvs { 124 m.NotifyFailedServer(srv) 125 } 126 127 s2 := m.FindServer() 128 if s1.Equal(s2) { 129 t.Fatalf("Expected different server") 130 } 131 } 132 133 func TestServers_New(t *testing.T) { 134 ci.Parallel(t) 135 136 logger := testlog.HCLogger(t) 137 shutdownCh := make(chan struct{}) 138 m := servers.New(logger, shutdownCh, &fauxConnPool{}) 139 if m == nil { 140 t.Fatalf("Manager nil") 141 } 142 } 143 144 func TestServers_NotifyFailedServer(t *testing.T) { 145 ci.Parallel(t) 146 147 m := testManager(t) 148 149 if m.NumServers() != 0 { 150 t.Fatalf("Expected zero servers to start") 151 } 152 153 s1 := &servers.Server{Addr: &fauxAddr{"s1"}} 154 s2 := &servers.Server{Addr: &fauxAddr{"s2"}} 155 156 // Try notifying for a server that is not managed by Manager 157 m.NotifyFailedServer(s1) 158 if m.NumServers() != 0 { 159 t.Fatalf("Expected zero servers to start") 160 } 161 m.SetServers([]*servers.Server{s1}) 162 163 // Test again w/ a server not in the list 164 m.NotifyFailedServer(s2) 165 if m.NumServers() != 1 { 166 t.Fatalf("Expected one server") 167 } 168 169 m.SetServers([]*servers.Server{s1, s2}) 170 if m.NumServers() != 2 { 171 t.Fatalf("Expected two servers") 172 } 173 174 // Grab a server 175 first := m.FindServer() 176 177 // Find the other server 178 second := s1 179 if first.Equal(s1) { 180 second = s2 181 } 182 183 // Fail the other server 184 m.NotifyFailedServer(second) 185 next := m.FindServer() 186 if !next.Equal(first) { 187 t.Fatalf("Expected first server (still)") 188 } 189 190 // Fail the first 191 m.NotifyFailedServer(first) 192 next = m.FindServer() 193 if !next.Equal(second) { 194 t.Fatalf("Expected second server") 195 } 196 197 // Fail the second 198 m.NotifyFailedServer(second) 199 next = m.FindServer() 200 if !next.Equal(first) { 201 t.Fatalf("Expected first server") 202 } 203 } 204 205 func TestServers_NumServers(t *testing.T) { 206 ci.Parallel(t) 207 208 m := testManager(t) 209 var num int 210 num = m.NumServers() 211 if num != 0 { 212 t.Fatalf("Expected zero servers to start") 213 } 214 215 s := &servers.Server{Addr: &fauxAddr{"server1"}} 216 m.SetServers([]*servers.Server{s}) 217 num = m.NumServers() 218 if num != 1 { 219 t.Fatalf("Expected one server after SetServers") 220 } 221 } 222 223 func TestServers_RebalanceServers(t *testing.T) { 224 ci.Parallel(t) 225 226 const failPct = 0.5 227 m := testManagerFailProb(t, failPct) 228 const maxServers = 100 229 const numShuffleTests = 100 230 const uniquePassRate = 0.5 231 232 // Make a huge list of nodes. 233 var srvs []*servers.Server 234 for i := 0; i < maxServers; i++ { 235 nodeName := fmt.Sprintf("s%02d", i) 236 srvs = append(srvs, &servers.Server{Addr: &fauxAddr{nodeName}}) 237 } 238 m.SetServers(srvs) 239 240 // Keep track of how many unique shuffles we get. 241 uniques := make(map[string]struct{}, maxServers) 242 for i := 0; i < numShuffleTests; i++ { 243 m.RebalanceServers() 244 245 var names []string 246 for j := 0; j < maxServers; j++ { 247 server := m.FindServer() 248 m.NotifyFailedServer(server) 249 names = append(names, server.String()) 250 } 251 key := strings.Join(names, "|") 252 uniques[key] = struct{}{} 253 } 254 255 // We have to allow for the fact that there won't always be a unique 256 // shuffle each pass, so we just look for smell here without the test 257 // being flaky. 258 if len(uniques) < int(maxServers*uniquePassRate) { 259 t.Fatalf("unique shuffle ratio too low: %d/%d", len(uniques), maxServers) 260 } 261 }