Repository: VictoriaMetrics/VictoriaMetrics
Pull request #5397 (2023-11-27 12:19:56)

app/vmalert: expose `/vmalert/api/v1/rule` and `/api/v1/rule` API which returns rule status in JSON format
This PR exposes the `/vmalert/api/v1/rule` and `/api/v1/rule` API.

Related issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5395

Signed-off-by: dmitryk-dk [d.kozlov@victoriametrics.com](mailto:d.kozlov@victoriametrics.com)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Attention: `20 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`5034aa0`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/5034aa0773d4a38f4e89352b33da10559538593f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 60.12% compared to head [(`f5d592a`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 60.07%.
> Report is 4 commits behind head on master.

| [Files](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | Patch % | Lines |
|---|---|---|
| [app/vmalert/web.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvd2ViLmdv) | 0.00% | [14 Missing :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [app/vmagent/remotewrite/remotewrite.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWdlbnQvcmVtb3Rld3JpdGUvcmVtb3Rld3JpdGUuZ28=) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5397      +/-   ##
==========================================
- Coverage   60.12%   60.07%   -0.05%     
==========================================
  Files         399      399              
  Lines       75126    75144      +18     
==========================================
- Hits        45169    45144      -25     
- Misses      27445    27482      +37     
- Partials     2512     2518       +6     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/5397?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).

hagen1778: @dima-vm @f41gh7  please take a look at updates I've added in the last commits. 
This suppose to reduce amount of changes through the code and add tests. 

Patch:
diff --git a/app/vmalert/rule/alerting.go b/app/vmalert/rule/alerting.go
index ae10f4fe3..93584930d 100644
--- a/app/vmalert/rule/alerting.go
+++ b/app/vmalert/rule/alerting.go
@@ -30,6 +30,7 @@ type AlertingRule struct {
 	Annotations   map[string]string
 	GroupID       uint64
 	GroupName     string
+	File          string
 	EvalInterval  time.Duration
 	Debug         bool
 
@@ -67,6 +68,7 @@ func NewAlertingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule
 		Annotations:   cfg.Annotations,
 		GroupID:       group.ID(),
 		GroupName:     group.Name,
+		File:          group.File,
 		EvalInterval:  group.Interval,
 		Debug:         cfg.Debug,
 		q: qb.BuildWithParams(datasource.QuerierParams{
diff --git a/app/vmalert/rule/recording.go b/app/vmalert/rule/recording.go
index e74d1b830..196f51456 100644
--- a/app/vmalert/rule/recording.go
+++ b/app/vmalert/rule/recording.go
@@ -17,12 +17,14 @@ import (
 // to evaluate configured Expression and
 // return TimeSeries as result.
 type RecordingRule struct {
-	Type    config.Type
-	RuleID  uint64
-	Name    string
-	Expr    string
-	Labels  map[string]string
-	GroupID uint64
+	Type      config.Type
+	RuleID    uint64
+	Name      string
+	Expr      string
+	Labels    map[string]string
+	GroupID   uint64
+	GroupName string
+	File      string
 
 	q datasource.Querier
 
@@ -52,13 +54,15 @@ func (rr *RecordingRule) ID() uint64 {
 // NewRecordingRule creates a new RecordingRule
 func NewRecordingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule) *RecordingRule {
 	rr := &RecordingRule{
-		Type:    group.Type,
-		RuleID:  cfg.ID,
-		Name:    cfg.Record,
-		Expr:    cfg.Expr,
-		Labels:  cfg.Labels,
-		GroupID: group.ID(),
-		metrics: &recordingRuleMetrics{},
+		Type:      group.Type,
+		RuleID:    cfg.ID,
+		Name:      cfg.Record,
+		Expr:      cfg.Expr,
+		Labels:    cfg.Labels,
+		GroupID:   group.ID(),
+		GroupName: group.Name,
+		File:      group.File,
+		metrics:   &recordingRuleMetrics{},
 		q: qb.BuildWithParams(datasource.QuerierParams{
 			DataSourceType:     group.Type.String(),
 			EvaluationInterval: group.Interval,
diff --git a/app/vmalert/rule/rule.go b/app/vmalert/rule/rule.go
index 10728cff6..0bad2ff65 100644
--- a/app/vmalert/rule/rule.go
+++ b/app/vmalert/rule/rule.go
@@ -43,26 +43,26 @@ type ruleState struct {
 // StateEntry stores rule's execution states
 type StateEntry struct {
 	// stores last moment of time rule.Exec was called
-	Time time.Time
+	Time time.Time `json:"time"`
 	// stores the timesteamp with which rule.Exec was called
-	At time.Time
+	At time.Time `json:"at"`
 	// stores the duration of the last rule.Exec call
-	Duration time.Duration
+	Duration time.Duration `json:"duration"`
 	// stores last error that happened in Exec func
 	// resets on every successful Exec
 	// may be used as Health ruleState
-	Err error
+	Err error `json:"error"`
 	// stores the number of samples returned during
 	// the last evaluation
-	Samples int
+	Samples int `json:"samples"`
 	// stores the number of time series fetched during
 	// the last evaluation.
 	// Is supported by VictoriaMetrics only, starting from v1.90.0
 	// If seriesFetched == nil, then this attribute was missing in
 	// datasource response (unsupported).
-	SeriesFetched *int
+	SeriesFetched *int `json:"series_fetched"`
 	// stores the curl command reflecting the HTTP request used during rule.Exec
-	Curl string
+	Curl string `json:"curl"`
 }
 
 // GetLastEntry returns latest stateEntry of rule
diff --git a/app/vmalert/web.go b/app/vmalert/web.go
index f34bad197..04004a446 100644
--- a/app/vmalert/web.go
+++ b/app/vmalert/web.go
@@ -132,6 +132,24 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
 		w.Header().Set("Content-Type", "application/json")
 		w.Write(data)
 		return true
+	case "/vmalert/api/v1/rule", "/api/v1/rule":
+		rule, err := rh.getRule(r)
+		if err != nil {
+			httpserver.Errorf(w, r, "%s", err)
+			return true
+		}
+		rwu := apiRuleWithUpdates{
+			apiRule:      rule,
+			StateUpdates: rule.Updates,
+		}
+		data, err := json.Marshal(rwu)
+		if err != nil {
+			httpserver.Errorf(w, r, "failed to marshal rule: %s", err)
+			return true
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(data)
+		return true
 	case "/-/reload":
 		logger.Infof("api config reload was called, sending sighup")
 		procutil.SelfSIGHUP()
diff --git a/app/vmalert/web_types.go b/app/vmalert/web_types.go
index 62d17f379..61c0d2191 100644
--- a/app/vmalert/web_types.go
+++ b/app/vmalert/web_types.go
@@ -151,6 +151,10 @@ type apiRule struct {
 	ID string `json:"id"`
 	// GroupID is an unique Group's ID
 	GroupID string `json:"group_id"`
+	// GroupName is Group name rule belong to
+	GroupName string `json:"group_name"`
+	// File is file name where rule is defined
+	File string `json:"file"`
 	// Debug shows whether debug mode is enabled
 	Debug bool `json:"debug"`
 
@@ -160,6 +164,19 @@ type apiRule struct {
 	Updates []rule.StateEntry `json:"-"`
 }
 
+// apiRuleWithUpdates represents apiRule but with extra fields for marshalling
+type apiRuleWithUpdates struct {
+	apiRule
+	// Updates contains the ordered list of recorded ruleStateEntry objects
+	StateUpdates []rule.StateEntry `json:"updates,omitempty"`
+}
+
+// APILink returns a link to the rule's JSON representation.
+func (ar apiRule) APILink() string {
+	return fmt.Sprintf("api/v1/rule?%s=%s&%s=%s",
+		paramGroupID, ar.GroupID, paramRuleID, ar.ID)
+}
+
 // WebLink returns a link to the alert which can be used in UI.
 func (ar apiRule) WebLink() string {
 	return fmt.Sprintf("rule?%s=%s&%s=%s",
@@ -227,8 +244,10 @@ func alertingToAPI(ar *rule.AlertingRule) apiRule {
 		Debug:             ar.Debug,
 
 		// encode as strings to avoid rounding in JSON
-		ID:      fmt.Sprintf("%d", ar.ID()),
-		GroupID: fmt.Sprintf("%d", ar.GroupID),
+		ID:        fmt.Sprintf("%d", ar.ID()),
+		GroupID:   fmt.Sprintf("%d", ar.GroupID),
+		GroupName: ar.GroupName,
+		File:      ar.File,
 	}
 	if lastState.Err != nil {
 		r.LastError = lastState.Err.Error()
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 762bdb3bd..e1da7470a 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -37,6 +37,7 @@ The sandbox cluster installation is running under the constant load generated by
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): show all the dropped targets together with the reason why they are dropped at `http://vmagent:8429/service-discovery` page. Previously targets, which were dropped because of [target sharding](https://docs.victoriametrics.com/vmagent.html#scraping-big-number-of-targets) weren't displayed on this page. This could complicate service discovery debugging. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5389).
 * FEATURE: reduce the default value for `-import.maxLineLen` command-line flag from 100MB to 10MB in order to prevent excessive memory usage during data import via [/api/v1/import](https://docs.victoriametrics.com/#how-to-import-data-in-json-line-format).
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `keep_if_contains` and `drop_if_contains` relabeling actions. See [these docs](https://docs.victoriametrics.com/vmagent.html#relabeling-enhancements) for details.
+* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): provide `/vmalert/api/v1/rule` and `/api/v1/rule` API endpoints to get the rule object in JSON format. See [these docs](https://docs.victoriametrics.com/vmalert.html#web) for details.
 * FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add [day_of_year()](https://docs.victoriametrics.com/MetricsQL.html#day_of_year) function, which returns the day of the year for each of the given unix timestamps. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5345) for details. Thanks to @luckyxiaoqiang for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5368/).
 * FEATURE: all VictoriaMetrics binaries: expose additional metrics at `/metrics` page, which may simplify debugging of VictoriaMetrics components (see [this feature request](https://github.com/VictoriaMetrics/metrics/issues/54)):
   * `go_sched_latencies_seconds` - the [histogram](https://docs.victoriametrics.com/keyConcepts.html#histogram), which shows the time goroutines have spent in runnable state before actually running. Big values point to the lack of CPU time for the current workload.
@@ -52,6 +53,7 @@ The sandbox cluster installation is running under the constant load generated by
 * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly add new labels at `output_relabel_configs` during [stream aggregation](https://docs.victoriametrics.com/stream-aggregation.html). Previously this could lead to corrupted labels in output samples. Thanks to @ChengChung for providing [detailed report for this bug](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5402).
 * BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/#vmalert-tool): allow using arbitrary `eval_time`  in [alert_rule_test](https://docs.victoriametrics.com/vmalert-tool.html#alert_test_case) case. Previously, test cases with `eval_time`  not being a multiple of `evaluation_interval` would fail.
 * BUGFIX: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager.html): fix `vmbackupmanager` not deleting previous object versions from S3 when applying retention policy with `-deleteAllObjectVersions` command-line flag.
+* BUGFIX: [vminsert](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): fix panic when ingesting data via [NewRelic protocol](https://docs.victoriametrics.com/#how-to-send-data-from-newrelic-agent) into VictoriaMetrics cluster. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5416).
 
 
 ## [v1.95.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.95.1)
diff --git a/docs/vmalert.md b/docs/vmalert.md
index 4ebf79550..ce932df41 100644
--- a/docs/vmalert.md
+++ b/docs/vmalert.md
@@ -659,6 +659,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm
   Used as alert source in AlertManager.
 * `http://<vmalert-addr>/vmalert/alert?group_id=<group_id>&alert_id=<alert_id>` - get alert status in web UI.
 * `http://<vmalert-addr>/vmalert/rule?group_id=<group_id>&rule_id=<rule_id>` - get rule status in web UI.
+* `http://<vmalert-addr>/vmalert/api/v1/rule?group_id=<group_id>&alert_id=<alert_id>` - get rule status in JSON format.
 * `http://<vmalert-addr>/metrics` - application metrics.
 * `http://<vmalert-addr>/-/reload` - hot configuration reload.
 


Test patch:
diff --git a/app/vmalert/web_test.go b/app/vmalert/web_test.go
index c55ef5cbc..12beb63fa 100644
--- a/app/vmalert/web_test.go
+++ b/app/vmalert/web_test.go
@@ -143,6 +143,28 @@ func TestHandler(t *testing.T) {
 			t.Errorf("expected 1 group got %d", length)
 		}
 	})
+	t.Run("/api/v1/rule?ruleID&groupID", func(t *testing.T) {
+		expRule := ruleToAPI(ar)
+		gotRule := apiRule{}
+		getResp(ts.URL+"/"+expRule.APILink(), &gotRule, 200)
+
+		if expRule.ID != gotRule.ID {
+			t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID)
+		}
+
+		gotRule = apiRule{}
+		getResp(ts.URL+"/vmalert/"+expRule.APILink(), &gotRule, 200)
+
+		if expRule.ID != gotRule.ID {
+			t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID)
+		}
+
+		gotRuleWithUpdates := apiRuleWithUpdates{}
+		getResp(ts.URL+"/"+expRule.APILink(), &gotRuleWithUpdates, 200)
+		if gotRuleWithUpdates.StateUpdates == nil || len(gotRuleWithUpdates.StateUpdates) < 1 {
+			t.Fatalf("expected %+v to have state updates field not empty", gotRuleWithUpdates.StateUpdates)
+		}
+	})
 }
 
 func TestEmptyResponse(t *testing.T) {