2021-03-18 13:12:28 -05:00
package store
import (
"context"
2022-03-23 03:31:46 -05:00
"crypto/md5"
2021-03-18 13:12:28 -05:00
"fmt"
2022-06-27 17:20:21 -05:00
"time"
2021-03-18 13:12:28 -05:00
2022-10-19 08:02:15 -05:00
"github.com/grafana/grafana/pkg/infra/db"
2021-03-18 13:12:28 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
var (
// ErrNoAlertmanagerConfiguration is an error for when no alertmanager configuration is found.
2021-03-24 09:20:44 -05:00
ErrNoAlertmanagerConfiguration = fmt . Errorf ( "could not find an Alertmanager configuration" )
2022-03-23 03:31:46 -05:00
// ErrVersionLockedObjectNotFound is returned when an object is not
// found using the current hash.
ErrVersionLockedObjectNotFound = fmt . Errorf ( "could not find object using provided id and hash" )
2022-07-20 09:54:18 -05:00
// ConfigRecordsLimit defines the limit of how many alertmanager configuration versions
// should be stored in the database for each organization including the current one.
// Has to be > 0
2022-08-01 16:48:34 -05:00
ConfigRecordsLimit int = 100
2021-03-18 13:12:28 -05:00
)
// GetLatestAlertmanagerConfiguration returns the lastest version of the alertmanager configuration.
// It returns ErrNoAlertmanagerConfiguration if no configuration is found.
2023-03-28 03:34:35 -05:00
func ( st * DBstore ) GetLatestAlertmanagerConfiguration ( ctx context . Context , query * models . GetLatestAlertmanagerConfigurationQuery ) ( result * models . AlertConfiguration , err error ) {
err = st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2021-05-14 15:13:44 -05:00
c := & models . AlertConfiguration { }
// The ID is already an auto incremental column, using the ID as an order should guarantee the latest.
2023-01-04 10:43:26 -06:00
ok , err := sess . Table ( "alert_configuration" ) . Where ( "org_id = ?" , query . OrgID ) . Get ( c )
2021-03-18 13:12:28 -05:00
if err != nil {
return err
}
2021-05-14 15:13:44 -05:00
if ! ok {
return ErrNoAlertmanagerConfiguration
}
2023-03-28 03:34:35 -05:00
result = c
2021-03-31 15:00:56 -05:00
return nil
} )
2023-03-28 03:34:35 -05:00
return result , err
2021-03-31 15:00:56 -05:00
}
2021-03-18 13:12:28 -05:00
2021-09-21 10:01:23 -05:00
// GetAllLatestAlertmanagerConfiguration returns the latest configuration of every organization
func ( st * DBstore ) GetAllLatestAlertmanagerConfiguration ( ctx context . Context ) ( [ ] * models . AlertConfiguration , error ) {
var result [ ] * models . AlertConfiguration
2022-10-19 08:02:15 -05:00
err := st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2023-01-04 10:43:26 -06:00
if err := sess . Table ( "alert_configuration" ) . Find ( & result ) ; err != nil {
2021-09-21 10:01:23 -05:00
return err
}
return nil
} )
if err != nil {
return nil , err
}
return result , nil
}
2021-03-31 15:00:56 -05:00
// SaveAlertmanagerConfiguration creates an alertmanager configuration.
2022-02-09 03:22:09 -06:00
func ( st DBstore ) SaveAlertmanagerConfiguration ( ctx context . Context , cmd * models . SaveAlertmanagerConfigurationCmd ) error {
return st . SaveAlertmanagerConfigurationWithCallback ( ctx , cmd , func ( ) error { return nil } )
2021-05-14 13:49:54 -05:00
}
type SaveCallback func ( ) error
// SaveAlertmanagerConfigurationWithCallback creates an alertmanager configuration version and then executes a callback.
2021-09-21 10:01:23 -05:00
// If the callback results in error it rolls back the transaction.
2022-02-09 03:22:09 -06:00
func ( st DBstore ) SaveAlertmanagerConfigurationWithCallback ( ctx context . Context , cmd * models . SaveAlertmanagerConfigurationCmd , callback SaveCallback ) error {
2022-10-19 08:02:15 -05:00
return st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2021-03-31 15:00:56 -05:00
config := models . AlertConfiguration {
AlertmanagerConfiguration : cmd . AlertmanagerConfiguration ,
2022-03-23 03:31:46 -05:00
ConfigurationHash : fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( cmd . AlertmanagerConfiguration ) ) ) ,
2021-03-31 15:00:56 -05:00
ConfigurationVersion : cmd . ConfigurationVersion ,
2021-05-14 13:49:54 -05:00
Default : cmd . Default ,
2021-08-12 08:04:09 -05:00
OrgID : cmd . OrgID ,
2023-01-04 10:43:26 -06:00
CreatedAt : time . Now ( ) . Unix ( ) ,
2021-03-31 15:00:56 -05:00
}
2023-02-02 11:45:17 -06:00
2023-01-04 10:43:26 -06:00
// TODO: If we are more structured around how we seed configurations in the future, this can be a pure update instead of upsert. This should improve perf and code clarity.
upsertSQL := st . SQLStore . GetDialect ( ) . UpsertSQL (
"alert_configuration" ,
[ ] string { "org_id" } ,
[ ] string { "alertmanager_configuration" , "configuration_version" , "created_at" , "default" , "org_id" , "configuration_hash" } ,
)
2023-08-30 10:46:47 -05:00
params := append ( make ( [ ] any , 0 ) , cmd . AlertmanagerConfiguration , cmd . ConfigurationVersion , config . CreatedAt , config . Default , config . OrgID , config . ConfigurationHash )
2023-01-04 10:43:26 -06:00
if _ , err := sess . SQL ( upsertSQL , params ... ) . Query ( ) ; err != nil {
2021-03-31 15:00:56 -05:00
return err
}
2023-01-04 10:43:26 -06:00
2023-02-02 11:45:17 -06:00
historicConfig := models . HistoricConfigFromAlertConfig ( config )
historicConfig . LastApplied = cmd . LastApplied
if _ , err := sess . Table ( "alert_configuration_history" ) . Insert ( historicConfig ) ; err != nil {
2023-01-04 10:43:26 -06:00
return err
}
2022-07-20 09:54:18 -05:00
if _ , err := st . deleteOldConfigurations ( ctx , cmd . OrgID , ConfigRecordsLimit ) ; err != nil {
2023-09-04 11:46:34 -05:00
st . Logger . Warn ( "Failed to delete old am configs" , "org" , cmd . OrgID , "error" , err )
2022-07-20 09:54:18 -05:00
}
2023-02-02 11:45:17 -06:00
2021-05-14 13:49:54 -05:00
if err := callback ( ) ; err != nil {
return err
}
2021-03-31 15:00:56 -05:00
return nil
} )
}
2022-03-23 03:31:46 -05:00
2023-01-04 10:43:26 -06:00
// UpdateAlertmanagerConfiguration replaces an alertmanager configuration with optimistic locking. It assumes that an existing revision of the configuration exists in the store, and will return an error otherwise.
2022-04-05 16:48:51 -05:00
func ( st * DBstore ) UpdateAlertmanagerConfiguration ( ctx context . Context , cmd * models . SaveAlertmanagerConfigurationCmd ) error {
2022-10-19 08:02:15 -05:00
return st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-03-23 03:31:46 -05:00
config := models . AlertConfiguration {
AlertmanagerConfiguration : cmd . AlertmanagerConfiguration ,
ConfigurationHash : fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( cmd . AlertmanagerConfiguration ) ) ) ,
ConfigurationVersion : cmd . ConfigurationVersion ,
Default : cmd . Default ,
OrgID : cmd . OrgID ,
2022-06-27 17:20:21 -05:00
CreatedAt : time . Now ( ) . Unix ( ) ,
}
2023-01-04 10:43:26 -06:00
rows , err := sess . Table ( "alert_configuration" ) .
Where ( "org_id = ? AND configuration_hash = ?" , config . OrgID , cmd . FetchedConfigurationHash ) .
Update ( config )
2022-06-27 17:20:21 -05:00
if err != nil {
return err
2022-03-23 03:31:46 -05:00
}
if rows == 0 {
return ErrVersionLockedObjectNotFound
}
2023-02-02 11:45:17 -06:00
historicConfig := models . HistoricConfigFromAlertConfig ( config )
if _ , err := sess . Table ( "alert_configuration_history" ) . Insert ( historicConfig ) ; err != nil {
2023-01-04 10:43:26 -06:00
return err
}
2022-07-20 09:54:18 -05:00
if _ , err := st . deleteOldConfigurations ( ctx , cmd . OrgID , ConfigRecordsLimit ) ; err != nil {
2023-09-04 11:46:34 -05:00
st . Logger . Warn ( "Failed to delete old am configs" , "org" , cmd . OrgID , "error" , err )
2022-07-20 09:54:18 -05:00
}
2023-01-04 10:43:26 -06:00
return nil
2022-03-23 03:31:46 -05:00
} )
}
2022-06-27 17:20:21 -05:00
2023-02-02 11:45:17 -06:00
// MarkConfigurationAsApplied sets the `last_applied` field of the last config with the given hash to the current UNIX timestamp.
func ( st * DBstore ) MarkConfigurationAsApplied ( ctx context . Context , cmd * models . MarkConfigurationAsAppliedCmd ) error {
return st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2023-08-30 10:46:47 -05:00
update := map [ string ] any { "last_applied" : time . Now ( ) . UTC ( ) . Unix ( ) }
2023-02-02 11:45:17 -06:00
rowsAffected , err := sess . Table ( "alert_configuration_history" ) .
Desc ( "id" ) .
Limit ( 1 ) .
Where ( "org_id = ? AND configuration_hash = ?" , cmd . OrgID , cmd . ConfigurationHash ) .
Cols ( "last_applied" ) .
Update ( & update )
if err != nil {
return err
}
if rowsAffected != 1 {
2023-02-07 08:04:05 -06:00
st . Logger . Warn ( "Unexpected number of rows updating alert configuration history" , "rows" , rowsAffected , "org" , cmd . OrgID , "hash" , cmd . ConfigurationHash )
2023-02-02 11:45:17 -06:00
}
return nil
} )
}
// GetAppliedConfigurations returns all configurations that have been marked as applied, ordered newest -> oldest by id.
2023-03-31 15:43:04 -05:00
func ( st * DBstore ) GetAppliedConfigurations ( ctx context . Context , orgID int64 , limit int ) ( [ ] * models . HistoricAlertConfiguration , error ) {
if limit < 1 || limit > ConfigRecordsLimit {
limit = ConfigRecordsLimit
}
var configs [ ] * models . HistoricAlertConfiguration
if err := st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2023-02-02 11:45:17 -06:00
cfgs := [ ] * models . HistoricAlertConfiguration { }
err := sess . Table ( "alert_configuration_history" ) .
Desc ( "id" ) .
2023-03-31 15:43:04 -05:00
Where ( "org_id = ? AND last_applied != 0" , orgID ) .
Limit ( limit ) .
2023-02-02 11:45:17 -06:00
Find ( & cfgs )
if err != nil {
return err
}
2023-03-31 15:43:04 -05:00
configs = cfgs
2023-02-02 11:45:17 -06:00
return nil
2023-03-31 15:43:04 -05:00
} ) ; err != nil {
return [ ] * models . HistoricAlertConfiguration { } , err
}
return configs , nil
2023-02-02 11:45:17 -06:00
}
2023-04-05 13:10:03 -05:00
// GetHistoricalConfiguration returns a single historical configuration based on provided org and id.
func ( st * DBstore ) GetHistoricalConfiguration ( ctx context . Context , orgID int64 , id int64 ) ( * models . HistoricAlertConfiguration , error ) {
var config models . HistoricAlertConfiguration
if err := st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
ok , err := sess . Table ( "alert_configuration_history" ) .
Where ( "id = ? AND org_id = ?" , id , orgID ) .
Get ( & config )
if err != nil {
return err
}
if ! ok {
return ErrNoAlertmanagerConfiguration
}
return nil
} ) ; err != nil {
return nil , err
}
return & config , nil
}
2022-08-01 16:48:34 -05:00
func ( st * DBstore ) deleteOldConfigurations ( ctx context . Context , orgID int64 , limit int ) ( int64 , error ) {
2022-07-20 09:54:18 -05:00
if limit < 1 {
return 0 , fmt . Errorf ( "failed to delete old configurations: limit is set to '%d' but needs to be > 0" , limit )
}
2022-08-01 16:48:34 -05:00
if limit < 1 {
limit = ConfigRecordsLimit
}
var affectedRows int64
2022-10-19 08:02:15 -05:00
err := st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2023-02-02 11:45:17 -06:00
highest := & models . HistoricAlertConfiguration { }
2023-01-04 10:43:26 -06:00
ok , err := sess . Table ( "alert_configuration_history" ) . Desc ( "id" ) . Where ( "org_id = ?" , orgID ) . OrderBy ( "id" ) . Limit ( 1 , limit - 1 ) . Get ( highest )
2022-08-01 16:48:34 -05:00
if err != nil {
return err
}
if ! ok {
// No configurations exist. Nothing to clean up.
affectedRows = 0
return nil
}
threshold := highest . ID - 1
if threshold < 1 {
// Fewer than `limit` records even exist. Nothing to clean up.
affectedRows = 0
return nil
}
2022-07-20 09:54:18 -05:00
res , err := sess . Exec ( `
2023-04-05 13:10:03 -05:00
DELETE FROM
2023-01-04 10:43:26 -06:00
alert_configuration_history
2022-07-20 09:54:18 -05:00
WHERE
org_id = ?
2023-04-05 13:10:03 -05:00
AND
2022-08-01 16:48:34 -05:00
id < ?
` , orgID , threshold )
2022-07-20 09:54:18 -05:00
if err != nil {
return err
}
rows , err := res . RowsAffected ( )
if err != nil {
return err
}
2022-08-01 16:48:34 -05:00
affectedRows = rows
if affectedRows > 0 {
2023-09-04 11:46:34 -05:00
st . Logger . Info ( "Deleted old alert_configuration(s)" , "org" , orgID , "limit" , limit , "delete_count" , affectedRows )
2022-07-20 09:54:18 -05:00
}
return nil
} )
2022-08-01 16:48:34 -05:00
return affectedRows , err
2022-07-20 09:54:18 -05:00
}