grafana/pkg/services/ngalert/sender/router_test.go
Matthew Jacobson e86929eb0a
Alerting: Managed receiver resource permission in config api (#93632)
* Alerting: Managed receiver resource permission in config api
2024-09-25 09:39:36 -04:00

747 lines
26 KiB
Go

package sender
import (
"context"
"fmt"
"math/rand"
"net/url"
"testing"
"time"
"github.com/benbjohnson/clock"
"github.com/go-openapi/strfmt"
models2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/services/datasources"
fake_ds "github.com/grafana/grafana/pkg/services/datasources/fakes"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
fake_secrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
func TestIntegrationSendingToExternalAlertmanager(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ruleKey := models.GenerateRuleKey(1)
fakeAM := NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeAdminConfigStore := &store.AdminConfigurationStoreMock{}
mockedGetAdminConfigurations := fakeAdminConfigStore.EXPECT().GetAdminConfigurations()
mockedClock := clock.NewMock()
moa := createMultiOrgAlertmanager(t, []int64{1})
appUrl := &url.URL{
Scheme: "http",
Host: "localhost",
}
ds1 := datasources.DataSource{
URL: fakeAM.Server.URL,
OrgID: ruleKey.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
alertsRouter := NewAlertsRouter(moa, fakeAdminConfigStore, mockedClock, appUrl, map[int64]struct{}{}, 10*time.Minute,
&fake_ds.FakeDataSourceService{DataSources: []*datasources.DataSource{&ds1}}, fake_secrets.NewFakeSecretsService(), featuremgmt.WithFeatures())
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.AllAlertmanagers},
}, nil)
// Make sure we sync the configuration at least once before the evaluation happens to guarantee the sender is running
// when the first alert triggers.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagersCfgHash))
// Then, ensure we've discovered the Alertmanager.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey.OrgID, 1, 0)
var expected []*models2.PostableAlert
alerts := definitions.PostableAlerts{}
for i := 0; i < rand.Intn(5)+1; i++ {
alert := generatePostableAlert(t, mockedClock)
expected = append(expected, &alert)
alerts.PostableAlerts = append(alerts.PostableAlerts, alert)
}
alertsRouter.Send(context.Background(), ruleKey, alerts)
// Eventually, our Alertmanager should have received at least one alert.
assertAlertsDelivered(t, fakeAM, expected)
// Now, let's remove the Alertmanager from the admin configuration.
mockedGetAdminConfigurations.Return(nil, nil)
// Again, make sure we sync and verify the externalAlertmanagers.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagersCfgHash))
// Then, ensure we've dropped the Alertmanager.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey.OrgID, 0, 0)
}
func TestIntegrationSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ruleKey1 := models.GenerateRuleKey(1)
ruleKey2 := models.GenerateRuleKey(2)
fakeAM := NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeAdminConfigStore := &store.AdminConfigurationStoreMock{}
mockedGetAdminConfigurations := fakeAdminConfigStore.EXPECT().GetAdminConfigurations()
mockedClock := clock.NewMock()
moa := createMultiOrgAlertmanager(t, []int64{1, 2})
appUrl := &url.URL{
Scheme: "http",
Host: "localhost",
}
ds1 := datasources.DataSource{
URL: fakeAM.Server.URL,
OrgID: ruleKey1.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
fakeDs := &fake_ds.FakeDataSourceService{DataSources: []*datasources.DataSource{&ds1}}
alertsRouter := NewAlertsRouter(moa, fakeAdminConfigStore, mockedClock, appUrl, map[int64]struct{}{}, 10*time.Minute,
fakeDs, fake_secrets.NewFakeSecretsService(), featuremgmt.WithFeatures())
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey1.OrgID, SendAlertsTo: models.AllAlertmanagers},
}, nil)
// Make sure we sync the configuration at least once before the evaluation happens to guarantee the sender is running
// when the first alert triggers.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagersCfgHash))
// Then, ensure we've discovered the Alertmanager.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey1.OrgID, 1, 0)
// 1. Now, let's assume a new org comes along.
ds2 := datasources.DataSource{
URL: fakeAM.Server.URL,
OrgID: ruleKey2.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
fakeDs.DataSources = append(fakeDs.DataSources, &ds2)
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey1.OrgID, SendAlertsTo: models.AllAlertmanagers},
{OrgID: ruleKey2.OrgID},
}, nil)
// If we sync again, new externalAlertmanagers must have spawned.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 2, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 2, len(alertsRouter.externalAlertmanagersCfgHash))
// Then, ensure we've discovered the Alertmanager for the new organization.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey1.OrgID, 1, 0)
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey2.OrgID, 1, 0)
var expected []*models2.PostableAlert
alerts1 := definitions.PostableAlerts{}
for i := 0; i < rand.Intn(5)+1; i++ {
alert := generatePostableAlert(t, mockedClock)
expected = append(expected, &alert)
alerts1.PostableAlerts = append(alerts1.PostableAlerts, alert)
}
alerts2 := definitions.PostableAlerts{}
for i := 0; i < rand.Intn(5)+1; i++ {
alert := generatePostableAlert(t, mockedClock)
expected = append(expected, &alert)
alerts2.PostableAlerts = append(alerts2.PostableAlerts, alert)
}
alertsRouter.Send(context.Background(), ruleKey1, alerts1)
alertsRouter.Send(context.Background(), ruleKey2, alerts2)
assertAlertsDelivered(t, fakeAM, expected)
// 2. Next, let's modify the configuration of an organization by adding an extra alertmanager.
fakeAM2 := NewFakeExternalAlertmanager(t)
ds3 := datasources.DataSource{
URL: fakeAM2.Server.URL,
OrgID: ruleKey2.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
fakeDs.DataSources = append(fakeDs.DataSources, &ds3)
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey1.OrgID, SendAlertsTo: models.AllAlertmanagers},
{OrgID: ruleKey2.OrgID},
}, nil)
// Before we sync, let's grab the existing hash of this particular org.
currentHash := alertsRouter.externalAlertmanagersCfgHash[ruleKey2.OrgID]
// Now, sync again.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
// The hash for org two should not be the same and we should still have two externalAlertmanagers.
require.NotEqual(t, alertsRouter.externalAlertmanagersCfgHash[ruleKey2.OrgID], currentHash)
require.Equal(t, 2, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 2, len(alertsRouter.externalAlertmanagersCfgHash))
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey2.OrgID, 2, 0)
// 3. Now, let's provide a configuration that fails for OrgID = 1.
fakeDs.DataSources[0].URL = "123://invalid.org"
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey1.OrgID, SendAlertsTo: models.AllAlertmanagers},
{OrgID: ruleKey2.OrgID},
}, nil)
// Before we sync, let's get the current config hash.
currentHash = alertsRouter.externalAlertmanagersCfgHash[ruleKey1.OrgID]
// Now, sync again.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
// The old configuration should not be running.
require.NotEqual(t, alertsRouter.externalAlertmanagersCfgHash[ruleKey1.OrgID], currentHash)
require.Equal(t, 0, len(alertsRouter.AlertmanagersFor(ruleKey1.OrgID)))
// If we fix it - it should be applied.
fakeDs.DataSources[0].URL = "notarealalertmanager:3030"
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey1.OrgID, SendAlertsTo: models.AllAlertmanagers},
{OrgID: ruleKey2.OrgID},
}, nil)
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.NotEqual(t, alertsRouter.externalAlertmanagersCfgHash[ruleKey1.OrgID], currentHash)
// Finally, remove everything.
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{}, nil)
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagersCfgHash))
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey1.OrgID, 0, 0)
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey2.OrgID, 0, 0)
}
func TestChangingAlertmanagersChoice(t *testing.T) {
ruleKey := models.GenerateRuleKey(1)
fakeAM := NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeAdminConfigStore := &store.AdminConfigurationStoreMock{}
mockedGetAdminConfigurations := fakeAdminConfigStore.EXPECT().GetAdminConfigurations()
mockedClock := clock.NewMock()
mockedClock.Set(time.Now())
moa := createMultiOrgAlertmanager(t, []int64{1})
appUrl := &url.URL{
Scheme: "http",
Host: "localhost",
}
ds := datasources.DataSource{
URL: fakeAM.Server.URL,
OrgID: ruleKey.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
alertsRouter := NewAlertsRouter(moa, fakeAdminConfigStore, mockedClock, appUrl, map[int64]struct{}{},
10*time.Minute, &fake_ds.FakeDataSourceService{DataSources: []*datasources.DataSource{&ds}}, fake_secrets.NewFakeSecretsService(), featuremgmt.WithFeatures())
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.AllAlertmanagers},
}, nil)
// Make sure we sync the configuration at least once before the evaluation happens to guarantee the sender is running
// when the first alert triggers.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagersCfgHash))
require.Equal(t, models.AllAlertmanagers, alertsRouter.sendAlertsTo[ruleKey.OrgID])
// Then, ensure we've discovered the Alertmanager.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey.OrgID, 1, 0)
var expected []*models2.PostableAlert
alerts := definitions.PostableAlerts{}
for i := 0; i < rand.Intn(5)+1; i++ {
alert := generatePostableAlert(t, mockedClock)
expected = append(expected, &alert)
alerts.PostableAlerts = append(alerts.PostableAlerts, alert)
}
alertsRouter.Send(context.Background(), ruleKey, alerts)
// Eventually, our Alertmanager should have received at least one alert.
assertAlertsDelivered(t, fakeAM, expected)
// Now, let's change the Alertmanagers choice to send only to the external Alertmanager.
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.ExternalAlertmanagers},
}, nil)
// Again, make sure we sync and verify the externalAlertmanagers.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagersCfgHash))
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey.OrgID, 1, 0)
require.Equal(t, models.ExternalAlertmanagers, alertsRouter.sendAlertsTo[ruleKey.OrgID])
// Finally, let's change the Alertmanagers choice to send only to the internal Alertmanager.
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.InternalAlertmanager},
}, nil)
// Again, make sure we sync and verify the externalAlertmanagers.
// externalAlertmanagers should be running even though alerts are being handled externally.
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 1, len(alertsRouter.externalAlertmanagersCfgHash))
// Then, ensure the Alertmanager is still listed and the Alertmanagers choice has changed.
assertAlertmanagersStatusForOrg(t, alertsRouter, ruleKey.OrgID, 1, 0)
require.Equal(t, models.InternalAlertmanager, alertsRouter.sendAlertsTo[ruleKey.OrgID])
alertsRouter.Send(context.Background(), ruleKey, alerts)
am, err := moa.AlertmanagerFor(ruleKey.OrgID)
require.NoError(t, err)
actualAlerts, err := am.GetAlerts(context.Background(), true, true, true, nil, "")
require.NoError(t, err)
require.Len(t, actualAlerts, len(expected))
}
func TestAlertmanagersChoiceWithDisableExternalFeatureToggle(t *testing.T) {
ruleKey := models.GenerateRuleKey(1)
fakeAM := NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeAdminConfigStore := &store.AdminConfigurationStoreMock{}
mockedGetAdminConfigurations := fakeAdminConfigStore.EXPECT().GetAdminConfigurations()
mockedClock := clock.NewMock()
mockedClock.Set(time.Now())
moa := createMultiOrgAlertmanager(t, []int64{1})
appUrl := &url.URL{
Scheme: "http",
Host: "localhost",
}
ds := datasources.DataSource{
URL: fakeAM.Server.URL,
OrgID: ruleKey.OrgID,
Type: datasources.DS_ALERTMANAGER,
JsonData: simplejson.NewFromAny(map[string]any{
"handleGrafanaManagedAlerts": true,
"implementation": "prometheus",
}),
}
var expected []*models2.PostableAlert
alerts := definitions.PostableAlerts{}
for i := 0; i < rand.Intn(5)+1; i++ {
alert := generatePostableAlert(t, mockedClock)
expected = append(expected, &alert)
alerts.PostableAlerts = append(alerts.PostableAlerts, alert)
}
alertsRouter := NewAlertsRouter(moa, fakeAdminConfigStore, mockedClock, appUrl, map[int64]struct{}{},
10*time.Minute, &fake_ds.FakeDataSourceService{DataSources: []*datasources.DataSource{&ds}},
fake_secrets.NewFakeSecretsService(), featuremgmt.WithFeatures(featuremgmt.FlagAlertingDisableSendAlertsExternal))
// Test that we only send to the internal Alertmanager even though the configuration specifies AllAlertmanagers.
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.AllAlertmanagers},
}, nil)
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagersCfgHash))
require.Equal(t, models.InternalAlertmanager, alertsRouter.sendAlertsTo[ruleKey.OrgID])
alertsRouter.Send(context.Background(), ruleKey, alerts)
am, err := moa.AlertmanagerFor(ruleKey.OrgID)
require.NoError(t, err)
actualAlerts, err := am.GetAlerts(context.Background(), true, true, true, nil, "")
require.NoError(t, err)
require.Len(t, actualAlerts, len(expected))
// Test that we still only send to the internal alertmanager even though the configuration specifies ExternalAlertmanagers.
mockedGetAdminConfigurations.Return([]*models.AdminConfiguration{
{OrgID: ruleKey.OrgID, SendAlertsTo: models.ExternalAlertmanagers},
}, nil)
require.NoError(t, alertsRouter.SyncAndApplyConfigFromDatabase(context.Background()))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagers))
require.Equal(t, 0, len(alertsRouter.externalAlertmanagersCfgHash))
require.Equal(t, models.InternalAlertmanager, alertsRouter.sendAlertsTo[ruleKey.OrgID])
alertsRouter.Send(context.Background(), ruleKey, alerts)
am, err = moa.AlertmanagerFor(ruleKey.OrgID)
require.NoError(t, err)
actualAlerts, err = am.GetAlerts(context.Background(), true, true, true, nil, "")
require.NoError(t, err)
require.Len(t, actualAlerts, len(expected))
}
func assertAlertmanagersStatusForOrg(t *testing.T, alertsRouter *AlertsRouter, orgID int64, active, dropped int) {
t.Helper()
require.Eventuallyf(t, func() bool {
return len(alertsRouter.AlertmanagersFor(orgID)) == active && len(alertsRouter.DroppedAlertmanagersFor(orgID)) == dropped
}, 10*time.Second, 200*time.Millisecond,
fmt.Sprintf("expected %d active Alertmanagers and %d dropped ones but got %d active and %d dropped", active, dropped, len(alertsRouter.AlertmanagersFor(orgID)), len(alertsRouter.DroppedAlertmanagersFor(orgID))))
}
func assertAlertsDelivered(t *testing.T, fakeAM *FakeExternalAlertmanager, expectedAlerts []*models2.PostableAlert) {
t.Helper()
require.Eventuallyf(t, func() bool {
return fakeAM.AlertsCount() == len(expectedAlerts)
}, 10*time.Second, 200*time.Millisecond, fmt.Sprintf("expected %d alerts to be delivered to remote Alertmanager but only %d was delivered", len(expectedAlerts), fakeAM.AlertsCount()))
require.Len(t, fakeAM.Alerts(), len(expectedAlerts))
}
func generatePostableAlert(t *testing.T, clk clock.Clock) models2.PostableAlert {
t.Helper()
u := url.URL{
Scheme: "http",
Host: "localhost",
RawPath: "/" + util.GenerateShortUID(),
}
return models2.PostableAlert{
Annotations: models2.LabelSet(models.GenerateAlertLabels(5, "ann-")),
EndsAt: strfmt.DateTime(clk.Now().Add(1 * time.Minute)),
StartsAt: strfmt.DateTime(clk.Now()),
Alert: models2.Alert{
GeneratorURL: strfmt.URI(u.String()),
Labels: models2.LabelSet(models.GenerateAlertLabels(5, "lbl-")),
},
}
}
func createMultiOrgAlertmanager(t *testing.T, orgs []int64) *notifier.MultiOrgAlertmanager {
t.Helper()
tmpDir := t.TempDir()
orgStore := notifier.NewFakeOrgStore(t, orgs)
cfg := &setting.Cfg{
DataPath: tmpDir,
UnifiedAlerting: setting.UnifiedAlertingSettings{
AlertmanagerConfigPollInterval: 3 * time.Minute,
DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(),
DisabledOrgs: map[int64]struct{}{},
}, // do not poll in tests.
}
cfgStore := notifier.NewFakeConfigStore(t, make(map[int64]*models.AlertConfiguration))
kvStore := fakes.NewFakeKVStore(t)
registry := prometheus.NewPedanticRegistry()
m := metrics.NewNGAlert(registry)
secretsService := secretsManager.SetupTestService(t, fake_secrets.NewFakeSecretsStore())
decryptFn := secretsService.GetDecryptedValue
moa, err := notifier.NewMultiOrgAlertmanager(
cfg,
cfgStore,
orgStore,
kvStore,
fakes.NewFakeProvisioningStore(),
decryptFn,
m.GetMultiOrgAlertmanagerMetrics(),
nil,
fakes.NewFakeReceiverPermissionsService(),
log.New("testlogger"),
secretsService,
featuremgmt.WithFeatures(),
)
require.NoError(t, err)
require.NoError(t, moa.LoadAndSyncAlertmanagersForOrgs(context.Background()))
require.Eventually(t, func() bool {
for _, org := range orgs {
_, err := moa.AlertmanagerFor(org)
if err != nil {
return false
}
}
return true
}, 10*time.Second, 100*time.Millisecond)
return moa
}
func TestBuildExternalURL(t *testing.T) {
sch := AlertsRouter{
secretService: fake_secrets.NewFakeSecretsService(),
}
tests := []struct {
name string
ds *datasources.DataSource
expectedURL string
}{
{
name: "datasource without auth",
ds: &datasources.DataSource{
URL: "https://localhost:9000",
},
expectedURL: "https://localhost:9000",
},
{
name: "datasource without auth and with path",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/am",
},
expectedURL: "https://localhost:9000/path/to/am",
},
{
name: "datasource with auth",
ds: &datasources.DataSource{
URL: "https://localhost:9000",
BasicAuth: true,
BasicAuthUser: "johndoe",
SecureJsonData: map[string][]byte{
"basicAuthPassword": []byte("123"),
},
},
expectedURL: "https://johndoe:123@localhost:9000",
},
{
name: "datasource with auth that needs escaping",
ds: &datasources.DataSource{
URL: "https://localhost:9000",
BasicAuth: true,
BasicAuthUser: "johndoe",
SecureJsonData: map[string][]byte{
"basicAuthPassword": []byte("123#!"),
},
},
expectedURL: "https://johndoe:123%23%21@localhost:9000",
},
{
name: "datasource with auth and path",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/am",
BasicAuth: true,
BasicAuthUser: "johndoe",
SecureJsonData: map[string][]byte{
"basicAuthPassword": []byte("123"),
},
},
expectedURL: "https://johndoe:123@localhost:9000/path/to/am",
},
{
name: "with no scheme specified in the datasource",
ds: &datasources.DataSource{
URL: "localhost:9000/path/to/am",
BasicAuth: true,
BasicAuthUser: "johndoe",
SecureJsonData: map[string][]byte{
"basicAuthPassword": []byte("123"),
},
},
expectedURL: "http://johndoe:123@localhost:9000/path/to/am",
},
{
name: "with no scheme specified not auth in the datasource",
ds: &datasources.DataSource{
URL: "localhost:9000/path/to/am",
},
expectedURL: "http://localhost:9000/path/to/am",
},
{
name: "adds /alertmanager to path when implementation is mimir",
ds: &datasources.DataSource{
URL: "https://localhost:9000",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "mimir")
return r
}(),
},
expectedURL: "https://localhost:9000/alertmanager",
},
{
name: "adds /alertmanager to path when implementation is cortex",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/am",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "cortex")
return r
}(),
},
expectedURL: "https://localhost:9000/path/to/am/alertmanager",
},
{
name: "do nothing when implementation is prometheus",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/am",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "prometheus")
return r
}(),
},
expectedURL: "https://localhost:9000/path/to/am",
},
{
name: "do not add /alertmanager to path when last segment already contains it",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/alertmanager",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "mimir")
return r
}(),
},
expectedURL: "https://localhost:9000/path/to/alertmanager",
},
{
name: "add /alertmanager to path when last segment does not exactly match",
ds: &datasources.DataSource{
URL: "https://localhost:9000/path/to/alertmanagerasdf",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "mimir")
return r
}(),
},
expectedURL: "https://localhost:9000/path/to/alertmanagerasdf/alertmanager",
},
{
name: "add /alertmanager to path when exists but is not last segment",
ds: &datasources.DataSource{
URL: "https://localhost:9000/alertmanager/path/to/am",
JsonData: func() *simplejson.Json {
r := simplejson.New()
r.Set("implementation", "mimir")
return r
}(),
},
expectedURL: "https://localhost:9000/alertmanager/path/to/am/alertmanager",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
url, err := sch.buildExternalURL(test.ds)
require.NoError(t, err)
require.Equal(t, test.expectedURL, url)
})
}
}
func TestAlertManegers_asSHA256(t *testing.T) {
tc := []struct {
name string
amUrls []string
ciphertext string
}{
{
name: "asSHA256",
amUrls: []string{"http://localhost:9093"},
ciphertext: "3ec9db375a5ba12f7c7b704922cf4b8e21a31e30d85be2386803829f0ee24410",
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.ciphertext, asSHA256(tt.amUrls))
})
}
}
func TestAlertManagers_buildRedactedAMs(t *testing.T) {
fakeLogger := logtest.Fake{}
tc := []struct {
name string
orgId int64
amUrls []string
errCalls int
errLog string
errCtx []any
expected []string
}{
{
name: "buildRedactedAMs",
orgId: 1,
amUrls: []string{"http://user:password@localhost:9093"},
errCalls: 0,
errLog: "",
expected: []string{"http://user:xxxxx@localhost:9093"},
},
{
name: "Error building redacted AM URLs",
orgId: 2,
amUrls: []string{"1234://user:password@localhost:9094"},
errCalls: 1,
errLog: "Failed to parse alertmanager string",
expected: []string{},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
var cfgs []ExternalAMcfg
for _, url := range tt.amUrls {
cfgs = append(cfgs, ExternalAMcfg{
URL: url,
})
}
require.Equal(t, tt.expected, buildRedactedAMs(&fakeLogger, cfgs, tt.orgId))
require.Equal(t, tt.errCalls, fakeLogger.ErrorLogs.Calls)
require.Equal(t, tt.errLog, fakeLogger.ErrorLogs.Message)
})
}
}