2021-04-29 12:24:37 -05:00
package ualert
import (
"encoding/json"
"fmt"
"time"
2021-10-07 16:30:06 -05:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2021-04-29 12:24:37 -05:00
"github.com/grafana/grafana/pkg/util"
)
type alertRule struct {
2021-08-12 08:04:09 -05:00
OrgID int64 ` xorm:"org_id" `
2021-04-29 12:24:37 -05:00
Title string
Condition string
Data [ ] alertQuery
IntervalSeconds int64
2021-04-30 14:08:01 -05:00
Version int64
2021-08-12 08:04:09 -05:00
UID string ` xorm:"uid" `
NamespaceUID string ` xorm:"namespace_uid" `
2021-04-29 12:24:37 -05:00
RuleGroup string
NoDataState string
ExecErrState string
For duration
Updated time . Time
Annotations map [ string ] string
2021-04-30 14:08:01 -05:00
Labels map [ string ] string // (Labels are not Created in the migration)
}
type alertRuleVersion struct {
RuleOrgID int64 ` xorm:"rule_org_id" `
RuleUID string ` xorm:"rule_uid" `
RuleNamespaceUID string ` xorm:"rule_namespace_uid" `
RuleGroup string
ParentVersion int64
RestoredFrom int64
Version int64
Created time . Time
Title string
Condition string
Data [ ] alertQuery
IntervalSeconds int64
NoDataState string
ExecErrState string
// ideally this field should have been apimodels.ApiDuration
// but this is currently not possible because of circular dependencies
For duration
Annotations map [ string ] string
Labels map [ string ] string
}
func ( a * alertRule ) makeVersion ( ) * alertRuleVersion {
return & alertRuleVersion {
2021-08-12 08:04:09 -05:00
RuleOrgID : a . OrgID ,
RuleUID : a . UID ,
RuleNamespaceUID : a . NamespaceUID ,
2021-04-30 14:08:01 -05:00
RuleGroup : a . RuleGroup ,
ParentVersion : 0 ,
RestoredFrom : 0 ,
Version : 1 ,
Created : time . Now ( ) . UTC ( ) ,
Title : a . Title ,
Condition : a . Condition ,
Data : a . Data ,
IntervalSeconds : a . IntervalSeconds ,
NoDataState : a . NoDataState ,
ExecErrState : a . ExecErrState ,
For : a . For ,
Annotations : a . Annotations ,
Labels : map [ string ] string { } ,
}
2021-04-29 12:24:37 -05:00
}
2021-05-31 09:17:17 -05:00
func addMigrationInfo ( da * dashAlert ) ( map [ string ] string , map [ string ] string ) {
lbls := da . ParsedSettings . AlertRuleTags
if lbls == nil {
lbls = make ( map [ string ] string )
2021-05-03 10:42:31 -05:00
}
2021-05-31 09:17:17 -05:00
annotations := make ( map [ string ] string , 3 )
2021-10-07 16:30:06 -05:00
annotations [ ngmodels . DashboardUIDAnnotation ] = da . DashboardUID
annotations [ ngmodels . PanelIDAnnotation ] = fmt . Sprintf ( "%v" , da . PanelId )
2021-05-03 10:42:31 -05:00
annotations [ "__alertId__" ] = fmt . Sprintf ( "%v" , da . Id )
2021-05-31 09:17:17 -05:00
return lbls , annotations
2021-05-03 10:42:31 -05:00
}
func getMigrationString ( da dashAlert ) string {
2021-04-29 12:24:37 -05:00
return fmt . Sprintf ( ` { "dashboardUid": "%v", "panelId": %v, "alertId": %v} ` , da . DashboardUID , da . PanelId , da . Id )
}
func ( m * migration ) makeAlertRule ( cond condition , da dashAlert , folderUID string ) ( * alertRule , error ) {
2021-05-31 09:17:17 -05:00
lbls , annotations := addMigrationInfo ( & da )
lbls [ "alertname" ] = da . Name
annotations [ "message" ] = da . Message
2021-04-29 12:24:37 -05:00
ar := & alertRule {
2021-08-12 08:04:09 -05:00
OrgID : da . OrgId ,
2021-04-29 12:24:37 -05:00
Title : da . Name , // TODO: Make sure all names are unique, make new name on constraint insert error.
2021-08-12 08:04:09 -05:00
UID : util . GenerateShortUID ( ) ,
2021-04-29 12:24:37 -05:00
Condition : cond . Condition ,
Data : cond . Data ,
IntervalSeconds : ruleAdjustInterval ( da . Frequency ) ,
2021-04-30 14:08:01 -05:00
Version : 1 ,
2021-08-12 08:04:09 -05:00
NamespaceUID : folderUID , // Folder already created, comes from env var.
2021-04-29 12:24:37 -05:00
RuleGroup : da . Name ,
For : duration ( da . For ) ,
Updated : time . Now ( ) . UTC ( ) ,
Annotations : annotations ,
2021-05-31 09:17:17 -05:00
Labels : lbls ,
2021-04-29 12:24:37 -05:00
}
2021-04-30 12:35:26 -05:00
var err error
2021-04-29 12:24:37 -05:00
ar . NoDataState , err = transNoData ( da . ParsedSettings . NoDataState )
if err != nil {
return nil , err
}
ar . ExecErrState , err = transExecErr ( da . ParsedSettings . ExecutionErrorState )
if err != nil {
return nil , err
}
2021-05-31 07:00:58 -05:00
// Label for routing and silences.
2021-08-12 08:04:09 -05:00
n , v := getLabelForRouteMatching ( ar . UID )
2021-05-31 07:00:58 -05:00
ar . Labels [ n ] = v
if err := m . addSilence ( da , ar ) ; err != nil {
m . mg . Logger . Error ( "alert migration error: failed to create silence" , "rule_name" , ar . Title , "err" , err )
}
2021-11-04 15:42:34 -05:00
if err := m . addNoDataSilence ( da , ar ) ; err != nil {
m . mg . Logger . Error ( "alert migration error: failed to create silence for NoData" , "rule_name" , ar . Title , "err" , err )
}
2021-04-29 12:24:37 -05:00
return ar , nil
}
type alertQuery struct {
// RefID is the unique identifier of the query, set by the frontend call.
RefID string ` json:"refId" `
// QueryType is an optional identifier for the type of query.
// It can be used to distinguish different types of queries.
QueryType string ` json:"queryType" `
// RelativeTimeRange is the relative Start and End of the query as sent by the frontend.
RelativeTimeRange relativeTimeRange ` json:"relativeTimeRange" `
DatasourceUID string ` json:"datasourceUid" `
// JSON is the raw JSON query and includes the above properties as well as custom properties.
Model json . RawMessage ` json:"model" `
}
// RelativeTimeRange is the per query start and end time
// for requests.
type relativeTimeRange struct {
From duration ` json:"from" `
To duration ` json:"to" `
}
// duration is a type used for marshalling durations.
type duration time . Duration
func ( d duration ) String ( ) string {
return time . Duration ( d ) . String ( )
}
func ( d duration ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( time . Duration ( d ) . Seconds ( ) )
}
func ( d * duration ) UnmarshalJSON ( b [ ] byte ) error {
var v interface { }
if err := json . Unmarshal ( b , & v ) ; err != nil {
return err
}
switch value := v . ( type ) {
case float64 :
* d = duration ( time . Duration ( value ) * time . Second )
return nil
default :
return fmt . Errorf ( "invalid duration %v" , v )
}
}
func ruleAdjustInterval ( freq int64 ) int64 {
// 10 corresponds to the SchedulerCfg, but TODO not worrying about fetching for now.
var baseFreq int64 = 10
if freq <= baseFreq {
return 10
}
return freq - ( freq % baseFreq )
}
func transNoData ( s string ) ( string , error ) {
switch s {
case "ok" :
return "OK" , nil // values from ngalert/models/rule
case "" , "no_data" :
return "NoData" , nil
case "alerting" :
return "Alerting" , nil
case "keep_state" :
2021-11-04 15:42:34 -05:00
return "NoData" , nil // "keep last state" translates to no data because we now emit a special alert when the state is "noData". The result is that the evaluation will not return firing and instead we'll raise the special alert.
2021-04-29 12:24:37 -05:00
}
return "" , fmt . Errorf ( "unrecognized No Data setting %v" , s )
}
func transExecErr ( s string ) ( string , error ) {
switch s {
case "" , "alerting" :
return "Alerting" , nil
2021-05-18 12:55:43 -05:00
case "keep_state" :
return "Alerting" , nil
2021-04-29 12:24:37 -05:00
}
return "" , fmt . Errorf ( "unrecognized Execution Error setting %v" , s )
}