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 }