2023-10-20 07:08:13 -05:00
package remote
2023-09-14 06:55:01 -05:00
import (
2023-10-02 05:36:11 -05:00
"context"
2023-11-23 10:59:36 -06:00
"crypto/md5"
2023-11-29 05:49:39 -06:00
"encoding/base64"
2024-03-19 06:12:03 -05:00
"encoding/json"
"errors"
2023-11-23 10:59:36 -06:00
"fmt"
2024-03-19 06:12:03 -05:00
"io"
2023-11-23 10:59:36 -06:00
"net/http"
"net/http/httptest"
2023-10-17 05:21:45 -05:00
"os"
2024-03-19 06:12:03 -05:00
"strings"
2023-09-14 06:55:01 -05:00
"testing"
2023-10-17 05:21:45 -05:00
"time"
2023-09-14 06:55:01 -05:00
2023-10-02 05:36:11 -05:00
"github.com/go-openapi/strfmt"
2024-03-22 17:37:33 -05:00
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
2024-03-19 06:12:03 -05:00
"github.com/grafana/grafana/pkg/infra/db"
2023-10-02 05:36:11 -05:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2024-01-10 04:18:24 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
2023-11-23 10:59:36 -06:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2023-11-29 05:49:39 -06:00
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
2024-03-19 06:12:03 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/remote/client"
ngfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/secrets/database"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
2024-04-19 08:11:07 -05:00
"github.com/grafana/grafana/pkg/setting"
2024-03-19 06:12:03 -05:00
"github.com/grafana/grafana/pkg/tests/testsuite"
2023-10-17 05:21:45 -05:00
"github.com/grafana/grafana/pkg/util"
2024-05-03 06:59:02 -05:00
"gopkg.in/yaml.v3"
2023-09-14 06:55:01 -05:00
)
2024-04-19 08:11:07 -05:00
const (
// Valid Grafana Alertmanager configurations.
2024-06-04 13:29:37 -05:00
testGrafanaConfig = ` { "template_files": { },"alertmanager_config": { "route": { "receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[ { "name":"grafana-default-email","grafana_managed_receiver_configs":[ { "uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings": { "addresses":"\u003cexample@email.com\u003e"}}]}]}} `
testGrafanaConfigWithSecret = ` { "template_files": { },"alertmanager_config": { "route": { "receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[ { "name":"grafana-default-email","grafana_managed_receiver_configs":[ { "uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings": { "url":"http://localhost:8080","username":"test"},"secureSettings": { "password":"test"}}]}]}} `
2024-03-19 06:12:03 -05:00
2024-04-19 08:11:07 -05:00
// Valid Alertmanager state base64 encoded.
testSilence1 = "lwEKhgEKATESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsD"
testSilence2 = "lwEKhgEKATISFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsDlwEKhgEKATESFxIJYWxlcnRuYW1lGgp0ZXN0X2FsZXJ0EiMSDmdyYWZhbmFfZm9sZGVyGhF0ZXN0X2FsZXJ0X2ZvbGRlchoMCN2CkbAGEJbKrMsDIgwI7Z6RsAYQlsqsywMqCwiAkrjDmP7///8BQgxHcmFmYW5hIFRlc3RKDFRlc3QgU2lsZW5jZRIMCO2ekbAGEJbKrMsD"
testNflog1 = "OgoqCgZncm91cDESEgoJcmVjZWl2ZXIxEgV0ZXN0MyoMCIzm1bAGEPqx5uEBEgwInILWsAYQ+rHm4QE="
testNflog2 = "OgoqCgZncm91cDISEgoJcmVjZWl2ZXIyEgV0ZXN0MyoMCLSDkbAGEMvaofYCEgwIxJ+RsAYQy9qh9gI6CioKBmdyb3VwMRISCglyZWNlaXZlcjESBXRlc3QzKgwItIORsAYQy9qh9gISDAjEn5GwBhDL2qH2Ag=="
)
var (
defaultGrafanaConfig = setting . GetAlertmanagerDefaultConfiguration ( )
)
2024-04-09 12:39:34 -05:00
2024-03-19 06:12:03 -05:00
func TestMain ( m * testing . M ) {
testsuite . Run ( m )
}
2023-10-02 05:36:11 -05:00
2023-10-23 08:37:14 -05:00
func TestNewAlertmanager ( t * testing . T ) {
2023-09-14 06:55:01 -05:00
tests := [ ] struct {
2023-10-25 04:52:48 -05:00
name string
url string
tenantID string
password string
orgID int64
expErr string
2023-09-14 06:55:01 -05:00
} {
{
2023-10-25 04:52:48 -05:00
name : "empty URL" ,
url : "" ,
tenantID : "1234" ,
password : "test" ,
orgID : 1 ,
2023-11-24 05:05:13 -06:00
expErr : "empty remote Alertmanager URL for tenant '1234'" ,
} ,
{
name : "invalid URL" ,
url : "asdasd%sasdsd" ,
tenantID : "1234" ,
password : "test" ,
orgID : 1 ,
expErr : "unable to parse remote Alertmanager URL: parse \"asdasd%sasdsd\": invalid URL escape \"%sa\"" ,
2023-09-14 06:55:01 -05:00
} ,
{
2023-10-25 04:52:48 -05:00
name : "valid parameters" ,
url : "http://localhost:8080" ,
tenantID : "1234" ,
password : "test" ,
orgID : 1 ,
2023-09-14 06:55:01 -05:00
} ,
}
2024-03-19 06:12:03 -05:00
secretsService := secretsManager . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
2023-09-14 06:55:01 -05:00
for _ , test := range tests {
t . Run ( test . name , func ( tt * testing . T ) {
2023-10-23 08:37:14 -05:00
cfg := AlertmanagerConfig {
2023-12-19 11:41:48 -06:00
OrgID : test . orgID ,
2023-09-14 06:55:01 -05:00
URL : test . url ,
TenantID : test . tenantID ,
BasicAuthPassword : test . password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2023-09-14 06:55:01 -05:00
}
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , nil , secretsService . Decrypt , m )
2023-09-14 06:55:01 -05:00
if test . expErr != "" {
require . EqualError ( tt , err , test . expErr )
return
}
require . NoError ( tt , err )
require . Equal ( tt , am . tenantID , test . tenantID )
require . Equal ( tt , am . url , test . url )
2023-10-31 04:58:47 -05:00
require . Equal ( tt , am . orgID , test . orgID )
2023-09-14 06:55:01 -05:00
require . NotNil ( tt , am . amClient )
} )
}
}
2023-10-02 05:36:11 -05:00
2023-11-23 10:59:36 -06:00
func TestApplyConfig ( t * testing . T ) {
errorHandler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusInternalServerError )
} )
2024-03-19 06:12:03 -05:00
2024-05-16 05:06:03 -05:00
var configSent client . UserGrafanaConfig
2023-11-23 10:59:36 -06:00
okHandler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2024-03-19 06:12:03 -05:00
if r . Method == http . MethodPost && strings . Contains ( r . URL . Path , "/config" ) {
2024-05-16 05:06:03 -05:00
require . NoError ( t , json . NewDecoder ( r . Body ) . Decode ( & configSent ) )
2024-03-19 06:12:03 -05:00
}
2023-11-23 10:59:36 -06:00
w . WriteHeader ( http . StatusOK )
} )
2024-03-19 06:12:03 -05:00
// Encrypt receivers to save secrets in the database.
var c apimodels . PostableUserConfig
require . NoError ( t , json . Unmarshal ( [ ] byte ( testGrafanaConfigWithSecret ) , & c ) )
secretsService := secretsManager . SetupTestService ( t , database . ProvideSecretsStore ( db . InitTestDB ( t ) ) )
err := notifier . EncryptReceiverConfigs ( c . AlertmanagerConfig . Receivers , func ( ctx context . Context , payload [ ] byte ) ( [ ] byte , error ) {
return secretsService . Encrypt ( ctx , payload , secrets . WithoutScope ( ) )
} )
require . NoError ( t , err )
// The encrypted configuration should be different than the one we will send.
encryptedConfig , err := json . Marshal ( c )
require . NoError ( t , err )
2024-04-23 07:37:10 -05:00
require . NotEqual ( t , testGrafanaConfigWithSecret , encryptedConfig )
2024-03-19 06:12:03 -05:00
2023-11-23 10:59:36 -06:00
// ApplyConfig performs a readiness check at startup.
// A non-200 response should result in an error.
server := httptest . NewServer ( errorHandler )
cfg := AlertmanagerConfig {
2024-05-16 05:06:03 -05:00
OrgID : 1 ,
TenantID : "test" ,
URL : server . URL ,
DefaultConfig : defaultGrafanaConfig ,
PromoteConfig : true ,
2023-11-23 10:59:36 -06:00
}
2023-11-29 05:49:39 -06:00
ctx := context . Background ( )
2024-03-19 06:12:03 -05:00
store := ngfakes . NewFakeKVStore ( t )
2024-03-22 17:37:33 -05:00
fstore := notifier . NewFileStore ( 1 , store )
2024-04-09 12:39:34 -05:00
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , notifier . SilencesFilename , testSilence1 ) )
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , notifier . NotificationLogFilename , testNflog1 ) )
2023-11-29 05:49:39 -06:00
2024-03-19 06:12:03 -05:00
// An error response from the remote Alertmanager should result in the readiness check failing.
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , fstore , secretsService . Decrypt , m )
2023-11-23 10:59:36 -06:00
require . NoError ( t , err )
2024-03-19 06:12:03 -05:00
config := & ngmodels . AlertConfiguration {
AlertmanagerConfiguration : string ( encryptedConfig ) ,
}
2023-11-23 10:59:36 -06:00
require . Error ( t , am . ApplyConfig ( ctx , config ) )
require . False ( t , am . Ready ( ) )
// A 200 status code response should make the check succeed.
server . Config . Handler = okHandler
require . NoError ( t , am . ApplyConfig ( ctx , config ) )
require . True ( t , am . Ready ( ) )
2024-05-16 05:06:03 -05:00
// The sent configuration should be unencrypted and promoted.
amCfg , err := json . Marshal ( configSent . GrafanaAlertmanagerConfig )
require . NoError ( t , err )
require . JSONEq ( t , testGrafanaConfigWithSecret , string ( amCfg ) )
require . True ( t , configSent . Promoted )
2024-03-19 06:12:03 -05:00
2023-11-23 10:59:36 -06:00
// If we already got a 200 status code response, we shouldn't make the HTTP request again.
server . Config . Handler = errorHandler
require . NoError ( t , am . ApplyConfig ( ctx , config ) )
require . True ( t , am . Ready ( ) )
}
2024-03-19 06:12:03 -05:00
func TestCompareAndSendConfiguration ( t * testing . T ) {
2024-04-19 06:04:18 -05:00
cfgWithSecret , err := notifier . Load ( [ ] byte ( testGrafanaConfigWithSecret ) )
require . NoError ( t , err )
2024-03-19 06:12:03 -05:00
testValue := [ ] byte ( "test" )
testErr := errors . New ( "test error" )
decryptFn := func ( _ context . Context , payload [ ] byte ) ( [ ] byte , error ) {
if string ( payload ) == string ( testValue ) {
return testValue , nil
}
return nil , testErr
}
var got string
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Add ( "content-type" , "application/json" )
b , err := io . ReadAll ( r . Body )
require . NoError ( t , err )
require . NoError ( t , r . Body . Close ( ) )
got = string ( b )
_ , err = w . Write ( [ ] byte ( ` { "status": "success"} ` ) )
require . NoError ( t , err )
} ) )
2024-03-22 17:37:33 -05:00
fstore := notifier . NewFileStore ( 1 , ngfakes . NewFakeKVStore ( t ) )
2024-03-19 06:12:03 -05:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
cfg := AlertmanagerConfig {
2024-05-16 05:06:03 -05:00
OrgID : 1 ,
TenantID : "test" ,
URL : server . URL ,
DefaultConfig : defaultGrafanaConfig ,
2024-03-19 06:12:03 -05:00
}
am , err := NewAlertmanager ( cfg ,
fstore ,
decryptFn ,
m ,
)
require . NoError ( t , err )
tests := [ ] struct {
name string
config string
expCfg * client . UserGrafanaConfig
expErr string
} {
{
"invalid config" ,
"{}" ,
nil ,
"unable to parse Alertmanager configuration: no route provided in config" ,
} ,
{
"invalid base-64 in key" ,
strings . Replace ( testGrafanaConfigWithSecret , ` "password":"test" ` , ` "password":"!" ` , 1 ) ,
nil ,
2024-04-19 08:11:07 -05:00
"unable to decrypt the configuration: failed to decode value for key 'password': illegal base64 data at input byte 0" ,
2024-03-19 06:12:03 -05:00
} ,
{
"decrypt error" ,
testGrafanaConfigWithSecret ,
nil ,
2024-04-19 08:11:07 -05:00
fmt . Sprintf ( "unable to decrypt the configuration: failed to decrypt value for key 'password': %s" , testErr . Error ( ) ) ,
2024-03-19 06:12:03 -05:00
} ,
{
"no error" ,
strings . Replace ( testGrafanaConfigWithSecret , ` "password":"test" ` , fmt . Sprintf ( "%q:%q" , "password" , base64 . StdEncoding . EncodeToString ( testValue ) ) , 1 ) ,
& client . UserGrafanaConfig {
2024-04-19 06:04:18 -05:00
GrafanaAlertmanagerConfig : cfgWithSecret ,
2024-03-19 06:12:03 -05:00
} ,
"" ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( tt * testing . T ) {
cfg := ngmodels . AlertConfiguration {
AlertmanagerConfiguration : test . config ,
}
err = am . CompareAndSendConfiguration ( context . Background ( ) , & cfg )
if test . expErr == "" {
require . NoError ( tt , err )
rawCfg , err := json . Marshal ( test . expCfg )
require . NoError ( tt , err )
require . JSONEq ( tt , string ( rawCfg ) , got )
return
}
require . Equal ( tt , test . expErr , err . Error ( ) )
} )
}
}
2024-04-19 08:11:07 -05:00
func TestIntegrationRemoteAlertmanagerConfiguration ( t * testing . T ) {
2023-11-23 10:59:36 -06:00
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
amURL , ok := os . LookupEnv ( "AM_URL" )
if ! ok {
t . Skip ( "No Alertmanager URL provided" )
}
tenantID := os . Getenv ( "AM_TENANT_ID" )
password := os . Getenv ( "AM_PASSWORD" )
// ApplyConfig performs a readiness check.
cfg := AlertmanagerConfig {
2023-12-19 11:41:48 -06:00
OrgID : 1 ,
2023-11-23 10:59:36 -06:00
URL : amURL ,
TenantID : tenantID ,
BasicAuthPassword : password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2023-11-23 10:59:36 -06:00
}
2024-04-19 08:11:07 -05:00
testConfigHash := fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( testGrafanaConfig ) ) )
testConfigCreatedAt := time . Now ( ) . Unix ( )
testConfig := & ngmodels . AlertConfiguration {
2023-11-23 10:59:36 -06:00
AlertmanagerConfiguration : testGrafanaConfig ,
2024-04-19 08:11:07 -05:00
ConfigurationHash : testConfigHash ,
2023-11-23 10:59:36 -06:00
ConfigurationVersion : "v2" ,
2024-04-19 08:11:07 -05:00
CreatedAt : testConfigCreatedAt ,
2023-11-23 10:59:36 -06:00
OrgID : 1 ,
}
2024-03-19 06:12:03 -05:00
store := ngfakes . NewFakeKVStore ( t )
2024-03-22 17:37:33 -05:00
fstore := notifier . NewFileStore ( cfg . OrgID , store )
2023-11-29 05:49:39 -06:00
2023-11-23 10:59:36 -06:00
ctx := context . Background ( )
2024-04-09 12:39:34 -05:00
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , notifier . SilencesFilename , testSilence1 ) )
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , notifier . NotificationLogFilename , testNflog1 ) )
2023-11-29 05:49:39 -06:00
2024-04-23 07:37:10 -05:00
secretsService := secretsManager . SetupTestService ( t , database . ProvideSecretsStore ( db . InitTestDB ( t ) ) )
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , fstore , secretsService . Decrypt , m )
2023-11-29 05:49:39 -06:00
require . NoError ( t , err )
2024-03-22 17:37:33 -05:00
encodedFullState , err := am . getFullState ( ctx )
require . NoError ( t , err )
2023-11-29 05:49:39 -06:00
// We should have no configuration or state at first.
2023-11-23 10:59:36 -06:00
{
2023-11-29 05:49:39 -06:00
_ , err := am . mimirClient . GetGrafanaAlertmanagerConfig ( ctx )
require . Error ( t , err )
require . Equal ( t , "Error response from the Mimir API: alertmanager storage object not found" , err . Error ( ) )
_ , err = am . mimirClient . GetGrafanaAlertmanagerState ( ctx )
2023-11-23 10:59:36 -06:00
require . Error ( t , err )
require . Equal ( t , "Error response from the Mimir API: alertmanager storage object not found" , err . Error ( ) )
}
// Using `ApplyConfig` as a heuristic of a function that gets called when the Alertmanager starts
// We call it as if the Alertmanager were starting.
{
2024-04-19 08:11:07 -05:00
require . NoError ( t , am . ApplyConfig ( ctx , testConfig ) )
2023-11-23 10:59:36 -06:00
// First, we need to verify that the readiness check passes.
require . True ( t , am . Ready ( ) )
2023-11-29 05:49:39 -06:00
// Next, we need to verify that Mimir received both the configuration and state.
2023-11-23 10:59:36 -06:00
config , err := am . mimirClient . GetGrafanaAlertmanagerConfig ( ctx )
require . NoError ( t , err )
2024-04-19 06:04:18 -05:00
rawCfg , err := json . Marshal ( config . GrafanaAlertmanagerConfig )
require . NoError ( t , err )
require . JSONEq ( t , testGrafanaConfig , string ( rawCfg ) )
2024-04-19 08:11:07 -05:00
require . Equal ( t , testConfigHash , config . Hash )
require . Equal ( t , testConfigCreatedAt , config . CreatedAt )
require . Equal ( t , testConfig . Default , config . Default )
2023-11-23 10:59:36 -06:00
2023-11-29 05:49:39 -06:00
state , err := am . mimirClient . GetGrafanaAlertmanagerState ( ctx )
require . NoError ( t , err )
require . Equal ( t , encodedFullState , state . State )
2023-11-23 10:59:36 -06:00
}
2023-11-29 05:49:39 -06:00
// Calling `ApplyConfig` again with a changed configuration and state yields no effect.
2023-11-23 10:59:36 -06:00
{
2024-04-09 12:39:34 -05:00
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , "silences" , testSilence2 ) )
require . NoError ( t , store . Set ( ctx , cfg . OrgID , "alertmanager" , "notifications" , testNflog2 ) )
2024-04-19 08:11:07 -05:00
testConfig . CreatedAt = time . Now ( ) . Unix ( )
require . NoError ( t , am . ApplyConfig ( ctx , testConfig ) )
2023-11-23 10:59:36 -06:00
// The remote Alertmanager continues to be ready.
require . True ( t , am . Ready ( ) )
// Next, we need to verify that the config that was uploaded remains the same.
config , err := am . mimirClient . GetGrafanaAlertmanagerConfig ( ctx )
require . NoError ( t , err )
2024-04-19 06:04:18 -05:00
rawCfg , err := json . Marshal ( config . GrafanaAlertmanagerConfig )
require . NoError ( t , err )
require . JSONEq ( t , testGrafanaConfig , string ( rawCfg ) )
2024-04-19 08:11:07 -05:00
require . Equal ( t , testConfigHash , config . Hash )
require . Equal ( t , testConfigCreatedAt , config . CreatedAt )
require . False ( t , config . Default )
2023-11-29 05:49:39 -06:00
// Check that the state is the same as before.
state , err := am . mimirClient . GetGrafanaAlertmanagerState ( ctx )
require . NoError ( t , err )
require . Equal ( t , encodedFullState , state . State )
2023-11-23 10:59:36 -06:00
}
2024-04-23 07:37:10 -05:00
// `SaveAndApplyConfig` is called whenever a user manually changes the Alertmanager configuration.
// Calling this method should decrypt and send a configuration to the remote Alertmanager.
{
postableCfg , err := notifier . Load ( [ ] byte ( testGrafanaConfigWithSecret ) )
require . NoError ( t , err )
err = notifier . EncryptReceiverConfigs ( postableCfg . AlertmanagerConfig . Receivers , func ( ctx context . Context , payload [ ] byte ) ( [ ] byte , error ) {
return secretsService . Encrypt ( ctx , payload , secrets . WithoutScope ( ) )
} )
require . NoError ( t , err )
// The encrypted configuration should be different than the one we will send.
encryptedConfig , err := json . Marshal ( postableCfg )
require . NoError ( t , err )
require . NotEqual ( t , testGrafanaConfigWithSecret , encryptedConfig )
// Call `SaveAndApplyConfig` with the encrypted configuration.
require . NoError ( t , err )
require . NoError ( t , am . SaveAndApplyConfig ( ctx , postableCfg ) )
// Check that the configuration was uploaded to the remote Alertmanager.
config , err := am . mimirClient . GetGrafanaAlertmanagerConfig ( ctx )
require . NoError ( t , err )
got , err := json . Marshal ( config . GrafanaAlertmanagerConfig )
require . NoError ( t , err )
require . JSONEq ( t , testGrafanaConfigWithSecret , string ( got ) )
require . Equal ( t , fmt . Sprintf ( "%x" , md5 . Sum ( encryptedConfig ) ) , config . Hash )
require . False ( t , config . Default )
}
2024-04-19 08:11:07 -05:00
// `SaveAndApplyDefaultConfig` should send the default Alertmanager configuration to the remote Alertmanager.
{
require . NoError ( t , am . SaveAndApplyDefaultConfig ( ctx ) )
// Check that the default configuration was uploaded.
config , err := am . mimirClient . GetGrafanaAlertmanagerConfig ( ctx )
require . NoError ( t , err )
pCfg , err := notifier . Load ( [ ] byte ( defaultGrafanaConfig ) )
require . NoError ( t , err )
want , err := json . Marshal ( pCfg )
require . NoError ( t , err )
got , err := json . Marshal ( config . GrafanaAlertmanagerConfig )
require . NoError ( t , err )
require . JSONEq ( t , string ( want ) , string ( got ) )
require . Equal ( t , fmt . Sprintf ( "%x" , md5 . Sum ( want ) ) , config . Hash )
require . True ( t , config . Default )
}
2023-11-23 10:59:36 -06:00
// TODO: Now, shutdown the Alertmanager and we expect the latest configuration to be uploaded.
{
}
}
2024-05-03 06:59:02 -05:00
func TestIntegrationRemoteAlertmanagerGetStatus ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
amURL , ok := os . LookupEnv ( "AM_URL" )
if ! ok {
t . Skip ( "No Alertmanager URL provided" )
}
tenantID := os . Getenv ( "AM_TENANT_ID" )
password := os . Getenv ( "AM_PASSWORD" )
cfg := AlertmanagerConfig {
OrgID : 1 ,
URL : amURL ,
TenantID : tenantID ,
BasicAuthPassword : password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2024-05-03 06:59:02 -05:00
}
secretsService := secretsManager . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , nil , secretsService . Decrypt , m )
2024-05-03 06:59:02 -05:00
require . NoError ( t , err )
// We should get the default Cloud Alertmanager configuration.
ctx := context . Background ( )
status , err := am . GetStatus ( ctx )
require . NoError ( t , err )
b , err := yaml . Marshal ( status . Config )
require . NoError ( t , err )
require . YAMLEq ( t , defaultCloudAMConfig , string ( b ) )
}
2023-10-17 05:21:45 -05:00
func TestIntegrationRemoteAlertmanagerSilences ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
amURL , ok := os . LookupEnv ( "AM_URL" )
if ! ok {
t . Skip ( "No Alertmanager URL provided" )
}
tenantID := os . Getenv ( "AM_TENANT_ID" )
password := os . Getenv ( "AM_PASSWORD" )
2023-10-02 05:36:11 -05:00
2023-10-23 08:37:14 -05:00
cfg := AlertmanagerConfig {
2023-12-19 11:41:48 -06:00
OrgID : 1 ,
2023-11-23 10:59:36 -06:00
URL : amURL ,
2023-10-02 05:36:11 -05:00
TenantID : tenantID ,
2023-10-17 05:21:45 -05:00
BasicAuthPassword : password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2023-10-02 05:36:11 -05:00
}
2024-03-19 06:12:03 -05:00
secretsService := secretsManager . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , nil , secretsService . Decrypt , m )
2023-10-02 05:36:11 -05:00
require . NoError ( t , err )
// We should have no silences at first.
silences , err := am . ListSilences ( context . Background ( ) , [ ] string { } )
require . NoError ( t , err )
require . Equal ( t , 0 , len ( silences ) )
// Creating a silence should succeed.
2024-05-03 14:32:30 -05:00
gen := ngmodels . SilenceGen ( ngmodels . SilenceMuts . WithEmptyId ( ) )
testSilence := notifier . SilenceToPostableSilence ( gen ( ) )
2024-04-09 12:39:34 -05:00
id , err := am . CreateSilence ( context . Background ( ) , testSilence )
2023-10-02 05:36:11 -05:00
require . NoError ( t , err )
2023-10-17 05:21:45 -05:00
require . NotEmpty ( t , id )
testSilence . ID = id
2023-10-02 05:36:11 -05:00
// We should be able to retrieve a specific silence.
2023-10-17 05:21:45 -05:00
silence , err := am . GetSilence ( context . Background ( ) , testSilence . ID )
2023-10-02 05:36:11 -05:00
require . NoError ( t , err )
2023-10-17 05:21:45 -05:00
require . Equal ( t , testSilence . ID , * silence . ID )
2023-10-02 05:36:11 -05:00
// Trying to retrieve a non-existing silence should fail.
2023-10-17 05:21:45 -05:00
_ , err = am . GetSilence ( context . Background ( ) , util . GenerateShortUID ( ) )
2023-10-02 05:36:11 -05:00
require . Error ( t , err )
// After creating another silence, the total amount should be 2.
2024-05-03 14:32:30 -05:00
testSilence2 := notifier . SilenceToPostableSilence ( gen ( ) )
2024-04-09 12:39:34 -05:00
id , err = am . CreateSilence ( context . Background ( ) , testSilence2 )
2023-10-02 05:36:11 -05:00
require . NoError ( t , err )
2023-10-17 05:21:45 -05:00
require . NotEmpty ( t , id )
testSilence2 . ID = id
2023-10-02 05:36:11 -05:00
silences , err = am . ListSilences ( context . Background ( ) , [ ] string { } )
require . NoError ( t , err )
require . Equal ( t , 2 , len ( silences ) )
2023-10-17 05:21:45 -05:00
require . True ( t , * silences [ 0 ] . ID == testSilence . ID || * silences [ 0 ] . ID == testSilence2 . ID )
require . True ( t , * silences [ 1 ] . ID == testSilence . ID || * silences [ 1 ] . ID == testSilence2 . ID )
2023-10-02 05:36:11 -05:00
2023-10-17 05:21:45 -05:00
// After deleting one of those silences, the total amount should be 2 but one of those should be expired.
err = am . DeleteSilence ( context . Background ( ) , testSilence . ID )
2023-10-02 05:36:11 -05:00
require . NoError ( t , err )
silences , err = am . ListSilences ( context . Background ( ) , [ ] string { } )
require . NoError ( t , err )
2023-10-17 05:21:45 -05:00
for _ , s := range silences {
if * s . ID == testSilence . ID {
require . Equal ( t , * s . Status . State , "expired" )
} else {
2024-05-03 14:32:30 -05:00
require . Equal ( t , * s . Status . State , "active" )
2023-10-17 05:21:45 -05:00
}
}
// When deleting the other silence, both should be expired.
err = am . DeleteSilence ( context . Background ( ) , testSilence2 . ID )
require . NoError ( t , err )
silences , err = am . ListSilences ( context . Background ( ) , [ ] string { } )
require . NoError ( t , err )
require . Equal ( t , * silences [ 0 ] . Status . State , "expired" )
require . Equal ( t , * silences [ 1 ] . Status . State , "expired" )
2023-10-02 05:36:11 -05:00
}
2023-10-19 04:27:37 -05:00
func TestIntegrationRemoteAlertmanagerAlerts ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
amURL , ok := os . LookupEnv ( "AM_URL" )
if ! ok {
t . Skip ( "No Alertmanager URL provided" )
}
tenantID := os . Getenv ( "AM_TENANT_ID" )
password := os . Getenv ( "AM_PASSWORD" )
2023-10-23 08:37:14 -05:00
cfg := AlertmanagerConfig {
2023-12-19 11:41:48 -06:00
OrgID : 1 ,
2023-11-23 10:59:36 -06:00
URL : amURL ,
2023-10-19 04:27:37 -05:00
TenantID : tenantID ,
BasicAuthPassword : password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2023-10-19 04:27:37 -05:00
}
2024-03-19 06:12:03 -05:00
secretsService := secretsManager . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , nil , secretsService . Decrypt , m )
2023-10-19 04:27:37 -05:00
require . NoError ( t , err )
2023-10-25 04:52:48 -05:00
// Wait until the Alertmanager is ready to send alerts.
require . NoError ( t , am . checkReadiness ( context . Background ( ) ) )
require . True ( t , am . Ready ( ) )
2023-12-12 09:34:54 -06:00
require . Eventually ( t , func ( ) bool {
return len ( am . sender . Alertmanagers ( ) ) > 0
} , 10 * time . Second , 500 * time . Millisecond )
2023-10-25 04:52:48 -05:00
2023-10-19 04:27:37 -05:00
// We should have no alerts and no groups at first.
alerts , err := am . GetAlerts ( context . Background ( ) , true , true , true , [ ] string { } , "" )
require . NoError ( t , err )
require . Equal ( t , 0 , len ( alerts ) )
alertGroups , err := am . GetAlertGroups ( context . Background ( ) , true , true , true , [ ] string { } , "" )
require . NoError ( t , err )
require . Equal ( t , 0 , len ( alertGroups ) )
// Let's create two active alerts and one expired one.
alert1 := genAlert ( true , map [ string ] string { "test_1" : "test_1" } )
alert2 := genAlert ( true , map [ string ] string { "test_2" : "test_2" } )
alert3 := genAlert ( false , map [ string ] string { "test_3" : "test_3" } )
postableAlerts := apimodels . PostableAlerts {
PostableAlerts : [ ] amv2 . PostableAlert { alert1 , alert2 , alert3 } ,
}
err = am . PutAlerts ( context . Background ( ) , postableAlerts )
require . NoError ( t , err )
// We should have two alerts and one group now.
2023-10-25 04:52:48 -05:00
require . Eventually ( t , func ( ) bool {
alerts , err = am . GetAlerts ( context . Background ( ) , true , true , true , [ ] string { } , "" )
require . NoError ( t , err )
return len ( alerts ) == 2
} , 16 * time . Second , 1 * time . Second )
2023-10-19 04:27:37 -05:00
alertGroups , err = am . GetAlertGroups ( context . Background ( ) , true , true , true , [ ] string { } , "" )
require . NoError ( t , err )
require . Equal ( t , 1 , len ( alertGroups ) )
// Filtering by `test_1=test_1` should return one alert.
alerts , err = am . GetAlerts ( context . Background ( ) , true , true , true , [ ] string { "test_1=test_1" } , "" )
require . NoError ( t , err )
require . Equal ( t , 1 , len ( alerts ) )
}
2023-10-20 04:34:17 -05:00
func TestIntegrationRemoteAlertmanagerReceivers ( t * testing . T ) {
if testing . Short ( ) {
t . Skip ( "skipping integration test" )
}
amURL , ok := os . LookupEnv ( "AM_URL" )
if ! ok {
t . Skip ( "No Alertmanager URL provided" )
}
tenantID := os . Getenv ( "AM_TENANT_ID" )
password := os . Getenv ( "AM_PASSWORD" )
2023-10-23 08:37:14 -05:00
cfg := AlertmanagerConfig {
2023-12-19 11:41:48 -06:00
OrgID : 1 ,
2023-11-23 10:59:36 -06:00
URL : amURL ,
2023-10-20 04:34:17 -05:00
TenantID : tenantID ,
BasicAuthPassword : password ,
2024-05-16 05:06:03 -05:00
DefaultConfig : defaultGrafanaConfig ,
2023-10-20 04:34:17 -05:00
}
2024-03-19 06:12:03 -05:00
secretsService := secretsManager . SetupTestService ( t , fakes . NewFakeSecretsStore ( ) )
2024-01-10 04:18:24 -06:00
m := metrics . NewRemoteAlertmanagerMetrics ( prometheus . NewRegistry ( ) )
2024-05-16 05:06:03 -05:00
am , err := NewAlertmanager ( cfg , nil , secretsService . Decrypt , m )
2023-10-20 04:34:17 -05:00
require . NoError ( t , err )
// We should start with the default config.
rcvs , err := am . GetReceivers ( context . Background ( ) )
require . NoError ( t , err )
require . Equal ( t , "empty-receiver" , * rcvs [ 0 ] . Name )
}
2023-10-19 04:27:37 -05:00
func genAlert ( active bool , labels map [ string ] string ) amv2 . PostableAlert {
endsAt := time . Now ( )
if active {
endsAt = time . Now ( ) . Add ( 1 * time . Minute )
}
return amv2 . PostableAlert {
2023-10-20 07:08:13 -05:00
Annotations : map [ string ] string { "test_annotation" : "test_annotation_value" } ,
2023-10-19 04:27:37 -05:00
StartsAt : strfmt . DateTime ( time . Now ( ) ) ,
EndsAt : strfmt . DateTime ( endsAt ) ,
Alert : amv2 . Alert {
2023-10-20 07:08:13 -05:00
GeneratorURL : "http://localhost:8080" ,
Labels : labels ,
2023-10-19 04:27:37 -05:00
} ,
}
}
2024-05-03 06:59:02 -05:00
const defaultCloudAMConfig = `
global :
resolve_timeout : 5 m
http_config :
follow_redirects : true
enable_http2 : true
smtp_hello : localhost
smtp_require_tls : true
pagerduty_url : https : //events.pagerduty.com/v2/enqueue
opsgenie_api_url : https : //api.opsgenie.com/
wechat_api_url : https : //qyapi.weixin.qq.com/cgi-bin/
victorops_api_url : https : //alert.victorops.com/integrations/generic/20131114/alert/
telegram_api_url : https : //api.telegram.org
webex_api_url : https : //webexapis.com/v1/messages
route :
receiver : empty - receiver
continue : false
receivers :
- name : empty - receiver
`