mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add context.Context to RuleStore (#45004)
Alerting: Add context.Context to RuleStore
This commit is contained in:
parent
31ba5bfea0
commit
a9399ab3cd
@ -101,7 +101,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
}
|
||||
if err := srv.store.GetOrgRuleGroups(&ruleGroupQuery); err != nil {
|
||||
if err := srv.store.GetOrgRuleGroups(c.Req.Context(), &ruleGroupQuery); err != nil {
|
||||
ruleResponse.DiscoveryBase.Status = "error"
|
||||
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rule groups: %s", err.Error())
|
||||
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
|
||||
@ -113,7 +113,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
}
|
||||
if err := srv.store.GetOrgAlertRules(&alertRuleQuery); err != nil {
|
||||
if err := srv.store.GetOrgAlertRules(c.Req.Context(), &alertRuleQuery); err != nil {
|
||||
ruleResponse.DiscoveryBase.Status = "error"
|
||||
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rules: %s", err.Error())
|
||||
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
|
||||
|
@ -38,7 +38,7 @@ func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) respon
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
uids, err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespace.Uid)
|
||||
uids, err := srv.store.DeleteNamespaceAlertRules(c.Req.Context(), c.SignedInUser.OrgId, namespace.Uid)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to delete namespace alert rules")
|
||||
}
|
||||
@ -60,7 +60,7 @@ func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Re
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
ruleGroup := web.Params(c.Req)[":Groupname"]
|
||||
uids, err := srv.store.DeleteRuleGroupAlertRules(c.SignedInUser.OrgId, namespace.Uid, ruleGroup)
|
||||
uids, err := srv.store.DeleteRuleGroupAlertRules(c.Req.Context(), c.SignedInUser.OrgId, namespace.Uid, ruleGroup)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ngmodels.ErrRuleGroupNamespaceNotFound) {
|
||||
@ -90,7 +90,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespace.Uid,
|
||||
}
|
||||
if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
|
||||
if err := srv.store.GetNamespaceAlertRules(c.Req.Context(), &q); err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to update rule group")
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
|
||||
NamespaceUID: namespace.Uid,
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
|
||||
if err := srv.store.GetRuleGroupAlertRules(c.Req.Context(), &q); err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules")
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
PanelID: panelID,
|
||||
}
|
||||
|
||||
if err := srv.store.GetOrgAlertRules(&q); err != nil {
|
||||
if err := srv.store.GetOrgAlertRules(c.Req.Context(), &q); err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
||||
}
|
||||
}
|
||||
|
||||
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||
if err := srv.store.UpdateRuleGroup(c.Req.Context(), store.UpdateRuleGroupCmd{
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespace.Uid,
|
||||
RuleGroupConfig: ruleGroupConfig,
|
||||
|
@ -169,7 +169,7 @@ func (ng *AlertNG) init() error {
|
||||
// Run starts the scheduler and Alertmanager.
|
||||
func (ng *AlertNG) Run(ctx context.Context) error {
|
||||
ng.Log.Debug("ngalert starting")
|
||||
ng.stateManager.Warm()
|
||||
ng.stateManager.Warm(ctx)
|
||||
|
||||
children, subCtx := errgroup.WithContext(ctx)
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
func (sch *schedule) getAlertRules(disabledOrgs []int64) []*models.AlertRule {
|
||||
func (sch *schedule) getAlertRules(ctx context.Context, disabledOrgs []int64) []*models.AlertRule {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
sch.metrics.GetAlertRulesDuration.Observe(time.Since(start).Seconds())
|
||||
@ -15,7 +16,7 @@ func (sch *schedule) getAlertRules(disabledOrgs []int64) []*models.AlertRule {
|
||||
q := models.ListAlertRulesQuery{
|
||||
ExcludeOrgs: disabledOrgs,
|
||||
}
|
||||
err := sch.ruleStore.GetAlertRulesForScheduling(&q)
|
||||
err := sch.ruleStore.GetAlertRulesForScheduling(ctx, &q)
|
||||
if err != nil {
|
||||
sch.log.Error("failed to fetch alert definitions", "err", err)
|
||||
return nil
|
||||
|
@ -377,7 +377,7 @@ func (sch *schedule) schedulePeriodic(ctx context.Context) error {
|
||||
disabledOrgs = append(disabledOrgs, disabledOrg)
|
||||
}
|
||||
|
||||
alertRules := sch.getAlertRules(disabledOrgs)
|
||||
alertRules := sch.getAlertRules(ctx, disabledOrgs)
|
||||
sch.log.Debug("alert rules fetched", "count", len(alertRules), "disabled_orgs", disabledOrgs)
|
||||
|
||||
// registeredDefinitions is a map used for finding deleted alert rules
|
||||
@ -528,9 +528,9 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key models.AlertRul
|
||||
notify(expiredAlerts, logger)
|
||||
}
|
||||
|
||||
updateRule := func(oldRule *models.AlertRule) (*models.AlertRule, error) {
|
||||
updateRule := func(ctx context.Context, oldRule *models.AlertRule) (*models.AlertRule, error) {
|
||||
q := models.GetAlertRuleByUIDQuery{OrgID: key.OrgID, UID: key.UID}
|
||||
err := sch.ruleStore.GetAlertRuleByUID(&q)
|
||||
err := sch.ruleStore.GetAlertRuleByUID(ctx, &q)
|
||||
if err != nil {
|
||||
logger.Error("failed to fetch alert rule", "err", err)
|
||||
return nil, err
|
||||
@ -591,7 +591,7 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key models.AlertRul
|
||||
case <-updateCh:
|
||||
logger.Info("fetching new version of the rule")
|
||||
err := retryIfError(func(attempt int64) error {
|
||||
newRule, err := updateRule(currentRule)
|
||||
newRule, err := updateRule(grafanaCtx, currentRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -622,7 +622,7 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key models.AlertRul
|
||||
err := retryIfError(func(attempt int64) error {
|
||||
// fetch latest alert rule version
|
||||
if currentRule == nil || currentRule.Version < ctx.version {
|
||||
newRule, err := updateRule(currentRule)
|
||||
newRule, err := updateRule(grafanaCtx, currentRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -36,10 +36,11 @@ type evalAppliedInfo struct {
|
||||
func TestWarmStateCache(t *testing.T) {
|
||||
evaluationTime, err := time.Parse("2006-01-02", "2021-03-25")
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
ng, dbstore := tests.SetupTestEnv(t, 1)
|
||||
|
||||
const mainOrgID int64 = 1
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600, mainOrgID)
|
||||
rule := tests.CreateTestAlertRule(t, ctx, dbstore, 600, mainOrgID)
|
||||
|
||||
expectedEntries := []*state.State{
|
||||
{
|
||||
@ -105,7 +106,7 @@ func TestWarmStateCache(t *testing.T) {
|
||||
AdminConfigPollInterval: 10 * time.Minute, // do not poll in unit tests.
|
||||
}
|
||||
st := state.NewManager(schedCfg.Logger, testMetrics.GetStateMetrics(), nil, dbstore, dbstore, ng.SQLStore)
|
||||
st.Warm()
|
||||
st.Warm(ctx)
|
||||
|
||||
t.Run("instance cache has expected entries", func(t *testing.T) {
|
||||
for _, entry := range expectedEntries {
|
||||
@ -121,13 +122,14 @@ func TestWarmStateCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlertingTicker(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ng, dbstore := tests.SetupTestEnv(t, 1)
|
||||
|
||||
alerts := make([]*models.AlertRule, 0)
|
||||
|
||||
const mainOrgID int64 = 1
|
||||
// create alert rule under main org with one second interval
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, mainOrgID))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, ctx, dbstore, 1, mainOrgID))
|
||||
|
||||
const disabledOrgID int64 = 3
|
||||
|
||||
@ -162,8 +164,6 @@ func TestAlertingTicker(t *testing.T) {
|
||||
}
|
||||
sched := schedule.NewScheduler(schedCfg, nil, appUrl, st)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
go func() {
|
||||
err := sched.Run(ctx)
|
||||
require.NoError(t, err)
|
||||
@ -178,7 +178,7 @@ func TestAlertingTicker(t *testing.T) {
|
||||
|
||||
// add alert rule under main org with three seconds interval
|
||||
var threeSecInterval int64 = 3
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, threeSecInterval, mainOrgID))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, ctx, dbstore, threeSecInterval, mainOrgID))
|
||||
t.Logf("alert rule: %v added with interval: %d", alerts[1].GetKey(), threeSecInterval)
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[0].GetKey()}
|
||||
@ -200,7 +200,7 @@ func TestAlertingTicker(t *testing.T) {
|
||||
})
|
||||
|
||||
key := alerts[0].GetKey()
|
||||
err := dbstore.DeleteAlertRuleByUID(alerts[0].OrgID, alerts[0].UID)
|
||||
err := dbstore.DeleteAlertRuleByUID(ctx, alerts[0].OrgID, alerts[0].UID)
|
||||
require.NoError(t, err)
|
||||
t.Logf("alert rule: %v deleted", key)
|
||||
|
||||
@ -221,7 +221,7 @@ func TestAlertingTicker(t *testing.T) {
|
||||
})
|
||||
|
||||
// create alert rule with one second interval
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, mainOrgID))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, ctx, dbstore, 1, mainOrgID))
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[2].GetKey()}
|
||||
t.Run(fmt.Sprintf("on 7th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
|
||||
@ -230,7 +230,7 @@ func TestAlertingTicker(t *testing.T) {
|
||||
})
|
||||
|
||||
// create alert rule with one second interval under disabled org
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, dbstore, 1, disabledOrgID))
|
||||
alerts = append(alerts, tests.CreateTestAlertRule(t, ctx, dbstore, 1, disabledOrgID))
|
||||
|
||||
expectedAlertRulesEvaluated = []models.AlertRuleKey{alerts[2].GetKey()}
|
||||
t.Run(fmt.Sprintf("on 8th tick alert rules: %s should be evaluated", concatenate(expectedAlertRulesEvaluated)), func(t *testing.T) {
|
||||
|
@ -482,6 +482,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
|
||||
evalChan := make(chan *evalContext)
|
||||
evalAppliedChan := make(chan time.Time)
|
||||
|
||||
ctx := context.Background()
|
||||
sch, ruleStore, _, _, _ := createSchedule(evalAppliedChan)
|
||||
|
||||
rule := CreateTestAlertRule(t, ruleStore, 10, rand.Int63(), randomNormalState())
|
||||
@ -504,7 +505,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
|
||||
// Now update the rule
|
||||
newRule := *rule
|
||||
newRule.Version++
|
||||
ruleStore.putRule(&newRule)
|
||||
ruleStore.putRule(ctx, &newRule)
|
||||
|
||||
// and call with new version
|
||||
expectedTime = expectedTime.Add(time.Duration(rand.Intn(10)) * time.Second)
|
||||
@ -681,6 +682,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
|
||||
evalAppliedChan := make(chan time.Time)
|
||||
updateChan := make(chan struct{})
|
||||
|
||||
ctx := context.Background()
|
||||
sch, ruleStore, _, _, _ := createSchedule(evalAppliedChan)
|
||||
sch.senders[orgID] = s
|
||||
|
||||
@ -728,7 +730,7 @@ func TestSchedule_ruleRoutine(t *testing.T) {
|
||||
wg.Wait()
|
||||
newRule := rule
|
||||
newRule.Version++
|
||||
ruleStore.putRule(&newRule)
|
||||
ruleStore.putRule(ctx, &newRule)
|
||||
wg.Add(1)
|
||||
updateChan <- struct{}{}
|
||||
wg.Wait()
|
||||
@ -1041,6 +1043,8 @@ 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 {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Helper()
|
||||
records := make([]interface{}, 0, len(dbstore.recordedOps))
|
||||
copy(records, dbstore.recordedOps)
|
||||
@ -1080,7 +1084,7 @@ func CreateTestAlertRule(t *testing.T, dbstore *fakeRuleStore, intervalSeconds i
|
||||
require.Fail(t, "Alert rule with desired evaluation result NoData is not supported yet")
|
||||
}
|
||||
|
||||
err := dbstore.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||
err := dbstore.UpdateRuleGroup(ctx, store.UpdateRuleGroupCmd{
|
||||
OrgID: orgID,
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroupConfig: apimodels.PostableRuleGroupConfig{
|
||||
@ -1118,7 +1122,7 @@ func CreateTestAlertRule(t *testing.T, dbstore *fakeRuleStore, intervalSeconds i
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
err = dbstore.GetRuleGroupAlertRules(&q)
|
||||
err = dbstore.GetRuleGroupAlertRules(ctx, &q)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, q.Result)
|
||||
|
||||
|
@ -70,7 +70,7 @@ type fakeRuleStore struct {
|
||||
}
|
||||
|
||||
// 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(r *models.AlertRule) {
|
||||
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{
|
||||
@ -94,15 +94,17 @@ func (f *fakeRuleStore) getRecordedCommands(predicate func(cmd interface{}) (int
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *fakeRuleStore) DeleteAlertRuleByUID(_ int64, _ string) error { return nil }
|
||||
func (f *fakeRuleStore) DeleteNamespaceAlertRules(_ int64, _ string) ([]string, error) {
|
||||
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(_ int64, _ string, _ string) ([]string, error) {
|
||||
func (f *fakeRuleStore) DeleteRuleGroupAlertRules(_ context.Context, _ int64, _ string, _ string) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
func (f *fakeRuleStore) DeleteAlertInstancesByRuleUID(_ int64, _ string) error { return nil }
|
||||
func (f *fakeRuleStore) GetAlertRuleByUID(q *models.GetAlertRuleByUIDQuery) error {
|
||||
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)
|
||||
@ -129,7 +131,7 @@ func (f *fakeRuleStore) GetAlertRuleByUID(q *models.GetAlertRuleByUIDQuery) erro
|
||||
}
|
||||
|
||||
// For now, we're not implementing namespace filtering.
|
||||
func (f *fakeRuleStore) GetAlertRulesForScheduling(q *models.ListAlertRulesQuery) error {
|
||||
func (f *fakeRuleStore) GetAlertRulesForScheduling(_ context.Context, q *models.ListAlertRulesQuery) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.recordedOps = append(f.recordedOps, *q)
|
||||
@ -146,19 +148,19 @@ func (f *fakeRuleStore) GetAlertRulesForScheduling(q *models.ListAlertRulesQuery
|
||||
|
||||
return nil
|
||||
}
|
||||
func (f *fakeRuleStore) GetOrgAlertRules(q *models.ListAlertRulesQuery) error {
|
||||
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(q *models.ListNamespaceAlertRulesQuery) error {
|
||||
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(q *models.ListRuleGroupAlertRulesQuery) error {
|
||||
func (f *fakeRuleStore) GetRuleGroupAlertRules(_ context.Context, q *models.ListRuleGroupAlertRulesQuery) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.recordedOps = append(f.recordedOps, *q)
|
||||
@ -196,7 +198,7 @@ func (f *fakeRuleStore) GetNamespaces(_ context.Context, _ int64, _ *models2.Sig
|
||||
func (f *fakeRuleStore) GetNamespaceByTitle(_ context.Context, _ string, _ int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeRuleStore) GetOrgRuleGroups(q *models.ListOrgRuleGroupsQuery) error {
|
||||
func (f *fakeRuleStore) GetOrgRuleGroups(_ context.Context, q *models.ListOrgRuleGroupsQuery) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.recordedOps = append(f.recordedOps, *q)
|
||||
@ -206,7 +208,7 @@ func (f *fakeRuleStore) GetOrgRuleGroups(q *models.ListOrgRuleGroupsQuery) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeRuleStore) UpsertAlertRules(q []store.UpsertRule) error {
|
||||
func (f *fakeRuleStore) UpsertAlertRules(_ context.Context, q []store.UpsertRule) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.recordedOps = append(f.recordedOps, q)
|
||||
@ -215,7 +217,7 @@ func (f *fakeRuleStore) UpsertAlertRules(q []store.UpsertRule) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (f *fakeRuleStore) UpdateRuleGroup(cmd store.UpdateRuleGroupCmd) error {
|
||||
func (f *fakeRuleStore) UpdateRuleGroup(_ context.Context, cmd store.UpdateRuleGroupCmd) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
f.recordedOps = append(f.recordedOps, cmd)
|
||||
|
@ -54,7 +54,7 @@ func (st *Manager) Close() {
|
||||
st.quit <- struct{}{}
|
||||
}
|
||||
|
||||
func (st *Manager) Warm() {
|
||||
func (st *Manager) Warm(ctx context.Context) {
|
||||
st.log.Info("warming cache for startup")
|
||||
st.ResetCache()
|
||||
|
||||
@ -69,7 +69,7 @@ func (st *Manager) Warm() {
|
||||
ruleCmd := ngModels.ListAlertRulesQuery{
|
||||
OrgID: orgId,
|
||||
}
|
||||
if err := st.ruleStore.GetOrgAlertRules(&ruleCmd); err != nil {
|
||||
if err := st.ruleStore.GetOrgAlertRules(ctx, &ruleCmd); err != nil {
|
||||
st.log.Error("unable to fetch previous state", "msg", err.Error())
|
||||
}
|
||||
|
||||
|
@ -1513,10 +1513,11 @@ func TestStaleResultsHandler(t *testing.T) {
|
||||
t.Fatalf("error parsing date format: %s", err.Error())
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_, dbstore := tests.SetupTestEnv(t, 1)
|
||||
|
||||
const mainOrgID int64 = 1
|
||||
rule := tests.CreateTestAlertRule(t, dbstore, 600, mainOrgID)
|
||||
rule := tests.CreateTestAlertRule(t, ctx, dbstore, 600, mainOrgID)
|
||||
|
||||
saveCmd1 := &models.SaveAlertInstanceCommand{
|
||||
RuleOrgID: rule.OrgID,
|
||||
@ -1589,9 +1590,10 @@ func TestStaleResultsHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
ctx := context.Background()
|
||||
sqlStore := mockstore.NewSQLStoreMock()
|
||||
st := state.NewManager(log.New("test_stale_results_handler"), testMetrics.GetStateMetrics(), nil, dbstore, dbstore, sqlStore)
|
||||
st.Warm()
|
||||
st.Warm(ctx)
|
||||
existingStatesForRule := st.GetStatesForRuleUID(rule.OrgID, rule.UID)
|
||||
|
||||
// We have loaded the expected number of entries from the db
|
||||
|
@ -39,20 +39,20 @@ type UpsertRule struct {
|
||||
|
||||
// Store is the interface for persisting alert rules and instances
|
||||
type RuleStore interface {
|
||||
DeleteAlertRuleByUID(orgID int64, ruleUID string) error
|
||||
DeleteNamespaceAlertRules(orgID int64, namespaceUID string) ([]string, error)
|
||||
DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) ([]string, error)
|
||||
DeleteAlertInstancesByRuleUID(orgID int64, ruleUID string) error
|
||||
GetAlertRuleByUID(*ngmodels.GetAlertRuleByUIDQuery) error
|
||||
GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error
|
||||
GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error
|
||||
GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error
|
||||
GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error
|
||||
DeleteAlertRuleByUID(ctx context.Context, orgID int64, ruleUID string) error
|
||||
DeleteNamespaceAlertRules(ctx context.Context, orgID int64, namespaceUID string) ([]string, error)
|
||||
DeleteRuleGroupAlertRules(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) ([]string, error)
|
||||
DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error
|
||||
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) error
|
||||
GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
|
||||
GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
|
||||
GetNamespaceAlertRules(ctx context.Context, query *ngmodels.ListNamespaceAlertRulesQuery) error
|
||||
GetRuleGroupAlertRules(ctx context.Context, query *ngmodels.ListRuleGroupAlertRulesQuery) error
|
||||
GetNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error)
|
||||
GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error)
|
||||
GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error
|
||||
UpsertAlertRules([]UpsertRule) error
|
||||
UpdateRuleGroup(UpdateRuleGroupCmd) error
|
||||
GetOrgRuleGroups(ctx context.Context, query *ngmodels.ListOrgRuleGroupsQuery) error
|
||||
UpsertAlertRules(ctx context.Context, rule []UpsertRule) error
|
||||
UpdateRuleGroup(ctx context.Context, cmd UpdateRuleGroupCmd) error
|
||||
}
|
||||
|
||||
func getAlertRuleByUID(sess *sqlstore.DBSession, alertRuleUID string, orgID int64) (*ngmodels.AlertRule, error) {
|
||||
@ -69,8 +69,8 @@ func getAlertRuleByUID(sess *sqlstore.DBSession, alertRuleUID string, orgID int6
|
||||
}
|
||||
|
||||
// DeleteAlertRuleByUID is a handler for deleting an alert rule.
|
||||
func (st DBstore) DeleteAlertRuleByUID(orgID int64, ruleUID string) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) DeleteAlertRuleByUID(ctx context.Context, orgID int64, ruleUID string) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? AND uid = ?", orgID, ruleUID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -91,10 +91,10 @@ func (st DBstore) DeleteAlertRuleByUID(orgID int64, ruleUID string) error {
|
||||
}
|
||||
|
||||
// DeleteNamespaceAlertRules is a handler for deleting namespace alert rules. A list of deleted rule UIDs are returned.
|
||||
func (st DBstore) DeleteNamespaceAlertRules(orgID int64, namespaceUID string) ([]string, error) {
|
||||
func (st DBstore) DeleteNamespaceAlertRules(ctx context.Context, orgID int64, namespaceUID string) ([]string, error) {
|
||||
ruleUIDs := []string{}
|
||||
|
||||
err := st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID).Find(&ruleUIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -123,10 +123,10 @@ func (st DBstore) DeleteNamespaceAlertRules(orgID int64, namespaceUID string) ([
|
||||
}
|
||||
|
||||
// DeleteRuleGroupAlertRules is a handler for deleting rule group alert rules. A list of deleted rule UIDs are returned.
|
||||
func (st DBstore) DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) ([]string, error) {
|
||||
func (st DBstore) DeleteRuleGroupAlertRules(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) ([]string, error) {
|
||||
ruleUIDs := []string{}
|
||||
|
||||
err := st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?",
|
||||
orgID, namespaceUID, ruleGroup).Find(&ruleUIDs); err != nil {
|
||||
return err
|
||||
@ -161,8 +161,8 @@ func (st DBstore) DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ru
|
||||
}
|
||||
|
||||
// DeleteAlertInstanceByRuleUID is a handler for deleting alert instances by alert rule UID when a rule has been updated
|
||||
func (st DBstore) DeleteAlertInstancesByRuleUID(orgID int64, ruleUID string) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ?", orgID, ruleUID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -173,8 +173,8 @@ func (st DBstore) DeleteAlertInstancesByRuleUID(orgID int64, ruleUID string) err
|
||||
|
||||
// GetAlertRuleByUID is a handler for retrieving an alert rule from that database by its UID and organisation ID.
|
||||
// It returns ngmodels.ErrAlertRuleNotFound if no alert rule is found for the provided ID.
|
||||
func (st DBstore) GetAlertRuleByUID(query *ngmodels.GetAlertRuleByUIDQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alertRule, err := getAlertRuleByUID(sess, query.UID, query.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -185,8 +185,8 @@ func (st DBstore) GetAlertRuleByUID(query *ngmodels.GetAlertRuleByUIDQuery) erro
|
||||
}
|
||||
|
||||
// UpsertAlertRules is a handler for creating/updating alert rules.
|
||||
func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) UpsertAlertRules(ctx context.Context, rules []UpsertRule) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
newRules := make([]ngmodels.AlertRule, 0, len(rules))
|
||||
ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
@ -318,8 +318,8 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
||||
}
|
||||
|
||||
// GetOrgAlertRules is a handler for retrieving alert rules of specific organisation.
|
||||
func (st DBstore) GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetOrgAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alertRules := make([]*ngmodels.AlertRule, 0)
|
||||
q := "SELECT * FROM alert_rule WHERE org_id = ?"
|
||||
params := []interface{}{query.OrgID}
|
||||
@ -354,8 +354,8 @@ func (st DBstore) GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error {
|
||||
}
|
||||
|
||||
// GetNamespaceAlertRules is a handler for retrieving namespace alert rules of specific organisation.
|
||||
func (st DBstore) GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetNamespaceAlertRules(ctx context.Context, query *ngmodels.ListNamespaceAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alertRules := make([]*ngmodels.AlertRule, 0)
|
||||
// TODO rewrite using group by namespace_uid, rule_group
|
||||
q := "SELECT * FROM alert_rule WHERE org_id = ? and namespace_uid = ?"
|
||||
@ -369,8 +369,8 @@ func (st DBstore) GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRules
|
||||
}
|
||||
|
||||
// GetRuleGroupAlertRules is a handler for retrieving rule group alert rules of specific organisation.
|
||||
func (st DBstore) GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetRuleGroupAlertRules(ctx context.Context, query *ngmodels.ListRuleGroupAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
q := "SELECT * FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?"
|
||||
args := []interface{}{query.OrgID, query.NamespaceUID, query.RuleGroup}
|
||||
|
||||
@ -440,8 +440,8 @@ func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, org
|
||||
|
||||
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
||||
// that is useful for it's scheduling.
|
||||
func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alerts := make([]*ngmodels.AlertRule, 0)
|
||||
q := "SELECT uid, org_id, interval_seconds, version FROM alert_rule"
|
||||
if len(query.ExcludeOrgs) > 0 {
|
||||
@ -511,15 +511,15 @@ func (st DBstore) validateAlertRule(alertRule ngmodels.AlertRule) error {
|
||||
}
|
||||
|
||||
// UpdateRuleGroup creates new rules and updates and/or deletes existing rules
|
||||
func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) UpdateRuleGroup(ctx context.Context, cmd UpdateRuleGroupCmd) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
ruleGroup := cmd.RuleGroupConfig.Name
|
||||
q := &ngmodels.ListRuleGroupAlertRulesQuery{
|
||||
OrgID: cmd.OrgID,
|
||||
NamespaceUID: cmd.NamespaceUID,
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
if err := st.GetRuleGroupAlertRules(q); err != nil {
|
||||
if err := st.GetRuleGroupAlertRules(ctx, q); err != nil {
|
||||
return err
|
||||
}
|
||||
existingGroupRules := q.Result
|
||||
@ -578,7 +578,7 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
||||
upsertRules = append(upsertRules, upsertRule)
|
||||
}
|
||||
|
||||
if err := st.UpsertAlertRules(upsertRules); err != nil {
|
||||
if err := st.UpsertAlertRules(ctx, upsertRules); err != nil {
|
||||
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
|
||||
return ngmodels.ErrAlertRuleUniqueConstraintViolation
|
||||
}
|
||||
@ -588,7 +588,7 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
||||
// delete instances for rules that will not be removed
|
||||
for _, rule := range existingGroupRules {
|
||||
if _, ok := existingGroupRulesUIDs[rule.UID]; !ok {
|
||||
if err := st.DeleteAlertInstancesByRuleUID(cmd.OrgID, rule.UID); err != nil {
|
||||
if err := st.DeleteAlertInstancesByRuleUID(ctx, cmd.OrgID, rule.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -596,7 +596,7 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
||||
|
||||
// delete the remaining rules
|
||||
for ruleUID := range existingGroupRulesUIDs {
|
||||
if err := st.DeleteAlertRuleByUID(cmd.OrgID, ruleUID); err != nil {
|
||||
if err := st.DeleteAlertRuleByUID(ctx, cmd.OrgID, ruleUID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -604,8 +604,8 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
func (st DBstore) GetOrgRuleGroups(ctx context.Context, query *ngmodels.ListOrgRuleGroupsQuery) error {
|
||||
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var ruleGroups [][]string
|
||||
q := `
|
||||
SELECT DISTINCT
|
||||
|
@ -4,6 +4,7 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -26,20 +27,21 @@ func mockTimeNow() {
|
||||
}
|
||||
|
||||
func TestAlertInstanceOperations(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, dbstore := tests.SetupTestEnv(t, baseIntervalSeconds)
|
||||
|
||||
const mainOrgID int64 = 1
|
||||
|
||||
alertRule1 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
alertRule1 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
||||
orgID := alertRule1.OrgID
|
||||
|
||||
alertRule2 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
alertRule2 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule2.OrgID)
|
||||
|
||||
alertRule3 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
alertRule3 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule3.OrgID)
|
||||
|
||||
alertRule4 := tests.CreateTestAlertRule(t, dbstore, 60, mainOrgID)
|
||||
alertRule4 := tests.CreateTestAlertRule(t, ctx, dbstore, 60, mainOrgID)
|
||||
require.Equal(t, orgID, alertRule4.OrgID)
|
||||
|
||||
t.Run("can save and read new alert instance", func(t *testing.T) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
@ -50,9 +51,9 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
|
||||
}
|
||||
|
||||
// CreateTestAlertRule creates a dummy alert definition to be used by the tests.
|
||||
func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds int64, orgID int64) *models.AlertRule {
|
||||
func CreateTestAlertRule(t *testing.T, ctx context.Context, dbstore *store.DBstore, intervalSeconds int64, orgID int64) *models.AlertRule {
|
||||
ruleGroup := fmt.Sprintf("ruleGroup-%s", util.GenerateShortUID())
|
||||
err := dbstore.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||
err := dbstore.UpdateRuleGroup(ctx, store.UpdateRuleGroupCmd{
|
||||
OrgID: orgID,
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroupConfig: apimodels.PostableRuleGroupConfig{
|
||||
@ -92,7 +93,7 @@ func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds i
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
err = dbstore.GetRuleGroupAlertRules(&q)
|
||||
err = dbstore.GetRuleGroupAlertRules(ctx, &q)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, q.Result)
|
||||
|
||||
@ -102,7 +103,7 @@ func CreateTestAlertRule(t *testing.T, dbstore *store.DBstore, intervalSeconds i
|
||||
}
|
||||
|
||||
// updateTestAlertRule update a dummy alert definition to be used by the tests.
|
||||
func UpdateTestAlertRuleIntervalSeconds(t *testing.T, dbstore *store.DBstore, existingRule *models.AlertRule, intervalSeconds int64) *models.AlertRule {
|
||||
func UpdateTestAlertRuleIntervalSeconds(t *testing.T, ctx context.Context, dbstore *store.DBstore, existingRule *models.AlertRule, intervalSeconds int64) *models.AlertRule {
|
||||
cmd := store.UpdateRuleGroupCmd{
|
||||
OrgID: 1,
|
||||
NamespaceUID: "namespace",
|
||||
@ -119,7 +120,7 @@ func UpdateTestAlertRuleIntervalSeconds(t *testing.T, dbstore *store.DBstore, ex
|
||||
},
|
||||
}
|
||||
|
||||
err := dbstore.UpdateRuleGroup(cmd)
|
||||
err := dbstore.UpdateRuleGroup(ctx, cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
q := models.ListRuleGroupAlertRulesQuery{
|
||||
@ -127,7 +128,7 @@ func UpdateTestAlertRuleIntervalSeconds(t *testing.T, dbstore *store.DBstore, ex
|
||||
NamespaceUID: "namespace",
|
||||
RuleGroup: existingRule.RuleGroup,
|
||||
}
|
||||
err = dbstore.GetRuleGroupAlertRules(&q)
|
||||
err = dbstore.GetRuleGroupAlertRules(ctx, &q)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, q.Result)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user