github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/session_aggregate_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package client 22 23 import ( 24 "fmt" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/generated/thrift/rpc" 30 "github.com/m3db/m3/src/dbnode/storage/index" 31 "github.com/m3db/m3/src/dbnode/topology" 32 "github.com/m3db/m3/src/m3ninx/idx" 33 xerrors "github.com/m3db/m3/src/x/errors" 34 "github.com/m3db/m3/src/x/ident" 35 "github.com/m3db/m3/src/x/instrument" 36 xretry "github.com/m3db/m3/src/x/retry" 37 xtest "github.com/m3db/m3/src/x/test" 38 xtime "github.com/m3db/m3/src/x/time" 39 40 "github.com/golang/mock/gomock" 41 "github.com/stretchr/testify/assert" 42 "github.com/stretchr/testify/require" 43 ) 44 45 var ( 46 testSessionAggregateQuery = index.Query{idx.NewTermQuery([]byte("a"), []byte("b"))} 47 testSessionAggregateQueryOpts = func(t0, t1 xtime.UnixNano) index.AggregationOptions { 48 return index.AggregationOptions{ 49 QueryOptions: index.QueryOptions{StartInclusive: t0, EndExclusive: t1}, 50 Type: index.AggregateTagNamesAndValues, 51 } 52 } 53 ) 54 55 func TestSessionAggregateUnsupportedQuery(t *testing.T) { 56 ctrl := gomock.NewController(xtest.Reporter{t}) 57 defer ctrl.Finish() 58 59 opts := newSessionTestOptions(). 60 SetFetchRetrier(xretry.NewRetrier(xretry.NewOptions().SetMaxRetries(1))) 61 62 s, err := newSession(opts) 63 assert.NoError(t, err) 64 65 session, ok := s.(*session) 66 assert.True(t, ok) 67 68 mockHostQueues(ctrl, session, sessionTestReplicas, nil) 69 assert.NoError(t, session.Open()) 70 71 leakPool := injectLeakcheckAggregateAttempPool(session) 72 _, _, err = s.FetchTagged(testContext(), 73 ident.StringID("namespace"), 74 index.Query{}, 75 index.QueryOptions{}) 76 assert.Error(t, err) 77 assert.True(t, xerrors.IsNonRetryableError(err)) 78 leakPool.Check(t) 79 80 _, _, err = s.FetchTaggedIDs(testContext(), 81 ident.StringID("namespace"), 82 index.Query{}, 83 index.QueryOptions{}) 84 assert.Error(t, err) 85 assert.True(t, xerrors.IsNonRetryableError(err)) 86 leakPool.Check(t) 87 88 assert.NoError(t, session.Close()) 89 } 90 91 func TestSessionAggregateNotOpenError(t *testing.T) { 92 ctrl := gomock.NewController(t) 93 defer ctrl.Finish() 94 95 opts := newSessionTestOptions() 96 s, err := newSession(opts) 97 assert.NoError(t, err) 98 t0 := xtime.Now() 99 100 _, _, err = s.Aggregate(testContext(), ident.StringID("namespace"), 101 testSessionAggregateQuery, testSessionAggregateQueryOpts(t0, t0)) 102 assert.Error(t, err) 103 assert.Equal(t, ErrSessionStatusNotOpen, err) 104 } 105 106 func TestSessionAggregateGuardAgainstInvalidCall(t *testing.T) { 107 ctrl := gomock.NewController(t) 108 defer ctrl.Finish() 109 110 opts := newSessionTestOptions() 111 s, err := newSession(opts) 112 assert.NoError(t, err) 113 session := s.(*session) 114 115 start := xtime.Now().Truncate(time.Hour) 116 end := start.Add(2 * time.Hour) 117 118 mockHostQueues(ctrl, session, sessionTestReplicas, []testEnqueueFn{ 119 func(idx int, op op) { 120 go func() { 121 op.CompletionFn()(nil, nil) 122 }() 123 }, 124 }) 125 126 assert.NoError(t, session.Open()) 127 128 _, _, err = session.Aggregate(testContext(), ident.StringID("namespace"), 129 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 130 assert.Error(t, err) 131 assert.NoError(t, session.Close()) 132 } 133 134 func TestSessionAggregateGuardAgainstNilHost(t *testing.T) { 135 ctrl := gomock.NewController(t) 136 defer ctrl.Finish() 137 138 opts := newSessionTestOptions() 139 s, err := newSession(opts) 140 assert.NoError(t, err) 141 session := s.(*session) 142 143 start := xtime.Now().Truncate(time.Hour) 144 end := start.Add(2 * time.Hour) 145 146 mockHostQueues(ctrl, session, sessionTestReplicas, []testEnqueueFn{ 147 func(idx int, op op) { 148 go func() { 149 op.CompletionFn()(aggregateResultAccumulatorOpts{}, nil) 150 }() 151 }, 152 }) 153 154 assert.NoError(t, session.Open()) 155 156 _, _, err = session.Aggregate(testContext(), ident.StringID("namespace"), 157 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 158 assert.Error(t, err) 159 assert.NoError(t, session.Close()) 160 } 161 162 func TestSessionAggregateGuardAgainstInvalidHost(t *testing.T) { 163 ctrl := gomock.NewController(t) 164 defer ctrl.Finish() 165 166 opts := newSessionTestOptions() 167 s, err := newSession(opts) 168 assert.NoError(t, err) 169 session := s.(*session) 170 171 start := xtime.Now().Truncate(time.Hour) 172 end := start.Add(2 * time.Hour) 173 174 host := topology.NewHost("some-random-host", "some-random-host:12345") 175 mockHostQueues(ctrl, session, sessionTestReplicas, []testEnqueueFn{ 176 func(idx int, op op) { 177 go func() { 178 op.CompletionFn()(aggregateResultAccumulatorOpts{host: host}, nil) 179 }() 180 }, 181 }) 182 183 assert.NoError(t, session.Open()) 184 185 _, _, err = session.Aggregate(testContext(), ident.StringID("namespace"), 186 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 187 assert.Error(t, err) 188 assert.NoError(t, session.Close()) 189 } 190 191 func TestSessionAggregateIDsBadRequestErrorIsNonRetryable(t *testing.T) { 192 ctrl := gomock.NewController(t) 193 defer ctrl.Finish() 194 195 opts := newSessionTestOptions() 196 s, err := newSession(opts) 197 assert.NoError(t, err) 198 session := s.(*session) 199 200 start := xtime.Now().Truncate(time.Hour) 201 end := start.Add(2 * time.Hour) 202 203 topoInit := opts.TopologyInitializer() 204 topoWatch, err := topoInit.Init() 205 require.NoError(t, err) 206 topoMap := topoWatch.Get() 207 require.True(t, topoMap.HostsLen() > 0) 208 209 mockHostQueues(ctrl, session, sessionTestReplicas, []testEnqueueFn{ 210 func(idx int, op op) { 211 go func() { 212 host := topoMap.Hosts()[idx] 213 op.CompletionFn()(aggregateResultAccumulatorOpts{host: host}, &rpc.Error{ 214 Type: rpc.ErrorType_BAD_REQUEST, 215 Message: "expected bad request error", 216 }) 217 }() 218 }, 219 }) 220 221 assert.NoError(t, session.Open()) 222 // NB: stubbing needs to be done after session.Open 223 leakStatePool := injectLeakcheckFetchStatePool(session) 224 leakOpPool := injectLeakcheckAggregateOpPool(session) 225 226 _, _, err = session.Aggregate(testContext(), ident.StringID("namespace"), 227 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 228 assert.Error(t, err) 229 assert.NoError(t, session.Close()) 230 231 numStateAllocs := 0 232 leakStatePool.CheckExtended(t, func(e leakcheckFetchState) { 233 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 234 numStateAllocs++ 235 }) 236 require.Equal(t, 1, numStateAllocs) 237 238 numOpAllocs := 0 239 leakOpPool.CheckExtended(t, func(e leakcheckAggregateOp) { 240 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 241 numOpAllocs++ 242 }) 243 require.Equal(t, 1, numOpAllocs) 244 } 245 246 func TestSessionAggregateIDsEnqueueErr(t *testing.T) { 247 ctrl := gomock.NewController(t) 248 defer ctrl.Finish() 249 250 opts := newSessionTestOptions() 251 s, err := newSession(opts) 252 assert.NoError(t, err) 253 session := s.(*session) 254 255 start := xtime.Now().Truncate(time.Hour) 256 end := start.Add(2 * time.Hour) 257 258 require.Equal(t, 3, sessionTestReplicas) // the code below assumes this 259 mockExtendedHostQueues( 260 t, ctrl, session, sessionTestReplicas, 261 testHostQueueOpsByHost{ 262 testHostName(0): &testHostQueueOps{ 263 enqueues: []testEnqueue{ 264 { 265 enqueueFn: func(idx int, op op) {}, 266 }, 267 }, 268 }, 269 testHostName(1): &testHostQueueOps{ 270 enqueues: []testEnqueue{ 271 { 272 enqueueFn: func(idx int, op op) {}, 273 }, 274 }, 275 }, 276 testHostName(2): &testHostQueueOps{ 277 enqueues: []testEnqueue{ 278 { 279 enqueueErr: fmt.Errorf("random-error"), 280 }, 281 }, 282 }, 283 }) 284 285 assert.NoError(t, session.Open()) 286 287 defer instrument.SetShouldPanicEnvironmentVariable(true)() 288 require.Panics(t, func() { 289 _, _, _ = session.Aggregate(testContext(), ident.StringID("namespace"), 290 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 291 }) 292 } 293 294 func TestSessionAggregateMergeTest(t *testing.T) { 295 ctrl := gomock.NewController(t) 296 defer ctrl.Finish() 297 298 opts := newSessionTestOptions() 299 opts = opts.SetReadConsistencyLevel(topology.ReadConsistencyLevelAll) 300 s, err := newSession(opts) 301 assert.NoError(t, err) 302 session := s.(*session) 303 304 start := xtime.Now().Truncate(time.Hour) 305 end := start.Add(2 * time.Hour) 306 307 var ( 308 numPoints = 100 309 sg0 = newTestSerieses(1, 10) 310 sg1 = newTestSerieses(6, 15) 311 sg2 = newTestSerieses(11, 15) 312 ) 313 sg0.addDatapoints(numPoints, start, end) 314 sg1.addDatapoints(numPoints, start, end) 315 sg2.addDatapoints(numPoints, start, end) 316 317 topoInit := opts.TopologyInitializer() 318 topoWatch, err := topoInit.Init() 319 require.NoError(t, err) 320 topoMap := topoWatch.Get() 321 require.Equal(t, 3, topoMap.HostsLen()) // the code below assumes this 322 mockExtendedHostQueues( 323 t, ctrl, session, sessionTestReplicas, 324 testHostQueueOpsByHost{ 325 testHostName(0): &testHostQueueOps{ 326 enqueues: []testEnqueue{ 327 { 328 enqueueFn: func(idx int, op op) { 329 go func() { 330 op.CompletionFn()(aggregateResultAccumulatorOpts{ 331 host: topoMap.Hosts()[idx], 332 response: sg0.toRPCAggResult(true), 333 }, nil) 334 }() 335 }, 336 }, 337 }, 338 }, 339 testHostName(1): &testHostQueueOps{ 340 enqueues: []testEnqueue{ 341 { 342 enqueueFn: func(idx int, op op) { 343 go func() { 344 op.CompletionFn()(aggregateResultAccumulatorOpts{ 345 host: topoMap.Hosts()[idx], 346 response: sg1.toRPCAggResult(false), 347 }, nil) 348 }() 349 }, 350 }, 351 }, 352 }, 353 testHostName(2): &testHostQueueOps{ 354 enqueues: []testEnqueue{ 355 { 356 enqueueFn: func(idx int, op op) { 357 go func() { 358 op.CompletionFn()(aggregateResultAccumulatorOpts{ 359 host: topoMap.Hosts()[idx], 360 response: sg2.toRPCAggResult(true), 361 }, nil) 362 }() 363 }, 364 }, 365 }, 366 }, 367 }) 368 369 assert.NoError(t, session.Open()) 370 371 // NB: stubbing needs to be done after session.Open 372 leakStatePool := injectLeakcheckFetchStatePool(session) 373 leakOpPool := injectLeakcheckAggregateOpPool(session) 374 375 iters, metadata, err := session.Aggregate(testContext(), ident.StringID("namespace"), 376 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 377 assert.NoError(t, err) 378 assert.False(t, metadata.Exhaustive) 379 expected := append(sg0, sg1...) 380 expected = append(expected, sg2...) 381 expected.assertMatchesAggregatedTagsIter(t, iters) 382 383 assert.NoError(t, session.Close()) 384 385 numStateAllocs := 0 386 leakStatePool.CheckExtended(t, func(e leakcheckFetchState) { 387 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 388 numStateAllocs++ 389 }) 390 require.Equal(t, 1, numStateAllocs) 391 392 numOpAllocs := 0 393 leakOpPool.CheckExtended(t, func(e leakcheckAggregateOp) { 394 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 395 numOpAllocs++ 396 }) 397 require.Equal(t, 1, numOpAllocs) 398 } 399 400 func TestSessionAggregateMergeWithRetriesTest(t *testing.T) { 401 ctrl := gomock.NewController(t) 402 defer ctrl.Finish() 403 404 opts := newSessionTestOptions(). 405 SetReadConsistencyLevel(topology.ReadConsistencyLevelAll). 406 SetFetchRetrier(xretry.NewRetrier(xretry.NewOptions().SetMaxRetries(1))) 407 408 s, err := newSession(opts) 409 assert.NoError(t, err) 410 session := s.(*session) 411 412 start := xtime.Now().Truncate(time.Hour) 413 end := start.Add(2 * time.Hour) 414 415 var ( 416 numPoints = 100 417 sg0 = newTestSerieses(1, 5) 418 sg1 = newTestSerieses(6, 10) 419 sg2 = newTestSerieses(11, 15) 420 ) 421 sg0.addDatapoints(numPoints, start, end) 422 sg1.addDatapoints(numPoints, start, end) 423 sg2.addDatapoints(numPoints, start, end) 424 425 topoInit := opts.TopologyInitializer() 426 topoWatch, err := topoInit.Init() 427 require.NoError(t, err) 428 topoMap := topoWatch.Get() 429 require.Equal(t, 3, topoMap.HostsLen()) // the code below assumes this 430 mockExtendedHostQueues( 431 t, ctrl, session, sessionTestReplicas, 432 testHostQueueOpsByHost{ 433 testHostName(0): &testHostQueueOps{ 434 enqueues: []testEnqueue{ 435 { 436 enqueueFn: func(idx int, op op) { 437 go func() { 438 op.CompletionFn()(aggregateResultAccumulatorOpts{ 439 host: topoMap.Hosts()[idx], 440 }, fmt.Errorf("random-err-0")) 441 }() 442 }, 443 }, 444 { 445 enqueueFn: func(idx int, op op) { 446 go func() { 447 op.CompletionFn()(aggregateResultAccumulatorOpts{ 448 host: topoMap.Hosts()[idx], 449 response: sg0.toRPCAggResult(true), 450 }, nil) 451 }() 452 }, 453 }, 454 }, 455 }, 456 testHostName(1): &testHostQueueOps{ 457 enqueues: []testEnqueue{ 458 { 459 enqueueFn: func(idx int, op op) { 460 go func() { 461 op.CompletionFn()(aggregateResultAccumulatorOpts{ 462 host: topoMap.Hosts()[idx], 463 }, fmt.Errorf("random-err-1")) 464 }() 465 }, 466 }, 467 { 468 enqueueFn: func(idx int, op op) { 469 go func() { 470 op.CompletionFn()(aggregateResultAccumulatorOpts{ 471 host: topoMap.Hosts()[idx], 472 response: sg1.toRPCAggResult(false), 473 }, nil) 474 }() 475 }, 476 }, 477 }, 478 }, 479 testHostName(2): &testHostQueueOps{ 480 enqueues: []testEnqueue{ 481 { 482 enqueueFn: func(idx int, op op) { 483 go func() { 484 op.CompletionFn()(aggregateResultAccumulatorOpts{ 485 host: topoMap.Hosts()[idx], 486 }, fmt.Errorf("random-err-2")) 487 }() 488 }, 489 }, 490 { 491 enqueueFn: func(idx int, op op) { 492 go func() { 493 op.CompletionFn()(aggregateResultAccumulatorOpts{ 494 host: topoMap.Hosts()[idx], 495 response: sg2.toRPCAggResult(true), 496 }, nil) 497 }() 498 }, 499 }, 500 }, 501 }, 502 }) 503 504 assert.NoError(t, session.Open()) 505 506 // NB: stubbing needs to be done after session.Open 507 leakStatePool := injectLeakcheckFetchStatePool(session) 508 leakOpPool := injectLeakcheckAggregateOpPool(session) 509 iters, meta, err := session.Aggregate(testContext(), ident.StringID("namespace"), 510 testSessionAggregateQuery, testSessionAggregateQueryOpts(start, end)) 511 assert.NoError(t, err) 512 assert.False(t, meta.Exhaustive) 513 expected := append(sg0, sg1...) 514 expected = append(expected, sg2...) 515 expected.assertMatchesAggregatedTagsIter(t, iters) 516 517 numStateAllocs := 0 518 leakStatePool.CheckExtended(t, func(e leakcheckFetchState) { 519 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 520 numStateAllocs++ 521 }) 522 require.Equal(t, 2, numStateAllocs) 523 524 numOpAllocs := 0 525 leakOpPool.CheckExtended(t, func(e leakcheckAggregateOp) { 526 require.Equal(t, int32(0), atomic.LoadInt32(&e.Value.refCounter.n), string(e.GetStacktrace)) 527 numOpAllocs++ 528 }) 529 require.Equal(t, 2, numOpAllocs) 530 531 assert.NoError(t, session.Close()) 532 } 533 534 func injectLeakcheckAggregateAttempPool(session *session) *leakcheckAggregateAttemptPool { 535 leakPool := newLeakcheckAggregateAttemptPool(leakcheckAggregateAttemptPoolOpts{}, session.pools.aggregateAttempt) 536 session.pools.aggregateAttempt = leakPool 537 return leakPool 538 } 539 540 func injectLeakcheckAggregateOpPool(session *session) *leakcheckAggregateOpPool { 541 leakOpPool := newLeakcheckAggregateOpPool(leakcheckAggregateOpPoolOpts{}, session.pools.aggregateOp) 542 leakOpPool.opts.GetHookFn = func(f *aggregateOp) *aggregateOp { f.pool = leakOpPool; return f } 543 session.pools.aggregateOp = leakOpPool 544 return leakOpPool 545 }