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  }