github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/balancer/connections_state_test.go (about) 1 package balancer 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 balancerConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/balancer/config" 11 "github.com/ydb-platform/ydb-go-sdk/v3/internal/conn" 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/mock" 13 ) 14 15 func TestConnsToNodeIDMap(t *testing.T) { 16 table := []struct { 17 name string 18 source []conn.Conn 19 res map[uint32]conn.Conn 20 }{ 21 { 22 name: "Empty", 23 source: nil, 24 res: nil, 25 }, 26 { 27 name: "Zero", 28 source: []conn.Conn{ 29 &mock.Conn{NodeIDField: 0}, 30 }, 31 res: map[uint32]conn.Conn{ 32 0: &mock.Conn{NodeIDField: 0}, 33 }, 34 }, 35 { 36 name: "NonZero", 37 source: []conn.Conn{ 38 &mock.Conn{NodeIDField: 1}, 39 &mock.Conn{NodeIDField: 10}, 40 }, 41 res: map[uint32]conn.Conn{ 42 1: &mock.Conn{NodeIDField: 1}, 43 10: &mock.Conn{NodeIDField: 10}, 44 }, 45 }, 46 { 47 name: "Combined", 48 source: []conn.Conn{ 49 &mock.Conn{NodeIDField: 1}, 50 &mock.Conn{NodeIDField: 0}, 51 &mock.Conn{NodeIDField: 10}, 52 }, 53 res: map[uint32]conn.Conn{ 54 0: &mock.Conn{NodeIDField: 0}, 55 1: &mock.Conn{NodeIDField: 1}, 56 10: &mock.Conn{NodeIDField: 10}, 57 }, 58 }, 59 } 60 61 for _, test := range table { 62 t.Run(test.name, func(t *testing.T) { 63 require.Equal(t, test.res, connsToNodeIDMap(test.source)) 64 }) 65 } 66 } 67 68 type filterFunc func(info balancerConfig.Info, c conn.Conn) bool 69 70 func (f filterFunc) Allow(info balancerConfig.Info, c conn.Conn) bool { 71 return f(info, c) 72 } 73 74 func (f filterFunc) String() string { 75 return "Custom" 76 } 77 78 func TestSortPreferConnections(t *testing.T) { 79 table := []struct { 80 name string 81 source []conn.Conn 82 allowFallback bool 83 filter balancerConfig.Filter 84 prefer []conn.Conn 85 fallback []conn.Conn 86 }{ 87 { 88 name: "Empty", 89 source: nil, 90 allowFallback: false, 91 filter: nil, 92 prefer: nil, 93 fallback: nil, 94 }, 95 { 96 name: "NilFilter", 97 source: []conn.Conn{ 98 &mock.Conn{AddrField: "1"}, 99 &mock.Conn{AddrField: "2"}, 100 }, 101 allowFallback: false, 102 filter: nil, 103 prefer: []conn.Conn{ 104 &mock.Conn{AddrField: "1"}, 105 &mock.Conn{AddrField: "2"}, 106 }, 107 fallback: nil, 108 }, 109 { 110 name: "FilterNoFallback", 111 source: []conn.Conn{ 112 &mock.Conn{AddrField: "t1"}, 113 &mock.Conn{AddrField: "f1"}, 114 &mock.Conn{AddrField: "t2"}, 115 &mock.Conn{AddrField: "f2"}, 116 }, 117 allowFallback: false, 118 filter: filterFunc(func(_ balancerConfig.Info, c conn.Conn) bool { 119 return strings.HasPrefix(c.Endpoint().Address(), "t") 120 }), 121 prefer: []conn.Conn{ 122 &mock.Conn{AddrField: "t1"}, 123 &mock.Conn{AddrField: "t2"}, 124 }, 125 fallback: nil, 126 }, 127 { 128 name: "FilterWithFallback", 129 source: []conn.Conn{ 130 &mock.Conn{AddrField: "t1"}, 131 &mock.Conn{AddrField: "f1"}, 132 &mock.Conn{AddrField: "t2"}, 133 &mock.Conn{AddrField: "f2"}, 134 }, 135 allowFallback: true, 136 filter: filterFunc(func(_ balancerConfig.Info, c conn.Conn) bool { 137 return strings.HasPrefix(c.Endpoint().Address(), "t") 138 }), 139 prefer: []conn.Conn{ 140 &mock.Conn{AddrField: "t1"}, 141 &mock.Conn{AddrField: "t2"}, 142 }, 143 fallback: []conn.Conn{ 144 &mock.Conn{AddrField: "f1"}, 145 &mock.Conn{AddrField: "f2"}, 146 }, 147 }, 148 } 149 150 for _, test := range table { 151 t.Run(test.name, func(t *testing.T) { 152 prefer, fallback := sortPreferConnections(test.source, test.filter, balancerConfig.Info{}, test.allowFallback) 153 require.Equal(t, test.prefer, prefer) 154 require.Equal(t, test.fallback, fallback) 155 }) 156 } 157 } 158 159 func TestSelectRandomConnection(t *testing.T) { 160 s := newConnectionsState(nil, nil, balancerConfig.Info{}, false) 161 162 t.Run("Empty", func(t *testing.T) { 163 c, failedCount := s.selectRandomConnection(nil, false) 164 require.Nil(t, c) 165 require.Equal(t, 0, failedCount) 166 }) 167 168 t.Run("One", func(t *testing.T) { 169 for _, goodState := range []conn.State{conn.Online, conn.Offline, conn.Created} { 170 c, failedCount := s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: goodState}}, false) 171 require.Equal(t, &mock.Conn{AddrField: "asd", State: goodState}, c) 172 require.Equal(t, 0, failedCount) 173 } 174 }) 175 t.Run("OneBanned", func(t *testing.T) { 176 c, failedCount := s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: conn.Banned}}, false) 177 require.Nil(t, c) 178 require.Equal(t, 1, failedCount) 179 180 c, failedCount = s.selectRandomConnection([]conn.Conn{&mock.Conn{AddrField: "asd", State: conn.Banned}}, true) 181 require.Equal(t, &mock.Conn{AddrField: "asd", State: conn.Banned}, c) 182 require.Equal(t, 0, failedCount) 183 }) 184 t.Run("Two", func(t *testing.T) { 185 conns := []conn.Conn{ 186 &mock.Conn{AddrField: "1", State: conn.Online}, 187 &mock.Conn{AddrField: "2", State: conn.Online}, 188 } 189 first := 0 190 second := 0 191 for i := 0; i < 100; i++ { 192 c, _ := s.selectRandomConnection(conns, false) 193 if c.Endpoint().Address() == "1" { 194 first++ 195 } else { 196 second++ 197 } 198 } 199 require.Equal(t, 100, first+second) 200 require.InDelta(t, 50, first, 21) 201 require.InDelta(t, 50, second, 21) 202 }) 203 t.Run("TwoBanned", func(t *testing.T) { 204 conns := []conn.Conn{ 205 &mock.Conn{AddrField: "1", State: conn.Banned}, 206 &mock.Conn{AddrField: "2", State: conn.Banned}, 207 } 208 totalFailed := 0 209 for i := 0; i < 100; i++ { 210 c, failed := s.selectRandomConnection(conns, false) 211 require.Nil(t, c) 212 totalFailed += failed 213 } 214 require.Equal(t, 200, totalFailed) 215 }) 216 t.Run("ThreeWithBanned", func(t *testing.T) { 217 conns := []conn.Conn{ 218 &mock.Conn{AddrField: "1", State: conn.Online}, 219 &mock.Conn{AddrField: "2", State: conn.Online}, 220 &mock.Conn{AddrField: "3", State: conn.Banned}, 221 } 222 first := 0 223 second := 0 224 failed := 0 225 for i := 0; i < 100; i++ { 226 c, checkFailed := s.selectRandomConnection(conns, false) 227 failed += checkFailed 228 switch c.Endpoint().Address() { 229 case "1": 230 first++ 231 case "2": 232 second++ 233 default: 234 t.Errorf(c.Endpoint().Address()) 235 } 236 } 237 require.Equal(t, 100, first+second) 238 require.InDelta(t, 50, first, 21) 239 require.InDelta(t, 50, second, 21) 240 require.Greater(t, 10, failed) 241 }) 242 } 243 244 func TestNewState(t *testing.T) { 245 table := []struct { 246 name string 247 state *connectionsState 248 res *connectionsState 249 }{ 250 { 251 name: "Empty", 252 state: newConnectionsState(nil, nil, balancerConfig.Info{}, false), 253 res: &connectionsState{ 254 connByNodeID: nil, 255 prefer: nil, 256 fallback: nil, 257 all: nil, 258 }, 259 }, 260 { 261 name: "NoFilter", 262 state: newConnectionsState([]conn.Conn{ 263 &mock.Conn{AddrField: "1", NodeIDField: 1}, 264 &mock.Conn{AddrField: "2", NodeIDField: 2}, 265 }, nil, balancerConfig.Info{}, false), 266 res: &connectionsState{ 267 connByNodeID: map[uint32]conn.Conn{ 268 1: &mock.Conn{AddrField: "1", NodeIDField: 1}, 269 2: &mock.Conn{AddrField: "2", NodeIDField: 2}, 270 }, 271 prefer: []conn.Conn{ 272 &mock.Conn{AddrField: "1", NodeIDField: 1}, 273 &mock.Conn{AddrField: "2", NodeIDField: 2}, 274 }, 275 fallback: nil, 276 all: []conn.Conn{ 277 &mock.Conn{AddrField: "1", NodeIDField: 1}, 278 &mock.Conn{AddrField: "2", NodeIDField: 2}, 279 }, 280 }, 281 }, 282 { 283 name: "FilterDenyFallback", 284 state: newConnectionsState([]conn.Conn{ 285 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 286 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 287 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 288 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 289 }, filterFunc(func(info balancerConfig.Info, c conn.Conn) bool { 290 return info.SelfLocation == c.Endpoint().Location() 291 }), balancerConfig.Info{SelfLocation: "t"}, false), 292 res: &connectionsState{ 293 connByNodeID: map[uint32]conn.Conn{ 294 1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 295 2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 296 3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 297 4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 298 }, 299 prefer: []conn.Conn{ 300 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 301 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 302 }, 303 fallback: nil, 304 all: []conn.Conn{ 305 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 306 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 307 }, 308 }, 309 }, 310 { 311 name: "FilterAllowFallback", 312 state: newConnectionsState([]conn.Conn{ 313 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 314 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 315 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 316 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 317 }, filterFunc(func(info balancerConfig.Info, c conn.Conn) bool { 318 return info.SelfLocation == c.Endpoint().Location() 319 }), balancerConfig.Info{SelfLocation: "t"}, true), 320 res: &connectionsState{ 321 connByNodeID: map[uint32]conn.Conn{ 322 1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 323 2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 324 3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 325 4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 326 }, 327 prefer: []conn.Conn{ 328 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 329 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 330 }, 331 fallback: []conn.Conn{ 332 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 333 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 334 }, 335 all: []conn.Conn{ 336 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 337 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 338 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 339 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 340 }, 341 }, 342 }, 343 { 344 name: "WithNodeID", 345 state: newConnectionsState([]conn.Conn{ 346 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 347 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 348 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 349 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 350 }, filterFunc(func(info balancerConfig.Info, c conn.Conn) bool { 351 return info.SelfLocation == c.Endpoint().Location() 352 }), balancerConfig.Info{SelfLocation: "t"}, true), 353 res: &connectionsState{ 354 connByNodeID: map[uint32]conn.Conn{ 355 1: &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 356 2: &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 357 3: &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 358 4: &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 359 }, 360 prefer: []conn.Conn{ 361 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 362 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 363 }, 364 fallback: []conn.Conn{ 365 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 366 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 367 }, 368 all: []conn.Conn{ 369 &mock.Conn{AddrField: "t1", NodeIDField: 1, LocationField: "t"}, 370 &mock.Conn{AddrField: "f1", NodeIDField: 2, LocationField: "f"}, 371 &mock.Conn{AddrField: "t2", NodeIDField: 3, LocationField: "t"}, 372 &mock.Conn{AddrField: "f2", NodeIDField: 4, LocationField: "f"}, 373 }, 374 }, 375 }, 376 } 377 378 for _, test := range table { 379 t.Run(test.name, func(t *testing.T) { 380 require.NotNil(t, test.state.rand) 381 test.state.rand = nil 382 require.Equal(t, test.res, test.state) 383 }) 384 } 385 } 386 387 func TestConnection(t *testing.T) { 388 t.Run("Empty", func(t *testing.T) { 389 s := newConnectionsState(nil, nil, balancerConfig.Info{}, false) 390 c, failed := s.GetConnection(context.Background()) 391 require.Nil(t, c) 392 require.Equal(t, 0, failed) 393 }) 394 t.Run("AllGood", func(t *testing.T) { 395 s := newConnectionsState([]conn.Conn{ 396 &mock.Conn{AddrField: "1", State: conn.Online}, 397 &mock.Conn{AddrField: "2", State: conn.Online}, 398 }, nil, balancerConfig.Info{}, false) 399 c, failed := s.GetConnection(context.Background()) 400 require.NotNil(t, c) 401 require.Equal(t, 0, failed) 402 }) 403 t.Run("WithBanned", func(t *testing.T) { 404 s := newConnectionsState([]conn.Conn{ 405 &mock.Conn{AddrField: "1", State: conn.Online}, 406 &mock.Conn{AddrField: "2", State: conn.Banned}, 407 }, nil, balancerConfig.Info{}, false) 408 c, _ := s.GetConnection(context.Background()) 409 require.Equal(t, &mock.Conn{AddrField: "1", State: conn.Online}, c) 410 }) 411 t.Run("AllBanned", func(t *testing.T) { 412 s := newConnectionsState([]conn.Conn{ 413 &mock.Conn{AddrField: "t1", State: conn.Banned, LocationField: "t"}, 414 &mock.Conn{AddrField: "f2", State: conn.Banned, LocationField: "f"}, 415 }, filterFunc(func(info balancerConfig.Info, c conn.Conn) bool { 416 return c.Endpoint().Location() == info.SelfLocation 417 }), balancerConfig.Info{}, true) 418 preferred := 0 419 fallback := 0 420 for i := 0; i < 100; i++ { 421 c, failed := s.GetConnection(context.Background()) 422 require.NotNil(t, c) 423 require.Equal(t, 2, failed) 424 if c.Endpoint().Address() == "t1" { 425 preferred++ 426 } else { 427 fallback++ 428 } 429 } 430 require.Equal(t, 100, preferred+fallback) 431 require.InDelta(t, 50, preferred, 21) 432 require.InDelta(t, 50, fallback, 21) 433 }) 434 t.Run("PreferBannedWithFallback", func(t *testing.T) { 435 s := newConnectionsState([]conn.Conn{ 436 &mock.Conn{AddrField: "t1", State: conn.Banned, LocationField: "t"}, 437 &mock.Conn{AddrField: "f2", State: conn.Online, LocationField: "f"}, 438 }, filterFunc(func(info balancerConfig.Info, c conn.Conn) bool { 439 return c.Endpoint().Location() == info.SelfLocation 440 }), balancerConfig.Info{SelfLocation: "t"}, true) 441 c, failed := s.GetConnection(context.Background()) 442 require.Equal(t, &mock.Conn{AddrField: "f2", State: conn.Online, LocationField: "f"}, c) 443 require.Equal(t, 1, failed) 444 }) 445 t.Run("PreferNodeID", func(t *testing.T) { 446 s := newConnectionsState([]conn.Conn{ 447 &mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1}, 448 &mock.Conn{AddrField: "2", State: conn.Online, NodeIDField: 2}, 449 }, nil, balancerConfig.Info{}, false) 450 c, failed := s.GetConnection(WithEndpoint(context.Background(), &mock.Endpoint{AddrField: "2", NodeIDField: 2})) 451 require.Equal(t, &mock.Conn{AddrField: "2", State: conn.Online, NodeIDField: 2}, c) 452 require.Equal(t, 0, failed) 453 }) 454 t.Run("PreferNodeIDWithBadState", func(t *testing.T) { 455 s := newConnectionsState([]conn.Conn{ 456 &mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1}, 457 &mock.Conn{AddrField: "2", State: conn.Unknown, NodeIDField: 2}, 458 }, nil, balancerConfig.Info{}, false) 459 c, failed := s.GetConnection(WithEndpoint(context.Background(), &mock.Endpoint{AddrField: "2", NodeIDField: 2})) 460 require.Equal(t, &mock.Conn{AddrField: "1", State: conn.Online, NodeIDField: 1}, c) 461 require.Equal(t, 0, failed) 462 }) 463 }