diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index e3dea4e93f1..354943408c3 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -24,7 +24,7 @@ import ( type PrometheusSrv struct { log log.Logger - manager *state.Manager + manager state.AlertInstanceManager store store.RuleStore } @@ -43,14 +43,16 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *models.ReqContext) response.Re if alertState.State == eval.Alerting { valString = alertState.LastEvaluationString } + alertResponse.Data.Alerts = append(alertResponse.Data.Alerts, &apimodels.Alert{ Labels: map[string]string(alertState.Labels), - Annotations: map[string]string{}, //TODO: Once annotations are added to the evaluation result, set them here + Annotations: alertState.Annotations, State: alertState.State.String(), ActiveAt: &startsAt, Value: valString, }) } + return response.JSON(http.StatusOK, alertResponse) } diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go new file mode 100644 index 00000000000..f8b45baddc4 --- /dev/null +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -0,0 +1,80 @@ +package api + +import ( + "net/http" + "testing" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ngalert/store" + + "github.com/stretchr/testify/require" +) + +func TestRouteGetAlertStatuses(t *testing.T) { + fakeStore := store.NewFakeRuleStore(t) + fakeAlertInstanceManager := NewFakeAlertInstanceManager(t) + orgID := int64(1) + + server := PrometheusSrv{ + log: log.NewNopLogger(), + manager: fakeAlertInstanceManager, + store: fakeStore, + } + + c := &models.ReqContext{SignedInUser: &models.SignedInUser{OrgId: orgID}} + + t.Run("with no alerts", func(t *testing.T) { + r := server.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) { + fakeAlertInstanceManager.GenerateAlertInstances(1, 2) + r := server.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())) + }) +} diff --git a/pkg/services/ngalert/api/testing.go b/pkg/services/ngalert/api/testing.go index 398399fe768..b9b538bf398 100644 --- a/pkg/services/ngalert/api/testing.go +++ b/pkg/services/ngalert/api/testing.go @@ -2,10 +2,18 @@ package api import ( "context" + "fmt" + "sync" "testing" + "time" + "github.com/grafana/grafana/pkg/services/ngalert/eval" "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-plugin-sdk-go/data" ) type FakeAlertingStore struct { @@ -30,3 +38,85 @@ func (f FakeAlertingStore) GetLatestAlertmanagerConfiguration(_ context.Context, } return store.ErrNoAlertmanagerConfiguration } + +type fakeAlertInstanceManager struct { + mtx sync.Mutex + // orgID -> RuleID -> States + states map[int64]map[string][]*state.State +} + +func NewFakeAlertInstanceManager(t *testing.T) *fakeAlertInstanceManager { + t.Helper() + + return &fakeAlertInstanceManager{ + states: map[int64]map[string][]*state.State{}, + } +} + +func (f *fakeAlertInstanceManager) GetAll(orgID int64) []*state.State { + f.mtx.Lock() + defer f.mtx.Unlock() + var s []*state.State + + for orgID := range f.states { + for _, states := range f.states[orgID] { + s = append(s, states...) + } + } + + return s +} + +func (f *fakeAlertInstanceManager) GetStatesForRuleUID(orgID int64, alertRuleUID string) []*state.State { + f.mtx.Lock() + defer f.mtx.Unlock() + return f.states[orgID][alertRuleUID] +} + +func (f *fakeAlertInstanceManager) GenerateAlertInstances(orgID int64, count int) { + f.mtx.Lock() + defer f.mtx.Unlock() + + evaluationTime := time.Now() + evaluationDuration := 1 * time.Minute + alertRuleUID := util.GenerateShortUID() + + for i := 0; i < count; i++ { + _, ok := f.states[orgID] + if !ok { + f.states[orgID] = map[string][]*state.State{} + } + _, ok = f.states[orgID][alertRuleUID] + if !ok { + f.states[orgID][alertRuleUID] = []*state.State{} + } + + f.states[orgID][alertRuleUID] = append(f.states[orgID][alertRuleUID], &state.State{ + AlertRuleUID: fmt.Sprintf("alert_rule_%v", i), + OrgID: 1, + Labels: data.Labels{ + "__alert_rule_namespace_uid__": "test_namespace_uid", + "__alert_rule_uid__": fmt.Sprintf("test_alert_rule_uid_%v", i), + "alertname": fmt.Sprintf("test_title_%v", i), + "label": "test", + "instance_label": "test", + }, + State: eval.Normal, + Results: []state.Evaluation{ + { + EvaluationTime: evaluationTime, + EvaluationState: eval.Normal, + Values: make(map[string]*float64), + }, + { + EvaluationTime: evaluationTime.Add(1 * time.Minute), + EvaluationState: eval.Normal, + Values: make(map[string]*float64), + }, + }, + LastEvaluationTime: evaluationTime.Add(1 * time.Minute), + EvaluationDuration: evaluationDuration, + Annotations: map[string]string{"annotation": "test"}, + }) + } +} diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index edc26ce2399..c4cbc3b4ba0 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -22,6 +22,12 @@ import ( var ResendDelay = 30 * time.Second +// AlertInstanceManager defines the interface for querying the current alert instances. +type AlertInstanceManager interface { + GetAll(orgID int64) []*State + GetStatesForRuleUID(orgID int64, alertRuleUID string) []*State +} + type Manager struct { log log.Logger metrics *metrics.State