From fcc35ba55112d2d434a911ea528e7ee690a9c032 Mon Sep 17 00:00:00 2001 From: zhaowang Date: Fri, 23 Aug 2024 12:16:05 +0800 Subject: [PATCH] PromTool: Support Scrape Interval Lint Checking Signed-off-by: zhaowang --- cmd/promtool/main.go | 40 ++++++++++++++----- cmd/promtool/main_test.go | 15 +++++-- .../testdata/prometheus-config.lint.yml | 5 ++- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 4d033c3c09..d11c203b79 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -68,11 +68,15 @@ const ( // Exit code 3 is used for "one or more lint issues detected". lintErrExitCode = 3 - lintOptionAll = "all" - lintOptionDuplicateRules = "duplicate-rules" - lintOptionNone = "none" - checkHealth = "/-/healthy" - checkReadiness = "/-/ready" + lintOptionAll = "all" + lintOptionDuplicateRules = "duplicate-rules" + lintOptionLongScrapeInterval = "long-scrape-inerval" + lintOptionNone = "none" + checkHealth = "/-/healthy" + checkReadiness = "/-/ready" + + // Remove when the lookback delta can be set as a command line flag on PromTool. + defaultLookbackDelta = 5 * time.Minute ) var lintOptions = []string{lintOptionAll, lintOptionDuplicateRules, lintOptionNone} @@ -443,9 +447,10 @@ func checkExperimental(f bool) { var errLint = fmt.Errorf("lint error") type lintConfig struct { - all bool - duplicateRules bool - fatal bool + all bool + duplicateRules bool + longScrapeInterval bool + fatal bool } func newLintConfig(stringVal string, fatal bool) lintConfig { @@ -459,6 +464,8 @@ func newLintConfig(stringVal string, fatal bool) lintConfig { ls.all = true case lintOptionDuplicateRules: ls.duplicateRules = true + case lintOptionLongScrapeInterval: + ls.longScrapeInterval = true case lintOptionNone: default: fmt.Printf("WARNING: unknown lint option %s\n", setting) @@ -471,6 +478,10 @@ func (ls lintConfig) lintDuplicateRules() bool { return ls.all || ls.duplicateRules } +func (ls lintConfig) lintLongScrapeInterval() bool { + return ls.all || ls.longScrapeInterval +} + // CheckServerStatus - healthy & ready. func CheckServerStatus(serverURL *url.URL, checkEndpoint string, roundTripper http.RoundTripper) error { if serverURL.Scheme == "" { @@ -514,10 +525,10 @@ func CheckConfig(agentMode, checkSyntaxOnly bool, lintSettings lintConfig, files hasErrors := false for _, f := range files { - ruleFiles, err := checkConfig(agentMode, f, checkSyntaxOnly) + ruleFiles, err := checkConfig(agentMode, f, checkSyntaxOnly, lintSettings) if err != nil { fmt.Fprintln(os.Stderr, " FAILED:", err) - hasErrors = true + hasErrors = !errors.Is(err, errLint) failed = true } else { if len(ruleFiles) > 0 { @@ -571,7 +582,7 @@ func checkFileExists(fn string) error { return err } -func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]string, error) { +func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool, lintSettings lintConfig) ([]string, error) { fmt.Println("Checking", filename) cfg, err := config.LoadFile(filename, agentMode, false, log.NewNopLogger()) @@ -611,6 +622,13 @@ func checkConfig(agentMode bool, filename string, checkSyntaxOnly bool) ([]strin } for _, scfg := range scfgs { + if lintSettings.lintLongScrapeInterval() { + if scfg.ScrapeInterval >= model.Duration(defaultLookbackDelta) { + errMessage := fmt.Sprintf("Long Scrape Interval found. Data point will be marked as stale. Job: %s. Interval: %s", scfg.JobName, scfg.ScrapeInterval.String()) + return nil, fmt.Errorf("%w %s", errLint, errMessage) + } + } + if !checkSyntaxOnly && scfg.HTTPClientConfig.Authorization != nil { if err := checkFileExists(scfg.HTTPClientConfig.Authorization.CredentialsFile); err != nil { return nil, fmt.Errorf("error checking authorization credentials or bearer token file %q: %w", scfg.HTTPClientConfig.Authorization.CredentialsFile, err) diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index 78500fe937..d499d0d06d 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -219,7 +219,7 @@ func TestCheckTargetConfig(t *testing.T) { } for _, test := range cases { t.Run(test.name, func(t *testing.T) { - _, err := checkConfig(false, "testdata/"+test.file, false) + _, err := checkConfig(false, "testdata/"+test.file, false, newLintConfig(lintOptionNone, false)) if test.err != "" { require.Equalf(t, test.err, err.Error(), "Expected error %q, got %q", test.err, err.Error()) return @@ -302,7 +302,7 @@ func TestCheckConfigSyntax(t *testing.T) { } for _, test := range cases { t.Run(test.name, func(t *testing.T) { - _, err := checkConfig(false, "testdata/"+test.file, test.syntaxOnly) + _, err := checkConfig(false, "testdata/"+test.file, test.syntaxOnly, newLintConfig(lintOptionNone, false)) expectedErrMsg := test.err if strings.Contains(runtime.GOOS, "windows") { expectedErrMsg = test.errWindows @@ -336,7 +336,7 @@ func TestAuthorizationConfig(t *testing.T) { for _, test := range cases { t.Run(test.name, func(t *testing.T) { - _, err := checkConfig(false, "testdata/"+test.file, false) + _, err := checkConfig(false, "testdata/"+test.file, false, newLintConfig(lintOptionNone, false)) if test.err != "" { require.Contains(t, err.Error(), test.err, "Expected error to contain %q, got %q", test.err, err.Error()) return @@ -414,7 +414,7 @@ func TestExitCodes(t *testing.T) { t.Run(strconv.FormatBool(lintFatal), func(t *testing.T) { args := []string{"-test.main", "check", "config", "testdata/" + c.file} if lintFatal { - args = append(args, "--lint-fatal") + args = append(args, "--lint-fatal", "--lint=all") } tool := exec.Command(promtoolPath, args...) err := tool.Run() @@ -531,6 +531,13 @@ func TestCheckRules(t *testing.T) { exitCode := CheckRules(newLintConfig(lintOptionDuplicateRules, true)) require.Equal(t, lintErrExitCode, exitCode, "") }) + + t.Run("config-lint-fatal", func(t *testing.T) { + _, err := checkConfig(false, "./testdata/prometheus-config.lint.yml", false, newLintConfig(lintOptionLongScrapeInterval, false)) + expectedErrMsg := "lint error Long Scrape Interval found. Data point will be marked as stale. Job: long_scrape_interval_test. Interval: 1h" + require.Equalf(t, expectedErrMsg, err.Error(), "Expected error %q, got %q", expectedErrMsg, err.Error()) + return + }) } func TestCheckRulesWithRuleFiles(t *testing.T) { diff --git a/cmd/promtool/testdata/prometheus-config.lint.yml b/cmd/promtool/testdata/prometheus-config.lint.yml index ffa7c38434..c68510a00a 100644 --- a/cmd/promtool/testdata/prometheus-config.lint.yml +++ b/cmd/promtool/testdata/prometheus-config.lint.yml @@ -1,2 +1,3 @@ -rule_files: - - prometheus-rules.lint.yml +scrape_configs: + - job_name: long_scrape_interval_test + scrape_interval: 1h \ No newline at end of file