github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/rtt_test.go (about) 1 package consul 2 3 import ( 4 "fmt" 5 "net/rpc" 6 "os" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/consul/agent/structs" 12 "github.com/hashicorp/consul/lib" 13 "github.com/hashicorp/consul/testrpc" 14 "github.com/hashicorp/net-rpc-msgpackrpc" 15 ) 16 17 // verifyNodeSort makes sure the order of the nodes in the slice is the same as 18 // the expected order, expressed as a comma-separated string. 19 func verifyNodeSort(t *testing.T, nodes structs.Nodes, expected string) { 20 vec := make([]string, len(nodes)) 21 for i, node := range nodes { 22 vec[i] = node.Node 23 } 24 actual := strings.Join(vec, ",") 25 if actual != expected { 26 t.Fatalf("bad sort: %s != %s", actual, expected) 27 } 28 } 29 30 // verifyServiceNodeSort makes sure the order of the nodes in the slice is the 31 // same as the expected order, expressed as a comma-separated string. 32 func verifyServiceNodeSort(t *testing.T, nodes structs.ServiceNodes, expected string) { 33 vec := make([]string, len(nodes)) 34 for i, node := range nodes { 35 vec[i] = node.Node 36 } 37 actual := strings.Join(vec, ",") 38 if actual != expected { 39 t.Fatalf("bad sort: %s != %s", actual, expected) 40 } 41 } 42 43 // verifyHealthCheckSort makes sure the order of the nodes in the slice is the 44 // same as the expected order, expressed as a comma-separated string. 45 func verifyHealthCheckSort(t *testing.T, checks structs.HealthChecks, expected string) { 46 vec := make([]string, len(checks)) 47 for i, check := range checks { 48 vec[i] = check.Node 49 } 50 actual := strings.Join(vec, ",") 51 if actual != expected { 52 t.Fatalf("bad sort: %s != %s", actual, expected) 53 } 54 } 55 56 // verifyCheckServiceNodeSort makes sure the order of the nodes in the slice is 57 // the same as the expected order, expressed as a comma-separated string. 58 func verifyCheckServiceNodeSort(t *testing.T, nodes structs.CheckServiceNodes, expected string) { 59 vec := make([]string, len(nodes)) 60 for i, node := range nodes { 61 vec[i] = node.Node.Node 62 } 63 actual := strings.Join(vec, ",") 64 if actual != expected { 65 t.Fatalf("bad sort: %s != %s", actual, expected) 66 } 67 } 68 69 // seedCoordinates uses the client to set up a set of nodes with a specific 70 // set of distances from the origin. We also include the server so that we 71 // can wait for the coordinates to get committed to the Raft log. 72 // 73 // Here's the layout of the nodes: 74 // 75 // node3 node2 node5 node4 node1 76 // | | | | | | | | | | | 77 // 0 1 2 3 4 5 6 7 8 9 10 (ms) 78 // 79 func seedCoordinates(t *testing.T, codec rpc.ClientCodec, server *Server) { 80 // Register some nodes. 81 for i := 0; i < 5; i++ { 82 req := structs.RegisterRequest{ 83 Datacenter: "dc1", 84 Node: fmt.Sprintf("node%d", i+1), 85 Address: "127.0.0.1", 86 } 87 var reply struct{} 88 if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply); err != nil { 89 t.Fatalf("err: %v", err) 90 } 91 } 92 93 // Seed the fixed setup of the nodes. 94 updates := []structs.CoordinateUpdateRequest{ 95 structs.CoordinateUpdateRequest{ 96 Datacenter: "dc1", 97 Node: "node1", 98 Coord: lib.GenerateCoordinate(10 * time.Millisecond), 99 }, 100 structs.CoordinateUpdateRequest{ 101 Datacenter: "dc1", 102 Node: "node2", 103 Coord: lib.GenerateCoordinate(2 * time.Millisecond), 104 }, 105 structs.CoordinateUpdateRequest{ 106 Datacenter: "dc1", 107 Node: "node3", 108 Coord: lib.GenerateCoordinate(1 * time.Millisecond), 109 }, 110 structs.CoordinateUpdateRequest{ 111 Datacenter: "dc1", 112 Node: "node4", 113 Coord: lib.GenerateCoordinate(8 * time.Millisecond), 114 }, 115 structs.CoordinateUpdateRequest{ 116 Datacenter: "dc1", 117 Node: "node5", 118 Coord: lib.GenerateCoordinate(3 * time.Millisecond), 119 }, 120 } 121 122 // Apply the updates and wait a while for the batch to get committed to 123 // the Raft log. 124 for _, update := range updates { 125 var out struct{} 126 if err := msgpackrpc.CallWithCodec(codec, "Coordinate.Update", &update, &out); err != nil { 127 t.Fatalf("err: %v", err) 128 } 129 } 130 time.Sleep(2 * server.config.CoordinateUpdatePeriod) 131 } 132 133 func TestRTT_sortNodesByDistanceFrom(t *testing.T) { 134 t.Parallel() 135 dir, server := testServer(t) 136 defer os.RemoveAll(dir) 137 defer server.Shutdown() 138 139 codec := rpcClient(t, server) 140 defer codec.Close() 141 testrpc.WaitForTestAgent(t, server.RPC, "dc1") 142 143 seedCoordinates(t, codec, server) 144 nodes := structs.Nodes{ 145 &structs.Node{Node: "apple"}, 146 &structs.Node{Node: "node1"}, 147 &structs.Node{Node: "node2"}, 148 &structs.Node{Node: "node3"}, 149 &structs.Node{Node: "node4"}, 150 &structs.Node{Node: "node5"}, 151 } 152 153 // The zero value for the source should not trigger any sorting. 154 var source structs.QuerySource 155 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 156 t.Fatalf("err: %v", err) 157 } 158 verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5") 159 160 // Same for a source in some other DC. 161 source.Node = "node1" 162 source.Datacenter = "dc2" 163 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 164 t.Fatalf("err: %v", err) 165 } 166 verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5") 167 168 // Same for a source node in our DC that we have no coordinate for. 169 source.Node = "apple" 170 source.Datacenter = "dc1" 171 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 172 t.Fatalf("err: %v", err) 173 } 174 verifyNodeSort(t, nodes, "apple,node1,node2,node3,node4,node5") 175 176 // Now sort relative to node1, note that apple doesn't have any seeded 177 // coordinate info so it should end up at the end, despite its lexical 178 // hegemony. 179 source.Node = "node1" 180 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 181 t.Fatalf("err: %v", err) 182 } 183 verifyNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple") 184 } 185 186 func TestRTT_sortNodesByDistanceFrom_Nodes(t *testing.T) { 187 t.Parallel() 188 dir, server := testServer(t) 189 defer os.RemoveAll(dir) 190 defer server.Shutdown() 191 192 codec := rpcClient(t, server) 193 defer codec.Close() 194 testrpc.WaitForTestAgent(t, server.RPC, "dc1") 195 196 seedCoordinates(t, codec, server) 197 nodes := structs.Nodes{ 198 &structs.Node{Node: "apple"}, 199 &structs.Node{Node: "node1"}, 200 &structs.Node{Node: "node2"}, 201 &structs.Node{Node: "node3"}, 202 &structs.Node{Node: "node4"}, 203 &structs.Node{Node: "node5"}, 204 } 205 206 // Now sort relative to node1, note that apple doesn't have any 207 // seeded coordinate info so it should end up at the end, despite 208 // its lexical hegemony. 209 var source structs.QuerySource 210 source.Node = "node1" 211 source.Datacenter = "dc1" 212 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 213 t.Fatalf("err: %v", err) 214 } 215 verifyNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple") 216 217 // Try another sort from node2. Note that node5 and node3 are the 218 // same distance away so the stable sort should preserve the order 219 // they were in from the previous sort. 220 source.Node = "node2" 221 source.Datacenter = "dc1" 222 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 223 t.Fatalf("err: %v", err) 224 } 225 verifyNodeSort(t, nodes, "node2,node5,node3,node4,node1,apple") 226 227 // Let's exercise the stable sort explicitly to make sure we didn't 228 // just get lucky. 229 nodes[1], nodes[2] = nodes[2], nodes[1] 230 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 231 t.Fatalf("err: %v", err) 232 } 233 verifyNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple") 234 } 235 236 func TestRTT_sortNodesByDistanceFrom_ServiceNodes(t *testing.T) { 237 t.Parallel() 238 dir, server := testServer(t) 239 defer os.RemoveAll(dir) 240 defer server.Shutdown() 241 testrpc.WaitForTestAgent(t, server.RPC, "dc1") 242 243 codec := rpcClient(t, server) 244 defer codec.Close() 245 246 seedCoordinates(t, codec, server) 247 nodes := structs.ServiceNodes{ 248 &structs.ServiceNode{Node: "apple"}, 249 &structs.ServiceNode{Node: "node1"}, 250 &structs.ServiceNode{Node: "node2"}, 251 &structs.ServiceNode{Node: "node3"}, 252 &structs.ServiceNode{Node: "node4"}, 253 &structs.ServiceNode{Node: "node5"}, 254 } 255 256 // Now sort relative to node1, note that apple doesn't have any 257 // seeded coordinate info so it should end up at the end, despite 258 // its lexical hegemony. 259 var source structs.QuerySource 260 source.Node = "node1" 261 source.Datacenter = "dc1" 262 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 263 t.Fatalf("err: %v", err) 264 } 265 verifyServiceNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple") 266 267 // Try another sort from node2. Note that node5 and node3 are the 268 // same distance away so the stable sort should preserve the order 269 // they were in from the previous sort. 270 source.Node = "node2" 271 source.Datacenter = "dc1" 272 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 273 t.Fatalf("err: %v", err) 274 } 275 verifyServiceNodeSort(t, nodes, "node2,node5,node3,node4,node1,apple") 276 277 // Let's exercise the stable sort explicitly to make sure we didn't 278 // just get lucky. 279 nodes[1], nodes[2] = nodes[2], nodes[1] 280 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 281 t.Fatalf("err: %v", err) 282 } 283 verifyServiceNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple") 284 } 285 286 func TestRTT_sortNodesByDistanceFrom_HealthChecks(t *testing.T) { 287 t.Parallel() 288 dir, server := testServer(t) 289 defer os.RemoveAll(dir) 290 defer server.Shutdown() 291 292 codec := rpcClient(t, server) 293 defer codec.Close() 294 testrpc.WaitForLeader(t, server.RPC, "dc1") 295 296 seedCoordinates(t, codec, server) 297 checks := structs.HealthChecks{ 298 &structs.HealthCheck{Node: "apple"}, 299 &structs.HealthCheck{Node: "node1"}, 300 &structs.HealthCheck{Node: "node2"}, 301 &structs.HealthCheck{Node: "node3"}, 302 &structs.HealthCheck{Node: "node4"}, 303 &structs.HealthCheck{Node: "node5"}, 304 } 305 306 // Now sort relative to node1, note that apple doesn't have any 307 // seeded coordinate info so it should end up at the end, despite 308 // its lexical hegemony. 309 var source structs.QuerySource 310 source.Node = "node1" 311 source.Datacenter = "dc1" 312 if err := server.sortNodesByDistanceFrom(source, checks); err != nil { 313 t.Fatalf("err: %v", err) 314 } 315 verifyHealthCheckSort(t, checks, "node1,node4,node5,node2,node3,apple") 316 317 // Try another sort from node2. Note that node5 and node3 are the 318 // same distance away so the stable sort should preserve the order 319 // they were in from the previous sort. 320 source.Node = "node2" 321 source.Datacenter = "dc1" 322 if err := server.sortNodesByDistanceFrom(source, checks); err != nil { 323 t.Fatalf("err: %v", err) 324 } 325 verifyHealthCheckSort(t, checks, "node2,node5,node3,node4,node1,apple") 326 327 // Let's exercise the stable sort explicitly to make sure we didn't 328 // just get lucky. 329 checks[1], checks[2] = checks[2], checks[1] 330 if err := server.sortNodesByDistanceFrom(source, checks); err != nil { 331 t.Fatalf("err: %v", err) 332 } 333 verifyHealthCheckSort(t, checks, "node2,node3,node5,node4,node1,apple") 334 } 335 336 func TestRTT_sortNodesByDistanceFrom_CheckServiceNodes(t *testing.T) { 337 t.Parallel() 338 dir, server := testServer(t) 339 defer os.RemoveAll(dir) 340 defer server.Shutdown() 341 342 codec := rpcClient(t, server) 343 defer codec.Close() 344 testrpc.WaitForTestAgent(t, server.RPC, "dc1") 345 346 seedCoordinates(t, codec, server) 347 nodes := structs.CheckServiceNodes{ 348 structs.CheckServiceNode{Node: &structs.Node{Node: "apple"}}, 349 structs.CheckServiceNode{Node: &structs.Node{Node: "node1"}}, 350 structs.CheckServiceNode{Node: &structs.Node{Node: "node2"}}, 351 structs.CheckServiceNode{Node: &structs.Node{Node: "node3"}}, 352 structs.CheckServiceNode{Node: &structs.Node{Node: "node4"}}, 353 structs.CheckServiceNode{Node: &structs.Node{Node: "node5"}}, 354 } 355 356 // Now sort relative to node1, note that apple doesn't have any 357 // seeded coordinate info so it should end up at the end, despite 358 // its lexical hegemony. 359 var source structs.QuerySource 360 source.Node = "node1" 361 source.Datacenter = "dc1" 362 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 363 t.Fatalf("err: %v", err) 364 } 365 verifyCheckServiceNodeSort(t, nodes, "node1,node4,node5,node2,node3,apple") 366 367 // Try another sort from node2. Note that node5 and node3 are the 368 // same distance away so the stable sort should preserve the order 369 // they were in from the previous sort. 370 source.Node = "node2" 371 source.Datacenter = "dc1" 372 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 373 t.Fatalf("err: %v", err) 374 } 375 verifyCheckServiceNodeSort(t, nodes, "node2,node5,node3,node4,node1,apple") 376 377 // Let's exercise the stable sort explicitly to make sure we didn't 378 // just get lucky. 379 nodes[1], nodes[2] = nodes[2], nodes[1] 380 if err := server.sortNodesByDistanceFrom(source, nodes); err != nil { 381 t.Fatalf("err: %v", err) 382 } 383 verifyCheckServiceNodeSort(t, nodes, "node2,node3,node5,node4,node1,apple") 384 }