| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package alerting
- import (
- "errors"
- "fmt"
- "regexp"
- "strconv"
- "time"
- "github.com/grafana/grafana/pkg/components/simplejson"
- "github.com/grafana/grafana/pkg/models"
- )
- var (
- // ErrFrequencyCannotBeZeroOrLess frequency cannot be below zero
- ErrFrequencyCannotBeZeroOrLess = errors.New(`"evaluate every" cannot be zero or below`)
- // ErrFrequencyCouldNotBeParsed frequency cannot be parsed
- ErrFrequencyCouldNotBeParsed = errors.New(`"evaluate every" field could not be parsed`)
- )
- // Rule is the in-memory version of an alert rule.
- type Rule struct {
- ID int64
- OrgID int64
- DashboardID int64
- PanelID int64
- Frequency int64
- Name string
- Message string
- LastStateChange time.Time
- For time.Duration
- NoDataState models.NoDataOption
- ExecutionErrorState models.ExecutionErrorOption
- State models.AlertStateType
- Conditions []Condition
- Notifications []string
- AlertRuleTags []*models.Tag
- StateChanges int64
- }
- // ValidationError is a typed error with meta data
- // about the validation error.
- type ValidationError struct {
- Reason string
- Err error
- AlertID int64
- DashboardID int64
- PanelID int64
- }
- func (e ValidationError) Error() string {
- extraInfo := e.Reason
- if e.AlertID != 0 {
- extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.AlertID)
- }
- if e.PanelID != 0 {
- extraInfo = fmt.Sprintf("%s PanelId: %v", extraInfo, e.PanelID)
- }
- if e.DashboardID != 0 {
- extraInfo = fmt.Sprintf("%s DashboardId: %v", extraInfo, e.DashboardID)
- }
- if e.Err != nil {
- return fmt.Sprintf("Alert validation error: %s%s", e.Err.Error(), extraInfo)
- }
- return fmt.Sprintf("Alert validation error: %s", extraInfo)
- }
- var (
- valueFormatRegex = regexp.MustCompile(`^\d+`)
- unitFormatRegex = regexp.MustCompile(`\w{1}$`)
- )
- var unitMultiplier = map[string]int{
- "s": 1,
- "m": 60,
- "h": 3600,
- "d": 86400,
- }
- func getTimeDurationStringToSeconds(str string) (int64, error) {
- multiplier := 1
- matches := valueFormatRegex.FindAllString(str, 1)
- if len(matches) <= 0 {
- return 0, ErrFrequencyCouldNotBeParsed
- }
- value, err := strconv.Atoi(matches[0])
- if err != nil {
- return 0, err
- }
- if value == 0 {
- return 0, ErrFrequencyCannotBeZeroOrLess
- }
- unit := unitFormatRegex.FindAllString(str, 1)[0]
- if val, ok := unitMultiplier[unit]; ok {
- multiplier = val
- }
- return int64(value * multiplier), nil
- }
- // NewRuleFromDBAlert mappes an db version of
- // alert to an in-memory version.
- func NewRuleFromDBAlert(ruleDef *models.Alert) (*Rule, error) {
- model := &Rule{}
- model.ID = ruleDef.Id
- model.OrgID = ruleDef.OrgId
- model.DashboardID = ruleDef.DashboardId
- model.PanelID = ruleDef.PanelId
- model.Name = ruleDef.Name
- model.Message = ruleDef.Message
- model.State = ruleDef.State
- model.LastStateChange = ruleDef.NewStateDate
- model.For = ruleDef.For
- model.NoDataState = models.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data"))
- model.ExecutionErrorState = models.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
- model.StateChanges = ruleDef.StateChanges
- model.Frequency = ruleDef.Frequency
- // frequency cannot be zero since that would not execute the alert rule.
- // so we fallback to 60 seconds if `Freqency` is missing
- if model.Frequency == 0 {
- model.Frequency = 60
- }
- for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
- jsonModel := simplejson.NewFromAny(v)
- if id, err := jsonModel.Get("id").Int64(); err == nil {
- model.Notifications = append(model.Notifications, fmt.Sprintf("%09d", id))
- } else {
- uid, err := jsonModel.Get("uid").String()
- if err != nil {
- return nil, ValidationError{Reason: "Neither id nor uid is specified in 'notifications' block, " + err.Error(), DashboardID: model.DashboardID, AlertID: model.ID, PanelID: model.PanelID}
- }
- model.Notifications = append(model.Notifications, uid)
- }
- }
- model.AlertRuleTags = ruleDef.GetTagsFromSettings()
- for index, condition := range ruleDef.Settings.Get("conditions").MustArray() {
- conditionModel := simplejson.NewFromAny(condition)
- conditionType := conditionModel.Get("type").MustString()
- factory, exist := conditionFactories[conditionType]
- if !exist {
- return nil, ValidationError{Reason: "Unknown alert condition: " + conditionType, DashboardID: model.DashboardID, AlertID: model.ID, PanelID: model.PanelID}
- }
- queryCondition, err := factory(conditionModel, index)
- if err != nil {
- return nil, ValidationError{Err: err, DashboardID: model.DashboardID, AlertID: model.ID, PanelID: model.PanelID}
- }
- model.Conditions = append(model.Conditions, queryCondition)
- }
- if len(model.Conditions) == 0 {
- return nil, ValidationError{Reason: "Alert is missing conditions"}
- }
- return model, nil
- }
- // ConditionFactory is the function signature for creating `Conditions`.
- type ConditionFactory func(model *simplejson.Json, index int) (Condition, error)
- var conditionFactories = make(map[string]ConditionFactory)
- // RegisterCondition adds support for alerting conditions.
- func RegisterCondition(typeName string, factory ConditionFactory) {
- conditionFactories[typeName] = factory
- }
|