mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-07 06:37:17 +02:00
Merge 2d3be80d54
into 457a2381f9
This commit is contained in:
commit
8d0d51dce4
@ -651,7 +651,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse rule files to verify they exist and contain valid rules.
|
// Parse rule files to verify they exist and contain valid rules.
|
||||||
if err := rules.ParseFiles(cfgFile.RuleFiles); err != nil {
|
if err := rules.ParseFiles(cfgFile.RuleFiles, cfgFile.GlobalConfig.MetricNameValidationScheme); err != nil {
|
||||||
absPath, pathErr := filepath.Abs(cfg.configFile)
|
absPath, pathErr := filepath.Abs(cfg.configFile)
|
||||||
if pathErr != nil {
|
if pathErr != nil {
|
||||||
absPath = cfg.configFile
|
absPath = cfg.configFile
|
||||||
@ -790,7 +790,7 @@ func main() {
|
|||||||
ctxWeb, cancelWeb = context.WithCancel(context.Background())
|
ctxWeb, cancelWeb = context.WithCancel(context.Background())
|
||||||
ctxRule = context.Background()
|
ctxRule = context.Background()
|
||||||
|
|
||||||
notifierManager = notifier.NewManager(&cfg.notifier, logger.With("component", "notifier"))
|
notifierManager = notifier.NewManager(&cfg.notifier, cfgFile.GlobalConfig.MetricNameValidationScheme, logger.With("component", "notifier"))
|
||||||
|
|
||||||
ctxScrape, cancelScrape = context.WithCancel(context.Background())
|
ctxScrape, cancelScrape = context.WithCancel(context.Background())
|
||||||
ctxNotify, cancelNotify = context.WithCancel(context.Background())
|
ctxNotify, cancelNotify = context.WithCancel(context.Background())
|
||||||
@ -867,6 +867,7 @@ func main() {
|
|||||||
queryEngine = promql.NewEngine(opts)
|
queryEngine = promql.NewEngine(opts)
|
||||||
|
|
||||||
ruleManager = rules.NewManager(&rules.ManagerOptions{
|
ruleManager = rules.NewManager(&rules.ManagerOptions{
|
||||||
|
NameValidationScheme: cfgFile.GlobalConfig.MetricNameValidationScheme,
|
||||||
Appendable: fanoutStorage,
|
Appendable: fanoutStorage,
|
||||||
Queryable: localStorage,
|
Queryable: localStorage,
|
||||||
QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage),
|
QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage),
|
||||||
|
@ -359,7 +359,7 @@ func main() {
|
|||||||
os.Exit(CheckSD(*sdConfigFile, *sdJobName, *sdTimeout, prometheus.DefaultRegisterer))
|
os.Exit(CheckSD(*sdConfigFile, *sdJobName, *sdTimeout, prometheus.DefaultRegisterer))
|
||||||
|
|
||||||
case checkConfigCmd.FullCommand():
|
case checkConfigCmd.FullCommand():
|
||||||
os.Exit(CheckConfig(*agentMode, *checkConfigSyntaxOnly, newConfigLintConfig(*checkConfigLint, *checkConfigLintFatal, *checkConfigIgnoreUnknownFields, model.Duration(*checkLookbackDelta)), *configFiles...))
|
os.Exit(CheckConfig(*agentMode, *checkConfigSyntaxOnly, newConfigLintConfig(*checkConfigLint, *checkConfigLintFatal, *checkConfigIgnoreUnknownFields, model.UTF8Validation, model.Duration(*checkLookbackDelta)), *configFiles...))
|
||||||
|
|
||||||
case checkServerHealthCmd.FullCommand():
|
case checkServerHealthCmd.FullCommand():
|
||||||
os.Exit(checkErr(CheckServerStatus(serverURL, checkHealth, httpRoundTripper)))
|
os.Exit(checkErr(CheckServerStatus(serverURL, checkHealth, httpRoundTripper)))
|
||||||
@ -371,7 +371,7 @@ func main() {
|
|||||||
os.Exit(CheckWebConfig(*webConfigFiles...))
|
os.Exit(CheckWebConfig(*webConfigFiles...))
|
||||||
|
|
||||||
case checkRulesCmd.FullCommand():
|
case checkRulesCmd.FullCommand():
|
||||||
os.Exit(CheckRules(newRulesLintConfig(*checkRulesLint, *checkRulesLintFatal, *checkRulesIgnoreUnknownFields), *ruleFiles...))
|
os.Exit(CheckRules(newRulesLintConfig(*checkRulesLint, *checkRulesLintFatal, *checkRulesIgnoreUnknownFields, model.UTF8Validation), *ruleFiles...))
|
||||||
|
|
||||||
case checkMetricsCmd.FullCommand():
|
case checkMetricsCmd.FullCommand():
|
||||||
os.Exit(CheckMetrics(*checkMetricsExtended))
|
os.Exit(CheckMetrics(*checkMetricsExtended))
|
||||||
@ -436,7 +436,7 @@ func main() {
|
|||||||
os.Exit(backfillOpenMetrics(*importFilePath, *importDBPath, *importHumanReadable, *importQuiet, *maxBlockDuration, *openMetricsLabels))
|
os.Exit(backfillOpenMetrics(*importFilePath, *importDBPath, *importHumanReadable, *importQuiet, *maxBlockDuration, *openMetricsLabels))
|
||||||
|
|
||||||
case importRulesCmd.FullCommand():
|
case importRulesCmd.FullCommand():
|
||||||
os.Exit(checkErr(importRules(serverURL, httpRoundTripper, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...)))
|
os.Exit(checkErr(importRules(serverURL, httpRoundTripper, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, model.UTF8Validation, *importRulesFiles...)))
|
||||||
|
|
||||||
case queryAnalyzeCmd.FullCommand():
|
case queryAnalyzeCmd.FullCommand():
|
||||||
os.Exit(checkErr(queryAnalyzeCfg.run(serverURL, httpRoundTripper)))
|
os.Exit(checkErr(queryAnalyzeCfg.run(serverURL, httpRoundTripper)))
|
||||||
@ -468,17 +468,19 @@ func checkExperimental(f bool) {
|
|||||||
var errLint = errors.New("lint error")
|
var errLint = errors.New("lint error")
|
||||||
|
|
||||||
type rulesLintConfig struct {
|
type rulesLintConfig struct {
|
||||||
all bool
|
all bool
|
||||||
duplicateRules bool
|
duplicateRules bool
|
||||||
fatal bool
|
fatal bool
|
||||||
ignoreUnknownFields bool
|
ignoreUnknownFields bool
|
||||||
|
nameValidationScheme model.ValidationScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRulesLintConfig(stringVal string, fatal, ignoreUnknownFields bool) rulesLintConfig {
|
func newRulesLintConfig(stringVal string, fatal, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) rulesLintConfig {
|
||||||
items := strings.Split(stringVal, ",")
|
items := strings.Split(stringVal, ",")
|
||||||
ls := rulesLintConfig{
|
ls := rulesLintConfig{
|
||||||
fatal: fatal,
|
fatal: fatal,
|
||||||
ignoreUnknownFields: ignoreUnknownFields,
|
ignoreUnknownFields: ignoreUnknownFields,
|
||||||
|
nameValidationScheme: nameValidationScheme,
|
||||||
}
|
}
|
||||||
for _, setting := range items {
|
for _, setting := range items {
|
||||||
switch setting {
|
switch setting {
|
||||||
@ -504,7 +506,7 @@ type configLintConfig struct {
|
|||||||
lookbackDelta model.Duration
|
lookbackDelta model.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigLintConfig(optionsStr string, fatal, ignoreUnknownFields bool, lookbackDelta model.Duration) configLintConfig {
|
func newConfigLintConfig(optionsStr string, fatal, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme, lookbackDelta model.Duration) configLintConfig {
|
||||||
c := configLintConfig{
|
c := configLintConfig{
|
||||||
rulesLintConfig: rulesLintConfig{
|
rulesLintConfig: rulesLintConfig{
|
||||||
fatal: fatal,
|
fatal: fatal,
|
||||||
@ -533,7 +535,7 @@ func newConfigLintConfig(optionsStr string, fatal, ignoreUnknownFields bool, loo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rulesOptions) > 0 {
|
if len(rulesOptions) > 0 {
|
||||||
c.rulesLintConfig = newRulesLintConfig(strings.Join(rulesOptions, ","), fatal, ignoreUnknownFields)
|
c.rulesLintConfig = newRulesLintConfig(strings.Join(rulesOptions, ","), fatal, ignoreUnknownFields, nameValidationScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
@ -854,7 +856,7 @@ func checkRulesFromStdin(ls rulesLintConfig) (bool, bool) {
|
|||||||
fmt.Fprintln(os.Stderr, " FAILED:", err)
|
fmt.Fprintln(os.Stderr, " FAILED:", err)
|
||||||
return true, true
|
return true, true
|
||||||
}
|
}
|
||||||
rgs, errs := rulefmt.Parse(data, ls.ignoreUnknownFields)
|
rgs, errs := rulefmt.Parse(data, ls.ignoreUnknownFields, ls.nameValidationScheme)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
failed = true
|
failed = true
|
||||||
fmt.Fprintln(os.Stderr, " FAILED:")
|
fmt.Fprintln(os.Stderr, " FAILED:")
|
||||||
@ -888,7 +890,7 @@ func checkRules(files []string, ls rulesLintConfig) (bool, bool) {
|
|||||||
hasErrors := false
|
hasErrors := false
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
fmt.Println("Checking", f)
|
fmt.Println("Checking", f)
|
||||||
rgs, errs := rulefmt.ParseFile(f, ls.ignoreUnknownFields)
|
rgs, errs := rulefmt.ParseFile(f, ls.ignoreUnknownFields, ls.nameValidationScheme)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
failed = true
|
failed = true
|
||||||
fmt.Fprintln(os.Stderr, " FAILED:")
|
fmt.Fprintln(os.Stderr, " FAILED:")
|
||||||
@ -1225,7 +1227,7 @@ func (j *jsonPrinter) printLabelValues(v model.LabelValues) {
|
|||||||
|
|
||||||
// importRules backfills recording rules from the files provided. The output are blocks of data
|
// importRules backfills recording rules from the files provided. The output are blocks of data
|
||||||
// at the outputDir location.
|
// at the outputDir location.
|
||||||
func importRules(url *url.URL, roundTripper http.RoundTripper, start, end, outputDir string, evalInterval, maxBlockDuration time.Duration, files ...string) error {
|
func importRules(url *url.URL, roundTripper http.RoundTripper, start, end, outputDir string, evalInterval, maxBlockDuration time.Duration, nameValidationScheme model.ValidationScheme, files ...string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
var stime, etime time.Time
|
var stime, etime time.Time
|
||||||
var err error
|
var err error
|
||||||
@ -1248,11 +1250,12 @@ func importRules(url *url.URL, roundTripper http.RoundTripper, start, end, outpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfg := ruleImporterConfig{
|
cfg := ruleImporterConfig{
|
||||||
outputDir: outputDir,
|
outputDir: outputDir,
|
||||||
start: stime,
|
start: stime,
|
||||||
end: etime,
|
end: etime,
|
||||||
evalInterval: evalInterval,
|
evalInterval: evalInterval,
|
||||||
maxBlockDuration: maxBlockDuration,
|
maxBlockDuration: maxBlockDuration,
|
||||||
|
nameValidationScheme: nameValidationScheme,
|
||||||
}
|
}
|
||||||
api, err := newAPI(url, roundTripper, nil)
|
api, err := newAPI(url, roundTripper, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -186,7 +186,7 @@ func TestCheckDuplicates(t *testing.T) {
|
|||||||
c := test
|
c := test
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rgs, err := rulefmt.ParseFile(c.ruleFile, false)
|
rgs, err := rulefmt.ParseFile(c.ruleFile, false, model.UTF8Validation)
|
||||||
require.Empty(t, err)
|
require.Empty(t, err)
|
||||||
dups := checkDuplicates(rgs.Groups)
|
dups := checkDuplicates(rgs.Groups)
|
||||||
require.Equal(t, c.expectedDups, dups)
|
require.Equal(t, c.expectedDups, dups)
|
||||||
@ -195,7 +195,7 @@ func TestCheckDuplicates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkCheckDuplicates(b *testing.B) {
|
func BenchmarkCheckDuplicates(b *testing.B) {
|
||||||
rgs, err := rulefmt.ParseFile("./testdata/rules_large.yml", false)
|
rgs, err := rulefmt.ParseFile("./testdata/rules_large.yml", false, model.UTF8Validation)
|
||||||
require.Empty(b, err)
|
require.Empty(b, err)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
@ -509,7 +509,7 @@ func TestCheckRules(t *testing.T) {
|
|||||||
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
||||||
os.Stdin = r
|
os.Stdin = r
|
||||||
|
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false))
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false, model.UTF8Validation))
|
||||||
require.Equal(t, successExitCode, exitCode)
|
require.Equal(t, successExitCode, exitCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -531,7 +531,7 @@ func TestCheckRules(t *testing.T) {
|
|||||||
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
||||||
os.Stdin = r
|
os.Stdin = r
|
||||||
|
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false))
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false, model.UTF8Validation))
|
||||||
require.Equal(t, failureExitCode, exitCode)
|
require.Equal(t, failureExitCode, exitCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -553,7 +553,7 @@ func TestCheckRules(t *testing.T) {
|
|||||||
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
defer func(v *os.File) { os.Stdin = v }(os.Stdin)
|
||||||
os.Stdin = r
|
os.Stdin = r
|
||||||
|
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, true, false))
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, true, false, model.UTF8Validation))
|
||||||
require.Equal(t, lintErrExitCode, exitCode)
|
require.Equal(t, lintErrExitCode, exitCode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -571,19 +571,19 @@ func TestCheckRulesWithFeatureFlag(t *testing.T) {
|
|||||||
func TestCheckRulesWithRuleFiles(t *testing.T) {
|
func TestCheckRulesWithRuleFiles(t *testing.T) {
|
||||||
t.Run("rules-good", func(t *testing.T) {
|
t.Run("rules-good", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false), "./testdata/rules.yml")
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false, model.UTF8Validation), "./testdata/rules.yml")
|
||||||
require.Equal(t, successExitCode, exitCode)
|
require.Equal(t, successExitCode, exitCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("rules-bad", func(t *testing.T) {
|
t.Run("rules-bad", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false), "./testdata/rules-bad.yml")
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, false, false, model.UTF8Validation), "./testdata/rules-bad.yml")
|
||||||
require.Equal(t, failureExitCode, exitCode)
|
require.Equal(t, failureExitCode, exitCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("rules-lint-fatal", func(t *testing.T) {
|
t.Run("rules-lint-fatal", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, true, false), "./testdata/prometheus-rules.lint.yml")
|
exitCode := CheckRules(newRulesLintConfig(lintOptionDuplicateRules, true, false, model.UTF8Validation), "./testdata/prometheus-rules.lint.yml")
|
||||||
require.Equal(t, lintErrExitCode, exitCode)
|
require.Equal(t, lintErrExitCode, exitCode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -612,20 +612,20 @@ func TestCheckScrapeConfigs(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
// Non-fatal linting.
|
// Non-fatal linting.
|
||||||
code := CheckConfig(false, false, newConfigLintConfig(lintOptionTooLongScrapeInterval, false, false, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
code := CheckConfig(false, false, newConfigLintConfig(lintOptionTooLongScrapeInterval, false, false, model.UTF8Validation, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
||||||
require.Equal(t, successExitCode, code, "Non-fatal linting should return success")
|
require.Equal(t, successExitCode, code, "Non-fatal linting should return success")
|
||||||
// Fatal linting.
|
// Fatal linting.
|
||||||
code = CheckConfig(false, false, newConfigLintConfig(lintOptionTooLongScrapeInterval, true, false, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
code = CheckConfig(false, false, newConfigLintConfig(lintOptionTooLongScrapeInterval, true, false, model.UTF8Validation, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
||||||
if tc.expectError {
|
if tc.expectError {
|
||||||
require.Equal(t, lintErrExitCode, code, "Fatal linting should return error")
|
require.Equal(t, lintErrExitCode, code, "Fatal linting should return error")
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, successExitCode, code, "Fatal linting should return success when there are no problems")
|
require.Equal(t, successExitCode, code, "Fatal linting should return success when there are no problems")
|
||||||
}
|
}
|
||||||
// Check syntax only, no linting.
|
// Check syntax only, no linting.
|
||||||
code = CheckConfig(false, true, newConfigLintConfig(lintOptionTooLongScrapeInterval, true, false, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
code = CheckConfig(false, true, newConfigLintConfig(lintOptionTooLongScrapeInterval, true, false, model.UTF8Validation, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
||||||
require.Equal(t, successExitCode, code, "Fatal linting should return success when checking syntax only")
|
require.Equal(t, successExitCode, code, "Fatal linting should return success when checking syntax only")
|
||||||
// Lint option "none" should disable linting.
|
// Lint option "none" should disable linting.
|
||||||
code = CheckConfig(false, false, newConfigLintConfig(lintOptionNone+","+lintOptionTooLongScrapeInterval, true, false, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
code = CheckConfig(false, false, newConfigLintConfig(lintOptionNone+","+lintOptionTooLongScrapeInterval, true, false, model.UTF8Validation, tc.lookbackDelta), "./testdata/prometheus-config.lint.too_long_scrape_interval.yml")
|
||||||
require.Equal(t, successExitCode, code, `Fatal linting should return success when lint option "none" is specified`)
|
require.Equal(t, successExitCode, code, `Fatal linting should return success when lint option "none" is specified`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,12 @@ type ruleImporter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ruleImporterConfig struct {
|
type ruleImporterConfig struct {
|
||||||
outputDir string
|
outputDir string
|
||||||
start time.Time
|
start time.Time
|
||||||
end time.Time
|
end time.Time
|
||||||
evalInterval time.Duration
|
evalInterval time.Duration
|
||||||
maxBlockDuration time.Duration
|
maxBlockDuration time.Duration
|
||||||
|
nameValidationScheme model.ValidationScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRuleImporter creates a new rule importer that can be used to parse and evaluate recording rule files and create new series
|
// newRuleImporter creates a new rule importer that can be used to parse and evaluate recording rule files and create new series
|
||||||
@ -60,10 +61,12 @@ type ruleImporterConfig struct {
|
|||||||
func newRuleImporter(logger *slog.Logger, config ruleImporterConfig, apiClient queryRangeAPI) *ruleImporter {
|
func newRuleImporter(logger *slog.Logger, config ruleImporterConfig, apiClient queryRangeAPI) *ruleImporter {
|
||||||
logger.Info("new rule importer", "component", "backfiller", "start", config.start.Format(time.RFC822), "end", config.end.Format(time.RFC822))
|
logger.Info("new rule importer", "component", "backfiller", "start", config.start.Format(time.RFC822), "end", config.end.Format(time.RFC822))
|
||||||
return &ruleImporter{
|
return &ruleImporter{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
config: config,
|
config: config,
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
ruleManager: rules.NewManager(&rules.ManagerOptions{}),
|
ruleManager: rules.NewManager(&rules.ManagerOptions{
|
||||||
|
NameValidationScheme: config.nameValidationScheme,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,11 +165,12 @@ func TestBackfillRuleIntegration(t *testing.T) {
|
|||||||
func newTestRuleImporter(_ context.Context, start time.Time, tmpDir string, testSamples model.Matrix, maxBlockDuration time.Duration) (*ruleImporter, error) {
|
func newTestRuleImporter(_ context.Context, start time.Time, tmpDir string, testSamples model.Matrix, maxBlockDuration time.Duration) (*ruleImporter, error) {
|
||||||
logger := promslog.NewNopLogger()
|
logger := promslog.NewNopLogger()
|
||||||
cfg := ruleImporterConfig{
|
cfg := ruleImporterConfig{
|
||||||
outputDir: tmpDir,
|
outputDir: tmpDir,
|
||||||
start: start.Add(-10 * time.Hour),
|
start: start.Add(-10 * time.Hour),
|
||||||
end: start.Add(-7 * time.Hour),
|
end: start.Add(-7 * time.Hour),
|
||||||
evalInterval: 60 * time.Second,
|
evalInterval: 60 * time.Second,
|
||||||
maxBlockDuration: maxBlockDuration,
|
maxBlockDuration: maxBlockDuration,
|
||||||
|
nameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRuleImporter(logger, cfg, mockQueryRangeAPI{
|
return newRuleImporter(logger, cfg, mockQueryRangeAPI{
|
||||||
|
@ -42,11 +42,12 @@ func TestSDCheckResult(t *testing.T) {
|
|||||||
ScrapeInterval: model.Duration(1 * time.Minute),
|
ScrapeInterval: model.Duration(1 * time.Minute),
|
||||||
ScrapeTimeout: model.Duration(10 * time.Second),
|
ScrapeTimeout: model.Duration(10 * time.Second),
|
||||||
RelabelConfigs: []*relabel.Config{{
|
RelabelConfigs: []*relabel.Config{{
|
||||||
SourceLabels: model.LabelNames{"foo"},
|
SourceLabels: model.LabelNames{"foo"},
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
TargetLabel: "newfoo",
|
TargetLabel: "newfoo",
|
||||||
Regex: reg,
|
Regex: reg,
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,11 +222,12 @@ func (tg *testGroup) test(testname string, evalInterval time.Duration, groupOrde
|
|||||||
|
|
||||||
// Load the rule files.
|
// Load the rule files.
|
||||||
opts := &rules.ManagerOptions{
|
opts := &rules.ManagerOptions{
|
||||||
QueryFunc: rules.EngineQueryFunc(suite.QueryEngine(), suite.Storage()),
|
QueryFunc: rules.EngineQueryFunc(suite.QueryEngine(), suite.Storage()),
|
||||||
Appendable: suite.Storage(),
|
Appendable: suite.Storage(),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
NotifyFunc: func(_ context.Context, _ string, _ ...*rules.Alert) {},
|
NotifyFunc: func(_ context.Context, _ string, _ ...*rules.Alert) {},
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
m := rules.NewManager(opts)
|
m := rules.NewManager(opts)
|
||||||
groupsMap, ers := m.LoadGroups(time.Duration(tg.Interval), tg.ExternalLabels, tg.ExternalURL, nil, ignoreUnknownFields, ruleFiles...)
|
groupsMap, ers := m.LoadGroups(time.Duration(tg.Interval), tg.ExternalLabels, tg.ExternalURL, nil, ignoreUnknownFields, ruleFiles...)
|
||||||
|
@ -413,6 +413,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
jobNames[scfg.JobName] = struct{}{}
|
jobNames[scfg.JobName] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.AlertingConfig.Validate(c.GlobalConfig.MetricNameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
rwNames := map[string]struct{}{}
|
rwNames := map[string]struct{}{}
|
||||||
for _, rwcfg := range c.RemoteWriteConfigs {
|
for _, rwcfg := range c.RemoteWriteConfigs {
|
||||||
if rwcfg == nil {
|
if rwcfg == nil {
|
||||||
@ -422,6 +426,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
if _, ok := rwNames[rwcfg.Name]; ok && rwcfg.Name != "" {
|
if _, ok := rwNames[rwcfg.Name]; ok && rwcfg.Name != "" {
|
||||||
return fmt.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
|
return fmt.Errorf("found multiple remote write configs with job name %q", rwcfg.Name)
|
||||||
}
|
}
|
||||||
|
if err := rwcfg.Validate(c.GlobalConfig.MetricNameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
rwNames[rwcfg.Name] = struct{}{}
|
rwNames[rwcfg.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
rrNames := map[string]struct{}{}
|
rrNames := map[string]struct{}{}
|
||||||
@ -596,8 +603,14 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch gc.MetricNameValidationScheme {
|
||||||
|
case model.UTF8Validation, model.LegacyValidation:
|
||||||
|
default:
|
||||||
|
gc.MetricNameValidationScheme = DefaultGlobalConfig.MetricNameValidationScheme
|
||||||
|
}
|
||||||
|
|
||||||
if err := gc.ExternalLabels.Validate(func(l labels.Label) error {
|
if err := gc.ExternalLabels.Validate(func(l labels.Label) error {
|
||||||
if !model.LabelName(l.Name).IsValid() {
|
if !gc.MetricNameValidationScheme.IsValidLabelName(l.Name) {
|
||||||
return fmt.Errorf("%q is not a valid label name", l.Name)
|
return fmt.Errorf("%q is not a valid label name", l.Name)
|
||||||
}
|
}
|
||||||
if !model.LabelValue(l.Value).IsValid() {
|
if !model.LabelValue(l.Value).IsValid() {
|
||||||
@ -878,11 +891,9 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch globalConfig.MetricNameValidationScheme {
|
switch globalConfig.MetricNameValidationScheme {
|
||||||
case model.UnsetValidation:
|
|
||||||
globalConfig.MetricNameValidationScheme = model.UTF8Validation
|
|
||||||
case model.LegacyValidation, model.UTF8Validation:
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown global name validation method specified, must be either '', 'legacy' or 'utf8', got %s", globalConfig.MetricNameValidationScheme)
|
return errors.New("global name validation method must be set")
|
||||||
}
|
}
|
||||||
// Scrapeconfig validation scheme matches global if left blank.
|
// Scrapeconfig validation scheme matches global if left blank.
|
||||||
localValidationUnset := false
|
localValidationUnset := false
|
||||||
@ -944,6 +955,17 @@ func (c *ScrapeConfig) Validate(globalConfig GlobalConfig) error {
|
|||||||
c.AlwaysScrapeClassicHistograms = &global
|
c.AlwaysScrapeClassicHistograms = &global
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, rc := range c.RelabelConfigs {
|
||||||
|
if err := rc.Validate(c.MetricNameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rc := range c.MetricRelabelConfigs {
|
||||||
|
if err := rc.Validate(c.MetricNameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1096,6 +1118,20 @@ type AlertingConfig struct {
|
|||||||
AlertmanagerConfigs AlertmanagerConfigs `yaml:"alertmanagers,omitempty"`
|
AlertmanagerConfigs AlertmanagerConfigs `yaml:"alertmanagers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AlertingConfig) Validate(nameValidationScheme model.ValidationScheme) error {
|
||||||
|
for _, rc := range c.AlertRelabelConfigs {
|
||||||
|
if err := rc.Validate(nameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rc := range c.AlertmanagerConfigs {
|
||||||
|
if err := rc.Validate(nameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetDirectory joins any relative file paths with dir.
|
// SetDirectory joins any relative file paths with dir.
|
||||||
func (c *AlertingConfig) SetDirectory(dir string) {
|
func (c *AlertingConfig) SetDirectory(dir string) {
|
||||||
for _, c := range c.AlertmanagerConfigs {
|
for _, c := range c.AlertmanagerConfigs {
|
||||||
@ -1240,6 +1276,20 @@ func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AlertmanagerConfig) Validate(nameValidationScheme model.ValidationScheme) error {
|
||||||
|
for _, rc := range c.AlertRelabelConfigs {
|
||||||
|
if err := rc.Validate(nameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rc := range c.RelabelConfigs {
|
||||||
|
if err := rc.Validate(nameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalYAML implements the yaml.Marshaler interface.
|
// MarshalYAML implements the yaml.Marshaler interface.
|
||||||
func (c *AlertmanagerConfig) MarshalYAML() (interface{}, error) {
|
func (c *AlertmanagerConfig) MarshalYAML() (interface{}, error) {
|
||||||
return discovery.MarshalYAMLWithInlineConfigs(c)
|
return discovery.MarshalYAMLWithInlineConfigs(c)
|
||||||
@ -1377,6 +1427,16 @@ func (c *RemoteWriteConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
|
|||||||
return validateAuthConfigs(c)
|
return validateAuthConfigs(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RemoteWriteConfig) Validate(nameValidationScheme model.ValidationScheme) error {
|
||||||
|
for _, rc := range c.WriteRelabelConfigs {
|
||||||
|
if err := rc.Validate(nameValidationScheme); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateAuthConfigs validates that at most one of basic_auth, authorization, oauth2, sigv4, azuread or google_iam must be configured.
|
// validateAuthConfigs validates that at most one of basic_auth, authorization, oauth2, sigv4, azuread or google_iam must be configured.
|
||||||
func validateAuthConfigs(c *RemoteWriteConfig) error {
|
func validateAuthConfigs(c *RemoteWriteConfig) error {
|
||||||
var authConfigured []string
|
var authConfigured []string
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alecthomas/units"
|
"github.com/alecthomas/units"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/grafana/regexp"
|
"github.com/grafana/regexp"
|
||||||
"github.com/prometheus/common/config"
|
"github.com/prometheus/common/config"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
@ -106,6 +108,7 @@ var expectedConf = &Config{
|
|||||||
ScrapeProtocols: DefaultGlobalConfig.ScrapeProtocols,
|
ScrapeProtocols: DefaultGlobalConfig.ScrapeProtocols,
|
||||||
AlwaysScrapeClassicHistograms: false,
|
AlwaysScrapeClassicHistograms: false,
|
||||||
ConvertClassicHistogramsToNHCB: false,
|
ConvertClassicHistogramsToNHCB: false,
|
||||||
|
MetricNameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
|
|
||||||
Runtime: RuntimeConfig{
|
Runtime: RuntimeConfig{
|
||||||
@ -125,11 +128,12 @@ var expectedConf = &Config{
|
|||||||
Name: "drop_expensive",
|
Name: "drop_expensive",
|
||||||
WriteRelabelConfigs: []*relabel.Config{
|
WriteRelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__name__"},
|
SourceLabels: model.LabelNames{"__name__"},
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.MustNewRegexp("expensive.*"),
|
Regex: relabel.MustNewRegexp("expensive.*"),
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
Action: relabel.Drop,
|
Action: relabel.Drop,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
QueueConfig: DefaultQueueConfig,
|
QueueConfig: DefaultQueueConfig,
|
||||||
@ -279,50 +283,56 @@ var expectedConf = &Config{
|
|||||||
|
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"job", "__meta_dns_name"},
|
SourceLabels: model.LabelNames{"job", "__meta_dns_name"},
|
||||||
TargetLabel: "job",
|
TargetLabel: "job",
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
|
Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
|
||||||
Replacement: "foo-${1}",
|
Replacement: "foo-${1}",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"abc"},
|
SourceLabels: model.LabelNames{"abc"},
|
||||||
TargetLabel: "cde",
|
TargetLabel: "cde",
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TargetLabel: "abc",
|
TargetLabel: "abc",
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: "static",
|
Replacement: "static",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
TargetLabel: "abc",
|
TargetLabel: "abc",
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.MustNewRegexp(""),
|
Regex: relabel.MustNewRegexp(""),
|
||||||
Replacement: "static",
|
Replacement: "static",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"foo"},
|
SourceLabels: model.LabelNames{"foo"},
|
||||||
TargetLabel: "abc",
|
TargetLabel: "abc",
|
||||||
Action: relabel.KeepEqual,
|
Action: relabel.KeepEqual,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Separator: relabel.DefaultRelabelConfig.Separator,
|
Separator: relabel.DefaultRelabelConfig.Separator,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"foo"},
|
SourceLabels: model.LabelNames{"foo"},
|
||||||
TargetLabel: "abc",
|
TargetLabel: "abc",
|
||||||
Action: relabel.DropEqual,
|
Action: relabel.DropEqual,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Separator: relabel.DefaultRelabelConfig.Separator,
|
Separator: relabel.DefaultRelabelConfig.Separator,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -377,54 +387,61 @@ var expectedConf = &Config{
|
|||||||
|
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"job"},
|
SourceLabels: model.LabelNames{"job"},
|
||||||
Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
|
Regex: relabel.MustNewRegexp("(.*)some-[regex]"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.Drop,
|
Action: relabel.Drop,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__address__"},
|
SourceLabels: model.LabelNames{"__address__"},
|
||||||
TargetLabel: "__tmp_hash",
|
TargetLabel: "__tmp_hash",
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Modulus: 8,
|
Modulus: 8,
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Action: relabel.HashMod,
|
Action: relabel.HashMod,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__tmp_hash"},
|
SourceLabels: model.LabelNames{"__tmp_hash"},
|
||||||
Regex: relabel.MustNewRegexp("1"),
|
Regex: relabel.MustNewRegexp("1"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.Keep,
|
Action: relabel.Keep,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Regex: relabel.MustNewRegexp("1"),
|
Regex: relabel.MustNewRegexp("1"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.LabelMap,
|
Action: relabel.LabelMap,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Regex: relabel.MustNewRegexp("d"),
|
Regex: relabel.MustNewRegexp("d"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.LabelDrop,
|
Action: relabel.LabelDrop,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Regex: relabel.MustNewRegexp("k"),
|
Regex: relabel.MustNewRegexp("k"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.LabelKeep,
|
Action: relabel.LabelKeep,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MetricRelabelConfigs: []*relabel.Config{
|
MetricRelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__name__"},
|
SourceLabels: model.LabelNames{"__name__"},
|
||||||
Regex: relabel.MustNewRegexp("expensive_metric.*"),
|
Regex: relabel.MustNewRegexp("expensive_metric.*"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Action: relabel.Drop,
|
Action: relabel.Drop,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -479,12 +496,13 @@ var expectedConf = &Config{
|
|||||||
|
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__meta_sd_consul_tags"},
|
SourceLabels: model.LabelNames{"__meta_sd_consul_tags"},
|
||||||
Regex: relabel.MustNewRegexp("label:([^=]+)=([^,]+)"),
|
Regex: relabel.MustNewRegexp("label:([^=]+)=([^,]+)"),
|
||||||
Separator: ",",
|
Separator: ",",
|
||||||
TargetLabel: "${1}",
|
TargetLabel: "${1}",
|
||||||
Replacement: "${2}",
|
Replacement: "${2}",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1276,12 +1294,13 @@ var expectedConf = &Config{
|
|||||||
|
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Action: relabel.Uppercase,
|
Action: relabel.Uppercase,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Separator: relabel.DefaultRelabelConfig.Separator,
|
Separator: relabel.DefaultRelabelConfig.Separator,
|
||||||
SourceLabels: model.LabelNames{"instance"},
|
SourceLabels: model.LabelNames{"instance"},
|
||||||
TargetLabel: "instance",
|
TargetLabel: "instance",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1953,7 +1972,14 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
c, err := LoadFile("testdata/conf.good.yml", false, promslog.NewNopLogger())
|
c, err := LoadFile("testdata/conf.good.yml", false, promslog.NewNopLogger())
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedConf, c)
|
testutil.RequireEqualWithOptions(t, expectedConf, c, []cmp.Option{
|
||||||
|
cmpopts.IgnoreUnexported(config.ProxyConfig{}),
|
||||||
|
cmpopts.IgnoreUnexported(ionos.SDConfig{}),
|
||||||
|
cmpopts.IgnoreUnexported(stackit.SDConfig{}),
|
||||||
|
cmpopts.IgnoreUnexported(regexp.Regexp{}),
|
||||||
|
cmpopts.IgnoreUnexported(hetzner.SDConfig{}),
|
||||||
|
cmpopts.IgnoreUnexported(Config{}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScrapeIntervalLarger(t *testing.T) {
|
func TestScrapeIntervalLarger(t *testing.T) {
|
||||||
|
2
go.mod
2
go.mod
@ -50,7 +50,7 @@ require (
|
|||||||
github.com/prometheus/alertmanager v0.28.1
|
github.com/prometheus/alertmanager v0.28.1
|
||||||
github.com/prometheus/client_golang v1.23.0-rc.1
|
github.com/prometheus/client_golang v1.23.0-rc.1
|
||||||
github.com/prometheus/client_model v0.6.2
|
github.com/prometheus/client_model v0.6.2
|
||||||
github.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3
|
github.com/prometheus/common v0.65.1-0.20250801071412-c79a891c6c28
|
||||||
github.com/prometheus/common/assets v0.2.0
|
github.com/prometheus/common/assets v0.2.0
|
||||||
github.com/prometheus/exporter-toolkit v0.14.0
|
github.com/prometheus/exporter-toolkit v0.14.0
|
||||||
github.com/prometheus/sigv4 v0.2.0
|
github.com/prometheus/sigv4 v0.2.0
|
||||||
|
4
go.sum
4
go.sum
@ -454,8 +454,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
|||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
github.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3 h1:R/zO7ombSHCI8bjQusgCMSL+cE669w5/R2upq5WlPD0=
|
github.com/prometheus/common v0.65.1-0.20250801071412-c79a891c6c28 h1:9CaJtf5ZS3GQVCVoslEkJcKSVwiD9aTqwgMpG1n9zQw=
|
||||||
github.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
github.com/prometheus/common v0.65.1-0.20250801071412-c79a891c6c28/go.mod h1:LL3lcZII3UXGO4InbF+BTSsiAAPUBnwFVbp4gBWIMqw=
|
||||||
github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=
|
github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=
|
||||||
github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
|
github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
|
||||||
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
|
github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg=
|
||||||
|
@ -53,7 +53,7 @@ func (ls Labels) String() string {
|
|||||||
b.WriteByte(',')
|
b.WriteByte(',')
|
||||||
b.WriteByte(' ')
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
if !model.LabelName(l.Name).IsValidLegacy() {
|
if !model.LegacyValidation.IsValidLabelName(l.Name) {
|
||||||
b.Write(strconv.AppendQuote(b.AvailableBuffer(), l.Name))
|
b.Write(strconv.AppendQuote(b.AvailableBuffer(), l.Name))
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(l.Name)
|
b.WriteString(l.Name)
|
||||||
@ -106,18 +106,11 @@ func (ls Labels) IsValid(validationScheme model.ValidationScheme) bool {
|
|||||||
if l.Name == model.MetricNameLabel {
|
if l.Name == model.MetricNameLabel {
|
||||||
// If the default validation scheme has been overridden with legacy mode,
|
// If the default validation scheme has been overridden with legacy mode,
|
||||||
// we need to call the special legacy validation checker.
|
// we need to call the special legacy validation checker.
|
||||||
if validationScheme == model.LegacyValidation && !model.IsValidLegacyMetricName(string(model.LabelValue(l.Value))) {
|
if !validationScheme.IsValidMetricName(l.Value) {
|
||||||
return strconv.ErrSyntax
|
|
||||||
}
|
|
||||||
if !model.IsValidMetricName(model.LabelValue(l.Value)) {
|
|
||||||
return strconv.ErrSyntax
|
return strconv.ErrSyntax
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if validationScheme == model.LegacyValidation {
|
if !validationScheme.IsValidLabelName(l.Name) || !model.LabelValue(l.Value).IsValid() {
|
||||||
if !model.LabelName(l.Name).IsValidLegacy() || !model.LabelValue(l.Value).IsValid() {
|
|
||||||
return strconv.ErrSyntax
|
|
||||||
}
|
|
||||||
} else if !model.LabelName(l.Name).IsValid() || !model.LabelValue(l.Value).IsValid() {
|
|
||||||
return strconv.ErrSyntax
|
return strconv.ErrSyntax
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -100,6 +100,8 @@ type Config struct {
|
|||||||
Replacement string `yaml:"replacement,omitempty" json:"replacement,omitempty"`
|
Replacement string `yaml:"replacement,omitempty" json:"replacement,omitempty"`
|
||||||
// Action is the action to be performed for the relabeling.
|
// Action is the action to be performed for the relabeling.
|
||||||
Action Action `yaml:"action,omitempty" json:"action,omitempty"`
|
Action Action `yaml:"action,omitempty" json:"action,omitempty"`
|
||||||
|
// NameValidationScheme to use when validating labels.
|
||||||
|
NameValidationScheme model.ValidationScheme `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||||
@ -112,10 +114,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||||||
if c.Regex.Regexp == nil {
|
if c.Regex.Regexp == nil {
|
||||||
c.Regex = MustNewRegexp("")
|
c.Regex = MustNewRegexp("")
|
||||||
}
|
}
|
||||||
return c.Validate()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate(nameValidationScheme model.ValidationScheme) error {
|
||||||
if c.Action == "" {
|
if c.Action == "" {
|
||||||
return errors.New("relabel action cannot be empty")
|
return errors.New("relabel action cannot be empty")
|
||||||
}
|
}
|
||||||
@ -125,7 +127,17 @@ func (c *Config) Validate() error {
|
|||||||
if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
|
if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
|
||||||
return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
|
return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
|
||||||
}
|
}
|
||||||
if c.Action == Replace && !varInRegexTemplate(c.TargetLabel) && !model.LabelName(c.TargetLabel).IsValid() {
|
|
||||||
|
// Relabel config validation scheme matches global if left blank.
|
||||||
|
switch c.NameValidationScheme {
|
||||||
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
|
case model.UnsetValidation:
|
||||||
|
c.NameValidationScheme = nameValidationScheme
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown relabel config name validation method specified, must be either '', 'legacy' or 'utf8', got %s", c.NameValidationScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Action == Replace && !varInRegexTemplate(c.TargetLabel) && !c.NameValidationScheme.IsValidLabelName(c.TargetLabel) {
|
||||||
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,12 +145,12 @@ func (c *Config) Validate() error {
|
|||||||
// UTF-8 allows ${} characters, so standard validation allow $variables by default.
|
// UTF-8 allows ${} characters, so standard validation allow $variables by default.
|
||||||
// TODO(bwplotka): Relabelling users cannot put $ and ${<...>} characters in metric names or values.
|
// TODO(bwplotka): Relabelling users cannot put $ and ${<...>} characters in metric names or values.
|
||||||
// Design escaping mechanism to allow that, once valid use case appears.
|
// Design escaping mechanism to allow that, once valid use case appears.
|
||||||
return model.LabelName(value).IsValid()
|
return c.NameValidationScheme.IsValidLabelName(value)
|
||||||
}
|
}
|
||||||
if c.Action == Replace && varInRegexTemplate(c.TargetLabel) && !isValidLabelNameWithRegexVarFn(c.TargetLabel) {
|
if c.Action == Replace && varInRegexTemplate(c.TargetLabel) && !isValidLabelNameWithRegexVarFn(c.TargetLabel) {
|
||||||
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
||||||
}
|
}
|
||||||
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !model.LabelName(c.TargetLabel).IsValid() {
|
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !c.NameValidationScheme.IsValidLabelName(c.TargetLabel) {
|
||||||
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
||||||
}
|
}
|
||||||
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
|
if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
|
||||||
@ -147,7 +159,7 @@ func (c *Config) Validate() error {
|
|||||||
if c.Action == LabelMap && !isValidLabelNameWithRegexVarFn(c.Replacement) {
|
if c.Action == LabelMap && !isValidLabelNameWithRegexVarFn(c.Replacement) {
|
||||||
return fmt.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action)
|
return fmt.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action)
|
||||||
}
|
}
|
||||||
if c.Action == HashMod && !model.LabelName(c.TargetLabel).IsValid() {
|
if c.Action == HashMod && !c.NameValidationScheme.IsValidLabelName(c.TargetLabel) {
|
||||||
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,16 +330,16 @@ func relabel(cfg *Config, lb *labels.Builder) (keep bool) {
|
|||||||
if indexes == nil {
|
if indexes == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
|
target := string(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
|
||||||
if !target.IsValid() {
|
if !cfg.NameValidationScheme.IsValidLabelName(target) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
|
res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
|
||||||
if len(res) == 0 {
|
if len(res) == 0 {
|
||||||
lb.Del(string(target))
|
lb.Del(target)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lb.Set(string(target), string(res))
|
lb.Set(target, string(res))
|
||||||
case Lowercase:
|
case Lowercase:
|
||||||
lb.Set(cfg.TargetLabel, strings.ToLower(val))
|
lb.Set(cfg.TargetLabel, strings.ToLower(val))
|
||||||
case Uppercase:
|
case Uppercase:
|
||||||
|
@ -747,7 +747,8 @@ func TestRelabel(t *testing.T) {
|
|||||||
if cfg.Replacement == "" {
|
if cfg.Replacement == "" {
|
||||||
cfg.Replacement = DefaultRelabelConfig.Replacement
|
cfg.Replacement = DefaultRelabelConfig.Replacement
|
||||||
}
|
}
|
||||||
require.NoError(t, cfg.Validate())
|
cfg.NameValidationScheme = model.UTF8Validation
|
||||||
|
require.NoError(t, cfg.Validate(model.UTF8Validation))
|
||||||
}
|
}
|
||||||
|
|
||||||
res, keep := Process(test.input, test.relabel...)
|
res, keep := Process(test.input, test.relabel...)
|
||||||
@ -764,59 +765,76 @@ func TestRelabelValidate(t *testing.T) {
|
|||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config: Config{},
|
config: Config{
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
|
},
|
||||||
expected: `relabel action cannot be empty`,
|
expected: `relabel action cannot be empty`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
Action: Replace,
|
Action: Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
expected: `requires 'target_label' value`,
|
expected: `requires 'target_label' value`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
Action: Lowercase,
|
Action: Lowercase,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
expected: `requires 'target_label' value`,
|
expected: `requires 'target_label' value`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
Action: Lowercase,
|
Action: Lowercase,
|
||||||
Replacement: DefaultRelabelConfig.Replacement,
|
Replacement: DefaultRelabelConfig.Replacement,
|
||||||
TargetLabel: "${3}", // With UTF-8 naming, this is now a legal relabel rule.
|
TargetLabel: "${3}", // With UTF-8 naming, this is now a legal relabel rule.
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
SourceLabels: model.LabelNames{"a"},
|
Action: Lowercase,
|
||||||
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
Replacement: DefaultRelabelConfig.Replacement,
|
||||||
Action: Replace,
|
TargetLabel: "${3}", // Fails with legacy validation
|
||||||
Replacement: "${1}",
|
NameValidationScheme: model.LegacyValidation,
|
||||||
TargetLabel: "${3}",
|
},
|
||||||
|
expected: "\"${3}\" is invalid 'target_label' for lowercase action",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config: Config{
|
||||||
|
SourceLabels: model.LabelNames{"a"},
|
||||||
|
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
||||||
|
Action: Replace,
|
||||||
|
Replacement: "${1}",
|
||||||
|
TargetLabel: "${3}",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
SourceLabels: model.LabelNames{"a"},
|
SourceLabels: model.LabelNames{"a"},
|
||||||
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
||||||
Action: Replace,
|
Action: Replace,
|
||||||
Replacement: "${1}",
|
Replacement: "${1}",
|
||||||
TargetLabel: "0${3}", // With UTF-8 naming this targets a valid label.
|
TargetLabel: "0${3}", // With UTF-8 naming this targets a valid label.
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: Config{
|
config: Config{
|
||||||
SourceLabels: model.LabelNames{"a"},
|
SourceLabels: model.LabelNames{"a"},
|
||||||
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
Regex: MustNewRegexp("some-([^-]+)-([^,]+)"),
|
||||||
Action: Replace,
|
Action: Replace,
|
||||||
Replacement: "${1}",
|
Replacement: "${1}",
|
||||||
TargetLabel: "-${3}", // With UTF-8 naming this targets a valid label.
|
TargetLabel: "-${3}", // With UTF-8 naming this targets a valid label.
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
err := test.config.Validate()
|
err := test.config.Validate(model.UTF8Validation)
|
||||||
if test.expected == "" {
|
if test.expected == "" {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,7 +96,14 @@ type ruleGroups struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates all rules in the rule groups.
|
// Validate validates all rules in the rule groups.
|
||||||
func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
|
func (g *RuleGroups) Validate(node ruleGroups, nameValidationScheme model.ValidationScheme) (errs []error) {
|
||||||
|
switch nameValidationScheme {
|
||||||
|
case model.UTF8Validation, model.LegacyValidation:
|
||||||
|
default:
|
||||||
|
errs = append(errs, fmt.Errorf("unhandled nameValidationScheme: %s", nameValidationScheme))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
set := map[string]struct{}{}
|
set := map[string]struct{}{}
|
||||||
|
|
||||||
for j, g := range g.Groups {
|
for j, g := range g.Groups {
|
||||||
@ -112,7 +119,7 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range g.Labels {
|
for k, v := range g.Labels {
|
||||||
if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
|
if !nameValidationScheme.IsValidLabelName(k) || k == model.MetricNameLabel {
|
||||||
errs = append(
|
errs = append(
|
||||||
errs, fmt.Errorf("invalid label name: %s", k),
|
errs, fmt.Errorf("invalid label name: %s", k),
|
||||||
)
|
)
|
||||||
@ -128,7 +135,7 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
|
|||||||
set[g.Name] = struct{}{}
|
set[g.Name] = struct{}{}
|
||||||
|
|
||||||
for i, r := range g.Rules {
|
for i, r := range g.Rules {
|
||||||
for _, node := range r.Validate(node.Groups[j].Rules[i]) {
|
for _, node := range r.Validate(node.Groups[j].Rules[i], nameValidationScheme) {
|
||||||
var ruleName string
|
var ruleName string
|
||||||
if r.Alert != "" {
|
if r.Alert != "" {
|
||||||
ruleName = r.Alert
|
ruleName = r.Alert
|
||||||
@ -192,7 +199,7 @@ type RuleNode struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the rule and return a list of encountered errors.
|
// Validate the rule and return a list of encountered errors.
|
||||||
func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
|
func (r *Rule) Validate(node RuleNode, nameValidationScheme model.ValidationScheme) (nodes []WrappedError) {
|
||||||
if r.Record != "" && r.Alert != "" {
|
if r.Record != "" && r.Alert != "" {
|
||||||
nodes = append(nodes, WrappedError{
|
nodes = append(nodes, WrappedError{
|
||||||
err: errors.New("only one of 'record' and 'alert' must be set"),
|
err: errors.New("only one of 'record' and 'alert' must be set"),
|
||||||
@ -238,7 +245,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
|
|||||||
node: &node.Record,
|
node: &node.Record,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if !model.IsValidMetricName(model.LabelValue(r.Record)) {
|
if !nameValidationScheme.IsValidMetricName(r.Record) {
|
||||||
nodes = append(nodes, WrappedError{
|
nodes = append(nodes, WrappedError{
|
||||||
err: fmt.Errorf("invalid recording rule name: %s", r.Record),
|
err: fmt.Errorf("invalid recording rule name: %s", r.Record),
|
||||||
node: &node.Record,
|
node: &node.Record,
|
||||||
@ -255,7 +262,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.Labels {
|
for k, v := range r.Labels {
|
||||||
if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
|
if !nameValidationScheme.IsValidLabelName(k) || k == model.MetricNameLabel {
|
||||||
nodes = append(nodes, WrappedError{
|
nodes = append(nodes, WrappedError{
|
||||||
err: fmt.Errorf("invalid label name: %s", k),
|
err: fmt.Errorf("invalid label name: %s", k),
|
||||||
})
|
})
|
||||||
@ -269,7 +276,7 @@ func (r *Rule) Validate(node RuleNode) (nodes []WrappedError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k := range r.Annotations {
|
for k := range r.Annotations {
|
||||||
if !model.LabelName(k).IsValid() {
|
if !nameValidationScheme.IsValidLabelName(k) {
|
||||||
nodes = append(nodes, WrappedError{
|
nodes = append(nodes, WrappedError{
|
||||||
err: fmt.Errorf("invalid annotation name: %s", k),
|
err: fmt.Errorf("invalid annotation name: %s", k),
|
||||||
})
|
})
|
||||||
@ -333,7 +340,7 @@ func testTemplateParsing(rl *Rule) (errs []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses and validates a set of rules.
|
// Parse parses and validates a set of rules.
|
||||||
func Parse(content []byte, ignoreUnknownFields bool) (*RuleGroups, []error) {
|
func Parse(content []byte, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*RuleGroups, []error) {
|
||||||
var (
|
var (
|
||||||
groups RuleGroups
|
groups RuleGroups
|
||||||
node ruleGroups
|
node ruleGroups
|
||||||
@ -358,16 +365,16 @@ func Parse(content []byte, ignoreUnknownFields bool) (*RuleGroups, []error) {
|
|||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
return &groups, groups.Validate(node)
|
return &groups, groups.Validate(node, nameValidationScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFile reads and parses rules from a file.
|
// ParseFile reads and parses rules from a file.
|
||||||
func ParseFile(file string, ignoreUnknownFields bool) (*RuleGroups, []error) {
|
func ParseFile(file string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*RuleGroups, []error) {
|
||||||
b, err := os.ReadFile(file)
|
b, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []error{fmt.Errorf("%s: %w", file, err)}
|
return nil, []error{fmt.Errorf("%s: %w", file, err)}
|
||||||
}
|
}
|
||||||
rgs, errs := Parse(b, ignoreUnknownFields)
|
rgs, errs := Parse(b, ignoreUnknownFields, nameValidationScheme)
|
||||||
for i := range errs {
|
for i := range errs {
|
||||||
errs[i] = fmt.Errorf("%s: %w", file, errs[i])
|
errs[i] = fmt.Errorf("%s: %w", file, errs[i])
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,20 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseFileSuccess(t *testing.T) {
|
func TestParseFileSuccess(t *testing.T) {
|
||||||
_, errs := ParseFile("testdata/test.yaml", false)
|
_, errs := ParseFile("testdata/test.yaml", false, model.UTF8Validation)
|
||||||
require.Empty(t, errs, "unexpected errors parsing file")
|
require.Empty(t, errs, "unexpected errors parsing file")
|
||||||
|
|
||||||
_, errs = ParseFile("testdata/utf-8_lname.good.yaml", false)
|
_, errs = ParseFile("testdata/utf-8_lname.good.yaml", false, model.UTF8Validation)
|
||||||
require.Empty(t, errs, "unexpected errors parsing file")
|
require.Empty(t, errs, "unexpected errors parsing file")
|
||||||
_, errs = ParseFile("testdata/utf-8_annotation.good.yaml", false)
|
_, errs = ParseFile("testdata/utf-8_annotation.good.yaml", false, model.UTF8Validation)
|
||||||
|
require.Empty(t, errs, "unexpected errors parsing file")
|
||||||
|
_, errs = ParseFile("testdata/legacy_validation_annotation.good.yaml", false, model.LegacyValidation)
|
||||||
require.Empty(t, errs, "unexpected errors parsing file")
|
require.Empty(t, errs, "unexpected errors parsing file")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ func TestParseFileSuccessWithAliases(t *testing.T) {
|
|||||||
/
|
/
|
||||||
sum without(instance) (rate(requests_total[5m]))
|
sum without(instance) (rate(requests_total[5m]))
|
||||||
`
|
`
|
||||||
rgs, errs := ParseFile("testdata/test_aliases.yaml", false)
|
rgs, errs := ParseFile("testdata/test_aliases.yaml", false, model.UTF8Validation)
|
||||||
require.Empty(t, errs, "unexpected errors parsing file")
|
require.Empty(t, errs, "unexpected errors parsing file")
|
||||||
for _, rg := range rgs.Groups {
|
for _, rg := range rgs.Groups {
|
||||||
require.Equal(t, "HighAlert", rg.Rules[0].Alert)
|
require.Equal(t, "HighAlert", rg.Rules[0].Alert)
|
||||||
@ -62,8 +65,9 @@ sum without(instance) (rate(requests_total[5m]))
|
|||||||
|
|
||||||
func TestParseFileFailure(t *testing.T) {
|
func TestParseFileFailure(t *testing.T) {
|
||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
filename string
|
filename string
|
||||||
errMsg string
|
errMsg string
|
||||||
|
nameValidationScheme model.ValidationScheme
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
filename: "duplicate_grp.bad.yaml",
|
filename: "duplicate_grp.bad.yaml",
|
||||||
@ -105,9 +109,17 @@ func TestParseFileFailure(t *testing.T) {
|
|||||||
filename: "record_and_keep_firing_for.bad.yaml",
|
filename: "record_and_keep_firing_for.bad.yaml",
|
||||||
errMsg: "invalid field 'keep_firing_for' in recording rule",
|
errMsg: "invalid field 'keep_firing_for' in recording rule",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filename: "legacy_validation_annotation.bad.yaml",
|
||||||
|
nameValidationScheme: model.LegacyValidation,
|
||||||
|
errMsg: "invalid annotation name: ins-tance",
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(c.filename, func(t *testing.T) {
|
t.Run(c.filename, func(t *testing.T) {
|
||||||
_, errs := ParseFile(filepath.Join("testdata", c.filename), false)
|
if c.nameValidationScheme == model.UnsetValidation {
|
||||||
|
c.nameValidationScheme = model.UTF8Validation
|
||||||
|
}
|
||||||
|
_, errs := ParseFile(filepath.Join("testdata", c.filename), false, c.nameValidationScheme)
|
||||||
require.NotEmpty(t, errs, "Expected error parsing %s but got none", c.filename)
|
require.NotEmpty(t, errs, "Expected error parsing %s but got none", c.filename)
|
||||||
require.ErrorContainsf(t, errs[0], c.errMsg, "Expected error for %s.", c.filename)
|
require.ErrorContainsf(t, errs[0], c.errMsg, "Expected error for %s.", c.filename)
|
||||||
})
|
})
|
||||||
@ -203,7 +215,7 @@ groups:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tst := range tests {
|
for _, tst := range tests {
|
||||||
rgs, errs := Parse([]byte(tst.ruleString), false)
|
rgs, errs := Parse([]byte(tst.ruleString), false, model.UTF8Validation)
|
||||||
require.NotNil(t, rgs, "Rule parsing, rule=\n"+tst.ruleString)
|
require.NotNil(t, rgs, "Rule parsing, rule=\n"+tst.ruleString)
|
||||||
passed := (tst.shouldPass && len(errs) == 0) || (!tst.shouldPass && len(errs) > 0)
|
passed := (tst.shouldPass && len(errs) == 0) || (!tst.shouldPass && len(errs) > 0)
|
||||||
require.True(t, passed, "Rule validation failed, rule=\n"+tst.ruleString)
|
require.True(t, passed, "Rule validation failed, rule=\n"+tst.ruleString)
|
||||||
@ -230,7 +242,7 @@ groups:
|
|||||||
annotations:
|
annotations:
|
||||||
summary: "Instance {{ $labels.instance }} up"
|
summary: "Instance {{ $labels.instance }} up"
|
||||||
`
|
`
|
||||||
_, errs := Parse([]byte(group), false)
|
_, errs := Parse([]byte(group), false, model.UTF8Validation)
|
||||||
require.Len(t, errs, 2, "Expected two errors")
|
require.Len(t, errs, 2, "Expected two errors")
|
||||||
var err00 *Error
|
var err00 *Error
|
||||||
require.ErrorAs(t, errs[0], &err00)
|
require.ErrorAs(t, errs[0], &err00)
|
||||||
|
7
model/rulefmt/testdata/legacy_validation_annotation.bad.yaml
vendored
Normal file
7
model/rulefmt/testdata/legacy_validation_annotation.bad.yaml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
groups:
|
||||||
|
- name: yolo
|
||||||
|
rules:
|
||||||
|
- alert: hola
|
||||||
|
expr: 1
|
||||||
|
annotations:
|
||||||
|
ins-tance: localhost
|
7
model/rulefmt/testdata/legacy_validation_annotation.good.yaml
vendored
Normal file
7
model/rulefmt/testdata/legacy_validation_annotation.good.yaml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
groups:
|
||||||
|
- name: yolo
|
||||||
|
rules:
|
||||||
|
- alert: hola
|
||||||
|
expr: 1
|
||||||
|
annotations:
|
||||||
|
ins_tance: localhost
|
@ -428,7 +428,7 @@ func (p *ProtobufParser) Next() (Entry, error) {
|
|||||||
// We are at the beginning of a metric family. Put only the name
|
// We are at the beginning of a metric family. Put only the name
|
||||||
// into entryBytes and validate only name, help, and type for now.
|
// into entryBytes and validate only name, help, and type for now.
|
||||||
name := p.dec.GetName()
|
name := p.dec.GetName()
|
||||||
if !model.IsValidMetricName(model.LabelValue(name)) {
|
if !model.UTF8Validation.IsValidMetricName(name) {
|
||||||
return EntryInvalid, fmt.Errorf("invalid metric name: %s", name)
|
return EntryInvalid, fmt.Errorf("invalid metric name: %s", name)
|
||||||
}
|
}
|
||||||
if help := p.dec.GetHelp(); !utf8.ValidString(help) {
|
if help := p.dec.GetHelp(); !utf8.ValidString(help) {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
"github.com/prometheus/common/promslog"
|
"github.com/prometheus/common/promslog"
|
||||||
"github.com/prometheus/common/version"
|
"github.com/prometheus/common/version"
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ func do(ctx context.Context, client *http.Client, req *http.Request) (*http.Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewManager is the manager constructor.
|
// NewManager is the manager constructor.
|
||||||
func NewManager(o *Options, logger *slog.Logger) *Manager {
|
func NewManager(o *Options, nameValidationScheme model.ValidationScheme, logger *slog.Logger) *Manager {
|
||||||
if o.Do == nil {
|
if o.Do == nil {
|
||||||
o.Do = do
|
o.Do = do
|
||||||
}
|
}
|
||||||
@ -104,6 +105,14 @@ func NewManager(o *Options, logger *slog.Logger) *Manager {
|
|||||||
logger = promslog.NewNopLogger()
|
logger = promslog.NewNopLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, rc := range o.RelabelConfigs {
|
||||||
|
switch rc.NameValidationScheme {
|
||||||
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
|
default:
|
||||||
|
rc.NameValidationScheme = nameValidationScheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
n := &Manager{
|
n := &Manager{
|
||||||
queue: make([]*Alert, 0, o.QueueCapacity),
|
queue: make([]*Alert, 0, o.QueueCapacity),
|
||||||
more: make(chan struct{}, 1),
|
more: make(chan struct{}, 1),
|
||||||
@ -133,6 +142,13 @@ func (n *Manager) ApplyConfig(conf *config.Config) error {
|
|||||||
|
|
||||||
n.opts.ExternalLabels = conf.GlobalConfig.ExternalLabels
|
n.opts.ExternalLabels = conf.GlobalConfig.ExternalLabels
|
||||||
n.opts.RelabelConfigs = conf.AlertingConfig.AlertRelabelConfigs
|
n.opts.RelabelConfigs = conf.AlertingConfig.AlertRelabelConfigs
|
||||||
|
for i, rc := range n.opts.RelabelConfigs {
|
||||||
|
switch rc.NameValidationScheme {
|
||||||
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
|
default:
|
||||||
|
n.opts.RelabelConfigs[i].NameValidationScheme = conf.GlobalConfig.MetricNameValidationScheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
amSets := make(map[string]*alertmanagerSet)
|
amSets := make(map[string]*alertmanagerSet)
|
||||||
// configToAlertmanagers maps alertmanager sets for each unique AlertmanagerConfig,
|
// configToAlertmanagers maps alertmanager sets for each unique AlertmanagerConfig,
|
||||||
|
@ -45,7 +45,7 @@ import (
|
|||||||
const maxBatchSize = 256
|
const maxBatchSize = 256
|
||||||
|
|
||||||
func TestHandlerNextBatch(t *testing.T) {
|
func TestHandlerNextBatch(t *testing.T) {
|
||||||
h := NewManager(&Options{}, nil)
|
h := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
for i := range make([]struct{}, 2*maxBatchSize+1) {
|
for i := range make([]struct{}, 2*maxBatchSize+1) {
|
||||||
h.queue = append(h.queue, &Alert{
|
h.queue = append(h.queue, &Alert{
|
||||||
@ -125,7 +125,7 @@ func TestHandlerSendAll(t *testing.T) {
|
|||||||
defer server2.Close()
|
defer server2.Close()
|
||||||
defer server3.Close()
|
defer server3.Close()
|
||||||
|
|
||||||
h := NewManager(&Options{}, nil)
|
h := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
authClient, _ := config_util.NewClientFromConfig(
|
authClient, _ := config_util.NewClientFromConfig(
|
||||||
config_util.HTTPClientConfig{
|
config_util.HTTPClientConfig{
|
||||||
@ -235,7 +235,7 @@ func TestHandlerSendAllRemapPerAm(t *testing.T) {
|
|||||||
defer server2.Close()
|
defer server2.Close()
|
||||||
defer server3.Close()
|
defer server3.Close()
|
||||||
|
|
||||||
h := NewManager(&Options{}, nil)
|
h := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
h.alertmanagers = make(map[string]*alertmanagerSet)
|
h.alertmanagers = make(map[string]*alertmanagerSet)
|
||||||
|
|
||||||
am1Cfg := config.DefaultAlertmanagerConfig
|
am1Cfg := config.DefaultAlertmanagerConfig
|
||||||
@ -245,9 +245,10 @@ func TestHandlerSendAllRemapPerAm(t *testing.T) {
|
|||||||
am2Cfg.Timeout = model.Duration(time.Second)
|
am2Cfg.Timeout = model.Duration(time.Second)
|
||||||
am2Cfg.AlertRelabelConfigs = []*relabel.Config{
|
am2Cfg.AlertRelabelConfigs = []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"alertnamedrop"},
|
SourceLabels: model.LabelNames{"alertnamedrop"},
|
||||||
Action: "drop",
|
Action: "drop",
|
||||||
Regex: relabel.MustNewRegexp(".+"),
|
Regex: relabel.MustNewRegexp(".+"),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,9 +256,10 @@ func TestHandlerSendAllRemapPerAm(t *testing.T) {
|
|||||||
am3Cfg.Timeout = model.Duration(time.Second)
|
am3Cfg.Timeout = model.Duration(time.Second)
|
||||||
am3Cfg.AlertRelabelConfigs = []*relabel.Config{
|
am3Cfg.AlertRelabelConfigs = []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"alertname"},
|
SourceLabels: model.LabelNames{"alertname"},
|
||||||
Action: "drop",
|
Action: "drop",
|
||||||
Regex: relabel.MustNewRegexp(".+"),
|
Regex: relabel.MustNewRegexp(".+"),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +376,7 @@ func TestCustomDo(t *testing.T) {
|
|||||||
Body: io.NopCloser(bytes.NewBuffer(nil)),
|
Body: io.NopCloser(bytes.NewBuffer(nil)),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
}, nil)
|
}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
h.sendOne(context.Background(), nil, testURL, []byte(testBody))
|
h.sendOne(context.Background(), nil, testURL, []byte(testBody))
|
||||||
|
|
||||||
@ -388,14 +390,15 @@ func TestExternalLabels(t *testing.T) {
|
|||||||
ExternalLabels: labels.FromStrings("a", "b"),
|
ExternalLabels: labels.FromStrings("a", "b"),
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"alertname"},
|
SourceLabels: model.LabelNames{"alertname"},
|
||||||
TargetLabel: "a",
|
TargetLabel: "a",
|
||||||
Action: "replace",
|
Action: "replace",
|
||||||
Regex: relabel.MustNewRegexp("externalrelabelthis"),
|
Regex: relabel.MustNewRegexp("externalrelabelthis"),
|
||||||
Replacement: "c",
|
Replacement: "c",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
// This alert should get the external label attached.
|
// This alert should get the external label attached.
|
||||||
h.Send(&Alert{
|
h.Send(&Alert{
|
||||||
@ -422,19 +425,21 @@ func TestHandlerRelabel(t *testing.T) {
|
|||||||
MaxBatchSize: maxBatchSize,
|
MaxBatchSize: maxBatchSize,
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"alertname"},
|
SourceLabels: model.LabelNames{"alertname"},
|
||||||
Action: "drop",
|
Action: "drop",
|
||||||
Regex: relabel.MustNewRegexp("drop"),
|
Regex: relabel.MustNewRegexp("drop"),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"alertname"},
|
SourceLabels: model.LabelNames{"alertname"},
|
||||||
TargetLabel: "alertname",
|
TargetLabel: "alertname",
|
||||||
Action: "replace",
|
Action: "replace",
|
||||||
Regex: relabel.MustNewRegexp("rename"),
|
Regex: relabel.MustNewRegexp("rename"),
|
||||||
Replacement: "renamed",
|
Replacement: "renamed",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil)
|
}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
// This alert should be dropped due to the configuration
|
// This alert should be dropped due to the configuration
|
||||||
h.Send(&Alert{
|
h.Send(&Alert{
|
||||||
@ -500,6 +505,7 @@ func TestHandlerQueuing(t *testing.T) {
|
|||||||
QueueCapacity: 3 * maxBatchSize,
|
QueueCapacity: 3 * maxBatchSize,
|
||||||
MaxBatchSize: maxBatchSize,
|
MaxBatchSize: maxBatchSize,
|
||||||
},
|
},
|
||||||
|
model.UTF8Validation,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -606,7 +612,7 @@ func TestReload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := NewManager(&Options{}, nil)
|
n := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
cfg := &config.Config{}
|
cfg := &config.Config{}
|
||||||
s := `
|
s := `
|
||||||
@ -653,7 +659,7 @@ func TestDroppedAlertmanagers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
n := NewManager(&Options{}, nil)
|
n := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
|
|
||||||
cfg := &config.Config{}
|
cfg := &config.Config{}
|
||||||
s := `
|
s := `
|
||||||
@ -766,6 +772,7 @@ func TestHangingNotifier(t *testing.T) {
|
|||||||
&Options{
|
&Options{
|
||||||
QueueCapacity: alertsCount,
|
QueueCapacity: alertsCount,
|
||||||
},
|
},
|
||||||
|
model.UTF8Validation,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
notifier.alertmanagers = make(map[string]*alertmanagerSet)
|
notifier.alertmanagers = make(map[string]*alertmanagerSet)
|
||||||
@ -883,6 +890,7 @@ func TestStop_DrainingDisabled(t *testing.T) {
|
|||||||
QueueCapacity: 10,
|
QueueCapacity: 10,
|
||||||
DrainOnShutdown: false,
|
DrainOnShutdown: false,
|
||||||
},
|
},
|
||||||
|
model.UTF8Validation,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -969,6 +977,7 @@ func TestStop_DrainingEnabled(t *testing.T) {
|
|||||||
QueueCapacity: 10,
|
QueueCapacity: 10,
|
||||||
DrainOnShutdown: true,
|
DrainOnShutdown: true,
|
||||||
},
|
},
|
||||||
|
model.UTF8Validation,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1031,7 +1040,7 @@ func TestApplyConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
alertmanagerURL := fmt.Sprintf("http://%s/api/v2/alerts", targetURL)
|
alertmanagerURL := fmt.Sprintf("http://%s/api/v2/alerts", targetURL)
|
||||||
|
|
||||||
n := NewManager(&Options{}, nil)
|
n := NewManager(&Options{}, model.UTF8Validation, nil)
|
||||||
cfg := &config.Config{}
|
cfg := &config.Config{}
|
||||||
s := `
|
s := `
|
||||||
alerting:
|
alerting:
|
||||||
|
@ -177,6 +177,7 @@ func (m *MetricStreamingDecoder) Label(b scratchBuilder) error {
|
|||||||
// via UnsafeAddBytes method to reuse strings.
|
// via UnsafeAddBytes method to reuse strings.
|
||||||
func parseLabel(dAtA []byte, b scratchBuilder) error {
|
func parseLabel(dAtA []byte, b scratchBuilder) error {
|
||||||
var name, value []byte
|
var name, value []byte
|
||||||
|
var unsafeName string
|
||||||
l := len(dAtA)
|
l := len(dAtA)
|
||||||
iNdEx := 0
|
iNdEx := 0
|
||||||
for iNdEx < l {
|
for iNdEx < l {
|
||||||
@ -236,8 +237,9 @@ func parseLabel(dAtA []byte, b scratchBuilder) error {
|
|||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
name = dAtA[iNdEx:postIndex]
|
name = dAtA[iNdEx:postIndex]
|
||||||
if !model.LabelName(name).IsValid() {
|
unsafeName = yoloString(name)
|
||||||
return fmt.Errorf("invalid label name: %s", name)
|
if !model.UTF8Validation.IsValidLabelName(unsafeName) {
|
||||||
|
return fmt.Errorf("invalid label name: %s", unsafeName)
|
||||||
}
|
}
|
||||||
iNdEx = postIndex
|
iNdEx = postIndex
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -1676,7 +1676,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
|
|||||||
|
|
||||||
if e.Op == parser.COUNT_VALUES {
|
if e.Op == parser.COUNT_VALUES {
|
||||||
valueLabel := param.(*parser.StringLiteral)
|
valueLabel := param.(*parser.StringLiteral)
|
||||||
if !model.LabelName(valueLabel.Val).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(valueLabel.Val) {
|
||||||
ev.errorf("invalid label name %s", valueLabel)
|
ev.errorf("invalid label name %s", valueLabel)
|
||||||
}
|
}
|
||||||
if !e.Without {
|
if !e.Without {
|
||||||
|
@ -1584,7 +1584,7 @@ func (ev *evaluator) evalLabelReplace(ctx context.Context, args parser.Expressio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
|
panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr))
|
||||||
}
|
}
|
||||||
if !model.LabelName(dst).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(dst) {
|
||||||
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
|
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,12 +1632,12 @@ func (ev *evaluator) evalLabelJoin(ctx context.Context, args parser.Expressions)
|
|||||||
)
|
)
|
||||||
for i := 3; i < len(args); i++ {
|
for i := 3; i < len(args); i++ {
|
||||||
src := stringFromArg(args[i])
|
src := stringFromArg(args[i])
|
||||||
if !model.LabelName(src).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(src) {
|
||||||
panic(fmt.Errorf("invalid source label name in label_join(): %s", src))
|
panic(fmt.Errorf("invalid source label name in label_join(): %s", src))
|
||||||
}
|
}
|
||||||
srcLabels[i-3] = src
|
srcLabels[i-3] = src
|
||||||
}
|
}
|
||||||
if !model.LabelName(dst).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(dst) {
|
||||||
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
|
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,14 +364,14 @@ grouping_label_list:
|
|||||||
|
|
||||||
grouping_label : maybe_label
|
grouping_label : maybe_label
|
||||||
{
|
{
|
||||||
if !model.LabelName($1.Val).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName($1.Val) {
|
||||||
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", $1.Val)
|
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", $1.Val)
|
||||||
}
|
}
|
||||||
$$ = $1
|
$$ = $1
|
||||||
}
|
}
|
||||||
| STRING {
|
| STRING {
|
||||||
unquoted := yylex.(*parser).unquoteString($1.Val)
|
unquoted := yylex.(*parser).unquoteString($1.Val)
|
||||||
if !model.LabelName(unquoted).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(unquoted) {
|
||||||
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", unquoted)
|
yylex.(*parser).addParseErrf($1.PositionRange(),"invalid label name for grouping: %q", unquoted)
|
||||||
}
|
}
|
||||||
$$ = $1
|
$$ = $1
|
||||||
|
@ -1317,7 +1317,7 @@ yydefault:
|
|||||||
case 59:
|
case 59:
|
||||||
yyDollar = yyS[yypt-1 : yypt+1]
|
yyDollar = yyS[yypt-1 : yypt+1]
|
||||||
{
|
{
|
||||||
if !model.LabelName(yyDollar[1].item.Val).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(yyDollar[1].item.Val) {
|
||||||
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", yyDollar[1].item.Val)
|
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", yyDollar[1].item.Val)
|
||||||
}
|
}
|
||||||
yyVAL.item = yyDollar[1].item
|
yyVAL.item = yyDollar[1].item
|
||||||
@ -1326,7 +1326,7 @@ yydefault:
|
|||||||
yyDollar = yyS[yypt-1 : yypt+1]
|
yyDollar = yyS[yypt-1 : yypt+1]
|
||||||
{
|
{
|
||||||
unquoted := yylex.(*parser).unquoteString(yyDollar[1].item.Val)
|
unquoted := yylex.(*parser).unquoteString(yyDollar[1].item.Val)
|
||||||
if !model.LabelName(unquoted).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(unquoted) {
|
||||||
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", unquoted)
|
yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid label name for grouping: %q", unquoted)
|
||||||
}
|
}
|
||||||
yyVAL.item = yyDollar[1].item
|
yyVAL.item = yyDollar[1].item
|
||||||
|
@ -100,7 +100,7 @@ func joinLabels(ss []string) string {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
b.WriteString(", ")
|
b.WriteString(", ")
|
||||||
}
|
}
|
||||||
if !model.IsValidLegacyMetricName(string(model.LabelValue(s))) {
|
if !model.LegacyValidation.IsValidMetricName(s) {
|
||||||
b.Write(strconv.AppendQuote(b.AvailableBuffer(), s))
|
b.Write(strconv.AppendQuote(b.AvailableBuffer(), s))
|
||||||
} else {
|
} else {
|
||||||
b.WriteString(s)
|
b.WriteString(s)
|
||||||
|
@ -771,15 +771,16 @@ func TestSendAlertsDontAffectActiveAlerts(t *testing.T) {
|
|||||||
QueueCapacity: 1,
|
QueueCapacity: 1,
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"a1"},
|
SourceLabels: model.LabelNames{"a1"},
|
||||||
Regex: relabel.MustNewRegexp("(.+)"),
|
Regex: relabel.MustNewRegexp("(.+)"),
|
||||||
TargetLabel: "a1",
|
TargetLabel: "a1",
|
||||||
Replacement: "bug",
|
Replacement: "bug",
|
||||||
Action: "replace",
|
Action: "replace",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
nm := notifier.NewManager(&opts, promslog.NewNopLogger())
|
nm := notifier.NewManager(&opts, model.UTF8Validation, promslog.NewNopLogger())
|
||||||
|
|
||||||
f := SendAlerts(nm, "")
|
f := SendAlerts(nm, "")
|
||||||
notifyFunc := func(ctx context.Context, expr string, alerts ...*Alert) {
|
notifyFunc := func(ctx context.Context, expr string, alerts ...*Alert) {
|
||||||
|
@ -98,7 +98,9 @@ type GroupOptions struct {
|
|||||||
func NewGroup(o GroupOptions) *Group {
|
func NewGroup(o GroupOptions) *Group {
|
||||||
opts := o.Opts
|
opts := o.Opts
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &ManagerOptions{}
|
opts = &ManagerOptions{
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
metrics := opts.Metrics
|
metrics := opts.Metrics
|
||||||
if metrics == nil {
|
if metrics == nil {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
"github.com/prometheus/common/promslog"
|
"github.com/prometheus/common/promslog"
|
||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
|
|
||||||
@ -107,6 +108,7 @@ type NotifyFunc func(ctx context.Context, expr string, alerts ...*Alert)
|
|||||||
|
|
||||||
// ManagerOptions bundles options for the Manager.
|
// ManagerOptions bundles options for the Manager.
|
||||||
type ManagerOptions struct {
|
type ManagerOptions struct {
|
||||||
|
NameValidationScheme model.ValidationScheme
|
||||||
ExternalURL *url.URL
|
ExternalURL *url.URL
|
||||||
QueryFunc QueryFunc
|
QueryFunc QueryFunc
|
||||||
NotifyFunc NotifyFunc
|
NotifyFunc NotifyFunc
|
||||||
@ -289,7 +291,7 @@ func (m *Manager) Update(interval time.Duration, files []string, externalLabels
|
|||||||
|
|
||||||
// GroupLoader is responsible for loading rule groups from arbitrary sources and parsing them.
|
// GroupLoader is responsible for loading rule groups from arbitrary sources and parsing them.
|
||||||
type GroupLoader interface {
|
type GroupLoader interface {
|
||||||
Load(identifier string, ignoreUnknownFields bool) (*rulefmt.RuleGroups, []error)
|
Load(identifier string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*rulefmt.RuleGroups, []error)
|
||||||
Parse(query string) (parser.Expr, error)
|
Parse(query string) (parser.Expr, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,8 +299,8 @@ type GroupLoader interface {
|
|||||||
// and parser.ParseExpr.
|
// and parser.ParseExpr.
|
||||||
type FileLoader struct{}
|
type FileLoader struct{}
|
||||||
|
|
||||||
func (FileLoader) Load(identifier string, ignoreUnknownFields bool) (*rulefmt.RuleGroups, []error) {
|
func (FileLoader) Load(identifier string, ignoreUnknownFields bool, nameValidationScheme model.ValidationScheme) (*rulefmt.RuleGroups, []error) {
|
||||||
return rulefmt.ParseFile(identifier, ignoreUnknownFields)
|
return rulefmt.ParseFile(identifier, ignoreUnknownFields, nameValidationScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (FileLoader) Parse(query string) (parser.Expr, error) { return parser.ParseExpr(query) }
|
func (FileLoader) Parse(query string) (parser.Expr, error) { return parser.ParseExpr(query) }
|
||||||
@ -312,7 +314,7 @@ func (m *Manager) LoadGroups(
|
|||||||
shouldRestore := !m.restored || m.restoreNewRuleGroups
|
shouldRestore := !m.restored || m.restoreNewRuleGroups
|
||||||
|
|
||||||
for _, fn := range filenames {
|
for _, fn := range filenames {
|
||||||
rgs, errs := m.opts.GroupLoader.Load(fn, ignoreUnknownFields)
|
rgs, errs := m.opts.GroupLoader.Load(fn, ignoreUnknownFields, m.opts.NameValidationScheme)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
@ -582,7 +584,7 @@ func FromMaps(maps ...map[string]string) labels.Labels {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseFiles parses the rule files corresponding to glob patterns.
|
// ParseFiles parses the rule files corresponding to glob patterns.
|
||||||
func ParseFiles(patterns []string) error {
|
func ParseFiles(patterns []string, nameValidationScheme model.ValidationScheme) error {
|
||||||
files := map[string]string{}
|
files := map[string]string{}
|
||||||
for _, pat := range patterns {
|
for _, pat := range patterns {
|
||||||
fns, err := filepath.Glob(pat)
|
fns, err := filepath.Glob(pat)
|
||||||
@ -602,7 +604,7 @@ func ParseFiles(patterns []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for fn, pat := range files {
|
for fn, pat := range files {
|
||||||
_, errs := rulefmt.ParseFile(fn, false)
|
_, errs := rulefmt.ParseFile(fn, false, nameValidationScheme)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return fmt.Errorf("parse rules from file %q (pattern: %q): %w", fn, pat, errors.Join(errs...))
|
return fmt.Errorf("parse rules from file %q (pattern: %q): %w", fn, pat, errors.Join(errs...))
|
||||||
}
|
}
|
||||||
|
@ -372,14 +372,15 @@ func TestForStateRestore(t *testing.T) {
|
|||||||
|
|
||||||
ng := testEngine(t)
|
ng := testEngine(t)
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(ng, storage),
|
QueryFunc: EngineQueryFunc(ng, storage),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
NotifyFunc: func(_ context.Context, _ string, _ ...*Alert) {},
|
NotifyFunc: func(_ context.Context, _ string, _ ...*Alert) {},
|
||||||
OutageTolerance: 30 * time.Minute,
|
OutageTolerance: 30 * time.Minute,
|
||||||
ForGracePeriod: 10 * time.Minute,
|
ForGracePeriod: 10 * time.Minute,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
alertForDuration := 25 * time.Minute
|
alertForDuration := 25 * time.Minute
|
||||||
@ -545,11 +546,12 @@ func TestStaleness(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, engineOpts)
|
engine := promqltest.NewTestEngineWithOpts(t, engineOpts)
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(engine, st),
|
QueryFunc: EngineQueryFunc(engine, st),
|
||||||
Appendable: st,
|
Appendable: st,
|
||||||
Queryable: st,
|
Queryable: st,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("a + 1")
|
expr, err := parser.ParseExpr("a + 1")
|
||||||
@ -647,6 +649,7 @@ groups:
|
|||||||
DefaultRuleQueryOffset: func() time.Duration {
|
DefaultRuleQueryOffset: func() time.Duration {
|
||||||
return time.Minute
|
return time.Minute
|
||||||
},
|
},
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
m.start()
|
m.start()
|
||||||
err = m.Update(time.Second, []string{fname}, labels.EmptyLabels(), "", nil)
|
err = m.Update(time.Second, []string{fname}, labels.EmptyLabels(), "", nil)
|
||||||
@ -739,6 +742,7 @@ func TestDeletedRuleMarkedStale(t *testing.T) {
|
|||||||
opts: &ManagerOptions{
|
opts: &ManagerOptions{
|
||||||
Appendable: st,
|
Appendable: st,
|
||||||
RuleConcurrencyController: sequentialRuleEvalController{},
|
RuleConcurrencyController: sequentialRuleEvalController{},
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
metrics: NewGroupMetrics(nil),
|
metrics: NewGroupMetrics(nil),
|
||||||
}
|
}
|
||||||
@ -779,11 +783,12 @@ func TestUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Appendable: st,
|
Appendable: st,
|
||||||
Queryable: st,
|
Queryable: st,
|
||||||
QueryFunc: EngineQueryFunc(engine, st),
|
QueryFunc: EngineQueryFunc(engine, st),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
defer ruleManager.Stop()
|
defer ruleManager.Stop()
|
||||||
@ -810,7 +815,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Groups will be recreated if updated.
|
// Groups will be recreated if updated.
|
||||||
rgs, errs := rulefmt.ParseFile("fixtures/rules.yaml", false)
|
rgs, errs := rulefmt.ParseFile("fixtures/rules.yaml", false, model.UTF8Validation)
|
||||||
require.Empty(t, errs, "file parsing failures")
|
require.Empty(t, errs, "file parsing failures")
|
||||||
|
|
||||||
tmpFile, err := os.CreateTemp("", "rules.test.*.yaml")
|
tmpFile, err := os.CreateTemp("", "rules.test.*.yaml")
|
||||||
@ -923,13 +928,14 @@ func TestNotify(t *testing.T) {
|
|||||||
lastNotified = alerts
|
lastNotified = alerts
|
||||||
}
|
}
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(engine, storage),
|
QueryFunc: EngineQueryFunc(engine, storage),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
NotifyFunc: notifyFunc,
|
NotifyFunc: notifyFunc,
|
||||||
ResendDelay: 2 * time.Second,
|
ResendDelay: 2 * time.Second,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("a > 1")
|
expr, err := parser.ParseExpr("a > 1")
|
||||||
@ -994,12 +1000,13 @@ func TestMetricsUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
QueryFunc: EngineQueryFunc(engine, storage),
|
QueryFunc: EngineQueryFunc(engine, storage),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
Registerer: registry,
|
Registerer: registry,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
defer ruleManager.Stop()
|
defer ruleManager.Stop()
|
||||||
@ -1065,11 +1072,12 @@ func TestGroupStalenessOnRemoval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
QueryFunc: EngineQueryFunc(engine, storage),
|
QueryFunc: EngineQueryFunc(engine, storage),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
var stopped bool
|
var stopped bool
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
@ -1142,11 +1150,12 @@ func TestMetricsStalenessOnManagerShutdown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
engine := promqltest.NewTestEngineWithOpts(t, opts)
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
QueryFunc: EngineQueryFunc(engine, storage),
|
QueryFunc: EngineQueryFunc(engine, storage),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
var stopped bool
|
var stopped bool
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
@ -1209,11 +1218,12 @@ func TestRuleMovedBetweenGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promql.NewEngine(opts)
|
engine := promql.NewEngine(opts)
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
QueryFunc: EngineQueryFunc(engine, storage),
|
QueryFunc: EngineQueryFunc(engine, storage),
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
var stopped bool
|
var stopped bool
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
@ -1291,11 +1301,12 @@ func TestRuleHealthUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(t, engineOpts)
|
engine := promqltest.NewTestEngineWithOpts(t, engineOpts)
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(engine, st),
|
QueryFunc: EngineQueryFunc(engine, st),
|
||||||
Appendable: st,
|
Appendable: st,
|
||||||
Queryable: st,
|
Queryable: st,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("a + 1")
|
expr, err := parser.ParseExpr("a + 1")
|
||||||
@ -1389,14 +1400,15 @@ func TestRuleGroupEvalIterationFunc(t *testing.T) {
|
|||||||
ng := testEngine(t)
|
ng := testEngine(t)
|
||||||
testFunc := func(tst testInput) {
|
testFunc := func(tst testInput) {
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(ng, storage),
|
QueryFunc: EngineQueryFunc(ng, storage),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
NotifyFunc: func(_ context.Context, _ string, _ ...*Alert) {},
|
NotifyFunc: func(_ context.Context, _ string, _ ...*Alert) {},
|
||||||
OutageTolerance: 30 * time.Minute,
|
OutageTolerance: 30 * time.Minute,
|
||||||
ForGracePeriod: 10 * time.Minute,
|
ForGracePeriod: 10 * time.Minute,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
activeAlert := &Alert{
|
activeAlert := &Alert{
|
||||||
@ -1473,11 +1485,12 @@ func TestNativeHistogramsInRecordingRules(t *testing.T) {
|
|||||||
|
|
||||||
ng := testEngine(t)
|
ng := testEngine(t)
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
QueryFunc: EngineQueryFunc(ng, storage),
|
QueryFunc: EngineQueryFunc(ng, storage),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
Queryable: storage,
|
Queryable: storage,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("sum(histogram_metric)")
|
expr, err := parser.ParseExpr("sum(histogram_metric)")
|
||||||
@ -1524,10 +1537,11 @@ func TestManager_LoadGroups_ShouldCheckWhetherEachRuleHasDependentsAndDependenci
|
|||||||
})
|
})
|
||||||
|
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("load a mix of dependent and independent rules", func(t *testing.T) {
|
t.Run("load a mix of dependent and independent rules", func(t *testing.T) {
|
||||||
@ -1580,8 +1594,9 @@ func TestManager_LoadGroups_ShouldCheckWhetherEachRuleHasDependentsAndDependenci
|
|||||||
func TestDependencyMap(t *testing.T) {
|
func TestDependencyMap(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
||||||
@ -1638,8 +1653,9 @@ func TestDependencyMap(t *testing.T) {
|
|||||||
func TestNoDependency(t *testing.T) {
|
func TestNoDependency(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
||||||
@ -1661,8 +1677,9 @@ func TestNoDependency(t *testing.T) {
|
|||||||
func TestDependenciesEdgeCases(t *testing.T) {
|
func TestDependenciesEdgeCases(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("empty group", func(t *testing.T) {
|
t.Run("empty group", func(t *testing.T) {
|
||||||
@ -1819,8 +1836,9 @@ func TestDependenciesEdgeCases(t *testing.T) {
|
|||||||
func TestNoMetricSelector(t *testing.T) {
|
func TestNoMetricSelector(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
||||||
@ -1848,8 +1866,9 @@ func TestNoMetricSelector(t *testing.T) {
|
|||||||
func TestDependentRulesWithNonMetricExpression(t *testing.T) {
|
func TestDependentRulesWithNonMetricExpression(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))")
|
||||||
@ -1880,8 +1899,9 @@ func TestDependentRulesWithNonMetricExpression(t *testing.T) {
|
|||||||
func TestRulesDependentOnMetaMetrics(t *testing.T) {
|
func TestRulesDependentOnMetaMetrics(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
opts := &ManagerOptions{
|
opts := &ManagerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This rule is not dependent on any other rules in its group but it does depend on `ALERTS`, which is produced by
|
// This rule is not dependent on any other rules in its group but it does depend on `ALERTS`, which is produced by
|
||||||
@ -1909,8 +1929,9 @@ func TestRulesDependentOnMetaMetrics(t *testing.T) {
|
|||||||
func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) {
|
func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) {
|
||||||
files := []string{"fixtures/rules.yaml"}
|
files := []string{"fixtures/rules.yaml"}
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
|
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
@ -2447,8 +2468,9 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) {
|
|||||||
func TestUpdateWhenStopped(t *testing.T) {
|
func TestUpdateWhenStopped(t *testing.T) {
|
||||||
files := []string{"fixtures/rules.yaml"}
|
files := []string{"fixtures/rules.yaml"}
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
})
|
})
|
||||||
ruleManager.start()
|
ruleManager.start()
|
||||||
err := ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil)
|
err := ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil)
|
||||||
@ -2509,6 +2531,7 @@ func optsFactory(storage storage.Storage, maxInflight, inflightQueries *atomic.I
|
|||||||
ConcurrentEvalsEnabled: concurrent,
|
ConcurrentEvalsEnabled: concurrent,
|
||||||
MaxConcurrentEvals: maxConcurrent,
|
MaxConcurrentEvals: maxConcurrent,
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
QueryFunc: func(_ context.Context, _ string, ts time.Time) (promql.Vector, error) {
|
QueryFunc: func(_ context.Context, _ string, ts time.Time) (promql.Vector, error) {
|
||||||
inflightMu.Lock()
|
inflightMu.Lock()
|
||||||
|
|
||||||
@ -2552,11 +2575,11 @@ func TestLabels_FromMaps(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseFiles(t *testing.T) {
|
func TestParseFiles(t *testing.T) {
|
||||||
t.Run("good files", func(t *testing.T) {
|
t.Run("good files", func(t *testing.T) {
|
||||||
err := ParseFiles([]string{filepath.Join("fixtures", "rules.y*ml")})
|
err := ParseFiles([]string{filepath.Join("fixtures", "rules.y*ml")}, model.UTF8Validation)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
t.Run("bad files", func(t *testing.T) {
|
t.Run("bad files", func(t *testing.T) {
|
||||||
err := ParseFiles([]string{filepath.Join("fixtures", "invalid_rules.y*ml")})
|
err := ParseFiles([]string{filepath.Join("fixtures", "invalid_rules.y*ml")}, model.UTF8Validation)
|
||||||
require.ErrorContains(t, err, "field unexpected_field not found in type rulefmt.Rule")
|
require.ErrorContains(t, err, "field unexpected_field not found in type rulefmt.Rule")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2666,10 +2689,11 @@ func TestRuleDependencyController_AnalyseRules(t *testing.T) {
|
|||||||
t.Cleanup(func() { storage.Close() })
|
t.Cleanup(func() { storage.Close() })
|
||||||
|
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
Logger: promslog.NewNopLogger(),
|
Logger: promslog.NewNopLogger(),
|
||||||
Appendable: storage,
|
Appendable: storage,
|
||||||
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
NameValidationScheme: model.UTF8Validation,
|
||||||
|
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
||||||
})
|
})
|
||||||
|
|
||||||
groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, false, tc.ruleFile)
|
groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, false, tc.ruleFile)
|
||||||
@ -2695,10 +2719,11 @@ func BenchmarkRuleDependencyController_AnalyseRules(b *testing.B) {
|
|||||||
b.Cleanup(func() { storage.Close() })
|
b.Cleanup(func() { storage.Close() })
|
||||||
|
|
||||||
ruleManager := NewManager(&ManagerOptions{
|
ruleManager := NewManager(&ManagerOptions{
|
||||||
Context: context.Background(),
|
NameValidationScheme: model.UTF8Validation,
|
||||||
Logger: promslog.NewNopLogger(),
|
Context: context.Background(),
|
||||||
Appendable: storage,
|
Logger: promslog.NewNopLogger(),
|
||||||
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
Appendable: storage,
|
||||||
|
QueryFunc: func(_ context.Context, _ string, _ time.Time) (promql.Vector, error) { return nil, nil },
|
||||||
})
|
})
|
||||||
|
|
||||||
groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, false, "fixtures/rules_multiple.yaml")
|
groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, false, "fixtures/rules_multiple.yaml")
|
||||||
|
@ -188,11 +188,12 @@ func TestPopulateLabels(t *testing.T) {
|
|||||||
ScrapeTimeout: model.Duration(time.Second),
|
ScrapeTimeout: model.Duration(time.Second),
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
Regex: relabel.MustNewRegexp("(.*)"),
|
Regex: relabel.MustNewRegexp("(.*)"),
|
||||||
SourceLabels: model.LabelNames{"custom"},
|
SourceLabels: model.LabelNames{"custom"},
|
||||||
Replacement: "${1}",
|
Replacement: "${1}",
|
||||||
TargetLabel: string(model.AddressLabel),
|
TargetLabel: string(model.AddressLabel),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -226,11 +227,12 @@ func TestPopulateLabels(t *testing.T) {
|
|||||||
ScrapeTimeout: model.Duration(time.Second),
|
ScrapeTimeout: model.Duration(time.Second),
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
Regex: relabel.MustNewRegexp("(.*)"),
|
Regex: relabel.MustNewRegexp("(.*)"),
|
||||||
SourceLabels: model.LabelNames{"custom"},
|
SourceLabels: model.LabelNames{"custom"},
|
||||||
Replacement: "${1}",
|
Replacement: "${1}",
|
||||||
TargetLabel: string(model.AddressLabel),
|
TargetLabel: string(model.AddressLabel),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -450,6 +452,10 @@ func TestPopulateLabels(t *testing.T) {
|
|||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
in := maps.Clone(c.in)
|
in := maps.Clone(c.in)
|
||||||
lb := labels.NewBuilder(labels.EmptyLabels())
|
lb := labels.NewBuilder(labels.EmptyLabels())
|
||||||
|
c.cfg.MetricNameValidationScheme = model.UTF8Validation
|
||||||
|
for i := range c.cfg.RelabelConfigs {
|
||||||
|
c.cfg.RelabelConfigs[i].NameValidationScheme = model.UTF8Validation
|
||||||
|
}
|
||||||
res, err := PopulateLabels(lb, c.cfg, c.in, nil)
|
res, err := PopulateLabels(lb, c.cfg, c.in, nil)
|
||||||
if c.err != "" {
|
if c.err != "" {
|
||||||
require.EqualError(t, err, c.err)
|
require.EqualError(t, err, c.err)
|
||||||
|
@ -154,6 +154,11 @@ func newScrapePool(cfg *config.ScrapeConfig, app storage.Appendable, offsetSeed
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch cfg.MetricNameValidationScheme {
|
||||||
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("newScrapePool: MetricNameValidationScheme must be set in scrape configuration")
|
||||||
|
}
|
||||||
var escapingScheme model.EscapingScheme
|
var escapingScheme model.EscapingScheme
|
||||||
escapingScheme, err = config.ToEscapingScheme(cfg.MetricNameEscapingScheme, cfg.MetricNameValidationScheme)
|
escapingScheme, err = config.ToEscapingScheme(cfg.MetricNameEscapingScheme, cfg.MetricNameValidationScheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -326,6 +331,11 @@ func (sp *scrapePool) reload(cfg *config.ScrapeConfig) error {
|
|||||||
sp.config = cfg
|
sp.config = cfg
|
||||||
oldClient := sp.client
|
oldClient := sp.client
|
||||||
sp.client = client
|
sp.client = client
|
||||||
|
switch cfg.MetricNameValidationScheme {
|
||||||
|
case model.LegacyValidation, model.UTF8Validation:
|
||||||
|
default:
|
||||||
|
return errors.New("scrapePool.reload: MetricNameValidationScheme must be set in scrape configuration")
|
||||||
|
}
|
||||||
sp.validationScheme = cfg.MetricNameValidationScheme
|
sp.validationScheme = cfg.MetricNameValidationScheme
|
||||||
var escapingScheme model.EscapingScheme
|
var escapingScheme model.EscapingScheme
|
||||||
escapingScheme, err = model.ToEscapingScheme(cfg.MetricNameEscapingScheme)
|
escapingScheme, err = model.ToEscapingScheme(cfg.MetricNameEscapingScheme)
|
||||||
|
@ -334,9 +334,10 @@ func TestDroppedTargetsList(t *testing.T) {
|
|||||||
MetricNameEscapingScheme: model.AllowUTF8,
|
MetricNameEscapingScheme: model.AllowUTF8,
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Action: relabel.Drop,
|
Action: relabel.Drop,
|
||||||
Regex: relabel.MustNewRegexp("dropMe"),
|
Regex: relabel.MustNewRegexp("dropMe"),
|
||||||
SourceLabels: model.LabelNames{"job"},
|
SourceLabels: model.LabelNames{"job"},
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1367,10 +1368,11 @@ func TestScrapeLoopFailWithInvalidLabelsAfterRelabel(t *testing.T) {
|
|||||||
labels: labels.FromStrings("pod_label_invalid_012\xff", "test"),
|
labels: labels.FromStrings("pod_label_invalid_012\xff", "test"),
|
||||||
}
|
}
|
||||||
relabelConfig := []*relabel.Config{{
|
relabelConfig := []*relabel.Config{{
|
||||||
Action: relabel.LabelMap,
|
Action: relabel.LabelMap,
|
||||||
Regex: relabel.MustNewRegexp("pod_label_invalid_(.+)"),
|
Regex: relabel.MustNewRegexp("pod_label_invalid_(.+)"),
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}}
|
}}
|
||||||
sl := newBasicScrapeLoop(t, ctx, &testScraper{}, s.Appender, 0)
|
sl := newBasicScrapeLoop(t, ctx, &testScraper{}, s.Appender, 0)
|
||||||
sl.sampleMutator = func(l labels.Labels) labels.Labels {
|
sl.sampleMutator = func(l labels.Labels) labels.Labels {
|
||||||
@ -4182,18 +4184,20 @@ func TestTargetScrapeIntervalAndTimeoutRelabel(t *testing.T) {
|
|||||||
MetricNameEscapingScheme: model.AllowUTF8,
|
MetricNameEscapingScheme: model.AllowUTF8,
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{model.ScrapeIntervalLabel},
|
SourceLabels: model.LabelNames{model.ScrapeIntervalLabel},
|
||||||
Regex: relabel.MustNewRegexp("2s"),
|
Regex: relabel.MustNewRegexp("2s"),
|
||||||
Replacement: "3s",
|
Replacement: "3s",
|
||||||
TargetLabel: model.ScrapeIntervalLabel,
|
TargetLabel: model.ScrapeIntervalLabel,
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{model.ScrapeTimeoutLabel},
|
SourceLabels: model.LabelNames{model.ScrapeTimeoutLabel},
|
||||||
Regex: relabel.MustNewRegexp("500ms"),
|
Regex: relabel.MustNewRegexp("500ms"),
|
||||||
Replacement: "750ms",
|
Replacement: "750ms",
|
||||||
TargetLabel: model.ScrapeTimeoutLabel,
|
TargetLabel: model.ScrapeTimeoutLabel,
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -4220,20 +4224,22 @@ func TestLeQuantileReLabel(t *testing.T) {
|
|||||||
JobName: "test",
|
JobName: "test",
|
||||||
MetricRelabelConfigs: []*relabel.Config{
|
MetricRelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"le", "__name__"},
|
SourceLabels: model.LabelNames{"le", "__name__"},
|
||||||
Regex: relabel.MustNewRegexp("(\\d+)\\.0+;.*_bucket"),
|
Regex: relabel.MustNewRegexp("(\\d+)\\.0+;.*_bucket"),
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Separator: relabel.DefaultRelabelConfig.Separator,
|
Separator: relabel.DefaultRelabelConfig.Separator,
|
||||||
TargetLabel: "le",
|
TargetLabel: "le",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"quantile"},
|
SourceLabels: model.LabelNames{"quantile"},
|
||||||
Regex: relabel.MustNewRegexp("(\\d+)\\.0+"),
|
Regex: relabel.MustNewRegexp("(\\d+)\\.0+"),
|
||||||
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
Replacement: relabel.DefaultRelabelConfig.Replacement,
|
||||||
Separator: relabel.DefaultRelabelConfig.Separator,
|
Separator: relabel.DefaultRelabelConfig.Separator,
|
||||||
TargetLabel: "quantile",
|
TargetLabel: "quantile",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SampleLimit: 100,
|
SampleLimit: 100,
|
||||||
@ -4862,18 +4868,20 @@ func TestTypeUnitReLabel(t *testing.T) {
|
|||||||
JobName: "test",
|
JobName: "test",
|
||||||
MetricRelabelConfigs: []*relabel.Config{
|
MetricRelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__name__"},
|
SourceLabels: model.LabelNames{"__name__"},
|
||||||
Regex: relabel.MustNewRegexp(".*_total$"),
|
Regex: relabel.MustNewRegexp(".*_total$"),
|
||||||
Replacement: "counter",
|
Replacement: "counter",
|
||||||
TargetLabel: "__type__",
|
TargetLabel: "__type__",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceLabels: model.LabelNames{"__name__"},
|
SourceLabels: model.LabelNames{"__name__"},
|
||||||
Regex: relabel.MustNewRegexp(".*_bytes$"),
|
Regex: relabel.MustNewRegexp(".*_bytes$"),
|
||||||
Replacement: "bytes",
|
Replacement: "bytes",
|
||||||
TargetLabel: "__unit__",
|
TargetLabel: "__unit__",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SampleLimit: 100,
|
SampleLimit: 100,
|
||||||
@ -5482,25 +5490,28 @@ func TestTargetScrapeConfigWithLabels(t *testing.T) {
|
|||||||
Params: url.Values{"param": []string{secondParam}},
|
Params: url.Values{"param": []string{secondParam}},
|
||||||
RelabelConfigs: []*relabel.Config{
|
RelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Action: relabel.DefaultRelabelConfig.Action,
|
Action: relabel.DefaultRelabelConfig.Action,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
||||||
TargetLabel: model.ScrapeTimeoutLabel,
|
TargetLabel: model.ScrapeTimeoutLabel,
|
||||||
Replacement: expectedTimeoutLabel,
|
Replacement: expectedTimeoutLabel,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: relabel.DefaultRelabelConfig.Action,
|
Action: relabel.DefaultRelabelConfig.Action,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
||||||
TargetLabel: paramLabel,
|
TargetLabel: paramLabel,
|
||||||
Replacement: expectedParam,
|
Replacement: expectedParam,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: relabel.DefaultRelabelConfig.Action,
|
Action: relabel.DefaultRelabelConfig.Action,
|
||||||
Regex: relabel.DefaultRelabelConfig.Regex,
|
Regex: relabel.DefaultRelabelConfig.Regex,
|
||||||
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
SourceLabels: relabel.DefaultRelabelConfig.SourceLabels,
|
||||||
TargetLabel: model.MetricsPathLabel,
|
TargetLabel: model.MetricsPathLabel,
|
||||||
Replacement: expectedPath,
|
Replacement: expectedPath,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -766,10 +766,10 @@ func (it *chunkedSeriesIterator) Err() error {
|
|||||||
// also making sure that there are no labels with duplicate names.
|
// also making sure that there are no labels with duplicate names.
|
||||||
func validateLabelsAndMetricName(ls []prompb.Label) error {
|
func validateLabelsAndMetricName(ls []prompb.Label) error {
|
||||||
for i, l := range ls {
|
for i, l := range ls {
|
||||||
if l.Name == labels.MetricName && !model.IsValidMetricName(model.LabelValue(l.Value)) {
|
if l.Name == labels.MetricName && !model.UTF8Validation.IsValidMetricName(l.Value) {
|
||||||
return fmt.Errorf("invalid metric name: %v", l.Value)
|
return fmt.Errorf("invalid metric name: %v", l.Value)
|
||||||
}
|
}
|
||||||
if !model.LabelName(l.Name).IsValid() {
|
if !model.UTF8Validation.IsValidLabelName(l.Name) {
|
||||||
return fmt.Errorf("invalid label name: %v", l.Name)
|
return fmt.Errorf("invalid label name: %v", l.Name)
|
||||||
}
|
}
|
||||||
if !model.LabelValue(l.Value).IsValid() {
|
if !model.LabelValue(l.Value).IsValid() {
|
||||||
|
@ -1418,12 +1418,13 @@ func BenchmarkStoreSeries(b *testing.B) {
|
|||||||
{Name: "replica", Value: "1"},
|
{Name: "replica", Value: "1"},
|
||||||
}
|
}
|
||||||
relabelConfigs := []*relabel.Config{{
|
relabelConfigs := []*relabel.Config{{
|
||||||
SourceLabels: model.LabelNames{"namespace"},
|
SourceLabels: model.LabelNames{"namespace"},
|
||||||
Separator: ";",
|
Separator: ";",
|
||||||
Regex: relabel.MustNewRegexp("kube.*"),
|
Regex: relabel.MustNewRegexp("kube.*"),
|
||||||
TargetLabel: "job",
|
TargetLabel: "job",
|
||||||
Replacement: "$1",
|
Replacement: "$1",
|
||||||
Action: relabel.Replace,
|
Action: relabel.Replace,
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
}}
|
}}
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -282,7 +282,8 @@ func TestWriteStorageApplyConfig_PartialUpdate(t *testing.T) {
|
|||||||
QueueConfig: config.DefaultQueueConfig,
|
QueueConfig: config.DefaultQueueConfig,
|
||||||
WriteRelabelConfigs: []*relabel.Config{
|
WriteRelabelConfigs: []*relabel.Config{
|
||||||
{
|
{
|
||||||
Regex: relabel.MustNewRegexp(".+"),
|
Regex: relabel.MustNewRegexp(".+"),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ProtobufMessage: config.RemoteWriteProtoMsgV1,
|
ProtobufMessage: config.RemoteWriteProtoMsgV1,
|
||||||
@ -329,7 +330,10 @@ func TestWriteStorageApplyConfig_PartialUpdate(t *testing.T) {
|
|||||||
|
|
||||||
storeHashes()
|
storeHashes()
|
||||||
// Update c0 and c2.
|
// Update c0 and c2.
|
||||||
c0.WriteRelabelConfigs[0] = &relabel.Config{Regex: relabel.MustNewRegexp("foo")}
|
c0.WriteRelabelConfigs[0] = &relabel.Config{
|
||||||
|
Regex: relabel.MustNewRegexp("foo"),
|
||||||
|
NameValidationScheme: model.UTF8Validation,
|
||||||
|
}
|
||||||
c2.RemoteTimeout = model.Duration(50 * time.Second)
|
c2.RemoteTimeout = model.Duration(50 * time.Second)
|
||||||
conf = &config.Config{
|
conf = &config.Config{
|
||||||
GlobalConfig: config.GlobalConfig{},
|
GlobalConfig: config.GlobalConfig{},
|
||||||
|
@ -790,8 +790,7 @@ func (api *API) labelValues(r *http.Request) (result apiFuncResult) {
|
|||||||
name = model.UnescapeName(name, model.ValueEncodingEscaping)
|
name = model.UnescapeName(name, model.ValueEncodingEscaping)
|
||||||
}
|
}
|
||||||
|
|
||||||
label := model.LabelName(name)
|
if !model.UTF8Validation.IsValidLabelName(name) {
|
||||||
if !label.IsValid() {
|
|
||||||
return apiFuncResult{nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}, nil, nil}
|
return apiFuncResult{nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,11 +311,12 @@ func (m *rulesRetrieverMock) CreateRuleGroups() {
|
|||||||
}
|
}
|
||||||
engine := promqltest.NewTestEngineWithOpts(m.testing, engineOpts)
|
engine := promqltest.NewTestEngineWithOpts(m.testing, engineOpts)
|
||||||
opts := &rules.ManagerOptions{
|
opts := &rules.ManagerOptions{
|
||||||
QueryFunc: rules.EngineQueryFunc(engine, storage),
|
NameValidationScheme: model.UTF8Validation,
|
||||||
Appendable: storage,
|
QueryFunc: rules.EngineQueryFunc(engine, storage),
|
||||||
Context: context.Background(),
|
Appendable: storage,
|
||||||
Logger: promslog.NewNopLogger(),
|
Context: context.Background(),
|
||||||
NotifyFunc: func(_ context.Context, _ string, _ ...*rules.Alert) {},
|
Logger: promslog.NewNopLogger(),
|
||||||
|
NotifyFunc: func(_ context.Context, _ string, _ ...*rules.Alert) {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var r []rules.Rule
|
var r []rules.Rule
|
||||||
|
Loading…
Reference in New Issue
Block a user