-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Prometheus : parse alerting before loading WAL #16462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
2ca87e7
to
b76fec7
Compare
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
Signed-off-by: pc-casa-ubuntu <marcodebonis74@gmail.com>
func ParseFile(fn string) (interface{}, error) { | ||
content, err := os.ReadFile(fn) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var rules interface{} | ||
err = yaml.Unmarshal(content, &rules) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return rules, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an existing function in the rulefmt
package in prometheus called ParseFile()
that seems to do both yaml validation as well as validation of the literal rule groups. Is this new function necessary?
prometheus/model/rulefmt/rulefmt.go
Lines 364 to 375 in 9659e30
// ParseFile reads and parses rules from a file. | |
func ParseFile(file string, ignoreUnknownFields bool) (*RuleGroups, []error) { | |
b, err := os.ReadFile(file) | |
if err != nil { | |
return nil, []error{fmt.Errorf("%s: %w", file, err)} | |
} | |
rgs, errs := Parse(b, ignoreUnknownFields) | |
for i := range errs { | |
errs[i] = fmt.Errorf("%s: %w", file, errs[i]) | |
} | |
return rgs, errs | |
} |
.idea/modules.xml | ||
.idea/prometheus.iml | ||
.idea/vcs.xml | ||
.idea/workspace.xml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should go in your global git ignore file:
https://sebastiandedeyne.com/setting-up-a-global-gitignore-file/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spurious lines are still in here. Please remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, let me fix this evening . Thanks for suggestions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes #11486 by introducing early parsing and validation of alerting rules before loading the WAL.
- Added a new ParseFile function in rules/manager.go to parse YAML rule files.
- Introduced a TestParseFile unit test in rules/manager_test.go to verify YAML parsing.
- Updated cmd/prometheus/main.go to integrate rule file parsing and exit early on errors.
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
File | Description |
---|---|
rules/manager_test.go | Added TestParseFile to verify YAML parsing of rule files. |
rules/manager.go | Introduced ParseFile to load and unmarshal YAML rule definitions. |
cmd/prometheus/main.go | Integrated rule file parsing and error handling during startup. |
Comments suppressed due to low confidence (1)
rules/manager_test.go:2587
- [nitpick] To improve test coverage and robustness, add concrete assertions to validate the structure of the parsed rules rather than checking only for a non-nil result.
// Optionally, you can add more assertions to validate the structure of the parsed rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @tjhop that rulefmt.ParseFile
should be used.
|
||
// This section of the code is responsible for validating and parsing rule files | ||
// specified in the Prometheus configuration. It ensures that all rule files | ||
// match the provided patterns and are syntactically correct before Prometheus | ||
// starts processing them. | ||
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`). | ||
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files. | ||
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code. | ||
// 4. For each matching file, attempt to parse it using `rules.ParseFile`. | ||
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code. | ||
// | ||
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered. | ||
|
||
if len(cfgFile.RuleFiles) > 0 { | ||
for _, pat := range cfgFile.RuleFiles { | ||
files, err := filepath.Glob(pat) | ||
if err != nil { | ||
absPath, pathErr := filepath.Abs(pat) | ||
if pathErr != nil { | ||
absPath = pat | ||
} | ||
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err) | ||
os.Exit(2) | ||
} | ||
for _, fn := range files { | ||
if _, err := rules.ParseFile(fn); err != nil { | ||
absPath, pathErr := filepath.Abs(fn) | ||
if pathErr != nil { | ||
absPath = fn | ||
} | ||
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err) | ||
os.Exit(2) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to fully embed the logic in a function in the rules
package:
// This section of the code is responsible for validating and parsing rule files | |
// specified in the Prometheus configuration. It ensures that all rule files | |
// match the provided patterns and are syntactically correct before Prometheus | |
// starts processing them. | |
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`). | |
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files. | |
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code. | |
// 4. For each matching file, attempt to parse it using `rules.ParseFile`. | |
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code. | |
// | |
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered. | |
if len(cfgFile.RuleFiles) > 0 { | |
for _, pat := range cfgFile.RuleFiles { | |
files, err := filepath.Glob(pat) | |
if err != nil { | |
absPath, pathErr := filepath.Abs(pat) | |
if pathErr != nil { | |
absPath = pat | |
} | |
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err) | |
os.Exit(2) | |
} | |
for _, fn := range files { | |
if _, err := rules.ParseFile(fn); err != nil { | |
absPath, pathErr := filepath.Abs(fn) | |
if pathErr != nil { | |
absPath = fn | |
} | |
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err) | |
os.Exit(2) | |
} | |
} | |
} | |
} | |
if err := rules.ParseFiles(cfgFile.RuleFiles); err != nil { | |
absPath, pathErr := filepath.Abs(cfg.configFile) | |
if pathErr != nil { | |
absPath = cfg.configFile | |
} | |
logger.Error(fmt.Sprintf("Error loading rule file patterns from config (--config.file=%q)", cfg.configFile), "file", absPath, "err", err) | |
os.Exit(2) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment hasn't been addressed yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used the existing function and deleted my not useful method.
func ParseFile(fn string) (interface{}, error) { | ||
content, err := os.ReadFile(fn) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var rules interface{} | ||
err = yaml.Unmarshal(content, &rules) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return rules, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following makes more sense to me:
func ParseFile(fn string) (interface{}, error) { | |
content, err := os.ReadFile(fn) | |
if err != nil { | |
return nil, err | |
} | |
var rules interface{} | |
err = yaml.Unmarshal(content, &rules) | |
if err != nil { | |
return nil, err | |
} | |
return rules, nil | |
} | |
// ParseFiles parses the rule files corresponding to glob patterns. | |
func ParseFiles(patterns []string) error { | |
files := map[string]string{} | |
for _, pat := range patterns { | |
fns, err := filepath.Glob(pat) | |
if err != nil { | |
return fmt.Errorf("failed retrieving rule files for %q: %w", pat, err) | |
} | |
for _, fn := range fns { | |
absPath, err := filepath.Abs(fn) | |
if err != nil { | |
absPath = fn | |
} | |
cleanPath, err := filepath.EvalSymlinks(absPath) | |
if err != nil { | |
return fmt.Errorf("failed evaluating rule file path %q (pattern: %q): %w", absPath, pat, err) | |
} | |
files[cleanPath] = pat | |
} | |
} | |
for fn, pat := range files { | |
_, errs := rulefmt.ParseFile(fn, false) | |
if len(errs) > 0 { | |
return fmt.Errorf("parse rules from file %q (pattern: %q): %w", fn, pat, errors.Join(errs...)) | |
} | |
} | |
return nil | |
} |
func TestParseFile(t *testing.T) { | ||
// Create a temporary directory for the test. | ||
tmpDir := t.TempDir() | ||
|
||
// Define the test file path. | ||
testFile := filepath.Join(tmpDir, "test_rules.yml") | ||
|
||
// Define the content of the test YAML file. | ||
testContent := ` | ||
groups: | ||
- name: example | ||
rules: | ||
- alert: HighRequestLatency | ||
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 | ||
for: 10m | ||
labels: | ||
severity: warning | ||
annotations: | ||
summary: "High request latency" | ||
` | ||
|
||
// Write the test content to the file. | ||
err := os.WriteFile(testFile, []byte(testContent), 0o644) | ||
require.NoError(t, err) | ||
|
||
// Call the ParseFile function. | ||
result, err := ParseFile(testFile) | ||
require.NoError(t, err) | ||
|
||
// Assert that the result is not nil. | ||
require.NotNil(t, result, "Expected parsed rules to be non-nil") | ||
|
||
// Optionally, you can add more assertions to validate the structure of the parsed rules. | ||
// For example, if you know the expected structure, you can cast `result` to the appropriate type | ||
// and check its fields. | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified as follows, even with an error case added. Note that the error case needs an extra file invalid-rules.yaml
in rules/fixtures/.
func TestParseFile(t *testing.T) { | |
// Create a temporary directory for the test. | |
tmpDir := t.TempDir() | |
// Define the test file path. | |
testFile := filepath.Join(tmpDir, "test_rules.yml") | |
// Define the content of the test YAML file. | |
testContent := ` | |
groups: | |
- name: example | |
rules: | |
- alert: HighRequestLatency | |
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 | |
for: 10m | |
labels: | |
severity: warning | |
annotations: | |
summary: "High request latency" | |
` | |
// Write the test content to the file. | |
err := os.WriteFile(testFile, []byte(testContent), 0o644) | |
require.NoError(t, err) | |
// Call the ParseFile function. | |
result, err := ParseFile(testFile) | |
require.NoError(t, err) | |
// Assert that the result is not nil. | |
require.NotNil(t, result, "Expected parsed rules to be non-nil") | |
// Optionally, you can add more assertions to validate the structure of the parsed rules. | |
// For example, if you know the expected structure, you can cast `result` to the appropriate type | |
// and check its fields. | |
} | |
func TestParseFiles(t *testing.T) { | |
t.Run("good files", func(t *testing.T) { | |
err := ParseFiles([]string{filepath.Join("fixtures", "rules.y*ml")}) | |
require.NoError(t, err) | |
}) | |
t.Run("bad files", func(t *testing.T) { | |
err := ParseFiles([]string{filepath.Join("fixtures", "invalid-rules.y*ml")}) | |
require.ErrorContains(t, err, "field unexpected_field not found in type rulefmt.Rule") | |
}) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @tjhop and @aknuds1 for your reviews.
@marcodebba you resolved all the comments, but you haven't really addressed them.
Have you maybe forgotten to push your latest changes?
I agree with everything the reviewers said, and I think you should address their comments.
.idea/modules.xml | ||
.idea/prometheus.iml | ||
.idea/vcs.xml | ||
.idea/workspace.xml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spurious lines are still in here. Please remove them.
|
||
// This section of the code is responsible for validating and parsing rule files | ||
// specified in the Prometheus configuration. It ensures that all rule files | ||
// match the provided patterns and are syntactically correct before Prometheus | ||
// starts processing them. | ||
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`). | ||
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files. | ||
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code. | ||
// 4. For each matching file, attempt to parse it using `rules.ParseFile`. | ||
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code. | ||
// | ||
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered. | ||
|
||
if len(cfgFile.RuleFiles) > 0 { | ||
for _, pat := range cfgFile.RuleFiles { | ||
files, err := filepath.Glob(pat) | ||
if err != nil { | ||
absPath, pathErr := filepath.Abs(pat) | ||
if pathErr != nil { | ||
absPath = pat | ||
} | ||
logger.Error(fmt.Sprintf("Error loading rules pattern (--config.file=%q)", cfg.configFile), "file", absPath, "pattern", pat, "err", err) | ||
os.Exit(2) | ||
} | ||
for _, fn := range files { | ||
if _, err := rules.ParseFile(fn); err != nil { | ||
absPath, pathErr := filepath.Abs(fn) | ||
if pathErr != nil { | ||
absPath = fn | ||
} | ||
logger.Error(fmt.Sprintf("Error loading rules file (--config.file=%q)", cfg.configFile), "file", absPath, "err", err) | ||
os.Exit(2) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment hasn't been addressed yet.
// This section of the code is responsible for validating and parsing rule files | ||
// specified in the Prometheus configuration. It ensures that all rule files | ||
// match the provided patterns and are syntactically correct before Prometheus | ||
// starts processing them. | ||
// 1. Iterate through the list of rule file patterns specified in the configuration (`cfgFile.RuleFiles`). | ||
// 2. Use `filepath.Glob` to expand each pattern into a list of matching files. | ||
// 3. If an error occurs during pattern expansion, log the error, resolve the absolute path of the pattern, and exit with an error code. | ||
// 4. For each matching file, attempt to parse it using `rules.ParseFile`. | ||
// 5. If parsing fails, log the error, resolve the absolute path of the file, and exit with an error code. | ||
// | ||
// Errors are logged with details such as the configuration file path, the rule file pattern, and the specific error encountered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I like readable comments in proper English, this one is a bit too verbose.
A general good advice is that code should not be an English version of the Go code. You can assume that the reader of your code knows Go well (maybe even better than English ;).
If you move the code below into a function rules.ParseFiles
, your comment here can be its doc comment, but it should still focus more on what the function does rather than how.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for suggestions @beorn7 . I'll try to fix everything today
Co-authored-by: marcodebba <marcodebonis74@gmail.com> Co-authored-by: subhramit <subhramit.bb@live.in> Signed-off-by: subhramit <subhramit.bb@live.in>
Co-authored-by: marcodebba <marcodebonis74@gmail.com> Co-authored-by: subhramit <subhramit.bb@live.in> Signed-off-by: subhramit <subhramit.bb@live.in>
Builds over prometheus#16462 Co-authored-by: marcodebba <marcodebonis74@gmail.com> Co-authored-by: subhramit <subhramit.bb@live.in> Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
Builds over prometheus#16462 Addresses reviews, adds invalid rule test file Co-authored-by: marcodebba <marcodebon1s74@gmail.com> Co-authored-by: subhramit <subhramit.bb@live.in> Signed-off-by: Subhramit Basu <subhramit.bb@live.in>
Builds over prometheus#16462 Addresses comments, adds invalid rules file Co-authored-by: marcodebba <marcodebonis74@gmail.com> Co-authored-by: subhramit <subhramit.bb@live.in> Signed-off-by: subhramit <subhramit.bb@live.in>
Closing in lieu of #16601 . |
Builds over #16462 Addresses comments, adds invalid rules file Signed-off-by: subhramit <subhramit.bb@live.in> Co-authored-by: marcodebba <marcodebonis74@gmail.com>
Fixes #11486