github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/limiter_test.go (about) 1 package ingester 2 3 import ( 4 "errors" 5 "math" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/mock" 10 "github.com/stretchr/testify/require" 11 12 "github.com/cortexproject/cortex/pkg/util" 13 "github.com/cortexproject/cortex/pkg/util/validation" 14 ) 15 16 func TestLimiter_maxSeriesPerMetric(t *testing.T) { 17 applyLimits := func(limits *validation.Limits, localLimit, globalLimit int) { 18 limits.MaxLocalSeriesPerMetric = localLimit 19 limits.MaxGlobalSeriesPerMetric = globalLimit 20 } 21 22 runMaxFn := func(limiter *Limiter) int { 23 return limiter.maxSeriesPerMetric("test") 24 } 25 26 runLimiterMaxFunctionTest(t, applyLimits, runMaxFn, true) 27 } 28 29 func TestLimiter_maxMetadataPerMetric(t *testing.T) { 30 applyLimits := func(limits *validation.Limits, localLimit, globalLimit int) { 31 limits.MaxLocalMetadataPerMetric = localLimit 32 limits.MaxGlobalMetadataPerMetric = globalLimit 33 } 34 35 runMaxFn := func(limiter *Limiter) int { 36 return limiter.maxMetadataPerMetric("test") 37 } 38 39 runLimiterMaxFunctionTest(t, applyLimits, runMaxFn, true) 40 } 41 42 func TestLimiter_maxSeriesPerUser(t *testing.T) { 43 applyLimits := func(limits *validation.Limits, localLimit, globalLimit int) { 44 limits.MaxLocalSeriesPerUser = localLimit 45 limits.MaxGlobalSeriesPerUser = globalLimit 46 } 47 48 runMaxFn := func(limiter *Limiter) int { 49 return limiter.maxSeriesPerUser("test") 50 } 51 52 runLimiterMaxFunctionTest(t, applyLimits, runMaxFn, false) 53 } 54 55 func TestLimiter_maxMetadataPerUser(t *testing.T) { 56 applyLimits := func(limits *validation.Limits, localLimit, globalLimit int) { 57 limits.MaxLocalMetricsWithMetadataPerUser = localLimit 58 limits.MaxGlobalMetricsWithMetadataPerUser = globalLimit 59 } 60 61 runMaxFn := func(limiter *Limiter) int { 62 return limiter.maxMetadataPerUser("test") 63 } 64 65 runLimiterMaxFunctionTest(t, applyLimits, runMaxFn, false) 66 } 67 68 func runLimiterMaxFunctionTest( 69 t *testing.T, 70 applyLimits func(limits *validation.Limits, localLimit, globalLimit int), 71 runMaxFn func(limiter *Limiter) int, 72 globalLimitShardByMetricNameSupport bool, 73 ) { 74 tests := map[string]struct { 75 localLimit int 76 globalLimit int 77 ringReplicationFactor int 78 ringZoneAwarenessEnabled bool 79 ringIngesterCount int 80 ringZonesCount int 81 shardByAllLabels bool 82 shardSize int 83 expectedDefaultSharding int 84 expectedShuffleSharding int 85 }{ 86 "both local and global limits are disabled": { 87 localLimit: 0, 88 globalLimit: 0, 89 ringReplicationFactor: 1, 90 ringIngesterCount: 1, 91 ringZonesCount: 1, 92 shardByAllLabels: false, 93 expectedDefaultSharding: math.MaxInt32, 94 expectedShuffleSharding: math.MaxInt32, 95 }, 96 "only local limit is enabled": { 97 localLimit: 1000, 98 globalLimit: 0, 99 ringReplicationFactor: 1, 100 ringIngesterCount: 1, 101 ringZonesCount: 1, 102 shardByAllLabels: false, 103 expectedDefaultSharding: 1000, 104 expectedShuffleSharding: 1000, 105 }, 106 "only global limit is enabled with shard-by-all-labels=false and replication-factor=1": { 107 localLimit: 0, 108 globalLimit: 1000, 109 ringReplicationFactor: 1, 110 ringIngesterCount: 10, 111 ringZonesCount: 1, 112 shardByAllLabels: false, 113 shardSize: 5, 114 expectedDefaultSharding: func() int { 115 if globalLimitShardByMetricNameSupport { 116 return 1000 117 } 118 return math.MaxInt32 119 }(), 120 expectedShuffleSharding: func() int { 121 if globalLimitShardByMetricNameSupport { 122 return 1000 123 } 124 return math.MaxInt32 125 }(), 126 }, 127 "only global limit is enabled with shard-by-all-labels=true and replication-factor=1": { 128 localLimit: 0, 129 globalLimit: 1000, 130 ringReplicationFactor: 1, 131 ringIngesterCount: 10, 132 ringZonesCount: 1, 133 shardByAllLabels: true, 134 shardSize: 5, 135 expectedDefaultSharding: 100, 136 expectedShuffleSharding: 200, 137 }, 138 "only global limit is enabled with shard-by-all-labels=true and replication-factor=3": { 139 localLimit: 0, 140 globalLimit: 1000, 141 ringReplicationFactor: 3, 142 ringIngesterCount: 10, 143 ringZonesCount: 1, 144 shardByAllLabels: true, 145 shardSize: 5, 146 expectedDefaultSharding: 300, 147 expectedShuffleSharding: 600, 148 }, 149 "both local and global limits are set with local limit < global limit": { 150 localLimit: 150, 151 globalLimit: 1000, 152 ringReplicationFactor: 3, 153 ringIngesterCount: 10, 154 ringZonesCount: 1, 155 shardByAllLabels: true, 156 shardSize: 5, 157 expectedDefaultSharding: 150, 158 expectedShuffleSharding: 150, 159 }, 160 "both local and global limits are set with local limit > global limit": { 161 localLimit: 800, 162 globalLimit: 1000, 163 ringReplicationFactor: 3, 164 ringIngesterCount: 10, 165 ringZonesCount: 1, 166 shardByAllLabels: true, 167 shardSize: 5, 168 expectedDefaultSharding: 300, 169 expectedShuffleSharding: 600, 170 }, 171 "zone-awareness enabled, global limit enabled and the shard size is NOT divisible by number of zones": { 172 localLimit: 0, 173 globalLimit: 900, 174 ringReplicationFactor: 3, 175 ringZoneAwarenessEnabled: true, 176 ringIngesterCount: 9, 177 ringZonesCount: 3, 178 shardByAllLabels: true, 179 shardSize: 5, // Not divisible by number of zones. 180 expectedDefaultSharding: 300, 181 expectedShuffleSharding: 450, // (900 / 6) * 3 182 }, 183 "zone-awareness enabled, global limit enabled and the shard size is divisible by number of zones": { 184 localLimit: 0, 185 globalLimit: 900, 186 ringReplicationFactor: 3, 187 ringZoneAwarenessEnabled: true, 188 ringIngesterCount: 9, 189 ringZonesCount: 3, 190 shardByAllLabels: true, 191 shardSize: 6, // Divisible by number of zones. 192 expectedDefaultSharding: 300, 193 expectedShuffleSharding: 450, // (900 / 6) * 3 194 }, 195 "zone-awareness enabled, global limit enabled and the shard size > number of ingesters": { 196 localLimit: 0, 197 globalLimit: 900, 198 ringReplicationFactor: 3, 199 ringZoneAwarenessEnabled: true, 200 ringIngesterCount: 9, 201 ringZonesCount: 3, 202 shardByAllLabels: true, 203 shardSize: 20, // Greater than number of ingesters. 204 expectedDefaultSharding: 300, 205 expectedShuffleSharding: 300, 206 }, 207 } 208 209 for testName, testData := range tests { 210 testData := testData 211 212 t.Run(testName, func(t *testing.T) { 213 // Mock the ring 214 ring := &ringCountMock{} 215 ring.On("HealthyInstancesCount").Return(testData.ringIngesterCount) 216 ring.On("ZonesCount").Return(testData.ringZonesCount) 217 218 // Mock limits 219 limits := validation.Limits{IngestionTenantShardSize: testData.shardSize} 220 applyLimits(&limits, testData.localLimit, testData.globalLimit) 221 222 overrides, err := validation.NewOverrides(limits, nil) 223 require.NoError(t, err) 224 225 // Assert on default sharding strategy. 226 limiter := NewLimiter(overrides, ring, util.ShardingStrategyDefault, testData.shardByAllLabels, testData.ringReplicationFactor, testData.ringZoneAwarenessEnabled) 227 actual := runMaxFn(limiter) 228 assert.Equal(t, testData.expectedDefaultSharding, actual) 229 230 // Assert on shuffle sharding strategy. 231 limiter = NewLimiter(overrides, ring, util.ShardingStrategyShuffle, testData.shardByAllLabels, testData.ringReplicationFactor, testData.ringZoneAwarenessEnabled) 232 actual = runMaxFn(limiter) 233 assert.Equal(t, testData.expectedShuffleSharding, actual) 234 }) 235 } 236 } 237 238 func TestLimiter_AssertMaxSeriesPerMetric(t *testing.T) { 239 tests := map[string]struct { 240 maxLocalSeriesPerMetric int 241 maxGlobalSeriesPerMetric int 242 ringReplicationFactor int 243 ringIngesterCount int 244 shardByAllLabels bool 245 series int 246 expected error 247 }{ 248 "both local and global limit are disabled": { 249 maxLocalSeriesPerMetric: 0, 250 maxGlobalSeriesPerMetric: 0, 251 ringReplicationFactor: 1, 252 ringIngesterCount: 1, 253 shardByAllLabels: false, 254 series: 100, 255 expected: nil, 256 }, 257 "current number of series is below the limit": { 258 maxLocalSeriesPerMetric: 0, 259 maxGlobalSeriesPerMetric: 1000, 260 ringReplicationFactor: 3, 261 ringIngesterCount: 10, 262 shardByAllLabels: true, 263 series: 299, 264 expected: nil, 265 }, 266 "current number of series is above the limit": { 267 maxLocalSeriesPerMetric: 0, 268 maxGlobalSeriesPerMetric: 1000, 269 ringReplicationFactor: 3, 270 ringIngesterCount: 10, 271 shardByAllLabels: true, 272 series: 300, 273 expected: errMaxSeriesPerMetricLimitExceeded, 274 }, 275 } 276 277 for testName, testData := range tests { 278 testData := testData 279 280 t.Run(testName, func(t *testing.T) { 281 // Mock the ring 282 ring := &ringCountMock{} 283 ring.On("HealthyInstancesCount").Return(testData.ringIngesterCount) 284 ring.On("ZonesCount").Return(1) 285 286 // Mock limits 287 limits, err := validation.NewOverrides(validation.Limits{ 288 MaxLocalSeriesPerMetric: testData.maxLocalSeriesPerMetric, 289 MaxGlobalSeriesPerMetric: testData.maxGlobalSeriesPerMetric, 290 }, nil) 291 require.NoError(t, err) 292 293 limiter := NewLimiter(limits, ring, util.ShardingStrategyDefault, testData.shardByAllLabels, testData.ringReplicationFactor, false) 294 actual := limiter.AssertMaxSeriesPerMetric("test", testData.series) 295 296 assert.Equal(t, testData.expected, actual) 297 }) 298 } 299 } 300 func TestLimiter_AssertMaxMetadataPerMetric(t *testing.T) { 301 tests := map[string]struct { 302 maxLocalMetadataPerMetric int 303 maxGlobalMetadataPerMetric int 304 ringReplicationFactor int 305 ringIngesterCount int 306 shardByAllLabels bool 307 metadata int 308 expected error 309 }{ 310 "both local and global limit are disabled": { 311 maxLocalMetadataPerMetric: 0, 312 maxGlobalMetadataPerMetric: 0, 313 ringReplicationFactor: 1, 314 ringIngesterCount: 1, 315 shardByAllLabels: false, 316 metadata: 100, 317 expected: nil, 318 }, 319 "current number of metadata is below the limit": { 320 maxLocalMetadataPerMetric: 0, 321 maxGlobalMetadataPerMetric: 1000, 322 ringReplicationFactor: 3, 323 ringIngesterCount: 10, 324 shardByAllLabels: true, 325 metadata: 299, 326 expected: nil, 327 }, 328 "current number of metadata is above the limit": { 329 maxLocalMetadataPerMetric: 0, 330 maxGlobalMetadataPerMetric: 1000, 331 ringReplicationFactor: 3, 332 ringIngesterCount: 10, 333 shardByAllLabels: true, 334 metadata: 300, 335 expected: errMaxMetadataPerMetricLimitExceeded, 336 }, 337 } 338 339 for testName, testData := range tests { 340 testData := testData 341 342 t.Run(testName, func(t *testing.T) { 343 // Mock the ring 344 ring := &ringCountMock{} 345 ring.On("HealthyInstancesCount").Return(testData.ringIngesterCount) 346 ring.On("ZonesCount").Return(1) 347 348 // Mock limits 349 limits, err := validation.NewOverrides(validation.Limits{ 350 MaxLocalMetadataPerMetric: testData.maxLocalMetadataPerMetric, 351 MaxGlobalMetadataPerMetric: testData.maxGlobalMetadataPerMetric, 352 }, nil) 353 require.NoError(t, err) 354 355 limiter := NewLimiter(limits, ring, util.ShardingStrategyDefault, testData.shardByAllLabels, testData.ringReplicationFactor, false) 356 actual := limiter.AssertMaxMetadataPerMetric("test", testData.metadata) 357 358 assert.Equal(t, testData.expected, actual) 359 }) 360 } 361 } 362 363 func TestLimiter_AssertMaxSeriesPerUser(t *testing.T) { 364 tests := map[string]struct { 365 maxLocalSeriesPerUser int 366 maxGlobalSeriesPerUser int 367 ringReplicationFactor int 368 ringIngesterCount int 369 shardByAllLabels bool 370 series int 371 expected error 372 }{ 373 "both local and global limit are disabled": { 374 maxLocalSeriesPerUser: 0, 375 maxGlobalSeriesPerUser: 0, 376 ringReplicationFactor: 1, 377 ringIngesterCount: 1, 378 shardByAllLabels: false, 379 series: 100, 380 expected: nil, 381 }, 382 "current number of series is below the limit": { 383 maxLocalSeriesPerUser: 0, 384 maxGlobalSeriesPerUser: 1000, 385 ringReplicationFactor: 3, 386 ringIngesterCount: 10, 387 shardByAllLabels: true, 388 series: 299, 389 expected: nil, 390 }, 391 "current number of series is above the limit": { 392 maxLocalSeriesPerUser: 0, 393 maxGlobalSeriesPerUser: 1000, 394 ringReplicationFactor: 3, 395 ringIngesterCount: 10, 396 shardByAllLabels: true, 397 series: 300, 398 expected: errMaxSeriesPerUserLimitExceeded, 399 }, 400 } 401 402 for testName, testData := range tests { 403 testData := testData 404 405 t.Run(testName, func(t *testing.T) { 406 // Mock the ring 407 ring := &ringCountMock{} 408 ring.On("HealthyInstancesCount").Return(testData.ringIngesterCount) 409 ring.On("ZonesCount").Return(1) 410 411 // Mock limits 412 limits, err := validation.NewOverrides(validation.Limits{ 413 MaxLocalSeriesPerUser: testData.maxLocalSeriesPerUser, 414 MaxGlobalSeriesPerUser: testData.maxGlobalSeriesPerUser, 415 }, nil) 416 require.NoError(t, err) 417 418 limiter := NewLimiter(limits, ring, util.ShardingStrategyDefault, testData.shardByAllLabels, testData.ringReplicationFactor, false) 419 actual := limiter.AssertMaxSeriesPerUser("test", testData.series) 420 421 assert.Equal(t, testData.expected, actual) 422 }) 423 } 424 } 425 426 func TestLimiter_AssertMaxMetricsWithMetadataPerUser(t *testing.T) { 427 tests := map[string]struct { 428 maxLocalMetadataPerUser int 429 maxGlobalMetadataPerUser int 430 ringReplicationFactor int 431 ringIngesterCount int 432 shardByAllLabels bool 433 metadata int 434 expected error 435 }{ 436 "both local and global limit are disabled": { 437 maxLocalMetadataPerUser: 0, 438 maxGlobalMetadataPerUser: 0, 439 ringReplicationFactor: 1, 440 ringIngesterCount: 1, 441 shardByAllLabels: false, 442 metadata: 100, 443 expected: nil, 444 }, 445 "current number of metadata is below the limit": { 446 maxLocalMetadataPerUser: 0, 447 maxGlobalMetadataPerUser: 1000, 448 ringReplicationFactor: 3, 449 ringIngesterCount: 10, 450 shardByAllLabels: true, 451 metadata: 299, 452 expected: nil, 453 }, 454 "current number of metadata is above the limit": { 455 maxLocalMetadataPerUser: 0, 456 maxGlobalMetadataPerUser: 1000, 457 ringReplicationFactor: 3, 458 ringIngesterCount: 10, 459 shardByAllLabels: true, 460 metadata: 300, 461 expected: errMaxMetadataPerUserLimitExceeded, 462 }, 463 } 464 465 for testName, testData := range tests { 466 testData := testData 467 468 t.Run(testName, func(t *testing.T) { 469 // Mock the ring 470 ring := &ringCountMock{} 471 ring.On("HealthyInstancesCount").Return(testData.ringIngesterCount) 472 ring.On("ZonesCount").Return(1) 473 474 // Mock limits 475 limits, err := validation.NewOverrides(validation.Limits{ 476 MaxLocalMetricsWithMetadataPerUser: testData.maxLocalMetadataPerUser, 477 MaxGlobalMetricsWithMetadataPerUser: testData.maxGlobalMetadataPerUser, 478 }, nil) 479 require.NoError(t, err) 480 481 limiter := NewLimiter(limits, ring, util.ShardingStrategyDefault, testData.shardByAllLabels, testData.ringReplicationFactor, false) 482 actual := limiter.AssertMaxMetricsWithMetadataPerUser("test", testData.metadata) 483 484 assert.Equal(t, testData.expected, actual) 485 }) 486 } 487 } 488 489 func TestLimiter_FormatError(t *testing.T) { 490 // Mock the ring 491 ring := &ringCountMock{} 492 ring.On("HealthyInstancesCount").Return(3) 493 ring.On("ZonesCount").Return(1) 494 495 // Mock limits 496 limits, err := validation.NewOverrides(validation.Limits{ 497 MaxGlobalSeriesPerUser: 100, 498 MaxGlobalSeriesPerMetric: 20, 499 MaxGlobalMetricsWithMetadataPerUser: 10, 500 MaxGlobalMetadataPerMetric: 3, 501 }, nil) 502 require.NoError(t, err) 503 504 limiter := NewLimiter(limits, ring, util.ShardingStrategyDefault, true, 3, false) 505 506 actual := limiter.FormatError("user-1", errMaxSeriesPerUserLimitExceeded) 507 assert.EqualError(t, actual, "per-user series limit of 100 exceeded, please contact administrator to raise it (local limit: 0 global limit: 100 actual local limit: 100)") 508 509 actual = limiter.FormatError("user-1", errMaxSeriesPerMetricLimitExceeded) 510 assert.EqualError(t, actual, "per-metric series limit of 20 exceeded, please contact administrator to raise it (local limit: 0 global limit: 20 actual local limit: 20)") 511 512 actual = limiter.FormatError("user-1", errMaxMetadataPerUserLimitExceeded) 513 assert.EqualError(t, actual, "per-user metric metadata limit of 10 exceeded, please contact administrator to raise it (local limit: 0 global limit: 10 actual local limit: 10)") 514 515 actual = limiter.FormatError("user-1", errMaxMetadataPerMetricLimitExceeded) 516 assert.EqualError(t, actual, "per-metric metadata limit of 3 exceeded, please contact administrator to raise it (local limit: 0 global limit: 3 actual local limit: 3)") 517 518 input := errors.New("unknown error") 519 actual = limiter.FormatError("user-1", input) 520 assert.Equal(t, input, actual) 521 } 522 523 func TestLimiter_minNonZero(t *testing.T) { 524 t.Parallel() 525 526 tests := map[string]struct { 527 first int 528 second int 529 expected int 530 }{ 531 "both zero": { 532 first: 0, 533 second: 0, 534 expected: 0, 535 }, 536 "first is zero": { 537 first: 0, 538 second: 1, 539 expected: 1, 540 }, 541 "second is zero": { 542 first: 1, 543 second: 0, 544 expected: 1, 545 }, 546 "both non zero, second > first": { 547 first: 1, 548 second: 2, 549 expected: 1, 550 }, 551 "both non zero, first > second": { 552 first: 2, 553 second: 1, 554 expected: 1, 555 }, 556 } 557 558 for testName, testData := range tests { 559 testData := testData 560 561 t.Run(testName, func(t *testing.T) { 562 assert.Equal(t, testData.expected, minNonZero(testData.first, testData.second)) 563 }) 564 } 565 } 566 567 type ringCountMock struct { 568 mock.Mock 569 } 570 571 func (m *ringCountMock) HealthyInstancesCount() int { 572 args := m.Called() 573 return args.Int(0) 574 } 575 576 func (m *ringCountMock) ZonesCount() int { 577 args := m.Called() 578 return args.Int(0) 579 }