2023-06-08 18:59:54 -04:00
package state
2021-03-30 09:37:56 -07:00
import (
2022-10-10 13:40:21 +01:00
"encoding/json"
2021-06-16 15:34:12 +05:30
"fmt"
"net/url"
"path"
2022-11-07 21:33:25 +05:30
"strconv"
2021-05-19 13:15:09 -07:00
"time"
2022-01-11 11:39:34 -05:00
"github.com/benbjohnson/clock"
2021-04-02 08:11:33 -07:00
"github.com/go-openapi/strfmt"
2023-02-03 11:36:49 -05:00
alertingModels "github.com/grafana/alerting/models"
2021-06-24 03:15:49 -04:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2021-04-19 12:28:44 +05:30
"github.com/prometheus/alertmanager/api/v2/models"
2021-11-04 16:42:34 -04:00
"github.com/prometheus/common/model"
2021-04-19 12:28:44 +05:30
2021-11-04 16:42:34 -04:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
2021-06-16 15:34:12 +05:30
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
2021-03-30 09:37:56 -07:00
)
2021-11-04 16:42:34 -04:00
const (
NoDataAlertName = "DatasourceNoData"
2021-11-25 10:46:47 +00:00
ErrorAlertName = "DatasourceError"
2021-11-04 16:42:34 -04:00
Rulename = "rulename"
)
2023-06-08 18:59:54 -04:00
// StateToPostableAlert converts a state to a model that is accepted by Alertmanager. Annotations and Labels are copied from the state.
2021-11-04 16:42:34 -04:00
// - if state has at least one result, a new label '__value_string__' is added to the label set
2022-04-20 21:43:55 +08:00
// - the alert's GeneratorURL is constructed to point to the alert detail view
2021-11-04 16:42:34 -04:00
// - if evaluation state is either NoData or Error, the resulting set of labels is changed:
// - original alert name (label: model.AlertNameLabel) is backed up to OriginalAlertName
// - label model.AlertNameLabel is overwritten to either NoDataAlertName or ErrorAlertName
2024-01-09 14:47:19 -05:00
func StateToPostableAlert ( transition StateTransition , appURL * url . URL ) * models . PostableAlert {
alertState := transition . State
2021-11-04 16:42:34 -04:00
nL := alertState . Labels . Copy ( )
nA := data . Labels ( alertState . Annotations ) . Copy ( )
2022-10-10 13:40:21 +01:00
// encode the values as JSON where it will be expanded later
if len ( alertState . Values ) > 0 {
if b , err := json . Marshal ( alertState . Values ) ; err == nil {
2023-01-10 19:59:13 +00:00
nA [ alertingModels . ValuesAnnotation ] = string ( b )
2022-10-10 13:40:21 +01:00
}
}
2022-02-02 13:18:20 -05:00
if alertState . LastEvaluationString != "" {
2023-01-10 19:59:13 +00:00
nA [ alertingModels . ValueStringAnnotation ] = alertState . LastEvaluationString
2021-11-04 16:42:34 -04:00
}
2023-06-21 20:53:45 -03:00
if alertState . Image != nil && alertState . Image . Token != "" {
nA [ alertingModels . ImageTokenAnnotation ] = alertState . Image . Token
2022-05-23 10:53:41 +08:00
}
2022-09-21 13:24:47 -04:00
if alertState . StateReason != "" {
2023-01-10 19:59:13 +00:00
nA [ alertingModels . StateReasonAnnotation ] = alertState . StateReason
2022-09-21 13:24:47 -04:00
}
2022-11-07 21:33:25 +05:30
if alertState . OrgID != 0 {
2023-01-10 19:59:13 +00:00
nA [ alertingModels . OrgIDAnnotation ] = strconv . FormatInt ( alertState . OrgID , 10 )
2022-11-07 21:33:25 +05:30
}
2021-11-04 16:42:34 -04:00
var urlStr string
2023-01-10 19:59:13 +00:00
if uid := nL [ alertingModels . RuleUIDLabel ] ; len ( uid ) > 0 && appURL != nil {
2021-11-04 16:42:34 -04:00
u := * appURL
2022-04-20 21:43:55 +08:00
u . Path = path . Join ( u . Path , fmt . Sprintf ( "/alerting/grafana/%s/view" , uid ) )
2021-11-04 16:42:34 -04:00
urlStr = u . String ( )
} else if appURL != nil {
urlStr = appURL . String ( )
} else {
urlStr = ""
}
2024-01-09 14:47:19 -05:00
state := alertState . State
if alertState . Resolved {
// If this is a resolved alert, we need to send an alert with the correct labels such that they will expire the previous alert.
// In most cases the labels on the state will be correct, however when the previous alert was a NoData or Error alert, we need to
// ensure to modify it appropriately.
state = transition . PreviousState
}
if state == eval . NoData {
2021-11-04 16:42:34 -04:00
return noDataAlert ( nL , nA , alertState , urlStr )
}
2024-01-09 14:47:19 -05:00
if state == eval . Error {
2021-11-25 10:46:47 +00:00
return errorAlert ( nL , nA , alertState , urlStr )
}
2021-11-04 16:42:34 -04:00
return & models . PostableAlert {
Annotations : models . LabelSet ( nA ) ,
StartsAt : strfmt . DateTime ( alertState . StartsAt ) ,
EndsAt : strfmt . DateTime ( alertState . EndsAt ) ,
Alert : models . Alert {
Labels : models . LabelSet ( nL ) ,
GeneratorURL : strfmt . URI ( urlStr ) ,
} ,
}
}
// NoDataAlert is a special alert sent by Grafana to the Alertmanager, that indicates we received no data from the datasource.
// It effectively replaces the legacy behavior of "Keep Last State" by separating the regular alerting flow from the no data scenario into a separate alerts.
// The Alert is defined as:
// { alertname=DatasourceNoData rulename=original_alertname } + { rule labelset } + { rule annotations }
2023-06-08 18:59:54 -04:00
func noDataAlert ( labels data . Labels , annotations data . Labels , alertState * State , urlStr string ) * models . PostableAlert {
2021-11-04 16:42:34 -04:00
if name , ok := labels [ model . AlertNameLabel ] ; ok {
labels [ Rulename ] = name
}
labels [ model . AlertNameLabel ] = NoDataAlertName
return & models . PostableAlert {
Annotations : models . LabelSet ( annotations ) ,
StartsAt : strfmt . DateTime ( alertState . StartsAt ) ,
EndsAt : strfmt . DateTime ( alertState . EndsAt ) ,
Alert : models . Alert {
Labels : models . LabelSet ( labels ) ,
GeneratorURL : strfmt . URI ( urlStr ) ,
} ,
}
}
2021-11-25 10:46:47 +00:00
// errorAlert is a special alert sent when evaluation of an alert rule failed due to an error. Like noDataAlert, it
// replaces the old behaviour of "Keep Last State" creating a separate alert called DatasourceError.
2023-06-08 18:59:54 -04:00
func errorAlert ( labels , annotations data . Labels , alertState * State , urlStr string ) * models . PostableAlert {
2021-11-25 10:46:47 +00:00
if name , ok := labels [ model . AlertNameLabel ] ; ok {
labels [ Rulename ] = name
}
labels [ model . AlertNameLabel ] = ErrorAlertName
return & models . PostableAlert {
Annotations : models . LabelSet ( annotations ) ,
StartsAt : strfmt . DateTime ( alertState . StartsAt ) ,
EndsAt : strfmt . DateTime ( alertState . EndsAt ) ,
Alert : models . Alert {
Labels : models . LabelSet ( labels ) ,
GeneratorURL : strfmt . URI ( urlStr ) ,
} ,
}
}
2023-06-08 18:59:54 -04:00
func FromStateTransitionToPostableAlerts ( firingStates [ ] StateTransition , stateManager * Manager , appURL * url . URL ) apimodels . PostableAlerts {
2021-04-19 12:28:44 +05:30
alerts := apimodels . PostableAlerts { PostableAlerts : make ( [ ] models . PostableAlert , 0 , len ( firingStates ) ) }
2023-06-08 18:59:54 -04:00
var sentAlerts [ ] * State
2021-05-19 13:15:09 -07:00
ts := time . Now ( )
2021-06-16 15:34:12 +05:30
2021-04-02 08:11:33 -07:00
for _ , alertState := range firingStates {
2021-07-29 14:29:17 -04:00
if ! alertState . NeedsSending ( stateManager . ResendDelay ) {
continue
2021-04-02 08:11:33 -07:00
}
2024-01-09 14:47:19 -05:00
alert := StateToPostableAlert ( alertState , appURL )
2021-11-04 16:42:34 -04:00
alerts . PostableAlerts = append ( alerts . PostableAlerts , * alert )
2022-09-21 13:24:47 -04:00
if alertState . StateReason == ngModels . StateReasonMissingSeries { // do not put stale state back to state manager
continue
}
2021-07-29 14:29:17 -04:00
alertState . LastSentAt = ts
2022-12-06 13:07:39 -05:00
sentAlerts = append ( sentAlerts , alertState . State )
2021-03-30 09:37:56 -07:00
}
2021-05-19 13:15:09 -07:00
stateManager . Put ( sentAlerts )
2021-03-30 09:37:56 -07:00
return alerts
}
2022-01-11 11:39:34 -05:00
2023-01-27 03:46:21 -05:00
// FromAlertsStateToStoppedAlert selects only transitions from firing states (states eval.Alerting, eval.NoData, eval.Error)
// and converts them to models.PostableAlert with EndsAt set to time.Now
2023-06-08 18:59:54 -04:00
func FromAlertsStateToStoppedAlert ( firingStates [ ] StateTransition , appURL * url . URL , clock clock . Clock ) apimodels . PostableAlerts {
2022-01-11 11:39:34 -05:00
alerts := apimodels . PostableAlerts { PostableAlerts : make ( [ ] models . PostableAlert , 0 , len ( firingStates ) ) }
ts := clock . Now ( )
2023-01-27 03:46:21 -05:00
for _ , transition := range firingStates {
if transition . PreviousState == eval . Normal || transition . PreviousState == eval . Pending {
2022-01-11 11:39:34 -05:00
continue
}
2024-01-09 14:47:19 -05:00
postableAlert := StateToPostableAlert ( transition , appURL )
2022-01-11 11:39:34 -05:00
postableAlert . EndsAt = strfmt . DateTime ( ts )
alerts . PostableAlerts = append ( alerts . PostableAlerts , * postableAlert )
}
return alerts
}