2021-04-01 03:11:45 -05:00
package models
import (
2022-10-26 18:16:02 -05:00
"context"
2022-03-01 10:10:29 -06:00
"encoding/json"
2021-04-01 03:11:45 -05:00
"errors"
"fmt"
2024-05-09 12:12:44 -05:00
"hash/fnv"
2022-06-22 09:52:46 -05:00
"sort"
2022-08-03 09:05:32 -05:00
"strconv"
2024-03-12 09:00:43 -05:00
"strings"
2021-04-01 03:11:45 -05:00
"time"
2024-05-09 12:12:44 -05:00
"unsafe"
2022-03-01 10:10:29 -06:00
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
2023-02-03 10:36:49 -06:00
alertingModels "github.com/grafana/alerting/models"
2024-05-09 12:12:44 -05:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2023-01-27 02:46:21 -06:00
2022-11-14 13:08:10 -06:00
"github.com/grafana/grafana/pkg/services/quota"
2023-11-17 10:20:50 -06:00
"github.com/grafana/grafana/pkg/setting"
2022-03-01 10:10:29 -06:00
"github.com/grafana/grafana/pkg/util/cmputil"
2021-04-01 03:11:45 -05:00
)
var (
// ErrAlertRuleNotFound is an error for an unknown alert rule.
ErrAlertRuleNotFound = fmt . Errorf ( "could not find alert rule" )
// ErrAlertRuleFailedGenerateUniqueUID is an error for failure to generate alert rule UID
ErrAlertRuleFailedGenerateUniqueUID = errors . New ( "failed to generate alert rule UID" )
2021-04-15 07:54:37 -05:00
// ErrCannotEditNamespace is an error returned if the user does not have permissions to edit the namespace
2022-03-16 11:04:19 -05:00
ErrCannotEditNamespace = errors . New ( "user does not have permissions to edit the namespace" )
ErrRuleGroupNamespaceNotFound = errors . New ( "rule group not found under this namespace" )
ErrAlertRuleFailedValidation = errors . New ( "invalid alert rule" )
2024-02-07 11:55:48 -06:00
ErrAlertRuleUniqueConstraintViolation = errors . New ( "rule title under the same organisation and folder should be unique" )
2022-07-13 17:36:17 -05:00
ErrQuotaReached = errors . New ( "quota has been exceeded" )
2022-11-09 15:06:49 -06:00
// ErrNoDashboard is returned when the alert rule does not have a Dashboard UID
// in its annotations or the dashboard does not exist.
ErrNoDashboard = errors . New ( "no dashboard" )
// ErrNoPanel is returned when the alert rule does not have a PanelID in its
// annotations.
ErrNoPanel = errors . New ( "no panel" )
2021-04-01 03:11:45 -05:00
)
2022-06-10 09:25:15 -05:00
// swagger:enum NoDataState
2021-04-01 03:11:45 -05:00
type NoDataState string
func ( noDataState NoDataState ) String ( ) string {
return string ( noDataState )
}
2022-02-23 10:30:04 -06:00
func NoDataStateFromString ( state string ) ( NoDataState , error ) {
switch state {
case string ( Alerting ) :
return Alerting , nil
case string ( NoData ) :
return NoData , nil
case string ( OK ) :
return OK , nil
2024-03-12 09:00:43 -05:00
case string ( KeepLast ) :
return KeepLast , nil
2022-02-23 10:30:04 -06:00
default :
return "" , fmt . Errorf ( "unknown NoData state option %s" , state )
}
}
2021-04-01 03:11:45 -05:00
const (
2021-05-18 12:55:43 -05:00
Alerting NoDataState = "Alerting"
NoData NoDataState = "NoData"
OK NoDataState = "OK"
2024-03-12 09:00:43 -05:00
KeepLast NoDataState = "KeepLast"
2021-04-01 03:11:45 -05:00
)
2022-06-10 09:25:15 -05:00
// swagger:enum ExecutionErrorState
2021-04-01 03:11:45 -05:00
type ExecutionErrorState string
func ( executionErrorState ExecutionErrorState ) String ( ) string {
return string ( executionErrorState )
}
2022-02-23 10:30:04 -06:00
func ErrStateFromString ( opt string ) ( ExecutionErrorState , error ) {
switch opt {
case string ( Alerting ) :
return AlertingErrState , nil
case string ( ErrorErrState ) :
return ErrorErrState , nil
case string ( OkErrState ) :
return OkErrState , nil
2024-03-12 09:00:43 -05:00
case string ( KeepLastErrState ) :
return KeepLastErrState , nil
2022-02-23 10:30:04 -06:00
default :
return "" , fmt . Errorf ( "unknown Error state option %s" , opt )
}
}
2021-04-01 03:11:45 -05:00
const (
2021-05-18 12:55:43 -05:00
AlertingErrState ExecutionErrorState = "Alerting"
2021-11-25 04:46:47 -06:00
ErrorErrState ExecutionErrorState = "Error"
2022-02-23 10:30:04 -06:00
OkErrState ExecutionErrorState = "OK"
2024-03-12 09:00:43 -05:00
KeepLastErrState ExecutionErrorState = "KeepLast"
2021-04-01 03:11:45 -05:00
)
2021-04-23 14:32:25 -05:00
const (
2021-10-07 16:30:06 -05:00
// Annotations are actually a set of labels, so technically this is the label name of an annotation.
DashboardUIDAnnotation = "__dashboardUid__"
PanelIDAnnotation = "__panelId__"
2022-06-17 12:10:49 -05:00
// GrafanaReservedLabelPrefix contains the prefix for Grafana reserved labels. These differ from "__<label>__" labels
// in that they are not meant for internal-use only and will be passed-through to AMs and available to users in the same
// way as manually configured labels.
GrafanaReservedLabelPrefix = "grafana_"
// FolderTitleLabel is the label that will contain the title of an alert's folder/namespace.
FolderTitleLabel = GrafanaReservedLabelPrefix + "folder"
2022-09-21 12:24:47 -05:00
// StateReasonAnnotation is the name of the annotation that explains the difference between evaluation state and alert state (i.e. changing state when NoData or Error).
StateReasonAnnotation = GrafanaReservedLabelPrefix + "state_reason"
2023-12-19 12:25:13 -06:00
// MigratedLabelPrefix is a label prefix for all labels created during legacy migration.
MigratedLabelPrefix = "__legacy_"
// MigratedUseLegacyChannelsLabel is created during legacy migration to route to separate nested policies for migrated channels.
MigratedUseLegacyChannelsLabel = MigratedLabelPrefix + "use_channels__"
// MigratedContactLabelPrefix is created during legacy migration to route a migrated alert rule to a specific migrated channel.
MigratedContactLabelPrefix = MigratedLabelPrefix + "c_"
2024-01-24 14:56:19 -06:00
// MigratedSilenceLabelErrorKeepState is a label that will match a silence rule intended for legacy alerts with error state = keep_state.
MigratedSilenceLabelErrorKeepState = MigratedLabelPrefix + "silence_error_keep_state__"
// MigratedSilenceLabelNodataKeepState is a label that will match a silence rule intended for legacy alerts with nodata state = keep_state.
MigratedSilenceLabelNodataKeepState = MigratedLabelPrefix + "silence_nodata_keep_state__"
2023-12-19 12:25:13 -06:00
// MigratedAlertIdAnnotation is created during legacy migration to store the ID of the migrated legacy alert rule.
MigratedAlertIdAnnotation = "__alertId__"
// MigratedMessageAnnotation is created during legacy migration to store the migrated alert message.
MigratedMessageAnnotation = "message"
2024-02-15 08:45:10 -06:00
// AutogeneratedRouteLabel a label name used to distinguish alerts that are supposed to be handled by the autogenerated policy. Only expected value is `true`.
AutogeneratedRouteLabel = "__grafana_autogenerated__"
// AutogeneratedRouteReceiverNameLabel a label name that contains the name of the receiver that should be used to send notifications for the alert.
AutogeneratedRouteReceiverNameLabel = "__grafana_receiver__"
// AutogeneratedRouteSettingsHashLabel a label name that contains the hash of the notification settings that will be used to send notifications for the alert.
// This should uniquely identify the notification settings (group_by, group_wait, group_interval, repeat_interval, mute_time_intervals) for the alert.
AutogeneratedRouteSettingsHashLabel = "__grafana_route_settings_hash__"
2022-09-21 12:24:47 -05:00
)
2022-12-08 14:12:13 -06:00
const (
2022-09-21 12:24:47 -05:00
StateReasonMissingSeries = "MissingSeries"
2023-10-18 17:26:41 -05:00
StateReasonNoData = "NoData"
2022-12-08 14:12:13 -06:00
StateReasonError = "Error"
2023-01-26 11:29:10 -06:00
StateReasonPaused = "Paused"
StateReasonUpdated = "Updated"
2023-01-27 02:46:21 -06:00
StateReasonRuleDeleted = "RuleDeleted"
2024-03-12 09:00:43 -05:00
StateReasonKeepLast = "KeepLast"
2021-04-23 14:32:25 -05:00
)
2024-03-12 09:00:43 -05:00
func ConcatReasons ( reasons ... string ) string {
return strings . Join ( reasons , ", " )
}
2022-05-18 04:21:18 -05:00
var (
// InternalLabelNameSet are labels that grafana automatically include as part of the labelset.
InternalLabelNameSet = map [ string ] struct { } {
2023-01-10 13:59:13 -06:00
alertingModels . RuleUIDLabel : { } ,
alertingModels . NamespaceUIDLabel : { } ,
2022-05-18 04:21:18 -05:00
}
2022-05-22 21:53:41 -05:00
InternalAnnotationNameSet = map [ string ] struct { } {
2023-01-10 13:59:13 -06:00
DashboardUIDAnnotation : { } ,
PanelIDAnnotation : { } ,
alertingModels . ImageTokenAnnotation : { } ,
2022-05-22 21:53:41 -05:00
}
2024-02-15 08:45:10 -06:00
// LabelsUserCannotSpecify are labels that the user cannot specify when creating an alert rule.
LabelsUserCannotSpecify = map [ string ] struct { } {
AutogeneratedRouteLabel : { } ,
AutogeneratedRouteReceiverNameLabel : { } ,
AutogeneratedRouteSettingsHashLabel : { } ,
}
2022-05-18 04:21:18 -05:00
)
2022-08-12 16:36:50 -05:00
// AlertRuleGroup is the base model for a rule group in unified alerting.
type AlertRuleGroup struct {
Title string
FolderUID string
Interval int64
Provenance Provenance
Rules [ ] AlertRule
}
2023-03-29 12:34:59 -05:00
// AlertRuleGroupWithFolderTitle extends AlertRuleGroup with orgID and folder title
type AlertRuleGroupWithFolderTitle struct {
* AlertRuleGroup
OrgID int64
FolderTitle string
}
2023-10-02 10:47:59 -05:00
func NewAlertRuleGroupWithFolderTitle ( groupKey AlertRuleGroupKey , rules [ ] AlertRule , folderTitle string ) AlertRuleGroupWithFolderTitle {
SortAlertRulesByGroupIndex ( rules )
var interval int64
if len ( rules ) > 0 {
interval = rules [ 0 ] . IntervalSeconds
}
var result = AlertRuleGroupWithFolderTitle {
AlertRuleGroup : & AlertRuleGroup {
Title : groupKey . RuleGroup ,
FolderUID : groupKey . NamespaceUID ,
Interval : interval ,
Rules : rules ,
} ,
FolderTitle : folderTitle ,
OrgID : groupKey . OrgID ,
}
return result
}
func NewAlertRuleGroupWithFolderTitleFromRulesGroup ( groupKey AlertRuleGroupKey , rules RulesGroup , folderTitle string ) AlertRuleGroupWithFolderTitle {
derefRules := make ( [ ] AlertRule , 0 , len ( rules ) )
for _ , rule := range rules {
derefRules = append ( derefRules , * rule )
}
return NewAlertRuleGroupWithFolderTitle ( groupKey , derefRules , folderTitle )
}
// SortAlertRuleGroupWithFolderTitle sorts AlertRuleGroupWithFolderTitle by folder UID and group name
func SortAlertRuleGroupWithFolderTitle ( g [ ] AlertRuleGroupWithFolderTitle ) {
sort . SliceStable ( g , func ( i , j int ) bool {
if g [ i ] . AlertRuleGroup . FolderUID == g [ j ] . AlertRuleGroup . FolderUID {
return g [ i ] . AlertRuleGroup . Title < g [ j ] . AlertRuleGroup . Title
}
return g [ i ] . AlertRuleGroup . FolderUID < g [ j ] . AlertRuleGroup . FolderUID
} )
}
2021-04-01 03:11:45 -05:00
// AlertRule is the model for alert rules in unified alerting.
type AlertRule struct {
ID int64 ` xorm:"pk autoincr 'id'" `
OrgID int64 ` xorm:"org_id" `
Title string
Condition string
Data [ ] AlertQuery
Updated time . Time
IntervalSeconds int64
2022-06-13 11:15:28 -05:00
Version int64 ` xorm:"version" ` // this tag makes xorm add optimistic lock (see https://xorm.io/docs/chapter-06/1.lock/)
2021-10-04 10:33:55 -05:00
UID string ` xorm:"uid" `
NamespaceUID string ` xorm:"namespace_uid" `
DashboardUID * string ` xorm:"dashboard_uid" `
PanelID * int64 ` xorm:"panel_id" `
2021-04-01 03:11:45 -05:00
RuleGroup string
2024-05-09 12:12:44 -05:00
RuleGroupIndex int ` xorm:"rule_group_idx" `
2024-05-14 14:50:06 -05:00
Record * Record ` xorm:"json" `
2021-04-01 03:11:45 -05:00
NoDataState NoDataState
ExecErrState ExecutionErrorState
2021-04-15 07:54:37 -05:00
// ideally this field should have been apimodels.ApiDuration
// but this is currently not possible because of circular dependencies
2024-02-15 08:45:10 -06:00
For time . Duration
Annotations map [ string ] string
Labels map [ string ] string
IsPaused bool
NotificationSettings [ ] NotificationSettings ` xorm:"notification_settings" ` // we use slice to workaround xorm mapping that does not serialize a struct to JSON unless it's a slice
2021-04-01 03:11:45 -05:00
}
2023-02-01 06:15:03 -06:00
// AlertRuleWithOptionals This is to avoid having to pass in additional arguments deep in the call stack. Alert rule
// object is created in an early validation step without knowledge about current alert rule fields or if they need to be
// overridden. This is done in a later step and, in that step, we did not have knowledge about if a field was optional
// nor its possible value.
type AlertRuleWithOptionals struct {
AlertRule
// This parameter is to know if an optional API field was sent and, therefore, patch it with the current field from
// DB in case it was not sent.
HasPause bool
}
2023-04-17 11:45:06 -05:00
// AlertsRulesBy is a function that defines the ordering of alert rules.
type AlertRulesBy func ( a1 , a2 * AlertRule ) bool
func ( by AlertRulesBy ) Sort ( rules [ ] * AlertRule ) {
sort . Sort ( AlertRulesSorter { rules : rules , by : by } )
}
// AlertRulesByIndex orders alert rules by rule group index. You should
// make sure that all alert rules belong to the same rule group (have the
// same RuleGroupKey) before using this ordering.
func AlertRulesByIndex ( a1 , a2 * AlertRule ) bool {
return a1 . RuleGroupIndex < a2 . RuleGroupIndex
}
func AlertRulesByGroupKeyAndIndex ( a1 , a2 * AlertRule ) bool {
k1 , k2 := a1 . GetGroupKey ( ) , a2 . GetGroupKey ( )
if k1 == k2 {
return a1 . RuleGroupIndex < a2 . RuleGroupIndex
}
return AlertRuleGroupKeyByNamespaceAndRuleGroup ( & k1 , & k2 )
}
type AlertRulesSorter struct {
rules [ ] * AlertRule
by AlertRulesBy
}
func ( s AlertRulesSorter ) Len ( ) int { return len ( s . rules ) }
func ( s AlertRulesSorter ) Swap ( i , j int ) { s . rules [ i ] , s . rules [ j ] = s . rules [ j ] , s . rules [ i ] }
func ( s AlertRulesSorter ) Less ( i , j int ) bool { return s . by ( s . rules [ i ] , s . rules [ j ] ) }
2022-11-10 03:58:38 -06:00
// GetDashboardUID returns the DashboardUID or "".
func ( alertRule * AlertRule ) GetDashboardUID ( ) string {
if alertRule . DashboardUID != nil {
return * alertRule . DashboardUID
}
return ""
}
// GetPanelID returns the Panel ID or -1.
func ( alertRule * AlertRule ) GetPanelID ( ) int64 {
if alertRule . PanelID != nil {
return * alertRule . PanelID
}
return - 1
}
2022-03-16 11:04:19 -05:00
type LabelOption func ( map [ string ] string )
func WithoutInternalLabels ( ) LabelOption {
return func ( labels map [ string ] string ) {
for k := range labels {
if _ , ok := InternalLabelNameSet [ k ] ; ok {
delete ( labels , k )
}
}
}
}
// GetLabels returns the labels specified as part of the alert rule.
func ( alertRule * AlertRule ) GetLabels ( opts ... LabelOption ) map [ string ] string {
labels := alertRule . Labels
for _ , opt := range opts {
opt ( labels )
}
return labels
}
2022-07-12 15:51:32 -05:00
func ( alertRule * AlertRule ) GetEvalCondition ( ) Condition {
return Condition {
Condition : alertRule . Condition ,
Data : alertRule . Data ,
}
}
2022-03-01 10:10:29 -06:00
// Diff calculates diff between two alert rules. Returns nil if two rules are equal. Otherwise, returns cmputil.DiffReport
func ( alertRule * AlertRule ) Diff ( rule * AlertRule , ignore ... string ) cmputil . DiffReport {
var reporter cmputil . DiffReporter
2024-02-15 08:45:10 -06:00
ops := make ( [ ] cmp . Option , 0 , 6 )
2022-03-01 10:10:29 -06:00
// json.RawMessage is a slice of bytes and therefore cmp's default behavior is to compare it by byte, which is not really useful
var jsonCmp = cmp . Transformer ( "" , func ( in json . RawMessage ) string {
2024-03-20 12:10:39 -05:00
b , err := json . Marshal ( in )
if err != nil {
return string ( in )
}
return string ( b )
2022-03-01 10:10:29 -06:00
} )
2024-02-15 08:45:10 -06:00
ops = append (
ops ,
cmp . Reporter ( & reporter ) ,
cmpopts . IgnoreFields ( AlertQuery { } , "modelProps" ) ,
jsonCmp ,
cmpopts . EquateEmpty ( ) ,
)
2022-03-01 10:10:29 -06:00
if len ( ignore ) > 0 {
ops = append ( ops , cmpopts . IgnoreFields ( AlertRule { } , ignore ... ) )
}
cmp . Equal ( alertRule , rule , ops ... )
return reporter . Diffs
}
2022-12-16 04:47:25 -06:00
// SetDashboardAndPanelFromAnnotations will set the DashboardUID and PanelID field by doing a lookup in the annotations.
// Errors when the found annotations are not valid.
func ( alertRule * AlertRule ) SetDashboardAndPanelFromAnnotations ( ) error {
2022-08-03 09:05:32 -05:00
if alertRule . Annotations == nil {
return nil
}
dashUID := alertRule . Annotations [ DashboardUIDAnnotation ]
panelID := alertRule . Annotations [ PanelIDAnnotation ]
if dashUID != "" && panelID == "" || dashUID == "" && panelID != "" {
return fmt . Errorf ( "both annotations %s and %s must be specified" ,
DashboardUIDAnnotation , PanelIDAnnotation )
}
if dashUID != "" {
panelIDValue , err := strconv . ParseInt ( panelID , 10 , 64 )
if err != nil {
return fmt . Errorf ( "annotation %s must be a valid integer Panel ID" ,
PanelIDAnnotation )
}
alertRule . DashboardUID = & dashUID
alertRule . PanelID = & panelIDValue
}
return nil
}
2021-04-01 03:11:45 -05:00
// AlertRuleKey is the alert definition identifier
type AlertRuleKey struct {
2022-08-01 18:28:38 -05:00
OrgID int64 ` xorm:"org_id" `
UID string ` xorm:"uid" `
}
2023-08-30 10:46:47 -05:00
func ( k AlertRuleKey ) LogContext ( ) [ ] any {
return [ ] any { "rule_uid" , k . UID , "org_id" , k . OrgID }
2022-08-18 08:40:33 -05:00
}
2022-08-01 18:28:38 -05:00
type AlertRuleKeyWithVersion struct {
Version int64
AlertRuleKey ` xorm:"extends" `
2021-04-01 03:11:45 -05:00
}
2023-10-06 17:11:24 -05:00
type AlertRuleKeyWithId struct {
AlertRuleKey
ID int64
}
2022-05-16 14:45:45 -05:00
// AlertRuleGroupKey is the identifier of a group of alerts
type AlertRuleGroupKey struct {
OrgID int64
NamespaceUID string
RuleGroup string
}
func ( k AlertRuleGroupKey ) String ( ) string {
return fmt . Sprintf ( "{orgID: %d, namespaceUID: %s, groupName: %s}" , k . OrgID , k . NamespaceUID , k . RuleGroup )
}
2021-04-01 03:11:45 -05:00
func ( k AlertRuleKey ) String ( ) string {
return fmt . Sprintf ( "{orgID: %d, UID: %s}" , k . OrgID , k . UID )
}
2023-04-17 11:45:06 -05:00
// AlertRuleGroupKeyBy is a function that defines the ordering of alert rule group keys.
type AlertRuleGroupKeyBy func ( a1 , a2 * AlertRuleGroupKey ) bool
func ( by AlertRuleGroupKeyBy ) Sort ( keys [ ] AlertRuleGroupKey ) {
sort . Sort ( AlertRuleGroupKeySorter { keys : keys , by : by } )
}
func AlertRuleGroupKeyByNamespaceAndRuleGroup ( k1 , k2 * AlertRuleGroupKey ) bool {
if k1 . NamespaceUID == k2 . NamespaceUID {
return k1 . RuleGroup < k2 . RuleGroup
}
return k1 . NamespaceUID < k2 . NamespaceUID
}
type AlertRuleGroupKeySorter struct {
keys [ ] AlertRuleGroupKey
by AlertRuleGroupKeyBy
}
func ( s AlertRuleGroupKeySorter ) Len ( ) int { return len ( s . keys ) }
func ( s AlertRuleGroupKeySorter ) Swap ( i , j int ) { s . keys [ i ] , s . keys [ j ] = s . keys [ j ] , s . keys [ i ] }
func ( s AlertRuleGroupKeySorter ) Less ( i , j int ) bool { return s . by ( & s . keys [ i ] , & s . keys [ j ] ) }
2021-04-01 03:11:45 -05:00
// GetKey returns the alert definitions identifier
func ( alertRule * AlertRule ) GetKey ( ) AlertRuleKey {
return AlertRuleKey { OrgID : alertRule . OrgID , UID : alertRule . UID }
}
2022-05-16 14:45:45 -05:00
// GetGroupKey returns the identifier of a group the rule belongs to
func ( alertRule * AlertRule ) GetGroupKey ( ) AlertRuleGroupKey {
return AlertRuleGroupKey { OrgID : alertRule . OrgID , NamespaceUID : alertRule . NamespaceUID , RuleGroup : alertRule . RuleGroup }
}
2021-04-01 03:11:45 -05:00
// PreSave sets default values and loads the updated model for each alert query.
func ( alertRule * AlertRule ) PreSave ( timeNow func ( ) time . Time ) error {
for i , q := range alertRule . Data {
err := q . PreSave ( )
if err != nil {
return fmt . Errorf ( "invalid alert query %s: %w" , q . RefID , err )
}
alertRule . Data [ i ] = q
}
alertRule . Updated = timeNow ( )
return nil
}
2023-11-17 10:20:50 -06:00
// ValidateAlertRule validates various alert rule fields.
func ( alertRule * AlertRule ) ValidateAlertRule ( cfg setting . UnifiedAlertingSettings ) error {
if len ( alertRule . Data ) == 0 {
return fmt . Errorf ( "%w: no queries or expressions are found" , ErrAlertRuleFailedValidation )
}
if alertRule . Title == "" {
return fmt . Errorf ( "%w: title is empty" , ErrAlertRuleFailedValidation )
}
if err := ValidateRuleGroupInterval ( alertRule . IntervalSeconds , int64 ( cfg . BaseInterval . Seconds ( ) ) ) ; err != nil {
return err
}
if alertRule . OrgID == 0 {
return fmt . Errorf ( "%w: no organisation is found" , ErrAlertRuleFailedValidation )
}
if alertRule . DashboardUID == nil && alertRule . PanelID != nil {
return fmt . Errorf ( "%w: cannot have Panel ID without a Dashboard UID" , ErrAlertRuleFailedValidation )
}
2024-05-15 09:35:54 -05:00
if ! alertRule . IsRecordingRule ( ) {
if _ , err := ErrStateFromString ( string ( alertRule . ExecErrState ) ) ; err != nil {
return err
}
2023-11-17 10:20:50 -06:00
2024-05-15 09:35:54 -05:00
if _ , err := NoDataStateFromString ( string ( alertRule . NoDataState ) ) ; err != nil {
return err
}
2023-11-17 10:20:50 -06:00
}
if alertRule . For < 0 {
return fmt . Errorf ( "%w: field `for` cannot be negative" , ErrAlertRuleFailedValidation )
}
2024-02-15 08:45:10 -06:00
if len ( alertRule . Labels ) > 0 {
for label := range alertRule . Labels {
if _ , ok := LabelsUserCannotSpecify [ label ] ; ok {
return fmt . Errorf ( "%w: system reserved label %s cannot be defined" , ErrAlertRuleFailedValidation , label )
}
}
}
if len ( alertRule . NotificationSettings ) > 0 {
if len ( alertRule . NotificationSettings ) != 1 {
return fmt . Errorf ( "%w: only one notification settings entry is allowed" , ErrAlertRuleFailedValidation )
}
if err := alertRule . NotificationSettings [ 0 ] . Validate ( ) ; err != nil {
return errors . Join ( ErrAlertRuleFailedValidation , fmt . Errorf ( "invalid notification settings: %w" , err ) )
}
}
2023-11-17 10:20:50 -06:00
return nil
}
2022-02-04 13:23:19 -06:00
func ( alertRule * AlertRule ) ResourceType ( ) string {
return "alertRule"
}
func ( alertRule * AlertRule ) ResourceID ( ) string {
return alertRule . UID
}
func ( alertRule * AlertRule ) ResourceOrgID ( ) int64 {
return alertRule . OrgID
}
2024-01-30 16:14:11 -06:00
func ( alertRule * AlertRule ) GetFolderKey ( ) FolderKey {
return FolderKey {
OrgID : alertRule . OrgID ,
UID : alertRule . NamespaceUID ,
}
}
2024-05-14 14:50:06 -05:00
func ( alertRule * AlertRule ) IsRecordingRule ( ) bool {
return alertRule . Record != nil
}
2021-04-01 03:11:45 -05:00
// AlertRuleVersion is the model for alert rule versions in unified alerting.
type AlertRuleVersion struct {
ID int64 ` xorm:"pk autoincr 'id'" `
RuleOrgID int64 ` xorm:"rule_org_id" `
RuleUID string ` xorm:"rule_uid" `
RuleNamespaceUID string ` xorm:"rule_namespace_uid" `
RuleGroup string
2022-06-22 09:52:46 -05:00
RuleGroupIndex int ` xorm:"rule_group_idx" `
2021-04-01 03:11:45 -05:00
ParentVersion int64
RestoredFrom int64
Version int64
Created time . Time
Title string
Condition string
Data [ ] AlertQuery
IntervalSeconds int64
2024-05-14 14:50:06 -05:00
Record * Record ` xorm:"json" `
2021-04-01 03:11:45 -05:00
NoDataState NoDataState
ExecErrState ExecutionErrorState
2021-04-15 07:54:37 -05:00
// ideally this field should have been apimodels.ApiDuration
// but this is currently not possible because of circular dependencies
2024-02-15 08:45:10 -06:00
For time . Duration
Annotations map [ string ] string
Labels map [ string ] string
IsPaused bool
NotificationSettings [ ] NotificationSettings ` xorm:"notification_settings" ` // we use slice to workaround xorm mapping that does not serialize a struct to JSON unless it's a slice
2021-04-01 03:11:45 -05:00
}
// GetAlertRuleByUIDQuery is the query for retrieving/deleting an alert rule by UID and organisation ID.
type GetAlertRuleByUIDQuery struct {
UID string
OrgID int64
}
2022-06-01 09:23:54 -05:00
// GetAlertRulesGroupByRuleUIDQuery is the query for retrieving a group of alerts by UID of a rule that belongs to that group
type GetAlertRulesGroupByRuleUIDQuery struct {
UID string
OrgID int64
}
2021-04-01 03:11:45 -05:00
// ListAlertRulesQuery is the query for listing alert rules
type ListAlertRulesQuery struct {
2021-07-22 01:53:14 -05:00
OrgID int64
NamespaceUIDs [ ] string
2021-09-29 09:16:40 -05:00
ExcludeOrgs [ ] int64
2022-04-25 05:42:42 -05:00
RuleGroup string
2021-04-01 03:11:45 -05:00
2021-10-04 10:33:55 -05:00
// DashboardUID and PanelID are optional and allow filtering rules
// to return just those for a dashboard and panel.
DashboardUID string
PanelID int64
2024-02-15 08:45:10 -06:00
ReceiverName string
2021-04-01 03:11:45 -05:00
}
2022-11-08 04:51:00 -06:00
// CountAlertRulesQuery is the query for counting alert rules
type CountAlertRulesQuery struct {
OrgID int64
NamespaceUID string
}
2024-01-30 16:14:11 -06:00
type FolderKey struct {
OrgID int64
UID string
}
func ( f FolderKey ) String ( ) string {
return fmt . Sprintf ( "%d:%s" , f . OrgID , f . UID )
}
2022-05-12 08:55:05 -05:00
type GetAlertRulesForSchedulingQuery struct {
2022-08-31 10:08:19 -05:00
PopulateFolders bool
2023-04-13 06:55:42 -05:00
RuleGroups [ ] string
2022-08-31 10:08:19 -05:00
2024-01-17 03:07:39 -06:00
ResultRules [ ] * AlertRule
// A map of folder UID to folder Title in NamespaceKey format (see GetNamespaceKey)
2024-01-30 16:14:11 -06:00
ResultFoldersTitles map [ FolderKey ] string
2022-05-12 08:55:05 -05:00
}
2021-04-01 03:11:45 -05:00
// ListNamespaceAlertRulesQuery is the query for listing namespace alert rules
type ListNamespaceAlertRulesQuery struct {
OrgID int64
// Namespace is the folder slug
NamespaceUID string
}
2021-04-13 16:38:09 -05:00
// ListOrgRuleGroupsQuery is the query for listing unique rule groups
2022-04-21 11:59:22 -05:00
// for an organization
2021-04-13 16:38:09 -05:00
type ListOrgRuleGroupsQuery struct {
2021-07-22 01:53:14 -05:00
OrgID int64
NamespaceUIDs [ ] string
2021-04-13 16:38:09 -05:00
2021-10-04 10:33:55 -05:00
// DashboardUID and PanelID are optional and allow filtering rules
// to return just those for a dashboard and panel.
DashboardUID string
PanelID int64
2021-04-13 16:38:09 -05:00
}
2022-09-29 15:47:56 -05:00
type UpdateRule struct {
Existing * AlertRule
New AlertRule
}
2021-04-01 03:11:45 -05:00
// Condition contains backend expressions and queries and the RefID
// of the query or expression that will be evaluated.
type Condition struct {
// Condition is the RefID of the query or expression from
// the Data property to get the results for.
Condition string ` json:"condition" `
// Data is an array of data source queries and/or server side expressions.
Data [ ] AlertQuery ` json:"data" `
}
// IsValid checks the condition's validity.
func ( c Condition ) IsValid ( ) bool {
// TODO search for refIDs in QueriesAndExpressions
return len ( c . Data ) != 0
}
2022-02-23 10:30:04 -06:00
// PatchPartialAlertRule patches `ruleToPatch` by `existingRule` following the rule that if a field of `ruleToPatch` is empty or has the default value, it is populated by the value of the corresponding field from `existingRule`.
// There are several exceptions:
// 1. Following fields are not patched and therefore will be ignored: AlertRule.ID, AlertRule.OrgID, AlertRule.Updated, AlertRule.Version, AlertRule.UID, AlertRule.DashboardUID, AlertRule.PanelID, AlertRule.Annotations and AlertRule.Labels
// 2. There are fields that are patched together:
2022-09-01 11:15:44 -05:00
// - AlertRule.Condition and AlertRule.Data
//
2022-02-23 10:30:04 -06:00
// If either of the pair is specified, neither is patched.
2023-02-01 06:15:03 -06:00
func PatchPartialAlertRule ( existingRule * AlertRule , ruleToPatch * AlertRuleWithOptionals ) {
2022-02-23 10:30:04 -06:00
if ruleToPatch . Title == "" {
ruleToPatch . Title = existingRule . Title
}
if ruleToPatch . Condition == "" || len ( ruleToPatch . Data ) == 0 {
ruleToPatch . Condition = existingRule . Condition
ruleToPatch . Data = existingRule . Data
}
if ruleToPatch . IntervalSeconds == 0 {
ruleToPatch . IntervalSeconds = existingRule . IntervalSeconds
}
if ruleToPatch . NamespaceUID == "" {
ruleToPatch . NamespaceUID = existingRule . NamespaceUID
}
if ruleToPatch . RuleGroup == "" {
ruleToPatch . RuleGroup = existingRule . RuleGroup
}
if ruleToPatch . ExecErrState == "" {
ruleToPatch . ExecErrState = existingRule . ExecErrState
}
if ruleToPatch . NoDataState == "" {
ruleToPatch . NoDataState = existingRule . NoDataState
}
2022-06-30 10:46:26 -05:00
if ruleToPatch . For == - 1 {
2022-02-23 10:30:04 -06:00
ruleToPatch . For = existingRule . For
}
2023-02-01 06:15:03 -06:00
if ! ruleToPatch . HasPause {
ruleToPatch . IsPaused = existingRule . IsPaused
}
2022-02-23 10:30:04 -06:00
}
2022-06-09 02:28:32 -05:00
func ValidateRuleGroupInterval ( intervalSeconds , baseIntervalSeconds int64 ) error {
if intervalSeconds % baseIntervalSeconds != 0 || intervalSeconds <= 0 {
return fmt . Errorf ( "%w: interval (%v) should be non-zero and divided exactly by scheduler interval: %v" ,
ErrAlertRuleFailedValidation , time . Duration ( intervalSeconds ) * time . Second , baseIntervalSeconds )
}
return nil
}
2022-06-22 09:52:46 -05:00
type RulesGroup [ ] * AlertRule
func ( g RulesGroup ) SortByGroupIndex ( ) {
sort . Slice ( g , func ( i , j int ) bool {
if g [ i ] . RuleGroupIndex == g [ j ] . RuleGroupIndex {
return g [ i ] . ID < g [ j ] . ID
}
return g [ i ] . RuleGroupIndex < g [ j ] . RuleGroupIndex
} )
}
2022-10-26 18:16:02 -05:00
2023-10-02 10:47:59 -05:00
func SortAlertRulesByGroupIndex ( rules [ ] AlertRule ) {
sort . Slice ( rules , func ( i , j int ) bool {
if rules [ i ] . RuleGroupIndex == rules [ j ] . RuleGroupIndex {
return rules [ i ] . ID < rules [ j ] . ID
}
return rules [ i ] . RuleGroupIndex < rules [ j ] . RuleGroupIndex
} )
}
2022-11-14 13:08:10 -06:00
const (
QuotaTargetSrv quota . TargetSrv = "ngalert"
QuotaTarget quota . Target = "alert_rule"
)
2022-10-26 18:16:02 -05:00
type ruleKeyContextKey struct { }
func WithRuleKey ( ctx context . Context , ruleKey AlertRuleKey ) context . Context {
return context . WithValue ( ctx , ruleKeyContextKey { } , ruleKey )
}
func RuleKeyFromContext ( ctx context . Context ) ( AlertRuleKey , bool ) {
key , ok := ctx . Value ( ruleKeyContextKey { } ) . ( AlertRuleKey )
return key , ok
}
2023-09-26 11:45:22 -05:00
// GroupByAlertRuleGroupKey groups all rules by AlertRuleGroupKey. Returns map of RulesGroup sorted by AlertRule.RuleGroupIndex
func GroupByAlertRuleGroupKey ( rules [ ] * AlertRule ) map [ AlertRuleGroupKey ] RulesGroup {
result := make ( map [ AlertRuleGroupKey ] RulesGroup )
for _ , rule := range rules {
result [ rule . GetGroupKey ( ) ] = append ( result [ rule . GetGroupKey ( ) ] , rule )
}
for _ , group := range result {
group . SortByGroupIndex ( )
}
return result
}
2024-05-09 12:12:44 -05:00
// Record contains mapping information for Recording Rules.
type Record struct {
// Metric indicates a metric name to send results to.
Metric string
// From contains a query RefID, indicating which expression node is the output of the recording rule.
From string
}
func ( r * Record ) Fingerprint ( ) data . Fingerprint {
h := fnv . New64 ( )
writeString := func ( s string ) {
// save on extra slice allocation when string is converted to bytes.
_ , _ = h . Write ( unsafe . Slice ( unsafe . StringData ( s ) , len ( s ) ) ) //nolint:gosec
// ignore errors returned by Write method because fnv never returns them.
_ , _ = h . Write ( [ ] byte { 255 } ) // use an invalid utf-8 sequence as separator
}
writeString ( r . Metric )
writeString ( r . From )
return data . Fingerprint ( h . Sum64 ( ) )
}