mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			415 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| package api
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/infra/log"
 | |
| 	"github.com/grafana/grafana/pkg/models"
 | |
| 	ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/state"
 | |
| 	"github.com/grafana/grafana/pkg/services/ngalert/store"
 | |
| 	"github.com/grafana/grafana/pkg/util"
 | |
| 	"github.com/grafana/grafana/pkg/web"
 | |
| 
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/data"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| )
 | |
| 
 | |
| func TestRouteGetAlertStatuses(t *testing.T) {
 | |
| 	orgID := int64(1)
 | |
| 
 | |
| 	t.Run("with no alerts", func(t *testing.T) {
 | |
| 		_, _, api := setupAPI(t)
 | |
| 		req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
 | |
| 		require.NoError(t, err)
 | |
| 		c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
 | |
| 
 | |
| 		r := api.RouteGetAlertStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"alerts": []
 | |
| 	}
 | |
| }
 | |
| `, string(r.Body()))
 | |
| 	})
 | |
| 
 | |
| 	t.Run("with two alerts", func(t *testing.T) {
 | |
| 		_, fakeAIM, api := setupAPI(t)
 | |
| 		fakeAIM.GenerateAlertInstances(1, util.GenerateShortUID(), 2)
 | |
| 		req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
 | |
| 		require.NoError(t, err)
 | |
| 		c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
 | |
| 
 | |
| 		r := api.RouteGetAlertStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"alerts": [{
 | |
| 			"labels": {
 | |
| 				"alertname": "test_title_0",
 | |
| 				"instance_label": "test",
 | |
| 				"label": "test"
 | |
| 			},
 | |
| 			"annotations": {
 | |
| 				"annotation": "test"
 | |
| 			},
 | |
| 			"state": "Normal",
 | |
| 			"activeAt": "0001-01-01T00:00:00Z",
 | |
| 			"value": ""
 | |
| 		}, {
 | |
| 			"labels": {
 | |
| 				"alertname": "test_title_1",
 | |
| 				"instance_label": "test",
 | |
| 				"label": "test"
 | |
| 			},
 | |
| 			"annotations": {
 | |
| 				"annotation": "test"
 | |
| 			},
 | |
| 			"state": "Normal",
 | |
| 			"activeAt": "0001-01-01T00:00:00Z",
 | |
| 			"value": ""
 | |
| 		}]
 | |
| 	}
 | |
| }`, string(r.Body()))
 | |
| 	})
 | |
| 
 | |
| 	t.Run("with the inclusion of internal labels", func(t *testing.T) {
 | |
| 		_, fakeAIM, api := setupAPI(t)
 | |
| 		fakeAIM.GenerateAlertInstances(orgID, util.GenerateShortUID(), 2)
 | |
| 		req, err := http.NewRequest("GET", "/api/v1/alerts?includeInternalLabels=true", nil)
 | |
| 		require.NoError(t, err)
 | |
| 		c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
 | |
| 
 | |
| 		r := api.RouteGetAlertStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"alerts": [{
 | |
| 			"labels": {
 | |
| 				"__alert_rule_namespace_uid__": "test_namespace_uid",
 | |
| 				"__alert_rule_uid__": "test_alert_rule_uid_0",
 | |
| 				"alertname": "test_title_0",
 | |
| 				"instance_label": "test",
 | |
| 				"label": "test"
 | |
| 			},
 | |
| 			"annotations": {
 | |
| 				"annotation": "test"
 | |
| 			},
 | |
| 			"state": "Normal",
 | |
| 			"activeAt": "0001-01-01T00:00:00Z",
 | |
| 			"value": ""
 | |
| 		}, {
 | |
| 			"labels": {
 | |
| 				"__alert_rule_namespace_uid__": "test_namespace_uid",
 | |
| 				"__alert_rule_uid__": "test_alert_rule_uid_1",
 | |
| 				"alertname": "test_title_1",
 | |
| 				"instance_label": "test",
 | |
| 				"label": "test"
 | |
| 			},
 | |
| 			"annotations": {
 | |
| 				"annotation": "test"
 | |
| 			},
 | |
| 			"state": "Normal",
 | |
| 			"activeAt": "0001-01-01T00:00:00Z",
 | |
| 			"value": ""
 | |
| 		}]
 | |
| 	}
 | |
| }`, string(r.Body()))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestRouteGetRuleStatuses(t *testing.T) {
 | |
| 	timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) }
 | |
| 	orgID := int64(1)
 | |
| 
 | |
| 	req, err := http.NewRequest("GET", "/api/v1/rules", nil)
 | |
| 	require.NoError(t, err)
 | |
| 	c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
 | |
| 
 | |
| 	t.Run("with no rules", func(t *testing.T) {
 | |
| 		_, _, api := setupAPI(t)
 | |
| 		r := api.RouteGetRuleStatuses(c)
 | |
| 
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"groups": []
 | |
| 	}
 | |
| }
 | |
| `, string(r.Body()))
 | |
| 	})
 | |
| 
 | |
| 	t.Run("with a rule that only has one query", func(t *testing.T) {
 | |
| 		fakeStore, fakeAIM, api := setupAPI(t)
 | |
| 		generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
 | |
| 
 | |
| 		r := api.RouteGetRuleStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"groups": [{
 | |
| 			"name": "rule-group",
 | |
| 			"file": "namespaceUID",
 | |
| 			"rules": [{
 | |
| 				"state": "inactive",
 | |
| 				"name": "AlwaysFiring",
 | |
| 				"query": "vector(1)",
 | |
| 				"alerts": [{
 | |
| 					"labels": {
 | |
| 						"job": "prometheus"
 | |
| 					},
 | |
| 					"annotations": {
 | |
| 						"severity": "critical"
 | |
| 					},
 | |
| 					"state": "Normal",
 | |
| 					"activeAt": "0001-01-01T00:00:00Z",
 | |
| 					"value": ""
 | |
| 				}],
 | |
| 				"labels": {
 | |
| 					"__a_private_label_on_the_rule__": "a_value"
 | |
| 				},
 | |
| 				"health": "ok",
 | |
| 				"type": "alerting",
 | |
| 				"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 				"duration": 180,
 | |
| 				"evaluationTime": 60
 | |
| 			}],
 | |
| 			"interval": 60,
 | |
| 			"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 			"evaluationTime": 0
 | |
| 		}]
 | |
| 	}
 | |
| }
 | |
| `, string(r.Body()))
 | |
| 	})
 | |
| 
 | |
| 	t.Run("with the inclusion of internal Labels", func(t *testing.T) {
 | |
| 		fakeStore, fakeAIM, api := setupAPI(t)
 | |
| 		generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
 | |
| 
 | |
| 		req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil)
 | |
| 		require.NoError(t, err)
 | |
| 		c := &models.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &models.SignedInUser{OrgId: orgID}}
 | |
| 
 | |
| 		r := api.RouteGetRuleStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"groups": [{
 | |
| 			"name": "rule-group",
 | |
| 			"file": "namespaceUID",
 | |
| 			"rules": [{
 | |
| 				"state": "inactive",
 | |
| 				"name": "AlwaysFiring",
 | |
| 				"query": "vector(1)",
 | |
| 				"alerts": [{
 | |
| 					"labels": {
 | |
| 						"job": "prometheus",
 | |
| 						"__alert_rule_namespace_uid__": "test_namespace_uid",
 | |
| 						"__alert_rule_uid__": "test_alert_rule_uid_0"
 | |
| 					},
 | |
| 					"annotations": {
 | |
| 						"severity": "critical"
 | |
| 					},
 | |
| 					"state": "Normal",
 | |
| 					"activeAt": "0001-01-01T00:00:00Z",
 | |
| 					"value": ""
 | |
| 				}],
 | |
| 				"labels": {
 | |
| 					"__a_private_label_on_the_rule__": "a_value",
 | |
| 					"__alert_rule_uid__": "RuleUID"
 | |
| 				},
 | |
| 				"health": "ok",
 | |
| 				"type": "alerting",
 | |
| 				"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 				"duration": 180,
 | |
| 				"evaluationTime": 60
 | |
| 			}],
 | |
| 			"interval": 60,
 | |
| 			"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 			"evaluationTime": 0
 | |
| 		}]
 | |
| 	}
 | |
| }
 | |
| `, string(r.Body()))
 | |
| 	})
 | |
| 
 | |
| 	t.Run("with a rule that has multiple queries", func(t *testing.T) {
 | |
| 		fakeStore, fakeAIM, api := setupAPI(t)
 | |
| 		generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery())
 | |
| 
 | |
| 		r := api.RouteGetRuleStatuses(c)
 | |
| 		require.Equal(t, http.StatusOK, r.Status())
 | |
| 		require.JSONEq(t, `
 | |
| {
 | |
| 	"status": "success",
 | |
| 	"data": {
 | |
| 		"groups": [{
 | |
| 			"name": "rule-group",
 | |
| 			"file": "namespaceUID",
 | |
| 			"rules": [{
 | |
| 				"state": "inactive",
 | |
| 				"name": "AlwaysFiring",
 | |
| 				"query": "vector(1) | vector(1)",
 | |
| 				"alerts": [{
 | |
| 					"labels": {
 | |
| 						"job": "prometheus"
 | |
| 					},
 | |
| 					"annotations": {
 | |
| 						"severity": "critical"
 | |
| 					},
 | |
| 					"state": "Normal",
 | |
| 					"activeAt": "0001-01-01T00:00:00Z",
 | |
| 					"value": ""
 | |
| 				}],
 | |
| 				"labels": {
 | |
| 					"__a_private_label_on_the_rule__": "a_value"
 | |
| 				},
 | |
| 				"health": "ok",
 | |
| 				"type": "alerting",
 | |
| 				"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 				"duration": 180,
 | |
| 				"evaluationTime": 60
 | |
| 			}],
 | |
| 			"interval": 60,
 | |
| 			"lastEvaluation": "2022-03-10T14:01:00Z",
 | |
| 			"evaluationTime": 0
 | |
| 		}]
 | |
| 	}
 | |
| }
 | |
| `, string(r.Body()))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func setupAPI(t *testing.T) (*store.FakeRuleStore, *fakeAlertInstanceManager, PrometheusSrv) {
 | |
| 	fakeStore := store.NewFakeRuleStore(t)
 | |
| 	fakeAIM := NewFakeAlertInstanceManager(t)
 | |
| 	api := PrometheusSrv{
 | |
| 		log:     log.NewNopLogger(),
 | |
| 		manager: fakeAIM,
 | |
| 		store:   fakeStore,
 | |
| 	}
 | |
| 
 | |
| 	return fakeStore, fakeAIM, api
 | |
| }
 | |
| 
 | |
| func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *store.FakeRuleStore, query func(r *ngmodels.AlertRule)) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), asFixture(), query))
 | |
| 
 | |
| 	fakeAIM.GenerateAlertInstances(orgID, rules[0].UID, 1, func(s *state.State) *state.State {
 | |
| 		s.Labels = data.Labels{
 | |
| 			"job":                      "prometheus",
 | |
| 			ngmodels.NamespaceUIDLabel: "test_namespace_uid",
 | |
| 			ngmodels.RuleUIDLabel:      "test_alert_rule_uid_0",
 | |
| 		}
 | |
| 		s.Annotations = data.Labels{"severity": "critical"}
 | |
| 		return s
 | |
| 	})
 | |
| 
 | |
| 	for _, r := range rules {
 | |
| 		fakeStore.PutRule(context.Background(), r)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // asFixture removes variable values of the alert rule.
 | |
| // we're not too interested in variability of the rule in this scenario.
 | |
| func asFixture() func(r *ngmodels.AlertRule) {
 | |
| 	return func(r *ngmodels.AlertRule) {
 | |
| 		r.Title = "AlwaysFiring"
 | |
| 		r.NamespaceUID = "namespaceUID"
 | |
| 		r.RuleGroup = "rule-group"
 | |
| 		r.UID = "RuleUID"
 | |
| 		r.Labels = map[string]string{
 | |
| 			"__a_private_label_on_the_rule__": "a_value",
 | |
| 			ngmodels.RuleUIDLabel:             "RuleUID",
 | |
| 		}
 | |
| 		r.Annotations = nil
 | |
| 		r.IntervalSeconds = 60
 | |
| 		r.For = 180 * time.Second
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) {
 | |
| 	return func(r *ngmodels.AlertRule) {
 | |
| 		queries := []ngmodels.AlertQuery{
 | |
| 			{
 | |
| 				RefID:             "A",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "AUID",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(prometheusQueryModel, "A")),
 | |
| 			},
 | |
| 			{
 | |
| 				RefID:             "B",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "-100",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(classicConditionsModel, "A", "B")),
 | |
| 			},
 | |
| 		}
 | |
| 		r.Data = queries
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func withExpressionsMultiQuery() func(r *ngmodels.AlertRule) {
 | |
| 	return func(r *ngmodels.AlertRule) {
 | |
| 		queries := []ngmodels.AlertQuery{
 | |
| 			{
 | |
| 				RefID:             "A",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "AUID",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(prometheusQueryModel, "A")),
 | |
| 			},
 | |
| 			{
 | |
| 				RefID:             "B",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "BUID",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(prometheusQueryModel, "B")),
 | |
| 			},
 | |
| 			{
 | |
| 				RefID:             "C",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "-100",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(reduceLastExpressionModel, "A", "C")),
 | |
| 			},
 | |
| 			{
 | |
| 				RefID:             "D",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "-100",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(reduceLastExpressionModel, "B", "D")),
 | |
| 			},
 | |
| 			{
 | |
| 				RefID:             "E",
 | |
| 				QueryType:         "",
 | |
| 				RelativeTimeRange: ngmodels.RelativeTimeRange{From: ngmodels.Duration(0), To: ngmodels.Duration(0)},
 | |
| 				DatasourceUID:     "-100",
 | |
| 				Model:             json.RawMessage(fmt.Sprintf(mathExpressionModel, "A", "B", "E")),
 | |
| 			},
 | |
| 		}
 | |
| 		r.Data = queries
 | |
| 	}
 | |
| }
 |