github.com/thanos-io/thanos@v0.32.5/internal/cortex/util/validation/limits_test.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package validation 5 6 import ( 7 "encoding/json" 8 "reflect" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/prometheus/common/model" 14 "github.com/prometheus/prometheus/model/relabel" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 "golang.org/x/time/rate" 18 "gopkg.in/yaml.v2" 19 20 "github.com/thanos-io/thanos/internal/cortex/util/flagext" 21 ) 22 23 // mockTenantLimits exposes per-tenant limits based on a provided map 24 type mockTenantLimits struct { 25 limits map[string]*Limits 26 } 27 28 // newMockTenantLimits creates a new mockTenantLimits that returns per-tenant limits based on 29 // the given map 30 func newMockTenantLimits(limits map[string]*Limits) *mockTenantLimits { 31 return &mockTenantLimits{ 32 limits: limits, 33 } 34 } 35 36 func (l *mockTenantLimits) ByUserID(userID string) *Limits { 37 return l.limits[userID] 38 } 39 40 func (l *mockTenantLimits) AllByUserID() map[string]*Limits { 41 return l.limits 42 } 43 44 func TestLimits_Validate(t *testing.T) { 45 t.Parallel() 46 47 tests := map[string]struct { 48 limits Limits 49 shardByAllLabels bool 50 expected error 51 }{ 52 "max-global-series-per-user disabled and shard-by-all-labels=false": { 53 limits: Limits{MaxGlobalSeriesPerUser: 0}, 54 shardByAllLabels: false, 55 expected: nil, 56 }, 57 "max-global-series-per-user enabled and shard-by-all-labels=false": { 58 limits: Limits{MaxGlobalSeriesPerUser: 1000}, 59 shardByAllLabels: false, 60 expected: errMaxGlobalSeriesPerUserValidation, 61 }, 62 "max-global-series-per-user disabled and shard-by-all-labels=true": { 63 limits: Limits{MaxGlobalSeriesPerUser: 1000}, 64 shardByAllLabels: true, 65 expected: nil, 66 }, 67 } 68 69 for testName, testData := range tests { 70 testData := testData 71 72 t.Run(testName, func(t *testing.T) { 73 assert.Equal(t, testData.expected, testData.limits.Validate(testData.shardByAllLabels)) 74 }) 75 } 76 } 77 78 func TestOverrides_MaxChunksPerQueryFromStore(t *testing.T) { 79 tests := map[string]struct { 80 setup func(limits *Limits) 81 expected int 82 }{ 83 "should return the default legacy setting with the default config": { 84 setup: func(limits *Limits) {}, 85 expected: 2000000, 86 }, 87 "the new config option should take precedence over the deprecated one": { 88 setup: func(limits *Limits) { 89 limits.MaxChunksPerQueryFromStore = 10 90 limits.MaxChunksPerQuery = 20 91 }, 92 expected: 20, 93 }, 94 "the deprecated config option should be used if the new config option is unset": { 95 setup: func(limits *Limits) { 96 limits.MaxChunksPerQueryFromStore = 10 97 }, 98 expected: 10, 99 }, 100 } 101 102 for testName, testData := range tests { 103 t.Run(testName, func(t *testing.T) { 104 limits := Limits{} 105 flagext.DefaultValues(&limits) 106 testData.setup(&limits) 107 108 overrides, err := NewOverrides(limits, nil) 109 require.NoError(t, err) 110 assert.Equal(t, testData.expected, overrides.MaxChunksPerQueryFromStore("test")) 111 }) 112 } 113 } 114 115 func TestOverridesManager_GetOverrides(t *testing.T) { 116 tenantLimits := map[string]*Limits{} 117 118 defaults := Limits{ 119 MaxLabelNamesPerSeries: 100, 120 } 121 ov, err := NewOverrides(defaults, newMockTenantLimits(tenantLimits)) 122 require.NoError(t, err) 123 124 require.Equal(t, 100, ov.MaxLabelNamesPerSeries("user1")) 125 require.Equal(t, 0, ov.MaxLabelValueLength("user1")) 126 127 // Update limits for tenant user1. We only update single field, the rest is copied from defaults. 128 // (That is how limits work when loaded from YAML) 129 l := Limits{} 130 l = defaults 131 l.MaxLabelValueLength = 150 132 133 tenantLimits["user1"] = &l 134 135 // Checking whether overrides were enforced 136 require.Equal(t, 100, ov.MaxLabelNamesPerSeries("user1")) 137 require.Equal(t, 150, ov.MaxLabelValueLength("user1")) 138 139 // Verifying user2 limits are not impacted by overrides 140 require.Equal(t, 100, ov.MaxLabelNamesPerSeries("user2")) 141 require.Equal(t, 0, ov.MaxLabelValueLength("user2")) 142 } 143 144 func TestLimitsLoadingFromYaml(t *testing.T) { 145 SetDefaultLimitsForYAMLUnmarshalling(Limits{ 146 MaxLabelNameLength: 100, 147 }) 148 149 inp := `ingestion_rate: 0.5` 150 151 l := Limits{} 152 err := yaml.UnmarshalStrict([]byte(inp), &l) 153 require.NoError(t, err) 154 155 assert.Equal(t, 0.5, l.IngestionRate, "from yaml") 156 assert.Equal(t, 100, l.MaxLabelNameLength, "from defaults") 157 } 158 159 func TestLimitsLoadingFromJson(t *testing.T) { 160 SetDefaultLimitsForYAMLUnmarshalling(Limits{ 161 MaxLabelNameLength: 100, 162 }) 163 164 inp := `{"ingestion_rate": 0.5}` 165 166 l := Limits{} 167 err := json.Unmarshal([]byte(inp), &l) 168 require.NoError(t, err) 169 170 assert.Equal(t, 0.5, l.IngestionRate, "from json") 171 assert.Equal(t, 100, l.MaxLabelNameLength, "from defaults") 172 173 // Unmarshal should fail if input contains unknown struct fields and 174 // the decoder flag `json.Decoder.DisallowUnknownFields()` is set 175 inp = `{"unknown_fields": 100}` 176 l = Limits{} 177 dec := json.NewDecoder(strings.NewReader(inp)) 178 dec.DisallowUnknownFields() 179 err = dec.Decode(&l) 180 assert.Error(t, err) 181 } 182 183 func TestLimitsTagsYamlMatchJson(t *testing.T) { 184 limits := reflect.TypeOf(Limits{}) 185 n := limits.NumField() 186 var mismatch []string 187 188 for i := 0; i < n; i++ { 189 field := limits.Field(i) 190 191 // Note that we aren't requiring YAML and JSON tags to match, just that 192 // they either both exist or both don't exist. 193 hasYAMLTag := field.Tag.Get("yaml") != "" 194 hasJSONTag := field.Tag.Get("json") != "" 195 196 if hasYAMLTag != hasJSONTag { 197 mismatch = append(mismatch, field.Name) 198 } 199 } 200 201 assert.Empty(t, mismatch, "expected no mismatched JSON and YAML tags") 202 } 203 204 func TestLimitsStringDurationYamlMatchJson(t *testing.T) { 205 inputYAML := ` 206 max_query_lookback: 1s 207 max_query_length: 1s 208 ` 209 inputJSON := `{"max_query_lookback": "1s", "max_query_length": "1s"}` 210 211 limitsYAML := Limits{} 212 err := yaml.Unmarshal([]byte(inputYAML), &limitsYAML) 213 require.NoError(t, err, "expected to be able to unmarshal from YAML") 214 215 limitsJSON := Limits{} 216 err = json.Unmarshal([]byte(inputJSON), &limitsJSON) 217 require.NoError(t, err, "expected to be able to unmarshal from JSON") 218 219 assert.Equal(t, limitsYAML, limitsJSON) 220 } 221 222 func TestLimitsAlwaysUsesPromDuration(t *testing.T) { 223 stdlibDuration := reflect.TypeOf(time.Duration(0)) 224 limits := reflect.TypeOf(Limits{}) 225 n := limits.NumField() 226 var badDurationType []string 227 228 for i := 0; i < n; i++ { 229 field := limits.Field(i) 230 if field.Type == stdlibDuration { 231 badDurationType = append(badDurationType, field.Name) 232 } 233 } 234 235 assert.Empty(t, badDurationType, "some Limits fields are using stdlib time.Duration instead of model.Duration") 236 } 237 238 func TestMetricRelabelConfigLimitsLoadingFromYaml(t *testing.T) { 239 SetDefaultLimitsForYAMLUnmarshalling(Limits{}) 240 241 inp := ` 242 metric_relabel_configs: 243 - action: drop 244 source_labels: [le] 245 regex: .+ 246 ` 247 exp := relabel.DefaultRelabelConfig 248 exp.Action = relabel.Drop 249 regex, err := relabel.NewRegexp(".+") 250 require.NoError(t, err) 251 exp.Regex = regex 252 exp.SourceLabels = model.LabelNames([]model.LabelName{"le"}) 253 254 l := Limits{} 255 err = yaml.UnmarshalStrict([]byte(inp), &l) 256 require.NoError(t, err) 257 258 assert.Equal(t, []*relabel.Config{&exp}, l.MetricRelabelConfigs) 259 } 260 261 func TestSmallestPositiveIntPerTenant(t *testing.T) { 262 tenantLimits := map[string]*Limits{ 263 "tenant-a": { 264 MaxQueryParallelism: 5, 265 }, 266 "tenant-b": { 267 MaxQueryParallelism: 10, 268 }, 269 } 270 271 defaults := Limits{ 272 MaxQueryParallelism: 0, 273 } 274 ov, err := NewOverrides(defaults, newMockTenantLimits(tenantLimits)) 275 require.NoError(t, err) 276 277 for _, tc := range []struct { 278 tenantIDs []string 279 expLimit int 280 }{ 281 {tenantIDs: []string{}, expLimit: 0}, 282 {tenantIDs: []string{"tenant-a"}, expLimit: 5}, 283 {tenantIDs: []string{"tenant-b"}, expLimit: 10}, 284 {tenantIDs: []string{"tenant-c"}, expLimit: 0}, 285 {tenantIDs: []string{"tenant-a", "tenant-b"}, expLimit: 5}, 286 {tenantIDs: []string{"tenant-c", "tenant-d", "tenant-e"}, expLimit: 0}, 287 {tenantIDs: []string{"tenant-a", "tenant-b", "tenant-c"}, expLimit: 0}, 288 } { 289 assert.Equal(t, tc.expLimit, SmallestPositiveIntPerTenant(tc.tenantIDs, ov.MaxQueryParallelism)) 290 } 291 } 292 293 func TestSmallestPositiveNonZeroIntPerTenant(t *testing.T) { 294 tenantLimits := map[string]*Limits{ 295 "tenant-a": { 296 MaxQueriersPerTenant: 5, 297 }, 298 "tenant-b": { 299 MaxQueriersPerTenant: 10, 300 }, 301 } 302 303 defaults := Limits{ 304 MaxQueriersPerTenant: 0, 305 } 306 ov, err := NewOverrides(defaults, newMockTenantLimits(tenantLimits)) 307 require.NoError(t, err) 308 309 for _, tc := range []struct { 310 tenantIDs []string 311 expLimit int 312 }{ 313 {tenantIDs: []string{}, expLimit: 0}, 314 {tenantIDs: []string{"tenant-a"}, expLimit: 5}, 315 {tenantIDs: []string{"tenant-b"}, expLimit: 10}, 316 {tenantIDs: []string{"tenant-c"}, expLimit: 0}, 317 {tenantIDs: []string{"tenant-a", "tenant-b"}, expLimit: 5}, 318 {tenantIDs: []string{"tenant-c", "tenant-d", "tenant-e"}, expLimit: 0}, 319 {tenantIDs: []string{"tenant-a", "tenant-b", "tenant-c"}, expLimit: 5}, 320 } { 321 assert.Equal(t, tc.expLimit, SmallestPositiveNonZeroIntPerTenant(tc.tenantIDs, ov.MaxQueriersPerUser)) 322 } 323 } 324 325 func TestSmallestPositiveNonZeroDurationPerTenant(t *testing.T) { 326 tenantLimits := map[string]*Limits{ 327 "tenant-a": { 328 MaxQueryLength: model.Duration(time.Hour), 329 }, 330 "tenant-b": { 331 MaxQueryLength: model.Duration(4 * time.Hour), 332 }, 333 } 334 335 defaults := Limits{ 336 MaxQueryLength: 0, 337 } 338 ov, err := NewOverrides(defaults, newMockTenantLimits(tenantLimits)) 339 require.NoError(t, err) 340 341 for _, tc := range []struct { 342 tenantIDs []string 343 expLimit time.Duration 344 }{ 345 {tenantIDs: []string{}, expLimit: time.Duration(0)}, 346 {tenantIDs: []string{"tenant-a"}, expLimit: time.Hour}, 347 {tenantIDs: []string{"tenant-b"}, expLimit: 4 * time.Hour}, 348 {tenantIDs: []string{"tenant-c"}, expLimit: time.Duration(0)}, 349 {tenantIDs: []string{"tenant-a", "tenant-b"}, expLimit: time.Hour}, 350 {tenantIDs: []string{"tenant-c", "tenant-d", "tenant-e"}, expLimit: time.Duration(0)}, 351 {tenantIDs: []string{"tenant-a", "tenant-b", "tenant-c"}, expLimit: time.Hour}, 352 } { 353 assert.Equal(t, tc.expLimit, SmallestPositiveNonZeroDurationPerTenant(tc.tenantIDs, ov.MaxQueryLength)) 354 } 355 } 356 357 func TestAlertmanagerNotificationLimits(t *testing.T) { 358 for name, tc := range map[string]struct { 359 inputYAML string 360 expectedRateLimit rate.Limit 361 expectedBurstSize int 362 }{ 363 "no email specific limit": { 364 inputYAML: ` 365 alertmanager_notification_rate_limit: 100 366 `, 367 expectedRateLimit: 100, 368 expectedBurstSize: 100, 369 }, 370 "zero limit": { 371 inputYAML: ` 372 alertmanager_notification_rate_limit: 100 373 374 alertmanager_notification_rate_limit_per_integration: 375 email: 0 376 `, 377 expectedRateLimit: rate.Inf, 378 expectedBurstSize: maxInt, 379 }, 380 381 "negative limit": { 382 inputYAML: ` 383 alertmanager_notification_rate_limit_per_integration: 384 email: -10 385 `, 386 expectedRateLimit: 0, 387 expectedBurstSize: 0, 388 }, 389 390 "positive limit, negative burst": { 391 inputYAML: ` 392 alertmanager_notification_rate_limit_per_integration: 393 email: 222 394 `, 395 expectedRateLimit: 222, 396 expectedBurstSize: 222, 397 }, 398 399 "infinte limit": { 400 inputYAML: ` 401 alertmanager_notification_rate_limit_per_integration: 402 email: .inf 403 `, 404 expectedRateLimit: rate.Inf, 405 expectedBurstSize: maxInt, 406 }, 407 } { 408 t.Run(name, func(t *testing.T) { 409 limitsYAML := Limits{} 410 err := yaml.Unmarshal([]byte(tc.inputYAML), &limitsYAML) 411 require.NoError(t, err, "expected to be able to unmarshal from YAML") 412 413 ov, err := NewOverrides(limitsYAML, nil) 414 require.NoError(t, err) 415 416 require.Equal(t, tc.expectedRateLimit, ov.NotificationRateLimit("user", "email")) 417 require.Equal(t, tc.expectedBurstSize, ov.NotificationBurstSize("user", "email")) 418 }) 419 } 420 } 421 422 func TestAlertmanagerNotificationLimitsOverrides(t *testing.T) { 423 baseYaml := ` 424 alertmanager_notification_rate_limit: 5 425 426 alertmanager_notification_rate_limit_per_integration: 427 email: 100 428 ` 429 430 overrideGenericLimitsOnly := ` 431 testuser: 432 alertmanager_notification_rate_limit: 333 433 ` 434 435 overrideEmailLimits := ` 436 testuser: 437 alertmanager_notification_rate_limit_per_integration: 438 email: 7777 439 ` 440 441 overrideGenericLimitsAndEmailLimits := ` 442 testuser: 443 alertmanager_notification_rate_limit: 333 444 445 alertmanager_notification_rate_limit_per_integration: 446 email: 7777 447 ` 448 449 differentUserOverride := ` 450 differentuser: 451 alertmanager_notification_limits_per_integration: 452 email: 500 453 ` 454 455 for name, tc := range map[string]struct { 456 testedIntegration string 457 overrides string 458 expectedRateLimit rate.Limit 459 expectedBurstSize int 460 }{ 461 "no overrides, pushover": { 462 testedIntegration: "pushover", 463 expectedRateLimit: 5, 464 expectedBurstSize: 5, 465 }, 466 467 "no overrides, email": { 468 testedIntegration: "email", 469 expectedRateLimit: 100, 470 expectedBurstSize: 100, 471 }, 472 473 "generic override, pushover": { 474 testedIntegration: "pushover", 475 overrides: overrideGenericLimitsOnly, 476 expectedRateLimit: 333, 477 expectedBurstSize: 333, 478 }, 479 480 "generic override, email": { 481 testedIntegration: "email", 482 overrides: overrideGenericLimitsOnly, 483 expectedRateLimit: 100, // there is email-specific override in default config. 484 expectedBurstSize: 100, 485 }, 486 487 "email limit override, pushover": { 488 testedIntegration: "pushover", 489 overrides: overrideEmailLimits, 490 expectedRateLimit: 5, // loaded from defaults when parsing YAML 491 expectedBurstSize: 5, 492 }, 493 494 "email limit override, email": { 495 testedIntegration: "email", 496 overrides: overrideEmailLimits, 497 expectedRateLimit: 7777, 498 expectedBurstSize: 7777, 499 }, 500 501 "generic and email limit override, pushover": { 502 testedIntegration: "pushover", 503 overrides: overrideGenericLimitsAndEmailLimits, 504 expectedRateLimit: 333, 505 expectedBurstSize: 333, 506 }, 507 508 "generic and email limit override, email": { 509 testedIntegration: "email", 510 overrides: overrideGenericLimitsAndEmailLimits, 511 expectedRateLimit: 7777, 512 expectedBurstSize: 7777, 513 }, 514 515 "partial email limit override": { 516 testedIntegration: "email", 517 overrides: ` 518 testuser: 519 alertmanager_notification_rate_limit_per_integration: 520 email: 500 521 `, 522 expectedRateLimit: 500, // overridden 523 expectedBurstSize: 500, // same as rate limit 524 }, 525 526 "different user override, pushover": { 527 testedIntegration: "pushover", 528 overrides: differentUserOverride, 529 expectedRateLimit: 5, 530 expectedBurstSize: 5, 531 }, 532 533 "different user overridem, email": { 534 testedIntegration: "email", 535 overrides: differentUserOverride, 536 expectedRateLimit: 100, 537 expectedBurstSize: 100, 538 }, 539 } { 540 t.Run(name, func(t *testing.T) { 541 SetDefaultLimitsForYAMLUnmarshalling(Limits{}) 542 543 limitsYAML := Limits{} 544 err := yaml.Unmarshal([]byte(baseYaml), &limitsYAML) 545 require.NoError(t, err, "expected to be able to unmarshal from YAML") 546 547 SetDefaultLimitsForYAMLUnmarshalling(limitsYAML) 548 549 overrides := map[string]*Limits{} 550 err = yaml.Unmarshal([]byte(tc.overrides), &overrides) 551 require.NoError(t, err, "parsing overrides") 552 553 tl := newMockTenantLimits(overrides) 554 555 ov, err := NewOverrides(limitsYAML, tl) 556 require.NoError(t, err) 557 558 require.Equal(t, tc.expectedRateLimit, ov.NotificationRateLimit("testuser", tc.testedIntegration)) 559 require.Equal(t, tc.expectedBurstSize, ov.NotificationBurstSize("testuser", tc.testedIntegration)) 560 }) 561 } 562 }