2021-03-30 11:37:56 -05:00
package schedule
import (
2021-06-16 05:04:12 -05:00
"fmt"
"net/url"
"path"
2021-05-19 15:15:09 -05:00
"time"
2022-01-11 10:39:34 -06:00
"github.com/benbjohnson/clock"
2021-04-02 10:11:33 -05:00
"github.com/go-openapi/strfmt"
2021-06-24 02:15:49 -05:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2021-04-19 01:58:44 -05:00
"github.com/prometheus/alertmanager/api/v2/models"
2021-11-04 15:42:34 -05:00
"github.com/prometheus/common/model"
2021-04-19 01:58:44 -05:00
2021-11-04 15:42:34 -05:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
2021-06-16 05:04:12 -05:00
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
2021-03-30 11:37:56 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/state"
)
2021-11-04 15:42:34 -05:00
const (
NoDataAlertName = "DatasourceNoData"
2021-11-25 04:46:47 -06:00
ErrorAlertName = "DatasourceError"
2021-11-04 15:42:34 -05: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 08:43:55 -05:00
// - the alert's GeneratorURL is constructed to point to the alert detail view
2021-11-04 15:42:34 -05: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-02-02 12:18:20 -06:00
if alertState . LastEvaluationString != "" {
nA [ "__value_string__" ] = alertState . LastEvaluationString
2021-11-04 15:42:34 -05:00
}
2022-05-22 21:53:41 -05:00
if alertState . Image != nil {
2022-07-04 05:05:36 -05:00
nA [ ngModels . ImageTokenAnnotation ] = alertState . Image . Token
2022-05-22 21:53:41 -05:00
}
2021-11-04 15:42:34 -05:00
var urlStr string
if uid := nL [ ngModels . RuleUIDLabel ] ; len ( uid ) > 0 && appURL != nil {
u := * appURL
2022-04-20 08:43:55 -05:00
u . Path = path . Join ( u . Path , fmt . Sprintf ( "/alerting/grafana/%s/view" , uid ) )
2021-11-04 15:42:34 -05: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 04:46:47 -06:00
if alertState . State == eval . Error {
return errorAlert ( nL , nA , alertState , urlStr )
}
2021-11-04 15:42:34 -05: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 04:46:47 -06: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 ) ,
} ,
}
}
2021-09-30 11:51:20 -05:00
func FromAlertStateToPostableAlerts ( firingStates [ ] * state . State , stateManager * state . Manager , appURL * url . URL ) apimodels . PostableAlerts {
2021-04-19 01:58:44 -05:00
alerts := apimodels . PostableAlerts { PostableAlerts : make ( [ ] models . PostableAlert , 0 , len ( firingStates ) ) }
2021-05-19 15:15:09 -05:00
var sentAlerts [ ] * state . State
ts := time . Now ( )
2021-06-16 05:04:12 -05:00
2021-04-02 10:11:33 -05:00
for _ , alertState := range firingStates {
2021-07-29 13:29:17 -05:00
if ! alertState . NeedsSending ( stateManager . ResendDelay ) {
continue
2021-04-02 10:11:33 -05:00
}
2021-11-04 15:42:34 -05:00
alert := stateToPostableAlert ( alertState , appURL )
alerts . PostableAlerts = append ( alerts . PostableAlerts , * alert )
2021-07-29 13:29:17 -05:00
alertState . LastSentAt = ts
sentAlerts = append ( sentAlerts , alertState )
2021-03-30 11:37:56 -05:00
}
2021-05-19 15:15:09 -05:00
stateManager . Put ( sentAlerts )
2021-03-30 11:37:56 -05:00
return alerts
}
2022-01-11 10:39:34 -06:00
// FromAlertsStateToStoppedAlert converts firingStates that have evaluation state either eval.Alerting or eval.NoData or eval.Error to models.PostableAlert that are accepted by notifiers.
// Returns a list of alert instances that have expiration time.Now
func FromAlertsStateToStoppedAlert ( firingStates [ ] * state . State , appURL * url . URL , clock clock . Clock ) apimodels . PostableAlerts {
alerts := apimodels . PostableAlerts { PostableAlerts : make ( [ ] models . PostableAlert , 0 , len ( firingStates ) ) }
ts := clock . Now ( )
for _ , alertState := range firingStates {
if alertState . State == eval . Normal || alertState . State == eval . Pending {
continue
}
postableAlert := stateToPostableAlert ( alertState , appURL )
postableAlert . EndsAt = strfmt . DateTime ( ts )
alerts . PostableAlerts = append ( alerts . PostableAlerts , * postableAlert )
}
return alerts
}