git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/pool/tree/pool_test.go (about)

     1  package tree
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
     9  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
    10  	grpcService "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
    11  	"github.com/stretchr/testify/require"
    12  	"go.uber.org/zap/zaptest"
    13  )
    14  
    15  type treeClientMock struct {
    16  	address string
    17  	err     bool
    18  }
    19  
    20  func (t *treeClientMock) serviceClient() (grpcService.TreeServiceClient, error) {
    21  	if t.err {
    22  		return nil, errors.New("serviceClient() mock error")
    23  	}
    24  	return nil, nil
    25  }
    26  
    27  func (t *treeClientMock) endpoint() string {
    28  	return t.address
    29  }
    30  
    31  func (t *treeClientMock) isHealthy() bool {
    32  	return true
    33  }
    34  
    35  func (t *treeClientMock) setHealthy(bool) {
    36  	return
    37  }
    38  
    39  func (t *treeClientMock) dial(context.Context) error {
    40  	return nil
    41  }
    42  
    43  func (t *treeClientMock) redialIfNecessary(context.Context) (bool, error) {
    44  	if t.err {
    45  		return false, errors.New("redialIfNecessary() mock error")
    46  	}
    47  	return false, nil
    48  }
    49  
    50  func (t *treeClientMock) close() error {
    51  	return nil
    52  }
    53  
    54  func TestHandleError(t *testing.T) {
    55  	defaultError := errors.New("default error")
    56  	for _, tc := range []struct {
    57  		err           error
    58  		expectedError error
    59  	}{
    60  		{
    61  			err:           defaultError,
    62  			expectedError: defaultError,
    63  		},
    64  		{
    65  			err:           errors.New("something not found"),
    66  			expectedError: ErrNodeNotFound,
    67  		},
    68  		{
    69  			err:           errors.New("something is denied by some acl rule"),
    70  			expectedError: ErrNodeAccessDenied,
    71  		},
    72  		{
    73  			err:           &apistatus.APEManagerAccessDenied{},
    74  			expectedError: ErrNodeAccessDenied,
    75  		},
    76  	} {
    77  		t.Run("", func(t *testing.T) {
    78  			err := handleError("err message", tc.err)
    79  			require.True(t, errors.Is(err, tc.expectedError))
    80  		})
    81  	}
    82  }
    83  
    84  func TestRetry(t *testing.T) {
    85  	ctx := context.Background()
    86  	nodes := [][]string{
    87  		{"node00", "node01", "node02", "node03"},
    88  		{"node10", "node11", "node12", "node13"},
    89  	}
    90  
    91  	var lenNodes int
    92  	for i := range nodes {
    93  		lenNodes += len(nodes[i])
    94  	}
    95  
    96  	p := &Pool{
    97  		logger:             zaptest.NewLogger(t),
    98  		innerPools:         makeInnerPool(nodes),
    99  		maxRequestAttempts: lenNodes,
   100  	}
   101  
   102  	makeFn := func(client grpcService.TreeServiceClient) error {
   103  		return nil
   104  	}
   105  
   106  	t.Run("first ok", func(t *testing.T) {
   107  		err := p.requestWithRetry(ctx, makeFn)
   108  		require.NoError(t, err)
   109  		checkIndicesAndReset(t, p, 0, 0)
   110  	})
   111  
   112  	t.Run("first failed", func(t *testing.T) {
   113  		setErrors(p, "node00")
   114  		err := p.requestWithRetry(ctx, makeFn)
   115  		require.NoError(t, err)
   116  		checkIndicesAndReset(t, p, 0, 1)
   117  	})
   118  
   119  	t.Run("all failed", func(t *testing.T) {
   120  		setErrors(p, nodes[0]...)
   121  		setErrors(p, nodes[1]...)
   122  		err := p.requestWithRetry(ctx, makeFn)
   123  		require.Error(t, err)
   124  		checkIndicesAndReset(t, p, 0, 0)
   125  	})
   126  
   127  	t.Run("round", func(t *testing.T) {
   128  		setErrors(p, nodes[0][0], nodes[0][1])
   129  		setErrors(p, nodes[1]...)
   130  		err := p.requestWithRetry(ctx, makeFn)
   131  		require.NoError(t, err)
   132  		checkIndices(t, p, 0, 2)
   133  		resetClientsErrors(p)
   134  
   135  		setErrors(p, nodes[0][2], nodes[0][3])
   136  		err = p.requestWithRetry(ctx, makeFn)
   137  		require.NoError(t, err)
   138  		checkIndicesAndReset(t, p, 0, 0)
   139  	})
   140  
   141  	t.Run("group switch", func(t *testing.T) {
   142  		setErrors(p, nodes[0]...)
   143  		setErrors(p, nodes[1][0])
   144  		err := p.requestWithRetry(ctx, makeFn)
   145  		require.NoError(t, err)
   146  		checkIndicesAndReset(t, p, 1, 1)
   147  	})
   148  
   149  	t.Run("group round", func(t *testing.T) {
   150  		setErrors(p, nodes[0][1:]...)
   151  		err := p.requestWithRetry(ctx, makeFn)
   152  		require.NoError(t, err)
   153  		checkIndicesAndReset(t, p, 0, 0)
   154  	})
   155  
   156  	t.Run("group round switch", func(t *testing.T) {
   157  		setErrors(p, nodes[0]...)
   158  		p.setStartIndices(0, 1)
   159  		err := p.requestWithRetry(ctx, makeFn)
   160  		require.NoError(t, err)
   161  		checkIndicesAndReset(t, p, 1, 0)
   162  	})
   163  
   164  	t.Run("no panic group switch", func(t *testing.T) {
   165  		setErrors(p, nodes[1]...)
   166  		p.setStartIndices(1, 0)
   167  		err := p.requestWithRetry(ctx, makeFn)
   168  		require.NoError(t, err)
   169  		checkIndicesAndReset(t, p, 0, 0)
   170  	})
   171  
   172  	t.Run("error empty result", func(t *testing.T) {
   173  		errNodes, index := 2, 0
   174  		err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) error {
   175  			if index < errNodes {
   176  				index++
   177  				return errNodeEmptyResult
   178  			}
   179  			return nil
   180  		})
   181  		require.NoError(t, err)
   182  		checkIndicesAndReset(t, p, 0, errNodes)
   183  	})
   184  
   185  	t.Run("error not found", func(t *testing.T) {
   186  		errNodes, index := 2, 0
   187  		err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) error {
   188  			if index < errNodes {
   189  				index++
   190  				return ErrNodeNotFound
   191  			}
   192  			return nil
   193  		})
   194  		require.NoError(t, err)
   195  		checkIndicesAndReset(t, p, 0, errNodes)
   196  	})
   197  
   198  	t.Run("error access denied", func(t *testing.T) {
   199  		var index int
   200  		err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) error {
   201  			index++
   202  			return ErrNodeAccessDenied
   203  		})
   204  		require.ErrorIs(t, err, ErrNodeAccessDenied)
   205  		require.Equal(t, 1, index)
   206  		checkIndicesAndReset(t, p, 0, 0)
   207  	})
   208  
   209  	t.Run("limit attempts", func(t *testing.T) {
   210  		oldVal := p.maxRequestAttempts
   211  		p.maxRequestAttempts = 2
   212  		setErrors(p, nodes[0]...)
   213  		setErrors(p, nodes[1]...)
   214  		err := p.requestWithRetry(ctx, makeFn)
   215  		require.Error(t, err)
   216  		checkIndicesAndReset(t, p, 0, 2)
   217  		p.maxRequestAttempts = oldVal
   218  	})
   219  }
   220  
   221  func TestRebalance(t *testing.T) {
   222  	nodes := [][]string{
   223  		{"node00", "node01"},
   224  		{"node10", "node11"},
   225  	}
   226  
   227  	p := &Pool{
   228  		logger:     zaptest.NewLogger(t),
   229  		innerPools: makeInnerPool(nodes),
   230  		rebalanceParams: rebalanceParameters{
   231  			nodesGroup: makeNodesGroup(nodes),
   232  		},
   233  	}
   234  
   235  	ctx := context.Background()
   236  	buffers := makeBuffer(p)
   237  
   238  	t.Run("check dirty buffers", func(t *testing.T) {
   239  		p.updateNodesHealth(ctx, buffers)
   240  		checkIndices(t, p, 0, 0)
   241  		setErrors(p, nodes[0][0])
   242  		p.updateNodesHealth(ctx, buffers)
   243  		checkIndices(t, p, 0, 1)
   244  		resetClients(p)
   245  	})
   246  
   247  	t.Run("don't change healthy status", func(t *testing.T) {
   248  		p.updateNodesHealth(ctx, buffers)
   249  		checkIndices(t, p, 0, 0)
   250  		resetClients(p)
   251  	})
   252  
   253  	t.Run("switch to second group", func(t *testing.T) {
   254  		setErrors(p, nodes[0][0], nodes[0][1])
   255  		p.updateNodesHealth(ctx, buffers)
   256  		checkIndices(t, p, 1, 0)
   257  		resetClients(p)
   258  	})
   259  
   260  	t.Run("switch back and forth", func(t *testing.T) {
   261  		setErrors(p, nodes[0][0], nodes[0][1])
   262  		p.updateNodesHealth(ctx, buffers)
   263  		checkIndices(t, p, 1, 0)
   264  
   265  		p.updateNodesHealth(ctx, buffers)
   266  		checkIndices(t, p, 1, 0)
   267  
   268  		setNoErrors(p, nodes[0][0])
   269  		p.updateNodesHealth(ctx, buffers)
   270  		checkIndices(t, p, 0, 0)
   271  
   272  		resetClients(p)
   273  	})
   274  }
   275  
   276  func makeInnerPool(nodes [][]string) []*innerPool {
   277  	res := make([]*innerPool, len(nodes))
   278  
   279  	for i, group := range nodes {
   280  		res[i] = &innerPool{clients: make([]client, len(group))}
   281  		for j, node := range group {
   282  			res[i].clients[j] = &treeClientMock{address: node}
   283  		}
   284  	}
   285  
   286  	return res
   287  }
   288  
   289  func makeNodesGroup(nodes [][]string) [][]pool.NodeParam {
   290  	res := make([][]pool.NodeParam, len(nodes))
   291  
   292  	for i, group := range nodes {
   293  		res[i] = make([]pool.NodeParam, len(group))
   294  		for j, node := range group {
   295  			res[i][j] = pool.NewNodeParam(1, node, 1)
   296  		}
   297  	}
   298  
   299  	return res
   300  }
   301  
   302  func makeBuffer(p *Pool) [][]bool {
   303  	buffers := make([][]bool, len(p.rebalanceParams.nodesGroup))
   304  	for i, nodes := range p.rebalanceParams.nodesGroup {
   305  		buffers[i] = make([]bool, len(nodes))
   306  	}
   307  	return buffers
   308  }
   309  
   310  func checkIndicesAndReset(t *testing.T, p *Pool, iExp, jExp int) {
   311  	checkIndices(t, p, iExp, jExp)
   312  	resetClients(p)
   313  }
   314  
   315  func checkIndices(t *testing.T, p *Pool, iExp, jExp int) {
   316  	i, j := p.getStartIndices()
   317  	require.Equal(t, [2]int{iExp, jExp}, [2]int{i, j})
   318  }
   319  
   320  func resetClients(p *Pool) {
   321  	resetClientsErrors(p)
   322  	p.setStartIndices(0, 0)
   323  }
   324  
   325  func resetClientsErrors(p *Pool) {
   326  	for _, group := range p.innerPools {
   327  		for _, cl := range group.clients {
   328  			node := cl.(*treeClientMock)
   329  			node.err = false
   330  		}
   331  	}
   332  }
   333  
   334  func setErrors(p *Pool, nodes ...string) {
   335  	setErrorsBase(p, true, nodes...)
   336  }
   337  
   338  func setNoErrors(p *Pool, nodes ...string) {
   339  	setErrorsBase(p, false, nodes...)
   340  }
   341  
   342  func setErrorsBase(p *Pool, err bool, nodes ...string) {
   343  	for _, group := range p.innerPools {
   344  		for _, cl := range group.clients {
   345  			node := cl.(*treeClientMock)
   346  			if containsStr(nodes, node.address) {
   347  				node.err = err
   348  			}
   349  		}
   350  	}
   351  }
   352  
   353  func containsStr(list []string, item string) bool {
   354  	for i := range list {
   355  		if list[i] == item {
   356  			return true
   357  		}
   358  	}
   359  
   360  	return false
   361  }