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  }