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 }