github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/agent/router/router_test.go (about) 1 package router 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "os" 8 "reflect" 9 "sort" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/agent/structs" 15 "github.com/hashicorp/consul/lib" 16 "github.com/hashicorp/consul/types" 17 "github.com/hashicorp/serf/coordinate" 18 "github.com/hashicorp/serf/serf" 19 ) 20 21 type mockCluster struct { 22 self string 23 members []serf.Member 24 coords map[string]*coordinate.Coordinate 25 addr int 26 } 27 28 func newMockCluster(self string) *mockCluster { 29 return &mockCluster{ 30 self: self, 31 coords: make(map[string]*coordinate.Coordinate), 32 addr: 1, 33 } 34 } 35 36 func (m *mockCluster) NumNodes() int { 37 return len(m.members) 38 } 39 40 func (m *mockCluster) Members() []serf.Member { 41 return m.members 42 } 43 44 func (m *mockCluster) GetCoordinate() (*coordinate.Coordinate, error) { 45 return m.coords[m.self], nil 46 } 47 48 func (m *mockCluster) GetCachedCoordinate(name string) (*coordinate.Coordinate, bool) { 49 coord, ok := m.coords[name] 50 return coord, ok 51 } 52 53 func (m *mockCluster) AddMember(dc string, name string, coord *coordinate.Coordinate) { 54 member := serf.Member{ 55 Name: fmt.Sprintf("%s.%s", name, dc), 56 Addr: net.ParseIP(fmt.Sprintf("127.0.0.%d", m.addr)), 57 Port: 8300, 58 Tags: map[string]string{ 59 "dc": dc, 60 "role": "consul", 61 "port": "8300", 62 "build": "0.8.0", 63 "vsn": "3", 64 }, 65 } 66 m.members = append(m.members, member) 67 if coord != nil { 68 m.coords[member.Name] = coord 69 } 70 m.addr++ 71 } 72 73 // testCluster is used to generate a single WAN-like area with a known set of 74 // member and RTT topology. 75 // 76 // Here's the layout of the nodes: 77 // 78 // /---- dc1 ----\ /- dc2 -\ /- dc0 -\ 79 // node2 node1 node3 node1 node0 80 // | | | | | | | | | | | 81 // 0 1 2 3 4 5 6 7 8 9 10 (ms) 82 // 83 // We also include a node4 in dc1 with no known coordinate, as well as a 84 // mysterious dcX with no nodes with known coordinates. 85 func testCluster(self string) *mockCluster { 86 c := newMockCluster(self) 87 c.AddMember("dc0", "node0", lib.GenerateCoordinate(10*time.Millisecond)) 88 c.AddMember("dc1", "node1", lib.GenerateCoordinate(3*time.Millisecond)) 89 c.AddMember("dc1", "node2", lib.GenerateCoordinate(2*time.Millisecond)) 90 c.AddMember("dc1", "node3", lib.GenerateCoordinate(5*time.Millisecond)) 91 c.AddMember("dc1", "node4", nil) 92 c.AddMember("dc2", "node1", lib.GenerateCoordinate(8*time.Millisecond)) 93 c.AddMember("dcX", "node1", nil) 94 return c 95 } 96 97 func testRouter(dc string) *Router { 98 logger := log.New(os.Stderr, "", log.LstdFlags) 99 return NewRouter(logger, dc) 100 } 101 102 func TestRouter_Shutdown(t *testing.T) { 103 r := testRouter("dc0") 104 105 // Create a WAN-looking area. 106 self := "node0.dc0" 107 wan := testCluster(self) 108 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil { 109 t.Fatalf("err: %v", err) 110 } 111 112 // Add another area. 113 otherID := types.AreaID("other") 114 other := newMockCluster(self) 115 other.AddMember("dcY", "node1", nil) 116 if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 _, _, ok := r.FindRoute("dcY") 120 if !ok { 121 t.Fatalf("bad") 122 } 123 124 // Shutdown and make sure we can't see any routes from before. 125 r.Shutdown() 126 _, _, ok = r.FindRoute("dcY") 127 if ok { 128 t.Fatalf("bad") 129 } 130 131 // You can't add areas once the router is shut down. 132 err := r.AddArea(otherID, other, &fauxConnPool{}, false) 133 if err == nil || !strings.Contains(err.Error(), "router is shut down") { 134 t.Fatalf("err: %v", err) 135 } 136 } 137 138 func TestRouter_Routing(t *testing.T) { 139 r := testRouter("dc0") 140 141 // Create a WAN-looking area. 142 self := "node0.dc0" 143 wan := testCluster(self) 144 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil { 145 t.Fatalf("err: %v", err) 146 } 147 148 // Adding the area should enable all the routes right away. 149 if _, _, ok := r.FindRoute("dc0"); !ok { 150 t.Fatalf("bad") 151 } 152 if _, _, ok := r.FindRoute("dc1"); !ok { 153 t.Fatalf("bad") 154 } 155 if _, _, ok := r.FindRoute("dc2"); !ok { 156 t.Fatalf("bad") 157 } 158 if _, _, ok := r.FindRoute("dcX"); !ok { 159 t.Fatalf("bad") 160 } 161 162 // This hasn't been added yet. 163 if _, _, ok := r.FindRoute("dcY"); ok { 164 t.Fatalf("bad") 165 } 166 167 // Add another area. 168 otherID := types.AreaID("other") 169 other := newMockCluster(self) 170 other.AddMember("dc0", "node0", nil) 171 other.AddMember("dc1", "node1", nil) 172 other.AddMember("dcY", "node1", nil) 173 if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil { 174 t.Fatalf("err: %v", err) 175 } 176 177 // Now we should have a route to every DC. 178 if _, _, ok := r.FindRoute("dc0"); !ok { 179 t.Fatalf("bad") 180 } 181 if _, _, ok := r.FindRoute("dc1"); !ok { 182 t.Fatalf("bad") 183 } 184 if _, _, ok := r.FindRoute("dc2"); !ok { 185 t.Fatalf("bad") 186 } 187 if _, _, ok := r.FindRoute("dcX"); !ok { 188 t.Fatalf("bad") 189 } 190 if _, _, ok := r.FindRoute("dcY"); !ok { 191 t.Fatalf("bad") 192 } 193 194 // Get the route for dcY and then fail the server. This will still 195 // give the server back since we have no other choice. 196 _, s, ok := r.FindRoute("dcY") 197 if !ok { 198 t.Fatalf("bad") 199 } 200 if err := r.FailServer(otherID, s); err != nil { 201 t.Fatalf("err: %v", err) 202 } 203 if _, _, ok := r.FindRoute("dcY"); !ok { 204 t.Fatalf("bad") 205 } 206 207 // But if we remove the server we won't get a route. 208 if err := r.RemoveServer(otherID, s); err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 if _, _, ok := r.FindRoute("dcY"); ok { 212 t.Fatalf("bad") 213 } 214 215 // Make sure the dcY manager also got removed from the area and from 216 // the index we use for routing. 217 func() { 218 r.RLock() 219 defer r.RUnlock() 220 221 area, ok := r.areas[otherID] 222 if !ok { 223 t.Fatalf("bad") 224 } 225 226 if _, ok := area.managers["dcY"]; ok { 227 t.Fatalf("bad") 228 } 229 230 if _, ok := r.managers["dcY"]; ok { 231 t.Fatalf("bad") 232 } 233 }() 234 235 // Do similar for dc0, which will take two removes because the dc0 is 236 // reachable from two different areas. 237 _, s, ok = r.FindRoute("dc0") 238 if !ok { 239 t.Fatalf("bad") 240 } 241 if err := r.RemoveServer(types.AreaWAN, s); err != nil { 242 t.Fatalf("err: %v", err) 243 } 244 if _, _, ok = r.FindRoute("dc0"); !ok { 245 t.Fatalf("bad") 246 } 247 if err := r.RemoveServer(otherID, s); err != nil { 248 t.Fatalf("err: %v", err) 249 } 250 if _, _, ok = r.FindRoute("dc0"); ok { 251 t.Fatalf("bad") 252 } 253 254 // Now delete some areas. 255 if _, _, ok = r.FindRoute("dc1"); !ok { 256 t.Fatalf("bad") 257 } 258 if err := r.RemoveArea(types.AreaWAN); err != nil { 259 t.Fatalf("err: %v", err) 260 } 261 if _, _, ok = r.FindRoute("dc1"); !ok { 262 t.Fatalf("bad") 263 } 264 if err := r.RemoveArea(otherID); err != nil { 265 t.Fatalf("err: %v", err) 266 } 267 if _, _, ok = r.FindRoute("dc1"); ok { 268 t.Fatalf("bad") 269 } 270 } 271 272 func TestRouter_Routing_Offline(t *testing.T) { 273 r := testRouter("dc0") 274 275 // Create a WAN-looking area. 276 self := "node0.dc0" 277 wan := testCluster(self) 278 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{1.0}, false); err != nil { 279 t.Fatalf("err: %v", err) 280 } 281 282 // Adding the area should enable all the routes right away. 283 if _, _, ok := r.FindRoute("dc0"); !ok { 284 t.Fatalf("bad") 285 } 286 if _, _, ok := r.FindRoute("dc1"); !ok { 287 t.Fatalf("bad") 288 } 289 if _, _, ok := r.FindRoute("dc2"); !ok { 290 t.Fatalf("bad") 291 } 292 if _, _, ok := r.FindRoute("dcX"); !ok { 293 t.Fatalf("bad") 294 } 295 296 // Do a rebalance for dc1, which should knock it offline. 297 func() { 298 r.Lock() 299 defer r.Unlock() 300 301 area, ok := r.areas[types.AreaWAN] 302 if !ok { 303 t.Fatalf("bad") 304 } 305 306 info, ok := area.managers["dc1"] 307 if !ok { 308 t.Fatalf("bad") 309 } 310 info.manager.RebalanceServers() 311 }() 312 313 // Recheck all the routes. 314 if _, _, ok := r.FindRoute("dc0"); !ok { 315 t.Fatalf("bad") 316 } 317 if _, _, ok := r.FindRoute("dc1"); ok { 318 t.Fatalf("bad") 319 } 320 if _, _, ok := r.FindRoute("dc2"); !ok { 321 t.Fatalf("bad") 322 } 323 if _, _, ok := r.FindRoute("dcX"); !ok { 324 t.Fatalf("bad") 325 } 326 327 // Add another area with a route to dc1. 328 otherID := types.AreaID("other") 329 other := newMockCluster(self) 330 other.AddMember("dc0", "node0", nil) 331 other.AddMember("dc1", "node1", nil) 332 if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil { 333 t.Fatalf("err: %v", err) 334 } 335 336 // Recheck all the routes and make sure it finds the one that's 337 // online. 338 if _, _, ok := r.FindRoute("dc0"); !ok { 339 t.Fatalf("bad") 340 } 341 if _, _, ok := r.FindRoute("dc1"); !ok { 342 t.Fatalf("bad") 343 } 344 if _, _, ok := r.FindRoute("dc2"); !ok { 345 t.Fatalf("bad") 346 } 347 if _, _, ok := r.FindRoute("dcX"); !ok { 348 t.Fatalf("bad") 349 } 350 } 351 352 func TestRouter_GetDatacenters(t *testing.T) { 353 r := testRouter("dc0") 354 355 self := "node0.dc0" 356 wan := testCluster(self) 357 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil { 358 t.Fatalf("err: %v", err) 359 } 360 361 actual := r.GetDatacenters() 362 expected := []string{"dc0", "dc1", "dc2", "dcX"} 363 if !reflect.DeepEqual(actual, expected) { 364 t.Fatalf("bad: %#v", actual) 365 } 366 } 367 368 func TestRouter_distanceSorter(t *testing.T) { 369 actual := &datacenterSorter{ 370 Names: []string{"foo", "bar", "baz", "zoo"}, 371 Vec: []float64{3.0, 1.0, 1.0, 0.0}, 372 } 373 sort.Stable(actual) 374 expected := &datacenterSorter{ 375 Names: []string{"zoo", "bar", "baz", "foo"}, 376 Vec: []float64{0.0, 1.0, 1.0, 3.0}, 377 } 378 if !reflect.DeepEqual(actual, expected) { 379 t.Fatalf("bad: %#v", *expected) 380 } 381 } 382 383 func TestRouter_GetDatacentersByDistance(t *testing.T) { 384 r := testRouter("dc0") 385 386 // Start with just the WAN area described in the diagram above. 387 self := "node0.dc0" 388 wan := testCluster(self) 389 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil { 390 t.Fatalf("err: %v", err) 391 } 392 393 actual, err := r.GetDatacentersByDistance() 394 if err != nil { 395 t.Fatalf("err: %v", err) 396 } 397 expected := []string{"dc0", "dc2", "dc1", "dcX"} 398 if !reflect.DeepEqual(actual, expected) { 399 t.Fatalf("bad: %#v", actual) 400 } 401 402 // Now add another area with a closer route for dc1. 403 otherID := types.AreaID("other") 404 other := newMockCluster(self) 405 other.AddMember("dc0", "node0", lib.GenerateCoordinate(20*time.Millisecond)) 406 other.AddMember("dc1", "node1", lib.GenerateCoordinate(21*time.Millisecond)) 407 if err := r.AddArea(otherID, other, &fauxConnPool{}, false); err != nil { 408 t.Fatalf("err: %v", err) 409 } 410 411 actual, err = r.GetDatacentersByDistance() 412 if err != nil { 413 t.Fatalf("err: %v", err) 414 } 415 expected = []string{"dc0", "dc1", "dc2", "dcX"} 416 if !reflect.DeepEqual(actual, expected) { 417 t.Fatalf("bad: %#v", actual) 418 } 419 } 420 421 func TestRouter_GetDatacenterMaps(t *testing.T) { 422 r := testRouter("dc0") 423 424 self := "node0.dc0" 425 wan := testCluster(self) 426 if err := r.AddArea(types.AreaWAN, wan, &fauxConnPool{}, false); err != nil { 427 t.Fatalf("err: %v", err) 428 } 429 430 actual, err := r.GetDatacenterMaps() 431 if err != nil { 432 t.Fatalf("err: %v", err) 433 } 434 if len(actual) != 3 { 435 t.Fatalf("bad: %#v", actual) 436 } 437 for _, entry := range actual { 438 switch entry.Datacenter { 439 case "dc0": 440 if !reflect.DeepEqual(entry, structs.DatacenterMap{ 441 Datacenter: "dc0", 442 AreaID: types.AreaWAN, 443 Coordinates: structs.Coordinates{ 444 &structs.Coordinate{ 445 Node: "node0.dc0", 446 Coord: lib.GenerateCoordinate(10 * time.Millisecond), 447 }, 448 }, 449 }) { 450 t.Fatalf("bad: %#v", entry) 451 } 452 case "dc1": 453 if !reflect.DeepEqual(entry, structs.DatacenterMap{ 454 Datacenter: "dc1", 455 AreaID: types.AreaWAN, 456 Coordinates: structs.Coordinates{ 457 &structs.Coordinate{ 458 Node: "node1.dc1", 459 Coord: lib.GenerateCoordinate(3 * time.Millisecond), 460 }, 461 &structs.Coordinate{ 462 Node: "node2.dc1", 463 Coord: lib.GenerateCoordinate(2 * time.Millisecond), 464 }, 465 &structs.Coordinate{ 466 Node: "node3.dc1", 467 Coord: lib.GenerateCoordinate(5 * time.Millisecond), 468 }, 469 }, 470 }) { 471 t.Fatalf("bad: %#v", entry) 472 } 473 case "dc2": 474 if !reflect.DeepEqual(entry, structs.DatacenterMap{ 475 Datacenter: "dc2", 476 AreaID: types.AreaWAN, 477 Coordinates: structs.Coordinates{ 478 &structs.Coordinate{ 479 Node: "node1.dc2", 480 Coord: lib.GenerateCoordinate(8 * time.Millisecond), 481 }, 482 }, 483 }) { 484 t.Fatalf("bad: %#v", entry) 485 } 486 default: 487 t.Fatalf("bad: %#v", entry) 488 } 489 } 490 }