Alerting: move fake stores to store package (#45428)

* make fake storage public
* move fake storages to store package
pull/45364/head^2
Yuriy Tseretyan 3 years ago committed by GitHub
parent 52ae586452
commit 02f8e99ca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 100
      pkg/services/ngalert/schedule/schedule_unit_test.go
  2. 439
      pkg/services/ngalert/schedule/testing.go
  3. 6
      pkg/services/ngalert/state/manager_test.go
  4. 444
      pkg/services/ngalert/store/testing.go
  5. 13
      pkg/tests/api/alerting/api_admin_configuration_test.go

@ -39,17 +39,17 @@ import (
)
func TestSendingToExternalAlertmanager(t *testing.T) {
fakeAM := NewFakeExternalAlertmanager(t)
fakeAM := store.NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeRuleStore := newFakeRuleStore(t)
fakeInstanceStore := &FakeInstanceStore{}
fakeAdminConfigStore := newFakeAdminConfigStore(t)
fakeRuleStore := store.NewFakeRuleStore(t)
fakeInstanceStore := &store.FakeInstanceStore{}
fakeAdminConfigStore := store.NewFakeAdminConfigStore(t)
// create alert rule with one second interval
alertRule := CreateTestAlertRule(t, fakeRuleStore, 1, 1, eval.Alerting)
// First, let's create an admin configuration that holds an alertmanager.
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.server.URL}, SendAlertsTo: models.AllAlertmanagers}
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.Server.URL}, SendAlertsTo: models.AllAlertmanagers}
cmd := store.UpdateAdminConfigurationCmd{AdminConfiguration: adminConfig}
require.NoError(t, fakeAdminConfigStore.UpdateAdminConfiguration(cmd))
@ -104,14 +104,14 @@ func TestSendingToExternalAlertmanager(t *testing.T) {
}
func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
fakeAM := NewFakeExternalAlertmanager(t)
fakeAM := store.NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeRuleStore := newFakeRuleStore(t)
fakeInstanceStore := &FakeInstanceStore{}
fakeAdminConfigStore := newFakeAdminConfigStore(t)
fakeRuleStore := store.NewFakeRuleStore(t)
fakeInstanceStore := &store.FakeInstanceStore{}
fakeAdminConfigStore := store.NewFakeAdminConfigStore(t)
// First, let's create an admin configuration that holds an alertmanager.
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.server.URL}}
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.Server.URL}}
cmd := store.UpdateAdminConfigurationCmd{AdminConfiguration: adminConfig}
require.NoError(t, fakeAdminConfigStore.UpdateAdminConfiguration(cmd))
@ -140,7 +140,7 @@ func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
}()
// 1. Now, let's assume a new org comes along.
adminConfig2 := &models.AdminConfiguration{OrgID: 2, Alertmanagers: []string{fakeAM.server.URL}}
adminConfig2 := &models.AdminConfiguration{OrgID: 2, Alertmanagers: []string{fakeAM.Server.URL}}
cmd = store.UpdateAdminConfigurationCmd{AdminConfiguration: adminConfig2}
require.NoError(t, fakeAdminConfigStore.UpdateAdminConfiguration(cmd))
@ -164,8 +164,8 @@ func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
// However, sometimes this does not happen.
// Create two alert rules with one second interval.
// alertRuleOrgOne := CreateTestAlertRule(t, fakeRuleStore, 1, 1)
// alertRuleOrgTwo := CreateTestAlertRule(t, fakeRuleStore, 1, 2)
// alertRuleOrgOne := CreateTestAlertRule(t, FakeRuleStore, 1, 1)
// alertRuleOrgTwo := CreateTestAlertRule(t, FakeRuleStore, 1, 2)
// Eventually, our Alertmanager should have received at least two alerts.
// var count int
// require.Eventuallyf(t, func() bool {
@ -174,8 +174,8 @@ func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
// }, 20*time.Second, 200*time.Millisecond, "Alertmanager never received an '%s' from org 1 or '%s' from org 2, the alert count was: %d", alertRuleOrgOne.Title, alertRuleOrgTwo.Title, count)
// 2. Next, let's modify the configuration of an organization by adding an extra alertmanager.
fakeAM2 := NewFakeExternalAlertmanager(t)
adminConfig2 = &models.AdminConfiguration{OrgID: 2, Alertmanagers: []string{fakeAM.server.URL, fakeAM2.server.URL}}
fakeAM2 := store.NewFakeExternalAlertmanager(t)
adminConfig2 = &models.AdminConfiguration{OrgID: 2, Alertmanagers: []string{fakeAM.Server.URL, fakeAM2.Server.URL}}
cmd = store.UpdateAdminConfigurationCmd{AdminConfiguration: adminConfig2}
require.NoError(t, fakeAdminConfigStore.UpdateAdminConfiguration(cmd))
@ -245,18 +245,18 @@ func TestSendingToExternalAlertmanager_WithMultipleOrgs(t *testing.T) {
}
func TestChangingAlertmanagersChoice(t *testing.T) {
fakeAM := NewFakeExternalAlertmanager(t)
fakeAM := store.NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
fakeRuleStore := newFakeRuleStore(t)
fakeInstanceStore := &FakeInstanceStore{}
fakeAdminConfigStore := newFakeAdminConfigStore(t)
fakeRuleStore := store.NewFakeRuleStore(t)
fakeInstanceStore := &store.FakeInstanceStore{}
fakeAdminConfigStore := store.NewFakeAdminConfigStore(t)
// create alert rule with one second interval and an Alertmanagers choice.
alertRule := CreateTestAlertRule(t, fakeRuleStore, 1, 1, eval.Alerting)
// First, let's create an admin configuration that holds an alertmanager
// and sends alerts to both internal and external alertmanagers (default).
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.server.URL}}
adminConfig := &models.AdminConfiguration{OrgID: 1, Alertmanagers: []string{fakeAM.Server.URL}}
cmd := store.UpdateAdminConfigurationCmd{AdminConfiguration: adminConfig}
require.NoError(t, fakeAdminConfigStore.UpdateAdminConfiguration(cmd))
@ -338,10 +338,10 @@ func TestChangingAlertmanagersChoice(t *testing.T) {
func TestSchedule_ruleRoutine(t *testing.T) {
createSchedule := func(
evalAppliedChan chan time.Time,
) (*schedule, *fakeRuleStore, *FakeInstanceStore, *fakeAdminConfigStore, prometheus.Gatherer) {
ruleStore := newFakeRuleStore(t)
instanceStore := &FakeInstanceStore{}
adminConfigStore := newFakeAdminConfigStore(t)
) (*schedule, *store.FakeRuleStore, *store.FakeInstanceStore, *store.FakeAdminConfigStore, prometheus.Gatherer) {
ruleStore := store.NewFakeRuleStore(t)
instanceStore := &store.FakeInstanceStore{}
adminConfigStore := store.NewFakeAdminConfigStore(t)
registry := prometheus.NewPedanticRegistry()
sch, _ := setupScheduler(t, ruleStore, instanceStore, adminConfigStore, registry)
@ -386,7 +386,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
t.Run("it should get rule from database when run the first time", func(t *testing.T) {
queries := make([]models.GetAlertRuleByUIDQuery, 0)
for _, op := range ruleStore.recordedOps {
for _, op := range ruleStore.RecordedOps {
switch q := op.(type) {
case models.GetAlertRuleByUIDQuery:
queries = append(queries, q)
@ -419,7 +419,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
s := states[0]
var cmd *models.SaveAlertInstanceCommand
for _, op := range instanceStore.recordedOps {
for _, op := range instanceStore.RecordedOps {
switch q := op.(type) {
case models.SaveAlertInstanceCommand:
cmd = &q
@ -505,7 +505,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
// Now update the rule
newRule := *rule
newRule.Version++
ruleStore.putRule(ctx, &newRule)
ruleStore.PutRule(ctx, &newRule)
// and call with new version
expectedTime = expectedTime.Add(time.Duration(rand.Intn(10)) * time.Second)
@ -518,7 +518,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
require.Equal(t, expectedTime, actualTime)
queries := make([]models.GetAlertRuleByUIDQuery, 0)
for _, op := range ruleStore.recordedOps {
for _, op := range ruleStore.RecordedOps {
switch q := op.(type) {
case models.GetAlertRuleByUIDQuery:
queries = append(queries, q)
@ -572,7 +572,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
require.Equal(t, expectedTime, actualTime)
queries := make([]models.GetAlertRuleByUIDQuery, 0)
for _, op := range ruleStore.recordedOps {
for _, op := range ruleStore.RecordedOps {
switch q := op.(type) {
case models.GetAlertRuleByUIDQuery:
queries = append(queries, q)
@ -601,12 +601,12 @@ func TestSchedule_ruleRoutine(t *testing.T) {
// wait for command to be executed
var queries []interface{}
require.Eventuallyf(t, func() bool {
queries = ruleStore.getRecordedCommands(func(cmd interface{}) (interface{}, bool) {
queries = ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
c, ok := cmd.(models.GetAlertRuleByUIDQuery)
return c, ok
})
return len(queries) == 1
}, 5*time.Second, 100*time.Millisecond, "Expected command a single %T to be recorded. All recordings: %#v", models.GetAlertRuleByUIDQuery{}, ruleStore.recordedOps)
}, 5*time.Second, 100*time.Millisecond, "Expected command a single %T to be recorded. All recordings: %#v", models.GetAlertRuleByUIDQuery{}, ruleStore.RecordedOps)
m := queries[0].(models.GetAlertRuleByUIDQuery)
require.Equal(t, rule.UID, m.UID)
@ -619,7 +619,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
}
waitForTimeChannel(t, evalAppliedChan)
queries = ruleStore.getRecordedCommands(func(cmd interface{}) (interface{}, bool) {
queries = ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
c, ok := cmd.(models.GetAlertRuleByUIDQuery)
return c, ok
})
@ -641,7 +641,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
_ = sch.ruleRoutine(ctx, rule.GetKey(), make(chan *evalContext), updateChan)
}()
ruleStore.hook = func(cmd interface{}) error {
ruleStore.Hook = func(cmd interface{}) error {
if _, ok := cmd.(models.GetAlertRuleByUIDQuery); !ok {
return nil
}
@ -651,24 +651,24 @@ func TestSchedule_ruleRoutine(t *testing.T) {
var queries []interface{}
require.Eventuallyf(t, func() bool {
queries = ruleStore.getRecordedCommands(func(cmd interface{}) (interface{}, bool) {
queries = ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
c, ok := cmd.(models.GetAlertRuleByUIDQuery)
return c, ok
})
return int64(len(queries)) == sch.maxAttempts
}, 5*time.Second, 100*time.Millisecond, "Expected exactly two request of %T. All recordings: %#v", models.GetAlertRuleByUIDQuery{}, ruleStore.recordedOps)
}, 5*time.Second, 100*time.Millisecond, "Expected exactly two request of %T. All recordings: %#v", models.GetAlertRuleByUIDQuery{}, ruleStore.RecordedOps)
})
})
t.Run("when rule version is updated", func(t *testing.T) {
t.Run("should clear the state and expire firing alerts", func(t *testing.T) {
fakeAM := NewFakeExternalAlertmanager(t)
fakeAM := store.NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
orgID := rand.Int63()
s, err := sender.New(nil)
require.NoError(t, err)
adminConfig := &models.AdminConfiguration{OrgID: orgID, Alertmanagers: []string{fakeAM.server.URL}}
adminConfig := &models.AdminConfiguration{OrgID: orgID, Alertmanagers: []string{fakeAM.Server.URL}}
err = s.ApplyConfig(adminConfig)
require.NoError(t, err)
s.Run()
@ -717,7 +717,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(1)
ruleStore.hook = func(cmd interface{}) error {
ruleStore.Hook = func(cmd interface{}) error {
_, ok := cmd.(models.GetAlertRuleByUIDQuery)
if ok {
wg.Done() // add synchronization.
@ -730,7 +730,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
wg.Wait()
newRule := rule
newRule.Version++
ruleStore.putRule(ctx, &newRule)
ruleStore.PutRule(ctx, &newRule)
wg.Add(1)
updateChan <- struct{}{}
wg.Wait()
@ -745,7 +745,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
return count == len(expectedToBeSent.PostableAlerts)
}, 20*time.Second, 200*time.Millisecond, "Alertmanager was expected to receive %d alerts, but received only %d", len(expectedToBeSent.PostableAlerts), count)
for _, alert := range fakeAM.alerts {
for _, alert := range fakeAM.Alerts() {
require.Equalf(t, sch.clock.Now().UTC(), time.Time(alert.EndsAt).UTC(), "Alert received by Alertmanager should be expired as of now")
}
})
@ -768,13 +768,13 @@ func TestSchedule_ruleRoutine(t *testing.T) {
t.Skip()
})
t.Run("it should send to external alertmanager if configured for organization", func(t *testing.T) {
fakeAM := NewFakeExternalAlertmanager(t)
fakeAM := store.NewFakeExternalAlertmanager(t)
defer fakeAM.Close()
orgID := rand.Int63()
s, err := sender.New(nil)
require.NoError(t, err)
adminConfig := &models.AdminConfiguration{OrgID: orgID, Alertmanagers: []string{fakeAM.server.URL}}
adminConfig := &models.AdminConfiguration{OrgID: orgID, Alertmanagers: []string{fakeAM.Server.URL}}
err = s.ApplyConfig(adminConfig)
require.NoError(t, err)
s.Run()
@ -997,9 +997,9 @@ func generateRuleKey() models.AlertRuleKey {
func setupSchedulerWithFakeStores(t *testing.T) *schedule {
t.Helper()
ruleStore := newFakeRuleStore(t)
instanceStore := &FakeInstanceStore{}
adminConfigStore := newFakeAdminConfigStore(t)
ruleStore := store.NewFakeRuleStore(t)
instanceStore := &store.FakeInstanceStore{}
adminConfigStore := store.NewFakeAdminConfigStore(t)
sch, _ := setupScheduler(t, ruleStore, instanceStore, adminConfigStore, nil)
return sch
}
@ -1007,7 +1007,7 @@ func setupSchedulerWithFakeStores(t *testing.T) *schedule {
func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, acs store.AdminConfigurationStore, registry *prometheus.Registry) (*schedule, *clock.Mock) {
t.Helper()
fakeAnnoRepo := NewFakeAnnotationsRepo()
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
mockedClock := clock.NewMock()
logger := log.New("ngalert schedule test")
@ -1042,15 +1042,15 @@ func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, ac
}
// createTestAlertRule creates a dummy alert definition to be used by the tests.
func CreateTestAlertRule(t *testing.T, dbstore *fakeRuleStore, intervalSeconds int64, orgID int64, evalResult eval.State) *models.AlertRule {
func CreateTestAlertRule(t *testing.T, dbstore *store.FakeRuleStore, intervalSeconds int64, orgID int64, evalResult eval.State) *models.AlertRule {
ctx := context.Background()
t.Helper()
records := make([]interface{}, 0, len(dbstore.recordedOps))
copy(records, dbstore.recordedOps)
records := make([]interface{}, 0, len(dbstore.RecordedOps))
copy(records, dbstore.RecordedOps)
defer func() {
// erase queries that were made by the testing suite
dbstore.recordedOps = records
dbstore.RecordedOps = records
}()
d := rand.Intn(1000)
ruleGroup := fmt.Sprintf("ruleGroup-%d", d)

@ -1,26 +1,8 @@
package schedule
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/grafana/grafana/pkg/services/annotations"
models2 "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// waitForTimeChannel blocks the execution until either the channel ch has some data or a timeout of 10 second expires.
@ -49,424 +31,3 @@ func waitForErrChannel(t *testing.T, ch chan error) error {
return nil
}
}
func newFakeRuleStore(t *testing.T) *fakeRuleStore {
return &fakeRuleStore{
t: t,
rules: map[int64]map[string]map[string][]*models.AlertRule{},
hook: func(interface{}) error {
return nil
},
}
}
// FakeRuleStore mocks the RuleStore of the scheduler.
type fakeRuleStore struct {
t *testing.T
mtx sync.Mutex
rules map[int64]map[string]map[string][]*models.AlertRule
hook func(cmd interface{}) error // use hook if you need to intercept some query and return an error
recordedOps []interface{}
}
// putRule puts the rule in the rules map. If there are existing rule in the same namespace, they will be overwritten
func (f *fakeRuleStore) putRule(_ context.Context, r *models.AlertRule) {
f.mtx.Lock()
defer f.mtx.Unlock()
f.rules[r.OrgID][r.RuleGroup][r.NamespaceUID] = []*models.AlertRule{
r,
}
}
// getRecordedCommands filters recorded commands using predicate function. Returns the subset of the recorded commands that meet the predicate
func (f *fakeRuleStore) getRecordedCommands(predicate func(cmd interface{}) (interface{}, bool)) []interface{} {
f.mtx.Lock()
defer f.mtx.Unlock()
result := make([]interface{}, 0, len(f.recordedOps))
for _, op := range f.recordedOps {
cmd, ok := predicate(op)
if !ok {
continue
}
result = append(result, cmd)
}
return result
}
func (f *fakeRuleStore) DeleteAlertRuleByUID(_ context.Context, _ int64, _ string) error { return nil }
func (f *fakeRuleStore) DeleteNamespaceAlertRules(_ context.Context, _ int64, _ string) ([]string, error) {
return []string{}, nil
}
func (f *fakeRuleStore) DeleteRuleGroupAlertRules(_ context.Context, _ int64, _ string, _ string) ([]string, error) {
return []string{}, nil
}
func (f *fakeRuleStore) DeleteAlertInstancesByRuleUID(_ context.Context, _ int64, _ string) error {
return nil
}
func (f *fakeRuleStore) GetAlertRuleByUID(_ context.Context, q *models.GetAlertRuleByUIDQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
if err := f.hook(*q); err != nil {
return err
}
rgs, ok := f.rules[q.OrgID]
if !ok {
return nil
}
for _, rg := range rgs {
for _, rules := range rg {
for _, r := range rules {
if r.UID == q.UID {
q.Result = r
break
}
}
}
}
return nil
}
// For now, we're not implementing namespace filtering.
func (f *fakeRuleStore) GetAlertRulesForScheduling(_ context.Context, q *models.ListAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
if err := f.hook(*q); err != nil {
return err
}
for _, rg := range f.rules {
for _, n := range rg {
for _, r := range n {
q.Result = append(q.Result, r...)
}
}
}
return nil
}
func (f *fakeRuleStore) GetOrgAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
return nil
}
func (f *fakeRuleStore) GetNamespaceAlertRules(_ context.Context, q *models.ListNamespaceAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
return nil
}
func (f *fakeRuleStore) GetRuleGroupAlertRules(_ context.Context, q *models.ListRuleGroupAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
if err := f.hook(*q); err != nil {
return err
}
rgs, ok := f.rules[q.OrgID]
if !ok {
return nil
}
rg, ok := rgs[q.RuleGroup]
if !ok {
return nil
}
if q.NamespaceUID != "" {
r, ok := rg[q.NamespaceUID]
if !ok {
return nil
}
q.Result = r
return nil
}
for _, r := range rg {
q.Result = append(q.Result, r...)
}
return nil
}
func (f *fakeRuleStore) GetNamespaces(_ context.Context, _ int64, _ *models2.SignedInUser) (map[string]*models2.Folder, error) {
return nil, nil
}
func (f *fakeRuleStore) GetNamespaceByTitle(_ context.Context, _ string, _ int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) {
return nil, nil
}
func (f *fakeRuleStore) GetOrgRuleGroups(_ context.Context, q *models.ListOrgRuleGroupsQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
if err := f.hook(*q); err != nil {
return err
}
return nil
}
func (f *fakeRuleStore) UpsertAlertRules(_ context.Context, q []store.UpsertRule) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, q)
if err := f.hook(q); err != nil {
return err
}
return nil
}
func (f *fakeRuleStore) UpdateRuleGroup(_ context.Context, cmd store.UpdateRuleGroupCmd) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, cmd)
if err := f.hook(cmd); err != nil {
return err
}
rgs, ok := f.rules[cmd.OrgID]
if !ok {
f.rules[cmd.OrgID] = map[string]map[string][]*models.AlertRule{}
}
rg, ok := rgs[cmd.RuleGroupConfig.Name]
if !ok {
f.rules[cmd.OrgID][cmd.RuleGroupConfig.Name] = map[string][]*models.AlertRule{}
}
_, ok = rg[cmd.NamespaceUID]
if !ok {
f.rules[cmd.OrgID][cmd.RuleGroupConfig.Name][cmd.NamespaceUID] = []*models.AlertRule{}
}
rules := []*models.AlertRule{}
for _, r := range cmd.RuleGroupConfig.Rules {
// TODO: Not sure why this is not being set properly, where is the code that sets this?
for i := range r.GrafanaManagedAlert.Data {
r.GrafanaManagedAlert.Data[i].DatasourceUID = "-100"
}
new := &models.AlertRule{
OrgID: cmd.OrgID,
Title: r.GrafanaManagedAlert.Title,
Condition: r.GrafanaManagedAlert.Condition,
Data: r.GrafanaManagedAlert.Data,
UID: util.GenerateShortUID(),
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
NamespaceUID: cmd.NamespaceUID,
RuleGroup: cmd.RuleGroupConfig.Name,
NoDataState: models.NoDataState(r.GrafanaManagedAlert.NoDataState),
ExecErrState: models.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
Version: 1,
}
if r.ApiRuleNode != nil {
new.For = time.Duration(r.ApiRuleNode.For)
new.Annotations = r.ApiRuleNode.Annotations
new.Labels = r.ApiRuleNode.Labels
}
if new.NoDataState == "" {
new.NoDataState = models.NoData
}
if new.ExecErrState == "" {
new.ExecErrState = models.AlertingErrState
}
err := new.PreSave(time.Now)
require.NoError(f.t, err)
rules = append(rules, new)
}
f.rules[cmd.OrgID][cmd.RuleGroupConfig.Name][cmd.NamespaceUID] = rules
return nil
}
type FakeInstanceStore struct {
mtx sync.Mutex
recordedOps []interface{}
}
func (f *FakeInstanceStore) GetAlertInstance(_ context.Context, q *models.GetAlertInstanceQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
return nil
}
func (f *FakeInstanceStore) ListAlertInstances(_ context.Context, q *models.ListAlertInstancesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
return nil
}
func (f *FakeInstanceStore) SaveAlertInstance(_ context.Context, q *models.SaveAlertInstanceCommand) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.recordedOps = append(f.recordedOps, *q)
return nil
}
func (f *FakeInstanceStore) FetchOrgIds(_ context.Context) ([]int64, error) { return []int64{}, nil }
func (f *FakeInstanceStore) DeleteAlertInstance(_ context.Context, _ int64, _, _ string) error {
return nil
}
func newFakeAdminConfigStore(t *testing.T) *fakeAdminConfigStore {
t.Helper()
return &fakeAdminConfigStore{configs: map[int64]*models.AdminConfiguration{}}
}
type fakeAdminConfigStore struct {
mtx sync.Mutex
configs map[int64]*models.AdminConfiguration
}
func (f *fakeAdminConfigStore) GetAdminConfiguration(orgID int64) (*models.AdminConfiguration, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
return f.configs[orgID], nil
}
func (f *fakeAdminConfigStore) GetAdminConfigurations() ([]*models.AdminConfiguration, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
acs := make([]*models.AdminConfiguration, 0, len(f.configs))
for _, ac := range f.configs {
acs = append(acs, ac)
}
return acs, nil
}
func (f *fakeAdminConfigStore) DeleteAdminConfiguration(orgID int64) error {
f.mtx.Lock()
defer f.mtx.Unlock()
delete(f.configs, orgID)
return nil
}
func (f *fakeAdminConfigStore) UpdateAdminConfiguration(cmd store.UpdateAdminConfigurationCmd) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.configs[cmd.AdminConfiguration.OrgID] = cmd.AdminConfiguration
return nil
}
type FakeExternalAlertmanager struct {
t *testing.T
mtx sync.Mutex
alerts amv2.PostableAlerts
server *httptest.Server
}
func NewFakeExternalAlertmanager(t *testing.T) *FakeExternalAlertmanager {
t.Helper()
am := &FakeExternalAlertmanager{
t: t,
alerts: amv2.PostableAlerts{},
}
am.server = httptest.NewServer(http.HandlerFunc(am.Handler()))
return am
}
func (am *FakeExternalAlertmanager) URL() string {
return am.server.URL
}
func (am *FakeExternalAlertmanager) AlertNamesCompare(expected []string) bool {
n := []string{}
alerts := am.Alerts()
if len(expected) != len(alerts) {
return false
}
for _, a := range am.Alerts() {
for k, v := range a.Alert.Labels {
if k == model.AlertNameLabel {
n = append(n, v)
}
}
}
return assert.ObjectsAreEqual(expected, n)
}
func (am *FakeExternalAlertmanager) AlertsCount() int {
am.mtx.Lock()
defer am.mtx.Unlock()
return len(am.alerts)
}
func (am *FakeExternalAlertmanager) Alerts() amv2.PostableAlerts {
am.mtx.Lock()
defer am.mtx.Unlock()
return am.alerts
}
func (am *FakeExternalAlertmanager) Handler() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
require.NoError(am.t, err)
a := amv2.PostableAlerts{}
require.NoError(am.t, json.Unmarshal(b, &a))
am.mtx.Lock()
am.alerts = append(am.alerts, a...)
am.mtx.Unlock()
}
}
func (am *FakeExternalAlertmanager) Close() {
am.server.Close()
}
type FakeAnnotationsRepo struct {
mtx sync.Mutex
items []*annotations.Item
}
func NewFakeAnnotationsRepo() *FakeAnnotationsRepo {
return &FakeAnnotationsRepo{
items: make([]*annotations.Item, 0),
}
}
func (repo *FakeAnnotationsRepo) Len() int {
repo.mtx.Lock()
defer repo.mtx.Unlock()
return len(repo.items)
}
func (repo *FakeAnnotationsRepo) Delete(params *annotations.DeleteParams) error {
return nil
}
func (repo *FakeAnnotationsRepo) Save(item *annotations.Item) error {
repo.mtx.Lock()
defer repo.mtx.Unlock()
repo.items = append(repo.items, item)
return nil
}
func (repo *FakeAnnotationsRepo) Update(item *annotations.Item) error {
return nil
}
func (repo *FakeAnnotationsRepo) Find(query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
annotations := []*annotations.ItemDTO{{Id: 1}}
return annotations, nil
}
func (repo *FakeAnnotationsRepo) FindTags(query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
result := annotations.FindTagsResult{
Tags: []*annotations.TagsDTO{},
}
return result, nil
}

@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana-plugin-sdk-go/data"
@ -17,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/services/ngalert/tests"
@ -1482,9 +1482,9 @@ func TestProcessEvalResults(t *testing.T) {
for _, tc := range testCases {
ss := mockstore.NewSQLStoreMock()
st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, &schedule.FakeInstanceStore{}, ss)
st := state.NewManager(log.New("test_state_manager"), testMetrics.GetStateMetrics(), nil, nil, &store.FakeInstanceStore{}, ss)
t.Run(tc.desc, func(t *testing.T) {
fakeAnnoRepo := schedule.NewFakeAnnotationsRepo()
fakeAnnoRepo := store.NewFakeAnnotationsRepo()
annotations.SetRepository(fakeAnnoRepo)
for _, res := range tc.evalResults {

@ -0,0 +1,444 @@
package store
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/grafana/grafana/pkg/services/annotations"
models2 "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/util"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func NewFakeRuleStore(t *testing.T) *FakeRuleStore {
return &FakeRuleStore{
t: t,
Rules: map[int64]map[string]map[string][]*models.AlertRule{},
Hook: func(interface{}) error {
return nil
},
}
}
// FakeRuleStore mocks the RuleStore of the scheduler.
type FakeRuleStore struct {
t *testing.T
mtx sync.Mutex
Rules map[int64]map[string]map[string][]*models.AlertRule
Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error
RecordedOps []interface{}
}
// PutRule puts the rule in the Rules map. If there are existing rule in the same namespace, they will be overwritten
func (f *FakeRuleStore) PutRule(_ context.Context, r *models.AlertRule) {
f.mtx.Lock()
defer f.mtx.Unlock()
f.Rules[r.OrgID][r.RuleGroup][r.NamespaceUID] = []*models.AlertRule{
r,
}
}
// GetRecordedCommands filters recorded commands using predicate function. Returns the subset of the recorded commands that meet the predicate
func (f *FakeRuleStore) GetRecordedCommands(predicate func(cmd interface{}) (interface{}, bool)) []interface{} {
f.mtx.Lock()
defer f.mtx.Unlock()
result := make([]interface{}, 0, len(f.RecordedOps))
for _, op := range f.RecordedOps {
cmd, ok := predicate(op)
if !ok {
continue
}
result = append(result, cmd)
}
return result
}
func (f *FakeRuleStore) DeleteAlertRuleByUID(_ context.Context, _ int64, _ string) error { return nil }
func (f *FakeRuleStore) DeleteNamespaceAlertRules(_ context.Context, _ int64, _ string) ([]string, error) {
return []string{}, nil
}
func (f *FakeRuleStore) DeleteRuleGroupAlertRules(_ context.Context, _ int64, _ string, _ string) ([]string, error) {
return []string{}, nil
}
func (f *FakeRuleStore) DeleteAlertInstancesByRuleUID(_ context.Context, _ int64, _ string) error {
return nil
}
func (f *FakeRuleStore) GetAlertRuleByUID(_ context.Context, q *models.GetAlertRuleByUIDQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
if err := f.Hook(*q); err != nil {
return err
}
rgs, ok := f.Rules[q.OrgID]
if !ok {
return nil
}
for _, rg := range rgs {
for _, rules := range rg {
for _, r := range rules {
if r.UID == q.UID {
q.Result = r
break
}
}
}
}
return nil
}
// For now, we're not implementing namespace filtering.
func (f *FakeRuleStore) GetAlertRulesForScheduling(_ context.Context, q *models.ListAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
if err := f.Hook(*q); err != nil {
return err
}
for _, rg := range f.Rules {
for _, n := range rg {
for _, r := range n {
q.Result = append(q.Result, r...)
}
}
}
return nil
}
func (f *FakeRuleStore) GetOrgAlertRules(_ context.Context, q *models.ListAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
return nil
}
func (f *FakeRuleStore) GetNamespaceAlertRules(_ context.Context, q *models.ListNamespaceAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
return nil
}
func (f *FakeRuleStore) GetRuleGroupAlertRules(_ context.Context, q *models.ListRuleGroupAlertRulesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
if err := f.Hook(*q); err != nil {
return err
}
rgs, ok := f.Rules[q.OrgID]
if !ok {
return nil
}
rg, ok := rgs[q.RuleGroup]
if !ok {
return nil
}
if q.NamespaceUID != "" {
r, ok := rg[q.NamespaceUID]
if !ok {
return nil
}
q.Result = r
return nil
}
for _, r := range rg {
q.Result = append(q.Result, r...)
}
return nil
}
func (f *FakeRuleStore) GetNamespaces(_ context.Context, _ int64, _ *models2.SignedInUser) (map[string]*models2.Folder, error) {
return nil, nil
}
func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, _ string, _ int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) {
return nil, nil
}
func (f *FakeRuleStore) GetOrgRuleGroups(_ context.Context, q *models.ListOrgRuleGroupsQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
if err := f.Hook(*q); err != nil {
return err
}
return nil
}
func (f *FakeRuleStore) UpsertAlertRules(_ context.Context, q []UpsertRule) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, q)
if err := f.Hook(q); err != nil {
return err
}
return nil
}
func (f *FakeRuleStore) UpdateRuleGroup(_ context.Context, cmd UpdateRuleGroupCmd) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, cmd)
if err := f.Hook(cmd); err != nil {
return err
}
rgs, ok := f.Rules[cmd.OrgID]
if !ok {
f.Rules[cmd.OrgID] = map[string]map[string][]*models.AlertRule{}
}
rg, ok := rgs[cmd.RuleGroupConfig.Name]
if !ok {
f.Rules[cmd.OrgID][cmd.RuleGroupConfig.Name] = map[string][]*models.AlertRule{}
}
_, ok = rg[cmd.NamespaceUID]
if !ok {
f.Rules[cmd.OrgID][cmd.RuleGroupConfig.Name][cmd.NamespaceUID] = []*models.AlertRule{}
}
rules := []*models.AlertRule{}
for _, r := range cmd.RuleGroupConfig.Rules {
// TODO: Not sure why this is not being set properly, where is the code that sets this?
for i := range r.GrafanaManagedAlert.Data {
r.GrafanaManagedAlert.Data[i].DatasourceUID = "-100"
}
new := &models.AlertRule{
OrgID: cmd.OrgID,
Title: r.GrafanaManagedAlert.Title,
Condition: r.GrafanaManagedAlert.Condition,
Data: r.GrafanaManagedAlert.Data,
UID: util.GenerateShortUID(),
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
NamespaceUID: cmd.NamespaceUID,
RuleGroup: cmd.RuleGroupConfig.Name,
NoDataState: models.NoDataState(r.GrafanaManagedAlert.NoDataState),
ExecErrState: models.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
Version: 1,
}
if r.ApiRuleNode != nil {
new.For = time.Duration(r.ApiRuleNode.For)
new.Annotations = r.ApiRuleNode.Annotations
new.Labels = r.ApiRuleNode.Labels
}
if new.NoDataState == "" {
new.NoDataState = models.NoData
}
if new.ExecErrState == "" {
new.ExecErrState = models.AlertingErrState
}
err := new.PreSave(time.Now)
require.NoError(f.t, err)
rules = append(rules, new)
}
f.Rules[cmd.OrgID][cmd.RuleGroupConfig.Name][cmd.NamespaceUID] = rules
return nil
}
type FakeInstanceStore struct {
mtx sync.Mutex
RecordedOps []interface{}
}
func (f *FakeInstanceStore) GetAlertInstance(_ context.Context, q *models.GetAlertInstanceQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
return nil
}
func (f *FakeInstanceStore) ListAlertInstances(_ context.Context, q *models.ListAlertInstancesQuery) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
return nil
}
func (f *FakeInstanceStore) SaveAlertInstance(_ context.Context, q *models.SaveAlertInstanceCommand) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, *q)
return nil
}
func (f *FakeInstanceStore) FetchOrgIds(_ context.Context) ([]int64, error) { return []int64{}, nil }
func (f *FakeInstanceStore) DeleteAlertInstance(_ context.Context, _ int64, _, _ string) error {
return nil
}
func NewFakeAdminConfigStore(t *testing.T) *FakeAdminConfigStore {
t.Helper()
return &FakeAdminConfigStore{Configs: map[int64]*models.AdminConfiguration{}}
}
type FakeAdminConfigStore struct {
mtx sync.Mutex
Configs map[int64]*models.AdminConfiguration
}
func (f *FakeAdminConfigStore) GetAdminConfiguration(orgID int64) (*models.AdminConfiguration, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
return f.Configs[orgID], nil
}
func (f *FakeAdminConfigStore) GetAdminConfigurations() ([]*models.AdminConfiguration, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
acs := make([]*models.AdminConfiguration, 0, len(f.Configs))
for _, ac := range f.Configs {
acs = append(acs, ac)
}
return acs, nil
}
func (f *FakeAdminConfigStore) DeleteAdminConfiguration(orgID int64) error {
f.mtx.Lock()
defer f.mtx.Unlock()
delete(f.Configs, orgID)
return nil
}
func (f *FakeAdminConfigStore) UpdateAdminConfiguration(cmd UpdateAdminConfigurationCmd) error {
f.mtx.Lock()
defer f.mtx.Unlock()
f.Configs[cmd.AdminConfiguration.OrgID] = cmd.AdminConfiguration
return nil
}
type FakeExternalAlertmanager struct {
t *testing.T
mtx sync.Mutex
alerts amv2.PostableAlerts
Server *httptest.Server
}
func NewFakeExternalAlertmanager(t *testing.T) *FakeExternalAlertmanager {
t.Helper()
am := &FakeExternalAlertmanager{
t: t,
alerts: amv2.PostableAlerts{},
}
am.Server = httptest.NewServer(http.HandlerFunc(am.Handler()))
return am
}
func (am *FakeExternalAlertmanager) URL() string {
return am.Server.URL
}
func (am *FakeExternalAlertmanager) AlertNamesCompare(expected []string) bool {
n := []string{}
alerts := am.Alerts()
if len(expected) != len(alerts) {
return false
}
for _, a := range am.Alerts() {
for k, v := range a.Alert.Labels {
if k == model.AlertNameLabel {
n = append(n, v)
}
}
}
return assert.ObjectsAreEqual(expected, n)
}
func (am *FakeExternalAlertmanager) AlertsCount() int {
am.mtx.Lock()
defer am.mtx.Unlock()
return len(am.alerts)
}
func (am *FakeExternalAlertmanager) Alerts() amv2.PostableAlerts {
am.mtx.Lock()
defer am.mtx.Unlock()
return am.alerts
}
func (am *FakeExternalAlertmanager) Handler() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
require.NoError(am.t, err)
a := amv2.PostableAlerts{}
require.NoError(am.t, json.Unmarshal(b, &a))
am.mtx.Lock()
am.alerts = append(am.alerts, a...)
am.mtx.Unlock()
}
}
func (am *FakeExternalAlertmanager) Close() {
am.Server.Close()
}
type FakeAnnotationsRepo struct {
mtx sync.Mutex
Items []*annotations.Item
}
func NewFakeAnnotationsRepo() *FakeAnnotationsRepo {
return &FakeAnnotationsRepo{
Items: make([]*annotations.Item, 0),
}
}
func (repo *FakeAnnotationsRepo) Len() int {
repo.mtx.Lock()
defer repo.mtx.Unlock()
return len(repo.Items)
}
func (repo *FakeAnnotationsRepo) Delete(params *annotations.DeleteParams) error {
return nil
}
func (repo *FakeAnnotationsRepo) Save(item *annotations.Item) error {
repo.mtx.Lock()
defer repo.mtx.Unlock()
repo.Items = append(repo.Items, item)
return nil
}
func (repo *FakeAnnotationsRepo) Update(item *annotations.Item) error {
return nil
}
func (repo *FakeAnnotationsRepo) Find(query *annotations.ItemQuery) ([]*annotations.ItemDTO, error) {
annotations := []*annotations.ItemDTO{{Id: 1}}
return annotations, nil
}
func (repo *FakeAnnotationsRepo) FindTags(query *annotations.TagsQuery) (annotations.FindTagsResult, error) {
result := annotations.FindTagsResult{
Tags: []*annotations.TagsDTO{},
}
return result, nil
}

@ -9,15 +9,16 @@ import (
"testing"
"time"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
@ -58,9 +59,9 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) {
})
// Create a couple of "fake" Alertmanagers
fakeAM1 := schedule.NewFakeExternalAlertmanager(t)
fakeAM2 := schedule.NewFakeExternalAlertmanager(t)
fakeAM3 := schedule.NewFakeExternalAlertmanager(t)
fakeAM1 := store.NewFakeExternalAlertmanager(t)
fakeAM2 := store.NewFakeExternalAlertmanager(t)
fakeAM3 := store.NewFakeExternalAlertmanager(t)
// Now, let's test the configuration API.
{

Loading…
Cancel
Save