mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmalert: parse multi doc yaml (#6995)
### Describe Your Changes This PR adds the feature to parse a multi yaml doc following the `\n---\n` The issue is [6753](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6753) ### Checklist The following checks are **mandatory**: - [x] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/). --------- Signed-off-by: kirti purohit <kirti.purohit@hpe.com> Co-authored-by: kirti purohit <kirti.purohit@hpe.com> Co-authored-by: Jiekun <jiekun@victoriametrics.com> Co-authored-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
parent
e2c73dc89f
commit
008b649658
9 changed files with 175 additions and 19 deletions
|
@ -1,19 +1,20 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config/log"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config/log"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Group contains list of Rules grouped into
|
// Group contains list of Rules grouped into
|
||||||
|
@ -298,16 +299,30 @@ func parseConfig(data []byte) ([]Group, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot expand environment vars: %w", err)
|
return nil, fmt.Errorf("cannot expand environment vars: %w", err)
|
||||||
}
|
}
|
||||||
g := struct {
|
|
||||||
|
var result []Group
|
||||||
|
type cfgFile struct {
|
||||||
Groups []Group `yaml:"groups"`
|
Groups []Group `yaml:"groups"`
|
||||||
// Catches all undefined fields and must be empty after parsing.
|
// Catches all undefined fields and must be empty after parsing.
|
||||||
XXX map[string]any `yaml:",inline"`
|
XXX map[string]any `yaml:",inline"`
|
||||||
}{}
|
|
||||||
err = yaml.Unmarshal(data, &g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return g.Groups, checkOverflow(g.XXX, "config")
|
|
||||||
|
decoder := yaml.NewDecoder(bytes.NewReader(data))
|
||||||
|
for {
|
||||||
|
var cf cfgFile
|
||||||
|
if err = decoder.Decode(&cf); err != nil {
|
||||||
|
if err == io.EOF { // EOF indicates no more documents to read
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = checkOverflow(cf.XXX, "config"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, cf.Groups...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOverflow(m map[string]any, ctx string) error {
|
func checkOverflow(m map[string]any, ctx string) error {
|
||||||
|
|
|
@ -9,11 +9,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
@ -40,6 +39,34 @@ groups:
|
||||||
w.Write([]byte(`
|
w.Write([]byte(`
|
||||||
groups:
|
groups:
|
||||||
- name: TestGroup
|
- name: TestGroup
|
||||||
|
rules:
|
||||||
|
- record: conns
|
||||||
|
expr: max(vm_tcplistener_conns)`))
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/good-multi-doc", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Write([]byte(`
|
||||||
|
groups:
|
||||||
|
- name: foo
|
||||||
|
rules:
|
||||||
|
- record: conns
|
||||||
|
expr: max(vm_tcplistener_conns)
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: bar
|
||||||
|
rules:
|
||||||
|
- record: conns
|
||||||
|
expr: max(vm_tcplistener_conns)`))
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/bad-multi-doc", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Write([]byte(`
|
||||||
|
bad_field:
|
||||||
|
- name: foo
|
||||||
|
rules:
|
||||||
|
- record: conns
|
||||||
|
expr: max(vm_tcplistener_conns)
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: bar
|
||||||
rules:
|
rules:
|
||||||
- record: conns
|
- record: conns
|
||||||
expr: max(vm_tcplistener_conns)`))
|
expr: max(vm_tcplistener_conns)`))
|
||||||
|
@ -48,13 +75,23 @@ groups:
|
||||||
srv := httptest.NewServer(mux)
|
srv := httptest.NewServer(mux)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
if _, err := Parse([]string{srv.URL + "/good-alert", srv.URL + "/good-rr"}, notifier.ValidateTemplates, true); err != nil {
|
f := func(urls []string, expErr bool) {
|
||||||
t.Fatalf("error parsing URLs %s", err)
|
for i, u := range urls {
|
||||||
|
urls[i] = srv.URL + u
|
||||||
|
}
|
||||||
|
_, err := Parse(urls, notifier.ValidateTemplates, true)
|
||||||
|
if err != nil && !expErr {
|
||||||
|
t.Fatalf("error parsing URLs %s", err)
|
||||||
|
}
|
||||||
|
if err == nil && expErr {
|
||||||
|
t.Fatalf("expecting error parsing URLs but got none")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := Parse([]string{srv.URL + "/bad"}, notifier.ValidateTemplates, true); err == nil {
|
f([]string{"/good-alert", "/good-rr", "/good-multi-doc"}, false)
|
||||||
t.Fatalf("expected parsing error: %s", err)
|
f([]string{"/bad"}, true)
|
||||||
}
|
f([]string{"/bad-multi-doc"}, true)
|
||||||
|
f([]string{"/good-alert", "/bad"}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse_Success(t *testing.T) {
|
func TestParse_Success(t *testing.T) {
|
||||||
|
@ -86,6 +123,8 @@ func TestParse_Failure(t *testing.T) {
|
||||||
f([]string{"testdata/dir/rules4-bad.rules"}, "either `record` or `alert` must be set")
|
f([]string{"testdata/dir/rules4-bad.rules"}, "either `record` or `alert` must be set")
|
||||||
f([]string{"testdata/rules/rules1-bad.rules"}, "bad graphite expr")
|
f([]string{"testdata/rules/rules1-bad.rules"}, "bad graphite expr")
|
||||||
f([]string{"testdata/dir/rules6-bad.rules"}, "missing ':' in header")
|
f([]string{"testdata/dir/rules6-bad.rules"}, "missing ':' in header")
|
||||||
|
f([]string{"testdata/rules/rules-multi-doc-bad.rules"}, "unknown fields")
|
||||||
|
f([]string{"testdata/rules/rules-multi-doc-duplicates-bad.rules"}, "duplicate")
|
||||||
f([]string{"http://unreachable-url"}, "failed to")
|
f([]string{"http://unreachable-url"}, "failed to")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
29
app/vmalert/config/testdata/rules/rules-multi-doc-bad.rules
vendored
Normal file
29
app/vmalert/config/testdata/rules/rules-multi-doc-bad.rules
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
groups:
|
||||||
|
- name: groupTest
|
||||||
|
rules:
|
||||||
|
- alert: VMRows
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows > 0
|
||||||
|
labels:
|
||||||
|
label: bar
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "{{ $value }}"
|
||||||
|
invalid-field-1: invalid-value-1
|
||||||
|
invalid-field-2: invalid-value-2
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: TestGroup
|
||||||
|
interval: 2s
|
||||||
|
concurrency: 2
|
||||||
|
type: graphite
|
||||||
|
rules:
|
||||||
|
- alert: Conns
|
||||||
|
expr: filterSeries(sumSeries(host.receiver.interface.cons),'last','>', 500)
|
||||||
|
for: 3m
|
||||||
|
|
||||||
|
annotations:
|
||||||
|
summary: Too high connection number for {{$labels.instance}}
|
||||||
|
description: "It is {{ $value }} connections for {{$labels.instance}}"
|
||||||
|
invalid-field-2: invalid-value-2
|
||||||
|
invalid-field-3: invalid-value-3
|
11
app/vmalert/config/testdata/rules/rules-multi-doc-duplicates-bad.rules
vendored
Normal file
11
app/vmalert/config/testdata/rules/rules-multi-doc-duplicates-bad.rules
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
groups:
|
||||||
|
- name: foo
|
||||||
|
rules:
|
||||||
|
- alert: VMRows
|
||||||
|
expr: vm_rows > 0
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: foo
|
||||||
|
rules:
|
||||||
|
- alert: VMRows
|
||||||
|
expr: vm_rows > 0
|
15
app/vmalert/config/testdata/rules/rules-multi-doc-good.rules
vendored
Normal file
15
app/vmalert/config/testdata/rules/rules-multi-doc-good.rules
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: groupTest
|
||||||
|
rules:
|
||||||
|
- alert: VMRows
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows > 0
|
||||||
|
labels:
|
||||||
|
label: bar
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "{{ $value }}"
|
||||||
|
---
|
||||||
|
groups:
|
46
app/vmalert/config/testdata/rules/rules-multi-doc2-good.rules
vendored
Normal file
46
app/vmalert/config/testdata/rules/rules-multi-doc2-good.rules
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: groupTest
|
||||||
|
rules:
|
||||||
|
- alert: VMRows
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows > 0
|
||||||
|
labels:
|
||||||
|
label: bar
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "{{ $value }}"
|
||||||
|
- name: groupTest-2
|
||||||
|
rules:
|
||||||
|
- alert: VMRows-2
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows_2 > 0
|
||||||
|
labels:
|
||||||
|
label: bar2
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "\n markdown result is : \n---\n # header\n body: \n text \n----\n"
|
||||||
|
---
|
||||||
|
groups:
|
||||||
|
- name: groupTest-3
|
||||||
|
rules:
|
||||||
|
- alert: VMRows-3
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows_3 > 0
|
||||||
|
labels:
|
||||||
|
label: bar_3
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "{{ $value }}"
|
||||||
|
- name: groupTest-4
|
||||||
|
rules:
|
||||||
|
- alert: VMRows-4
|
||||||
|
for: 1ms
|
||||||
|
expr: vm_rows_4 > 0
|
||||||
|
labels:
|
||||||
|
label: bar4
|
||||||
|
host: "{{ $labels.instance }}"
|
||||||
|
annotations:
|
||||||
|
summary: "{{ $value }}"
|
||||||
|
---
|
||||||
|
groups:
|
|
@ -31,14 +31,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rulePath = flagutil.NewArrayString("rule", `Path to the files or http url with alerting and/or recording rules.
|
rulePath = flagutil.NewArrayString("rule", `Path to the files or http url with alerting and/or recording rules in YAML format.
|
||||||
Supports hierarchical patterns and regexpes.
|
Supports hierarchical patterns and regexpes.
|
||||||
Examples:
|
Examples:
|
||||||
-rule="/path/to/file". Path to a single file with alerting rules.
|
-rule="/path/to/file". Path to a single file with alerting rules.
|
||||||
-rule="http://<some-server-addr>/path/to/rules". HTTP URL to a page with alerting rules.
|
-rule="http://<some-server-addr>/path/to/rules". HTTP URL to a page with alerting rules.
|
||||||
-rule="dir/*.yaml" -rule="/*.yaml" -rule="gcs://vmalert-rules/tenant_%{TENANT_ID}/prod".
|
-rule="dir/*.yaml" -rule="/*.yaml" -rule="gcs://vmalert-rules/tenant_%{TENANT_ID}/prod".
|
||||||
-rule="dir/**/*.yaml". Includes all the .yaml files in "dir" subfolders recursively.
|
-rule="dir/**/*.yaml". Includes all the .yaml files in "dir" subfolders recursively.
|
||||||
Rule files may contain %{ENV_VAR} placeholders, which are substituted by the corresponding env vars.
|
Rule files support YAML multi-document. Files may contain %{ENV_VAR} placeholders, which are substituted by the corresponding env vars.
|
||||||
|
|
||||||
Enterprise version of vmalert supports S3 and GCS paths to rules.
|
Enterprise version of vmalert supports S3 and GCS paths to rules.
|
||||||
For example: gs://bucket/path/to/rules, s3://bucket/path/to/rules
|
For example: gs://bucket/path/to/rules, s3://bucket/path/to/rules
|
||||||
|
|
|
@ -20,6 +20,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||||
|
|
||||||
* FEATURE: add Darwin binaries for [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) to the release flow. The binaries will be available in the new release.
|
* FEATURE: add Darwin binaries for [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) to the release flow. The binaries will be available in the new release.
|
||||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/): allow using HTTP/2 client for Kubernetes service discovery if `-promscrape.kubernetes.useHTTP2Client` cmd-line flag is set. This could help to reduce the amount of opened connections to the Kubernetes API server. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5971) for the details.
|
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/): allow using HTTP/2 client for Kubernetes service discovery if `-promscrape.kubernetes.useHTTP2Client` cmd-line flag is set. This could help to reduce the amount of opened connections to the Kubernetes API server. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5971) for the details.
|
||||||
|
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert/): `-rule` cmd-line flag now supports multi-document YAML files. This could be usefule when rules are retrieved from via HTTP where multiple rule files were merged together in one response. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6753). Thanks to @Irene-123 for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6995).
|
||||||
|
|
||||||
## [v1.104.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.104.0)
|
## [v1.104.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.104.0)
|
||||||
|
|
||||||
|
|
|
@ -1427,14 +1427,14 @@ The shortlist of configuration flags is the following:
|
||||||
-replay.timeTo string
|
-replay.timeTo string
|
||||||
The time filter in RFC3339 format to finish the replay by. E.g. '2020-01-01T20:07:00Z'. By default, is set to the current time.
|
The time filter in RFC3339 format to finish the replay by. E.g. '2020-01-01T20:07:00Z'. By default, is set to the current time.
|
||||||
-rule array
|
-rule array
|
||||||
Path to the files or http url with alerting and/or recording rules.
|
Path to the files or http url with alerting and/or recording rules in YAML format.
|
||||||
Supports hierarchical patterns and regexpes.
|
Supports hierarchical patterns and regexpes.
|
||||||
Examples:
|
Examples:
|
||||||
-rule="/path/to/file". Path to a single file with alerting rules.
|
-rule="/path/to/file". Path to a single file with alerting rules.
|
||||||
-rule="http://<some-server-addr>/path/to/rules". HTTP URL to a page with alerting rules.
|
-rule="http://<some-server-addr>/path/to/rules". HTTP URL to a page with alerting rules.
|
||||||
-rule="dir/*.yaml" -rule="/*.yaml" -rule="gcs://vmalert-rules/tenant_%{TENANT_ID}/prod".
|
-rule="dir/*.yaml" -rule="/*.yaml" -rule="gcs://vmalert-rules/tenant_%{TENANT_ID}/prod".
|
||||||
-rule="dir/**/*.yaml". Includes all the .yaml files in "dir" subfolders recursively.
|
-rule="dir/**/*.yaml". Includes all the .yaml files in "dir" subfolders recursively.
|
||||||
Rule files may contain %{ENV_VAR} placeholders, which are substituted by the corresponding env vars.
|
Rule files support YAML multi-document. Files may contain %{ENV_VAR} placeholders, which are substituted by the corresponding env vars.
|
||||||
Enterprise version of vmalert supports S3 and GCS paths to rules.
|
Enterprise version of vmalert supports S3 and GCS paths to rules.
|
||||||
For example: gs://bucket/path/to/rules, s3://bucket/path/to/rules
|
For example: gs://bucket/path/to/rules, s3://bucket/path/to/rules
|
||||||
S3 and GCS paths support only matching by prefix, e.g. s3://bucket/dir/rule_ matches
|
S3 and GCS paths support only matching by prefix, e.g. s3://bucket/dir/rule_ matches
|
||||||
|
|
Loading…
Reference in a new issue