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