grafana/pkg/services/ngalert/store/instance_database.go
Joe Blubaugh 1cc034d960
Alerting: Add a "Reason" to Alert Instances to show underlying cause of state. (#49259)
This change adds a field to state.State and models.AlertInstance
that indicate the "Reason" that an instance has its current state. This
helps us account for cases where the state is "Normal" but the
underlying evaluation returned "NoData" or "Error", for example.

Fixes #42606

Signed-off-by: Joe Blubaugh <joe.blubaugh@grafana.com>
2022-05-23 16:49:49 +08:00

161 lines
5.0 KiB
Go

package store
import (
"context"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
type InstanceStore interface {
GetAlertInstance(ctx context.Context, cmd *models.GetAlertInstanceQuery) error
ListAlertInstances(ctx context.Context, cmd *models.ListAlertInstancesQuery) error
SaveAlertInstance(ctx context.Context, cmd *models.SaveAlertInstanceCommand) error
FetchOrgIds(ctx context.Context) ([]int64, error)
DeleteAlertInstance(ctx context.Context, orgID int64, ruleUID, labelsHash string) error
}
// GetAlertInstance is a handler for retrieving an alert instance based on OrgId, AlertDefintionID, and
// the hash of the labels.
func (st DBstore) GetAlertInstance(ctx context.Context, cmd *models.GetAlertInstanceQuery) error {
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
instance := models.AlertInstance{}
s := strings.Builder{}
s.WriteString(`SELECT * FROM alert_instance
WHERE
rule_org_id=? AND
rule_uid=? AND
labels_hash=?
`)
_, hash, err := cmd.Labels.StringAndHash()
if err != nil {
return err
}
params := append(make([]interface{}, 0), cmd.RuleOrgID, cmd.RuleUID, hash)
has, err := sess.SQL(s.String(), params...).Get(&instance)
if !has {
return fmt.Errorf("instance not found for labels %v (hash: %v), alert rule %v (org %v)", cmd.Labels, hash, cmd.RuleUID, cmd.RuleOrgID)
}
if err != nil {
return err
}
cmd.Result = &instance
return nil
})
}
// ListAlertInstances is a handler for retrieving alert instances within specific organisation
// based on various filters.
func (st DBstore) ListAlertInstances(ctx context.Context, cmd *models.ListAlertInstancesQuery) error {
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
alertInstances := make([]*models.AlertInstance, 0)
s := strings.Builder{}
params := make([]interface{}, 0)
addToQuery := func(stmt string, p ...interface{}) {
s.WriteString(stmt)
params = append(params, p...)
}
addToQuery("SELECT alert_instance.*, alert_rule.title AS rule_title FROM alert_instance LEFT JOIN alert_rule ON alert_instance.rule_org_id = alert_rule.org_id AND alert_instance.rule_uid = alert_rule.uid WHERE rule_org_id = ?", cmd.RuleOrgID)
if cmd.RuleUID != "" {
addToQuery(` AND rule_uid = ?`, cmd.RuleUID)
}
if cmd.State != "" {
addToQuery(` AND current_state = ?`, cmd.State)
}
if cmd.StateReason != "" {
addToQuery(` AND current_reason = ?`, cmd.StateReason)
}
if err := sess.SQL(s.String(), params...).Find(&alertInstances); err != nil {
return err
}
cmd.Result = alertInstances
return nil
})
}
// SaveAlertInstance is a handler for saving a new alert instance.
func (st DBstore) SaveAlertInstance(ctx context.Context, cmd *models.SaveAlertInstanceCommand) error {
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
labelTupleJSON, labelsHash, err := cmd.Labels.StringAndHash()
if err != nil {
return err
}
alertInstance := &models.AlertInstance{
RuleOrgID: cmd.RuleOrgID,
RuleUID: cmd.RuleUID,
Labels: cmd.Labels,
LabelsHash: labelsHash,
CurrentState: cmd.State,
CurrentReason: cmd.StateReason,
CurrentStateSince: cmd.CurrentStateSince,
CurrentStateEnd: cmd.CurrentStateEnd,
LastEvalTime: cmd.LastEvalTime,
}
if err := models.ValidateAlertInstance(alertInstance); err != nil {
return err
}
params := append(make([]interface{}, 0), alertInstance.RuleOrgID, alertInstance.RuleUID, labelTupleJSON, alertInstance.LabelsHash, alertInstance.CurrentState, alertInstance.CurrentReason, alertInstance.CurrentStateSince.Unix(), alertInstance.CurrentStateEnd.Unix(), alertInstance.LastEvalTime.Unix())
upsertSQL := st.SQLStore.Dialect.UpsertSQL(
"alert_instance",
[]string{"rule_org_id", "rule_uid", "labels_hash"},
[]string{"rule_org_id", "rule_uid", "labels", "labels_hash", "current_state", "current_reason", "current_state_since", "current_state_end", "last_eval_time"})
_, err = sess.SQL(upsertSQL, params...).Query()
if err != nil {
return err
}
return nil
})
}
func (st DBstore) FetchOrgIds(ctx context.Context) ([]int64, error) {
orgIds := []int64{}
err := st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
s := strings.Builder{}
params := make([]interface{}, 0)
addToQuery := func(stmt string, p ...interface{}) {
s.WriteString(stmt)
params = append(params, p...)
}
addToQuery("SELECT DISTINCT rule_org_id FROM alert_instance")
if err := sess.SQL(s.String(), params...).Find(&orgIds); err != nil {
return err
}
return nil
})
return orgIds, err
}
func (st DBstore) DeleteAlertInstance(ctx context.Context, orgID int64, ruleUID, labelsHash string) error {
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
_, err := sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ? AND labels_hash = ?", orgID, ruleUID, labelsHash)
if err != nil {
return err
}
return nil
})
}