From 8548530dc4b0f9c585f31c538b39512448de5ace Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 17 Jul 2025 16:47:14 +0200 Subject: [PATCH] Alertmanager: Add MergeState method (#108242) * Alertmanager: Add MergeState method * remove RemoteState in favor of ExternalState * fix tests --- pkg/services/ngalert/notifier/alerts.go | 4 -- .../ngalert/notifier/multiorg_alertmanager.go | 9 +++- pkg/services/ngalert/notifier/silences.go | 4 -- pkg/services/ngalert/notifier/state.go | 9 ++++ pkg/services/ngalert/remote/alertmanager.go | 10 +--- .../remote/forked_alertmanager_test.go | 46 +++++++++++++------ 6 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 pkg/services/ngalert/notifier/state.go diff --git a/pkg/services/ngalert/notifier/alerts.go b/pkg/services/ngalert/notifier/alerts.go index ed55a72e4ef..2052b2a99d6 100644 --- a/pkg/services/ngalert/notifier/alerts.go +++ b/pkg/services/ngalert/notifier/alerts.go @@ -13,7 +13,3 @@ func (am *alertmanager) GetAlerts(_ context.Context, active, silenced, inhibited func (am *alertmanager) GetAlertGroups(_ context.Context, active, silenced, inhibited bool, filter []string, receivers string) (alertingNotify.AlertGroups, error) { return am.Base.GetAlertGroups(active, silenced, inhibited, filter, receivers) } - -func (am *alertmanager) MergeNflog(b []byte) error { - return am.Base.MergeNflog(b) -} diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 7b52cb68c3b..4ce738c08a9 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -78,10 +78,15 @@ type Alertmanager interface { Ready() bool } +// ExternalState holds nflog entries and silences from an external Alertmanager. +type ExternalState struct { + Silences []byte + Nflog []byte +} + // StateMerger describes a type that is able to merge external state (nflog, silences) with its own. type StateMerger interface { - MergeNflog([]byte) error - MergeSilences([]byte) error + MergeState(ExternalState) error } type MultiOrgAlertmanager struct { diff --git a/pkg/services/ngalert/notifier/silences.go b/pkg/services/ngalert/notifier/silences.go index dca86cb12cf..905fe231f8d 100644 --- a/pkg/services/ngalert/notifier/silences.go +++ b/pkg/services/ngalert/notifier/silences.go @@ -21,7 +21,3 @@ func (am *alertmanager) CreateSilence(_ context.Context, ps *alertingNotify.Post func (am *alertmanager) DeleteSilence(_ context.Context, silenceID string) error { return am.Base.DeleteSilence(silenceID) } - -func (am *alertmanager) MergeSilences(b []byte) error { - return am.Base.MergeSilences(b) -} diff --git a/pkg/services/ngalert/notifier/state.go b/pkg/services/ngalert/notifier/state.go new file mode 100644 index 00000000000..c8551d2ed1a --- /dev/null +++ b/pkg/services/ngalert/notifier/state.go @@ -0,0 +1,9 @@ +package notifier + +// MergeState incorporates external nflog entries and silences to the Alertmanager's state. +func (am *alertmanager) MergeState(state ExternalState) error { + if err := am.Base.MergeNflog(state.Nflog); err != nil { + return err + } + return am.Base.MergeSilences(state.Silences) +} diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 444fe0d9625..d80f6967692 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -389,15 +389,9 @@ func (am *Alertmanager) sendConfiguration(ctx context.Context, cfg *remoteClient return nil } -// RemoteState represents the state (silences, nflog) in use by a remote Alertmanager. -type RemoteState struct { - Silences []byte - Nflog []byte -} - // GetRemoteState gets the remote Alertmanager's internal state. -func (am *Alertmanager) GetRemoteState(ctx context.Context) (RemoteState, error) { - var rs RemoteState +func (am *Alertmanager) GetRemoteState(ctx context.Context) (notifier.ExternalState, error) { + var rs notifier.ExternalState s, err := am.mimirClient.GetFullState(ctx) if err != nil { diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 748ccf0fe75..565de4320f4 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -29,7 +29,7 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { t.Run("ApplyConfig", func(tt *testing.T) { { // If the remote Alertmanager is not ready, ApplyConfig should be called on both Alertmanagers. - internal, remote, forked := genTestAlertmanagersWithSyncInterval(tt, modeRemoteSecondary, 10*time.Minute) + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary, withSyncInterval(10*time.Minute)) internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() readyCall := remote.EXPECT().Ready().Return(false).Once() remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(readyCall) @@ -46,7 +46,7 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { // If the remote Alertmanager is ready and the sync interval has elapsed, // the forked Alertmanager should sync the configuration on the remote Alertmanager // and call ApplyConfig only on the internal Alertmanager. - internal, remote, forked := genTestAlertmanagersWithSyncInterval(tt, modeRemoteSecondary, 0) + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary) internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Twice() remote.EXPECT().Ready().Return(true).Twice() remote.EXPECT().CompareAndSendConfiguration(ctx, mock.Anything).Return(nil).Twice() @@ -58,7 +58,7 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { // An error in the remote Alertmanager should not be returned, // but it should result in the forked Alertmanager trying to sync // the configuration in the next call to ApplyConfig, regardless of the sync interval. - internal, remote, forked := genTestAlertmanagersWithSyncInterval(tt, modeRemoteSecondary, 10*time.Minute) + internal, remote, forked := genTestAlertmanagers(tt, modeRemoteSecondary, withSyncInterval(10*time.Minute)) internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Twice() remote.EXPECT().Ready().Return(false).Twice() remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Twice() @@ -66,7 +66,7 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) // Let's try the same thing but starting from a ready Alertmanager. - internal, remote, forked = genTestAlertmanagersWithSyncInterval(tt, modeRemoteSecondary, 10*time.Minute) + internal, remote, forked = genTestAlertmanagers(tt, modeRemoteSecondary, withSyncInterval(10*time.Minute)) internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Twice() remote.EXPECT().Ready().Return(true).Twice() remote.EXPECT().CompareAndSendConfiguration(ctx, mock.Anything).Return(expErr).Twice() @@ -696,14 +696,30 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { require.False(tt, forked.Ready()) }) } -func genTestAlertmanagers(t *testing.T, mode int) (*alertmanager_mock.AlertmanagerMock, *remote_alertmanager_mock.RemoteAlertmanagerMock, notifier.Alertmanager) { - t.Helper() - return genTestAlertmanagersWithSyncInterval(t, mode, 0) + +type internalAlertmanagerMock struct { + *alertmanager_mock.AlertmanagerMock + mergeStateCalled bool +} + +func (m *internalAlertmanagerMock) MergeState(notifier.ExternalState) error { + m.mergeStateCalled = true + return nil +} + +func withSyncInterval(syncInterval time.Duration) func(RemoteSecondaryConfig) RemoteSecondaryConfig { + return func(rsc RemoteSecondaryConfig) RemoteSecondaryConfig { + rsc.SyncInterval = syncInterval + return rsc + } } -func genTestAlertmanagersWithSyncInterval(t *testing.T, mode int, syncInterval time.Duration) (*alertmanager_mock.AlertmanagerMock, *remote_alertmanager_mock.RemoteAlertmanagerMock, notifier.Alertmanager) { +func genTestAlertmanagers(t *testing.T, mode int, options ...func(RemoteSecondaryConfig) RemoteSecondaryConfig) (*internalAlertmanagerMock, *remote_alertmanager_mock.RemoteAlertmanagerMock, notifier.Alertmanager) { t.Helper() - internal := alertmanager_mock.NewAlertmanagerMock(t) + internal := &internalAlertmanagerMock{ + alertmanager_mock.NewAlertmanagerMock(t), + false, + } remote := remote_alertmanager_mock.NewRemoteAlertmanagerMock(t) if mode == modeRemoteSecondary { @@ -711,11 +727,15 @@ func genTestAlertmanagersWithSyncInterval(t *testing.T, mode int, syncInterval t 1: {}, } cfg := RemoteSecondaryConfig{ - Logger: log.NewNopLogger(), - SyncInterval: syncInterval, - OrgID: 1, - Store: notifier.NewFakeConfigStore(t, configs), + Logger: log.NewNopLogger(), + OrgID: 1, + Store: notifier.NewFakeConfigStore(t, configs), } + + for _, opt := range options { + cfg = opt(cfg) + } + forked, err := NewRemoteSecondaryForkedAlertmanager(cfg, internal, remote) require.NoError(t, err) return internal, remote, forked