github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/util/validation/validate_test.go (about)

     1  package validation
     2  
     3  import (
     4  	"net/http"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/prometheus/client_golang/prometheus"
     9  	"github.com/prometheus/client_golang/prometheus/testutil"
    10  	"github.com/prometheus/common/model"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/weaveworks/common/httpgrpc"
    14  
    15  	"github.com/cortexproject/cortex/pkg/cortexpb"
    16  	util_log "github.com/cortexproject/cortex/pkg/util/log"
    17  )
    18  
    19  type validateLabelsCfg struct {
    20  	enforceMetricName      bool
    21  	maxLabelNamesPerSeries int
    22  	maxLabelNameLength     int
    23  	maxLabelValueLength    int
    24  }
    25  
    26  func (v validateLabelsCfg) EnforceMetricName(userID string) bool {
    27  	return v.enforceMetricName
    28  }
    29  
    30  func (v validateLabelsCfg) MaxLabelNamesPerSeries(userID string) int {
    31  	return v.maxLabelNamesPerSeries
    32  }
    33  
    34  func (v validateLabelsCfg) MaxLabelNameLength(userID string) int {
    35  	return v.maxLabelNameLength
    36  }
    37  
    38  func (v validateLabelsCfg) MaxLabelValueLength(userID string) int {
    39  	return v.maxLabelValueLength
    40  }
    41  
    42  type validateMetadataCfg struct {
    43  	enforceMetadataMetricName bool
    44  	maxMetadataLength         int
    45  }
    46  
    47  func (vm validateMetadataCfg) EnforceMetadataMetricName(userID string) bool {
    48  	return vm.enforceMetadataMetricName
    49  }
    50  
    51  func (vm validateMetadataCfg) MaxMetadataLength(userID string) int {
    52  	return vm.maxMetadataLength
    53  }
    54  
    55  func TestValidateLabels(t *testing.T) {
    56  	var cfg validateLabelsCfg
    57  	userID := "testUser"
    58  
    59  	cfg.maxLabelValueLength = 25
    60  	cfg.maxLabelNameLength = 25
    61  	cfg.maxLabelNamesPerSeries = 2
    62  	cfg.enforceMetricName = true
    63  
    64  	for _, c := range []struct {
    65  		metric                  model.Metric
    66  		skipLabelNameValidation bool
    67  		err                     error
    68  	}{
    69  		{
    70  			map[model.LabelName]model.LabelValue{},
    71  			false,
    72  			newNoMetricNameError(),
    73  		},
    74  		{
    75  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: " "},
    76  			false,
    77  			newInvalidMetricNameError(" "),
    78  		},
    79  		{
    80  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "valid", "foo ": "bar"},
    81  			false,
    82  			newInvalidLabelError([]cortexpb.LabelAdapter{
    83  				{Name: model.MetricNameLabel, Value: "valid"},
    84  				{Name: "foo ", Value: "bar"},
    85  			}, "foo "),
    86  		},
    87  		{
    88  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "valid"},
    89  			false,
    90  			nil,
    91  		},
    92  		{
    93  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "badLabelName", "this_is_a_really_really_long_name_that_should_cause_an_error": "test_value_please_ignore"},
    94  			false,
    95  			newLabelNameTooLongError([]cortexpb.LabelAdapter{
    96  				{Name: model.MetricNameLabel, Value: "badLabelName"},
    97  				{Name: "this_is_a_really_really_long_name_that_should_cause_an_error", Value: "test_value_please_ignore"},
    98  			}, "this_is_a_really_really_long_name_that_should_cause_an_error"),
    99  		},
   100  		{
   101  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "badLabelValue", "much_shorter_name": "test_value_please_ignore_no_really_nothing_to_see_here"},
   102  			false,
   103  			newLabelValueTooLongError([]cortexpb.LabelAdapter{
   104  				{Name: model.MetricNameLabel, Value: "badLabelValue"},
   105  				{Name: "much_shorter_name", Value: "test_value_please_ignore_no_really_nothing_to_see_here"},
   106  			}, "test_value_please_ignore_no_really_nothing_to_see_here"),
   107  		},
   108  		{
   109  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "bar": "baz", "blip": "blop"},
   110  			false,
   111  			newTooManyLabelsError([]cortexpb.LabelAdapter{
   112  				{Name: model.MetricNameLabel, Value: "foo"},
   113  				{Name: "bar", Value: "baz"},
   114  				{Name: "blip", Value: "blop"},
   115  			}, 2),
   116  		},
   117  		{
   118  			map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "invalid%label&name": "bar"},
   119  			true,
   120  			nil,
   121  		},
   122  	} {
   123  		err := ValidateLabels(cfg, userID, cortexpb.FromMetricsToLabelAdapters(c.metric), c.skipLabelNameValidation)
   124  		assert.Equal(t, c.err, err, "wrong error")
   125  	}
   126  
   127  	DiscardedSamples.WithLabelValues("random reason", "different user").Inc()
   128  
   129  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   130  			# HELP cortex_discarded_samples_total The total number of samples that were discarded.
   131  			# TYPE cortex_discarded_samples_total counter
   132  			cortex_discarded_samples_total{reason="label_invalid",user="testUser"} 1
   133  			cortex_discarded_samples_total{reason="label_name_too_long",user="testUser"} 1
   134  			cortex_discarded_samples_total{reason="label_value_too_long",user="testUser"} 1
   135  			cortex_discarded_samples_total{reason="max_label_names_per_series",user="testUser"} 1
   136  			cortex_discarded_samples_total{reason="metric_name_invalid",user="testUser"} 1
   137  			cortex_discarded_samples_total{reason="missing_metric_name",user="testUser"} 1
   138  
   139  			cortex_discarded_samples_total{reason="random reason",user="different user"} 1
   140  	`), "cortex_discarded_samples_total"))
   141  
   142  	DeletePerUserValidationMetrics(userID, util_log.Logger)
   143  
   144  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   145  			# HELP cortex_discarded_samples_total The total number of samples that were discarded.
   146  			# TYPE cortex_discarded_samples_total counter
   147  			cortex_discarded_samples_total{reason="random reason",user="different user"} 1
   148  	`), "cortex_discarded_samples_total"))
   149  }
   150  
   151  func TestValidateExemplars(t *testing.T) {
   152  	userID := "testUser"
   153  
   154  	invalidExemplars := []cortexpb.Exemplar{
   155  		{
   156  			// Missing labels
   157  			Labels: nil,
   158  		},
   159  		{
   160  			// Invalid timestamp
   161  			Labels: []cortexpb.LabelAdapter{{Name: "foo", Value: "bar"}},
   162  		},
   163  		{
   164  			// Combined labelset too long
   165  			Labels:      []cortexpb.LabelAdapter{{Name: "foo", Value: strings.Repeat("0", 126)}},
   166  			TimestampMs: 1000,
   167  		},
   168  	}
   169  
   170  	for _, ie := range invalidExemplars {
   171  		err := ValidateExemplar(userID, []cortexpb.LabelAdapter{}, ie)
   172  		assert.NotNil(t, err)
   173  	}
   174  
   175  	DiscardedExemplars.WithLabelValues("random reason", "different user").Inc()
   176  
   177  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   178  			# HELP cortex_discarded_exemplars_total The total number of exemplars that were discarded.
   179  			# TYPE cortex_discarded_exemplars_total counter
   180  			cortex_discarded_exemplars_total{reason="exemplar_labels_missing",user="testUser"} 1
   181  			cortex_discarded_exemplars_total{reason="exemplar_labels_too_long",user="testUser"} 1
   182  			cortex_discarded_exemplars_total{reason="exemplar_timestamp_invalid",user="testUser"} 1
   183  
   184  			cortex_discarded_exemplars_total{reason="random reason",user="different user"} 1
   185  		`), "cortex_discarded_exemplars_total"))
   186  
   187  	// Delete test user and verify only different remaining
   188  	DeletePerUserValidationMetrics(userID, util_log.Logger)
   189  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   190  			# HELP cortex_discarded_exemplars_total The total number of exemplars that were discarded.
   191  			# TYPE cortex_discarded_exemplars_total counter
   192  			cortex_discarded_exemplars_total{reason="random reason",user="different user"} 1
   193  	`), "cortex_discarded_exemplars_total"))
   194  }
   195  
   196  func TestValidateMetadata(t *testing.T) {
   197  	userID := "testUser"
   198  	var cfg validateMetadataCfg
   199  	cfg.enforceMetadataMetricName = true
   200  	cfg.maxMetadataLength = 22
   201  
   202  	for _, c := range []struct {
   203  		desc     string
   204  		metadata *cortexpb.MetricMetadata
   205  		err      error
   206  	}{
   207  		{
   208  			"with a valid config",
   209  			&cortexpb.MetricMetadata{MetricFamilyName: "go_goroutines", Type: cortexpb.COUNTER, Help: "Number of goroutines.", Unit: ""},
   210  			nil,
   211  		},
   212  		{
   213  			"with no metric name",
   214  			&cortexpb.MetricMetadata{MetricFamilyName: "", Type: cortexpb.COUNTER, Help: "Number of goroutines.", Unit: ""},
   215  			httpgrpc.Errorf(http.StatusBadRequest, "metadata missing metric name"),
   216  		},
   217  		{
   218  			"with a long metric name",
   219  			&cortexpb.MetricMetadata{MetricFamilyName: "go_goroutines_and_routines_and_routines", Type: cortexpb.COUNTER, Help: "Number of goroutines.", Unit: ""},
   220  			httpgrpc.Errorf(http.StatusBadRequest, "metadata 'METRIC_NAME' value too long: \"go_goroutines_and_routines_and_routines\" metric \"go_goroutines_and_routines_and_routines\""),
   221  		},
   222  		{
   223  			"with a long help",
   224  			&cortexpb.MetricMetadata{MetricFamilyName: "go_goroutines", Type: cortexpb.COUNTER, Help: "Number of goroutines that currently exist.", Unit: ""},
   225  			httpgrpc.Errorf(http.StatusBadRequest, "metadata 'HELP' value too long: \"Number of goroutines that currently exist.\" metric \"go_goroutines\""),
   226  		},
   227  		{
   228  			"with a long unit",
   229  			&cortexpb.MetricMetadata{MetricFamilyName: "go_goroutines", Type: cortexpb.COUNTER, Help: "Number of goroutines.", Unit: "a_made_up_unit_that_is_really_long"},
   230  			httpgrpc.Errorf(http.StatusBadRequest, "metadata 'UNIT' value too long: \"a_made_up_unit_that_is_really_long\" metric \"go_goroutines\""),
   231  		},
   232  	} {
   233  		t.Run(c.desc, func(t *testing.T) {
   234  			err := ValidateMetadata(cfg, userID, c.metadata)
   235  			assert.Equal(t, c.err, err, "wrong error")
   236  		})
   237  	}
   238  
   239  	DiscardedMetadata.WithLabelValues("random reason", "different user").Inc()
   240  
   241  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   242  			# HELP cortex_discarded_metadata_total The total number of metadata that were discarded.
   243  			# TYPE cortex_discarded_metadata_total counter
   244  			cortex_discarded_metadata_total{reason="help_too_long",user="testUser"} 1
   245  			cortex_discarded_metadata_total{reason="metric_name_too_long",user="testUser"} 1
   246  			cortex_discarded_metadata_total{reason="missing_metric_name",user="testUser"} 1
   247  			cortex_discarded_metadata_total{reason="unit_too_long",user="testUser"} 1
   248  
   249  			cortex_discarded_metadata_total{reason="random reason",user="different user"} 1
   250  	`), "cortex_discarded_metadata_total"))
   251  
   252  	DeletePerUserValidationMetrics(userID, util_log.Logger)
   253  
   254  	require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(`
   255  			# HELP cortex_discarded_metadata_total The total number of metadata that were discarded.
   256  			# TYPE cortex_discarded_metadata_total counter
   257  			cortex_discarded_metadata_total{reason="random reason",user="different user"} 1
   258  	`), "cortex_discarded_metadata_total"))
   259  }
   260  
   261  func TestValidateLabelOrder(t *testing.T) {
   262  	var cfg validateLabelsCfg
   263  	cfg.maxLabelNameLength = 10
   264  	cfg.maxLabelNamesPerSeries = 10
   265  	cfg.maxLabelValueLength = 10
   266  
   267  	userID := "testUser"
   268  
   269  	actual := ValidateLabels(cfg, userID, []cortexpb.LabelAdapter{
   270  		{Name: model.MetricNameLabel, Value: "m"},
   271  		{Name: "b", Value: "b"},
   272  		{Name: "a", Value: "a"},
   273  	}, false)
   274  	expected := newLabelsNotSortedError([]cortexpb.LabelAdapter{
   275  		{Name: model.MetricNameLabel, Value: "m"},
   276  		{Name: "b", Value: "b"},
   277  		{Name: "a", Value: "a"},
   278  	}, "a")
   279  	assert.Equal(t, expected, actual)
   280  }
   281  
   282  func TestValidateLabelDuplication(t *testing.T) {
   283  	var cfg validateLabelsCfg
   284  	cfg.maxLabelNameLength = 10
   285  	cfg.maxLabelNamesPerSeries = 10
   286  	cfg.maxLabelValueLength = 10
   287  
   288  	userID := "testUser"
   289  
   290  	actual := ValidateLabels(cfg, userID, []cortexpb.LabelAdapter{
   291  		{Name: model.MetricNameLabel, Value: "a"},
   292  		{Name: model.MetricNameLabel, Value: "b"},
   293  	}, false)
   294  	expected := newDuplicatedLabelError([]cortexpb.LabelAdapter{
   295  		{Name: model.MetricNameLabel, Value: "a"},
   296  		{Name: model.MetricNameLabel, Value: "b"},
   297  	}, model.MetricNameLabel)
   298  	assert.Equal(t, expected, actual)
   299  
   300  	actual = ValidateLabels(cfg, userID, []cortexpb.LabelAdapter{
   301  		{Name: model.MetricNameLabel, Value: "a"},
   302  		{Name: "a", Value: "a"},
   303  		{Name: "a", Value: "a"},
   304  	}, false)
   305  	expected = newDuplicatedLabelError([]cortexpb.LabelAdapter{
   306  		{Name: model.MetricNameLabel, Value: "a"},
   307  		{Name: "a", Value: "a"},
   308  		{Name: "a", Value: "a"},
   309  	}, "a")
   310  	assert.Equal(t, expected, actual)
   311  }