2021-03-30 09:37:56 -07:00
package schedule
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-01-30 09:55:35 +01:00
alertingModels "github.com/grafana/alerting/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
"github.com/grafana/grafana/pkg/services/ngalert/state"
)
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"
)
// stateToPostableAlert converts a state to a model that is accepted by Alertmanager. Annotations and Labels are copied from the state.
// - 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
func stateToPostableAlert ( alertState * state . State , appURL * url . URL ) * models . PostableAlert {
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
}
2022-05-23 10:53:41 +08:00
if alertState . Image != nil {
2023-01-10 19:59:13 +00:00
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 = ""
}
if alertState . State == eval . NoData {
return noDataAlert ( nL , nA , alertState , urlStr )
}
2021-11-25 10:46:47 +00:00
if alertState . State == eval . Error {
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 }
func noDataAlert ( labels data . Labels , annotations data . Labels , alertState * state . State , urlStr string ) * models . PostableAlert {
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.
func errorAlert ( labels , annotations data . Labels , alertState * state . State , urlStr string ) * models . PostableAlert {
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 ) ,
} ,
}
}
2022-12-06 13:07:39 -05:00
func FromStateTransitionToPostableAlerts ( firingStates [ ] state . StateTransition , stateManager * state . Manager , appURL * url . URL ) apimodels . PostableAlerts {
2021-04-19 12:28:44 +05:30
alerts := apimodels . PostableAlerts { PostableAlerts : make ( [ ] models . PostableAlert , 0 , len ( firingStates ) ) }
2021-05-19 13:15:09 -07:00
var sentAlerts [ ] * state . State
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
}
2022-12-06 13:07:39 -05:00
alert := stateToPostableAlert ( alertState . State , 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
func FromAlertsStateToStoppedAlert ( firingStates [ ] state . 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
}
2023-01-27 03:46:21 -05:00
postableAlert := stateToPostableAlert ( transition . State , appURL )
2022-01-11 11:39:34 -05:00
postableAlert . EndsAt = strfmt . DateTime ( ts )
alerts . PostableAlerts = append ( alerts . PostableAlerts , * postableAlert )
}
return alerts
}