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  }