Alerting: Add support for retrieving imported Prometheus Alertmanager configurations (#106864)
This commit is contained in:
@@ -3,12 +3,16 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
amconfig "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@@ -60,7 +64,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("without datasource UID header should return 400", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "")
|
||||
|
||||
@@ -71,7 +75,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with invalid datasource should return error", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "non-existing-ds")
|
||||
|
||||
@@ -81,7 +85,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with rule group without evaluation interval should return 202", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
@@ -90,7 +94,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("should replace an existing rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -152,7 +157,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("should fail to replace a provisioned rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -187,7 +193,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("with no access to the datasource should return 403", func(t *testing.T) {
|
||||
acFake := &acfakes.FakeRuleService{}
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withFakeAccessControlRuleService(acFake))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFakeAccessControlRuleService(acFake))
|
||||
|
||||
acFake.AuthorizeRuleChangesFunc = func(context.Context, identity.Requester, *store.GroupDelta) error {
|
||||
return datasources.ErrDataSourceAccessDenied
|
||||
@@ -203,7 +209,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
quotas := &provisioning.MockQuotaChecker{}
|
||||
quotas.EXPECT().LimitExceeded()
|
||||
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withQuotaChecker(quotas))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withQuotaChecker(quotas))
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "folder", simpleGroup)
|
||||
@@ -241,7 +247,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
@@ -274,7 +280,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
@@ -286,7 +292,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 202", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
@@ -315,7 +321,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(features))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(features))
|
||||
srv.cfg.RecordingRules.Enabled = tc.recordingRules
|
||||
rc := createRequestCtx()
|
||||
|
||||
@@ -327,7 +333,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("with disable provenance header should use ProvenanceNone", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -361,7 +368,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when target datasource does not exist", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(targetDatasourceUIDHeader, "some-data-source")
|
||||
|
||||
@@ -371,7 +378,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("uses target datasource for recording rules", func(t *testing.T) {
|
||||
srv, dsCache, ruleStore, _ := createConvertPrometheusSrv(t)
|
||||
srv, dsCache, ruleStore := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
targetDSUID := util.GenerateShortUID()
|
||||
ds := &datasources.DataSource{
|
||||
@@ -408,7 +415,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("sets notification settings for rules if specified", func(t *testing.T) {
|
||||
srv, _, ruleStore, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
receiver := "test-receiver"
|
||||
@@ -450,7 +457,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when notification settings header contains invalid JSON", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
rc.Req.Header.Set(notificationSettingsHeader, "{invalid json")
|
||||
@@ -472,7 +479,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when notification settings contain invalid values", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
settings := apimodels.AlertRuleNotificationSettings{
|
||||
@@ -515,7 +522,7 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("with non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetRuleGroup(rc, "non-existent", "test")
|
||||
@@ -523,7 +530,7 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with non-existent group should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetRuleGroup(rc, "test", "non-existent")
|
||||
@@ -531,7 +538,8 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 200", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create two folders in the root folder
|
||||
@@ -619,7 +627,7 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("with non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetNamespace(rc, "non-existent")
|
||||
@@ -627,7 +635,8 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 200", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create two folders in the root folder
|
||||
@@ -731,12 +740,13 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("for non-existent folder should return empty response", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
assertEmptyResponse(t, srv, rc)
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no children should return empty response", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
|
||||
fldr := randFolder()
|
||||
fldr.UID = unknownFolderUID
|
||||
@@ -757,7 +767,8 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with rules should return 200 with rules", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create a folder in the root
|
||||
@@ -797,7 +808,7 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
|
||||
func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteNamespace(rc, "non-existent")
|
||||
@@ -805,7 +816,8 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no groups should return 404", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
fldr := randFolder()
|
||||
@@ -820,7 +832,8 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initNamespace := func(promDefinition string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, append(opts, withFolderService(folderService))...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -927,7 +940,7 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
|
||||
func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, "non-existent", "test-group")
|
||||
@@ -935,7 +948,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no group should return 404", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
fldr := randFolder()
|
||||
@@ -952,7 +966,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initGroup := func(promDefinition string, groupName string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, append(opts, withFolderService(folderService))...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -1060,7 +1075,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouteConvertPrometheusPostRuleGroups(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
|
||||
req := createRequestCtx()
|
||||
req.Req.Header.Set(datasourceUIDHeader, existingDSUID)
|
||||
@@ -1265,6 +1281,8 @@ type convertPrometheusSrvOptions struct {
|
||||
fakeAccessControlRuleService *acfakes.FakeRuleService
|
||||
quotaChecker *provisioning.MockQuotaChecker
|
||||
featureToggles featuremgmt.FeatureToggles
|
||||
alertmanager Alertmanager
|
||||
folderService folder.Service
|
||||
}
|
||||
|
||||
type convertPrometheusSrvOptionsFunc func(*convertPrometheusSrvOptions)
|
||||
@@ -1293,7 +1311,19 @@ func withFeatureToggles(toggles featuremgmt.FeatureToggles) convertPrometheusSrv
|
||||
}
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *dsfakes.FakeCacheService, *fakes.RuleStore, *foldertest.FakeService) {
|
||||
func withAlertmanager(am Alertmanager) convertPrometheusSrvOptionsFunc {
|
||||
return func(opts *convertPrometheusSrvOptions) {
|
||||
opts.alertmanager = am
|
||||
}
|
||||
}
|
||||
|
||||
func withFolderService(f folder.Service) convertPrometheusSrvOptionsFunc {
|
||||
return func(opts *convertPrometheusSrvOptions) {
|
||||
opts.folderService = f
|
||||
}
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *dsfakes.FakeCacheService, *fakes.RuleStore) {
|
||||
t.Helper()
|
||||
|
||||
// By default the quota checker will allow the operation
|
||||
@@ -1304,6 +1334,7 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
provenanceStore: fakes.NewFakeProvisioningStore(),
|
||||
fakeAccessControlRuleService: &acfakes.FakeRuleService{},
|
||||
quotaChecker: quotas,
|
||||
folderService: foldertest.NewFakeService(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -1321,12 +1352,10 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
}
|
||||
dsCache.DataSources = append(dsCache.DataSources, ds)
|
||||
|
||||
folderService := foldertest.NewFakeService()
|
||||
|
||||
alertRuleService := provisioning.NewAlertRuleService(
|
||||
ruleStore,
|
||||
options.provenanceStore,
|
||||
folderService,
|
||||
options.folderService,
|
||||
options.quotaChecker,
|
||||
&provisioning.NopTransactionManager{},
|
||||
60,
|
||||
@@ -1344,9 +1373,9 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
},
|
||||
}
|
||||
|
||||
srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService, options.featureToggles)
|
||||
srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService, options.featureToggles, options.alertmanager)
|
||||
|
||||
return srv, dsCache, ruleStore, folderService
|
||||
return srv, dsCache, ruleStore
|
||||
}
|
||||
|
||||
func createRequestCtx() *contextmodel.ReqContext {
|
||||
@@ -1466,3 +1495,307 @@ func TestGetProvenance(t *testing.T) {
|
||||
require.Equal(t, models.ProvenanceNone, provenance)
|
||||
})
|
||||
}
|
||||
|
||||
type mockAlertmanager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) SaveAndApplyExtraConfiguration(ctx context.Context, org int64, extraConfig apimodels.ExtraConfiguration) error {
|
||||
args := m.Called(ctx, org, extraConfig)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) GetAlertmanagerConfiguration(ctx context.Context, org int64, withAutogen bool) (apimodels.GettableUserConfig, error) {
|
||||
args := m.Called(ctx, org, withAutogen)
|
||||
return args.Get(0).(apimodels.GettableUserConfig), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) DeleteAndApplyExtraConfiguration(ctx context.Context, org int64, identifier string) error {
|
||||
args := m.Called(ctx, org, identifier)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestRouteConvertPrometheusGetAlertmanagerConfig(t *testing.T) {
|
||||
const identifier = "test-config"
|
||||
const orgID = int64(1)
|
||||
|
||||
t.Run("without feature flag should return 501", func(t *testing.T) {
|
||||
ft := featuremgmt.WithFeatures()
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusNotImplemented, response.Status())
|
||||
})
|
||||
|
||||
t.Run("without config identifier header should return 400", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
})
|
||||
|
||||
t.Run("with empty config identifier header should return 400", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, "")
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should return config when it is found", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
expectedConfig := apimodels.GettableUserConfig{
|
||||
ExtraConfigs: []apimodels.ExtraConfiguration{
|
||||
{
|
||||
Identifier: identifier,
|
||||
TemplateFiles: map[string]string{
|
||||
"test.tmpl": "{{ define \"test\" }}Hello{{ end }}",
|
||||
},
|
||||
AlertmanagerConfig: `route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, int64(1), false).Return(expectedConfig, nil).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
expectedResponse := `alertmanager_config: |-
|
||||
route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default
|
||||
template_files:
|
||||
test.tmpl: '{{ define "test" }}Hello{{ end }}'`
|
||||
|
||||
require.YAMLEq(t, expectedResponse, string(response.Body()))
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("when config not found should return 404", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
expectedConfig := apimodels.GettableUserConfig{
|
||||
ExtraConfigs: []apimodels.ExtraConfiguration{
|
||||
{
|
||||
Identifier: "other-config",
|
||||
TemplateFiles: map[string]string{
|
||||
"test.tmpl": "{{ define \"test\" }}Hello{{ end }}",
|
||||
},
|
||||
AlertmanagerConfig: `route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, orgID, false).Return(expectedConfig, nil).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should return error when GetAlertmanagerConfiguration fails", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, orgID, false).Return(apimodels.GettableUserConfig{}, errors.New("config error")).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, response.Status())
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseMergeMatchersHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerValue string
|
||||
expectedError bool
|
||||
expectedMatchers amconfig.Matchers
|
||||
}{
|
||||
{
|
||||
name: "empty header should return error",
|
||||
headerValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "single matcher should parse correctly",
|
||||
headerValue: "env=prod",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple matchers should be parsed correctly",
|
||||
headerValue: "env=prod,team=alerting",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matchers with spaces should be parsed correctly",
|
||||
headerValue: " env = prod , team = alerting ",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid format without equals should return error",
|
||||
headerValue: "env:prod",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty key should return error",
|
||||
headerValue: "=prod",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty value should return error",
|
||||
headerValue: "env=",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "missing value should return error",
|
||||
headerValue: "env",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(mergeMatchersHeader, tc.headerValue)
|
||||
|
||||
matchers, err := parseMergeMatchersHeader(rc)
|
||||
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, tc.expectedMatchers, matchers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigIdentifierHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerValue string
|
||||
expectedValue string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "valid identifier should parse correctly",
|
||||
headerValue: "test-config",
|
||||
expectedValue: "test-config",
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "identifier with spaces should be trimmed",
|
||||
headerValue: " test-config ",
|
||||
expectedValue: "test-config",
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "empty identifier should return error",
|
||||
headerValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "whitespace only identifier should return error",
|
||||
headerValue: " ",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, tc.headerValue)
|
||||
|
||||
identifier, err := parseConfigIdentifierHeader(rc)
|
||||
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedValue, identifier)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatMergeMatchers(t *testing.T) {
|
||||
t.Run("empty matchers should return empty string", func(t *testing.T) {
|
||||
result := formatMergeMatchers(nil)
|
||||
require.Equal(t, "", result)
|
||||
})
|
||||
|
||||
t.Run("single matcher should format correctly", func(t *testing.T) {
|
||||
matchers := amconfig.Matchers{
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "env",
|
||||
Value: "prod",
|
||||
},
|
||||
}
|
||||
result := formatMergeMatchers(matchers)
|
||||
require.Equal(t, "env=prod", result)
|
||||
})
|
||||
|
||||
t.Run("multiple matchers should format correctly", func(t *testing.T) {
|
||||
matchers := amconfig.Matchers{
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "env",
|
||||
Value: "prod",
|
||||
},
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "team",
|
||||
Value: "backend",
|
||||
},
|
||||
}
|
||||
result := formatMergeMatchers(matchers)
|
||||
require.Equal(t, "env=prod,team=backend", result)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user