diff --git a/conf/defaults.ini b/conf/defaults.ini
index 2dfcbc85756..d5c221c4957 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -651,6 +651,9 @@ org_data_source = 10
# limit number of api_keys per Org.
org_api_key = 10
+# limit number of alerts per Org.
+org_alert_rule = 100
+
# limit number of orgs a user can create.
user_org = 10
@@ -669,6 +672,9 @@ global_api_key = -1
# global limit on number of logged in users.
global_session = -1
+# global limit of alerts
+global_alert_rule = -1
+
#################################### Alerting ############################
[alerting]
# Disable alerting engine & UI features
diff --git a/conf/sample.ini b/conf/sample.ini
index 5e7bd501d60..7a1cec43997 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -640,6 +640,9 @@
# limit number of api_keys per Org.
; org_api_key = 10
+# limit number of alerts per Org.
+;org_alert_rule = 100
+
# limit number of orgs a user can create.
; user_org = 10
@@ -658,6 +661,9 @@
# global limit on number of logged in users.
; global_session = -1
+# global limit of alerts
+;global_alert_rule = -1
+
#################################### Alerting ############################
[alerting]
# Disable alerting engine & UI features
diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md
index 21736b59de5..704dedcef67 100644
--- a/docs/sources/administration/configuration.md
+++ b/docs/sources/administration/configuration.md
@@ -1021,6 +1021,10 @@ Limit the number of data sources allowed per organization. Default is 10.
Limit the number of API keys that can be entered per organization. Default is 10.
+### org_alert_rule
+
+Limit the number of alert rules that can be entered per organization. Default is 100.
+
### user_org
Limit the number of organizations a user can create. Default is 10.
@@ -1045,6 +1049,10 @@ Sets global limit of API keys that can be entered. Default is -1 (unlimited).
Sets a global limit on number of users that can be logged in at one time. Default is -1 (unlimited).
+### global_alert_rule
+
+Sets a global limit on number of alert rules that can be created. Default is -1 (unlimited).
+
## [alerting]
diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go
index be0fbe60a1e..472be504285 100644
--- a/pkg/middleware/quota_test.go
+++ b/pkg/middleware/quota_test.go
@@ -208,6 +208,61 @@ func TestMiddlewareQuota(t *testing.T) {
cfg.Quota.Org.Dashboard = quotaUsed
cfg.Quota.Enabled = false
})
+
+ middlewareScenario(t, "org alert quota reached and ngalert enabled", func(t *testing.T, sc *scenarioContext) {
+ setUp(sc)
+
+ quotaHandler := getQuotaHandler(sc, "alert_rule")
+ sc.m.Get("/alert_rule", quotaHandler, sc.defaultHandler)
+ sc.fakeReq("GET", "/alert_rule").exec()
+ assert.Equal(t, 403, sc.resp.Code)
+ }, func(cfg *setting.Cfg) {
+ configure(cfg)
+
+ cfg.FeatureToggles = map[string]bool{"ngalert": true}
+ cfg.Quota.Org.AlertRule = quotaUsed
+ })
+
+ middlewareScenario(t, "org alert quota not reached and ngalert enabled", func(t *testing.T, sc *scenarioContext) {
+ setUp(sc)
+
+ quotaHandler := getQuotaHandler(sc, "alert_rule")
+ sc.m.Get("/alert_rule", quotaHandler, sc.defaultHandler)
+ sc.fakeReq("GET", "/alert_rule").exec()
+ assert.Equal(t, 200, sc.resp.Code)
+ }, func(cfg *setting.Cfg) {
+ configure(cfg)
+
+ cfg.FeatureToggles = map[string]bool{"ngalert": true}
+ cfg.Quota.Org.AlertRule = quotaUsed + 1
+ })
+
+ middlewareScenario(t, "org alert quota reached but ngalert disabled", func(t *testing.T, sc *scenarioContext) {
+ // this scenario can only happen if the feature was enabled and later disabled
+ setUp(sc)
+
+ quotaHandler := getQuotaHandler(sc, "alert_rule")
+ sc.m.Get("/alert_rule", quotaHandler, sc.defaultHandler)
+ sc.fakeReq("GET", "/alert_rule").exec()
+ assert.Equal(t, 403, sc.resp.Code)
+ }, func(cfg *setting.Cfg) {
+ configure(cfg)
+
+ cfg.Quota.Org.AlertRule = quotaUsed
+ })
+
+ middlewareScenario(t, "org alert quota not reached but ngalert disabled", func(t *testing.T, sc *scenarioContext) {
+ setUp(sc)
+
+ quotaHandler := getQuotaHandler(sc, "alert_rule")
+ sc.m.Get("/alert_rule", quotaHandler, sc.defaultHandler)
+ sc.fakeReq("GET", "/alert_rule").exec()
+ assert.Equal(t, 200, sc.resp.Code)
+ }, func(cfg *setting.Cfg) {
+ configure(cfg)
+
+ cfg.Quota.Org.AlertRule = quotaUsed + 1
+ })
})
}
@@ -230,6 +285,7 @@ func configure(cfg *setting.Cfg) {
Dashboard: 5,
DataSource: 5,
ApiKey: 5,
+ AlertRule: 5,
},
User: &setting.UserQuota{
Org: 5,
@@ -241,6 +297,7 @@ func configure(cfg *setting.Cfg) {
DataSource: 5,
ApiKey: 5,
Session: 5,
+ AlertRule: 5,
},
}
}
diff --git a/pkg/models/quotas.go b/pkg/models/quotas.go
index 43b28b9c3e6..864a0f57d4c 100644
--- a/pkg/models/quotas.go
+++ b/pkg/models/quotas.go
@@ -44,33 +44,38 @@ type GlobalQuotaDTO struct {
}
type GetOrgQuotaByTargetQuery struct {
- Target string
- OrgId int64
- Default int64
- Result *OrgQuotaDTO
+ Target string
+ OrgId int64
+ Default int64
+ IsNgAlertEnabled bool
+ Result *OrgQuotaDTO
}
type GetOrgQuotasQuery struct {
- OrgId int64
- Result []*OrgQuotaDTO
+ OrgId int64
+ IsNgAlertEnabled bool
+ Result []*OrgQuotaDTO
}
type GetUserQuotaByTargetQuery struct {
- Target string
- UserId int64
- Default int64
- Result *UserQuotaDTO
+ Target string
+ UserId int64
+ Default int64
+ IsNgAlertEnabled bool
+ Result *UserQuotaDTO
}
type GetUserQuotasQuery struct {
- UserId int64
- Result []*UserQuotaDTO
+ UserId int64
+ IsNgAlertEnabled bool
+ Result []*UserQuotaDTO
}
type GetGlobalQuotaByTargetQuery struct {
- Target string
- Default int64
- Result *GlobalQuotaDTO
+ Target string
+ Default int64
+ IsNgAlertEnabled bool
+ Result *GlobalQuotaDTO
}
type UpdateOrgQuotaCmd struct {
diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go
index f3a3ec1d974..9e24ef7aa93 100644
--- a/pkg/services/ngalert/api/api.go
+++ b/pkg/services/ngalert/api/api.go
@@ -3,6 +3,8 @@ package api
import (
"time"
+ "github.com/grafana/grafana/pkg/services/quota"
+
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/state"
@@ -41,6 +43,7 @@ type API struct {
DatasourceCache datasources.CacheService
RouteRegister routing.RouteRegister
DataService *tsdb.Service
+ QuotaService *quota.QuotaService
Schedule schedule.ScheduleService
RuleStore store.RuleStore
InstanceStore store.InstanceStore
@@ -73,7 +76,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.Metrics) {
api.RegisterRulerApiEndpoints(NewForkedRuler(
api.DatasourceCache,
NewLotexRuler(proxy, logger),
- RulerSrv{DatasourceCache: api.DatasourceCache, manager: api.StateManager, store: api.RuleStore, log: logger},
+ RulerSrv{DatasourceCache: api.DatasourceCache, QuotaService: api.QuotaService, manager: api.StateManager, store: api.RuleStore, log: logger},
), m)
api.RegisterTestingApiEndpoints(TestingApiSrv{
AlertingProxy: proxy,
diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go
index 8607666ba08..e79a280ca94 100644
--- a/pkg/services/ngalert/api/api_ruler.go
+++ b/pkg/services/ngalert/api/api_ruler.go
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/services/ngalert/store"
+ "github.com/grafana/grafana/pkg/services/quota"
coreapi "github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/api/response"
@@ -23,6 +24,7 @@ import (
type RulerSrv struct {
store store.RuleStore
DatasourceCache datasources.CacheService
+ QuotaService *quota.QuotaService
manager *state.Manager
log log.Logger
}
@@ -209,8 +211,18 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
return toNamespaceErrorResponse(err)
}
- // TODO check permissions
- // TODO check quota
+ // quotas are checked in advanced
+ // that is acceptable under the assumption that there will be only one alert rule under the rule group
+ // alternatively we should check the quotas after the rule group update
+ // and rollback the transaction in case of violation
+ limitReached, err := srv.QuotaService.QuotaReached(c, "alert_rule")
+ if err != nil {
+ return response.Error(http.StatusInternalServerError, "failed to get quota", err)
+ }
+ if limitReached {
+ return response.Error(http.StatusForbidden, "quota reached", nil)
+ }
+
// TODO validate UID uniqueness in the payload
//TODO: Should this belong in alerting-api?
diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go
index f9423051634..1172e8ad828 100644
--- a/pkg/services/ngalert/ngalert.go
+++ b/pkg/services/ngalert/ngalert.go
@@ -4,6 +4,8 @@ import (
"context"
"time"
+ "github.com/grafana/grafana/pkg/services/quota"
+
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/state"
@@ -46,6 +48,7 @@ type AlertNG struct {
DataService *tsdb.Service `inject:""`
Alertmanager *notifier.Alertmanager `inject:""`
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
+ QuotaService *quota.QuotaService `inject:""`
Metrics *metrics.Metrics `inject:""`
Log log.Logger
schedule schedule.ScheduleService
@@ -83,6 +86,7 @@ func (ng *AlertNG) Init() error {
DataService: ng.DataService,
Schedule: ng.schedule,
DataProxy: ng.DataProxy,
+ QuotaService: ng.QuotaService,
InstanceStore: store,
RuleStore: store,
AlertingStore: store,
diff --git a/pkg/services/quota/quota.go b/pkg/services/quota/quota.go
index 7b9b49c074b..5440ef9e62a 100644
--- a/pkg/services/quota/quota.go
+++ b/pkg/services/quota/quota.go
@@ -64,7 +64,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
}
continue
}
- query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target}
+ query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
if err := bus.Dispatch(&query); err != nil {
return true, err
}
@@ -75,7 +75,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
if !c.IsSignedIn {
continue
}
- query := models.GetOrgQuotaByTargetQuery{OrgId: c.OrgId, Target: scope.Target, Default: scope.DefaultLimit}
+ query := models.GetOrgQuotaByTargetQuery{OrgId: c.OrgId, Target: scope.Target, Default: scope.DefaultLimit, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
if err := bus.Dispatch(&query); err != nil {
return true, err
}
@@ -93,7 +93,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
if !c.IsSignedIn || c.UserId == 0 {
continue
}
- query := models.GetUserQuotaByTargetQuery{UserId: c.UserId, Target: scope.Target, Default: scope.DefaultLimit}
+ query := models.GetUserQuotaByTargetQuery{UserId: c.UserId, Target: scope.Target, Default: scope.DefaultLimit, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
if err := bus.Dispatch(&query); err != nil {
return true, err
}
@@ -151,6 +151,12 @@ func (qs *QuotaService) getQuotaScopes(target string) ([]models.QuotaScope, erro
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.Session},
)
return scopes, nil
+ case "alert_rule": // target need to match the respective database name
+ scopes = append(scopes,
+ models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.AlertRule},
+ models.QuotaScope{Name: "org", Target: target, DefaultLimit: qs.Cfg.Quota.Org.AlertRule},
+ )
+ return scopes, nil
default:
return scopes, ErrInvalidQuotaTarget
}
diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go
index ed8c2c72d4a..1db991c8fd2 100644
--- a/pkg/services/sqlstore/quota.go
+++ b/pkg/services/sqlstore/quota.go
@@ -9,6 +9,8 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
+const ALERT_RULE_TARGET = "alert_rule"
+
func init() {
bus.AddHandler("sql", GetOrgQuotaByTarget)
bus.AddHandler("sql", GetOrgQuotas)
@@ -35,18 +37,22 @@ func GetOrgQuotaByTarget(query *models.GetOrgQuotaByTargetQuery) error {
quota.Limit = query.Default
}
- // get quota used.
- rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target))
- resp := make([]*targetCount, 0)
- if err := x.SQL(rawSQL, query.OrgId).Find(&resp); err != nil {
- return err
+ var used int64
+ if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
+ // get quota used.
+ rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target))
+ resp := make([]*targetCount, 0)
+ if err := x.SQL(rawSQL, query.OrgId).Find(&resp); err != nil {
+ return err
+ }
+ used = resp[0].Count
}
query.Result = &models.OrgQuotaDTO{
Target: query.Target,
Limit: quota.Limit,
OrgId: query.OrgId,
- Used: resp[0].Count,
+ Used: used,
}
return nil
@@ -78,17 +84,21 @@ func GetOrgQuotas(query *models.GetOrgQuotasQuery) error {
result := make([]*models.OrgQuotaDTO, len(quotas))
for i, q := range quotas {
- // get quota used.
- rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
- resp := make([]*targetCount, 0)
- if err := x.SQL(rawSQL, q.OrgId).Find(&resp); err != nil {
- return err
+ var used int64
+ if q.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
+ // get quota used.
+ rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
+ resp := make([]*targetCount, 0)
+ if err := x.SQL(rawSQL, q.OrgId).Find(&resp); err != nil {
+ return err
+ }
+ used = resp[0].Count
}
result[i] = &models.OrgQuotaDTO{
Target: q.Target,
Limit: q.Limit,
OrgId: q.OrgId,
- Used: resp[0].Count,
+ Used: used,
}
}
query.Result = result
@@ -138,18 +148,22 @@ func GetUserQuotaByTarget(query *models.GetUserQuotaByTargetQuery) error {
quota.Limit = query.Default
}
- // get quota used.
- rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
- resp := make([]*targetCount, 0)
- if err := x.SQL(rawSQL, query.UserId).Find(&resp); err != nil {
- return err
+ var used int64
+ if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
+ // get quota used.
+ rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
+ resp := make([]*targetCount, 0)
+ if err := x.SQL(rawSQL, query.UserId).Find(&resp); err != nil {
+ return err
+ }
+ used = resp[0].Count
}
query.Result = &models.UserQuotaDTO{
Target: query.Target,
Limit: quota.Limit,
UserId: query.UserId,
- Used: resp[0].Count,
+ Used: used,
}
return nil
@@ -181,17 +195,21 @@ func GetUserQuotas(query *models.GetUserQuotasQuery) error {
result := make([]*models.UserQuotaDTO, len(quotas))
for i, q := range quotas {
- // get quota used.
- rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
- resp := make([]*targetCount, 0)
- if err := x.SQL(rawSQL, q.UserId).Find(&resp); err != nil {
- return err
+ var used int64
+ if q.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
+ // get quota used.
+ rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
+ resp := make([]*targetCount, 0)
+ if err := x.SQL(rawSQL, q.UserId).Find(&resp); err != nil {
+ return err
+ }
+ used = resp[0].Count
}
result[i] = &models.UserQuotaDTO{
Target: q.Target,
Limit: q.Limit,
UserId: q.UserId,
- Used: resp[0].Count,
+ Used: used,
}
}
query.Result = result
@@ -230,17 +248,22 @@ func UpdateUserQuota(cmd *models.UpdateUserQuotaCmd) error {
}
func GetGlobalQuotaByTarget(query *models.GetGlobalQuotaByTargetQuery) error {
- // get quota used.
- rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target))
- resp := make([]*targetCount, 0)
- if err := x.SQL(rawSQL).Find(&resp); err != nil {
- return err
+ var used int64
+ if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
+ // get quota used.
+
+ rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target))
+ resp := make([]*targetCount, 0)
+ if err := x.SQL(rawSQL).Find(&resp); err != nil {
+ return err
+ }
+ used = resp[0].Count
}
query.Result = &models.GlobalQuotaDTO{
Target: query.Target,
Limit: query.Default,
- Used: resp[0].Count,
+ Used: used,
}
return nil
diff --git a/pkg/services/sqlstore/quota_test.go b/pkg/services/sqlstore/quota_test.go
index 0fefb9ea2d4..d1314a97056 100644
--- a/pkg/services/sqlstore/quota_test.go
+++ b/pkg/services/sqlstore/quota_test.go
@@ -24,6 +24,7 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
Dashboard: 5,
DataSource: 5,
ApiKey: 5,
+ AlertRule: 5,
},
User: &setting.UserQuota{
Org: 5,
@@ -35,6 +36,7 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
DataSource: 5,
ApiKey: 5,
Session: 5,
+ AlertRule: 5,
},
}
@@ -87,12 +89,19 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
So(err, ShouldBeNil)
So(query.Result.Used, ShouldEqual, 0)
})
+ Convey("Should be able to get zero used org alert quota when table does not exist (ngalert is not enabled - default case)", func() {
+ query := models.GetOrgQuotaByTargetQuery{OrgId: 2, Target: "alert", Default: 11}
+ err = GetOrgQuotaByTarget(&query)
+
+ So(err, ShouldBeNil)
+ So(query.Result.Used, ShouldEqual, 0)
+ })
Convey("Should be able to quota list for org", func() {
query := models.GetOrgQuotasQuery{OrgId: orgId}
err = GetOrgQuotas(&query)
So(err, ShouldBeNil)
- So(len(query.Result), ShouldEqual, 4)
+ So(len(query.Result), ShouldEqual, 5)
for _, res := range query.Result {
limit := 5 // default quota limit
used := 0
@@ -169,6 +178,14 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
So(query.Result.Limit, ShouldEqual, 5)
So(query.Result.Used, ShouldEqual, 1)
})
+ Convey("Should be able to get zero used global alert quota when table does not exist (ngalert is not enabled - default case)", func() {
+ query := models.GetGlobalQuotaByTargetQuery{Target: "alert_rule", Default: 5}
+ err = GetGlobalQuotaByTarget(&query)
+ So(err, ShouldBeNil)
+
+ So(query.Result.Limit, ShouldEqual, 5)
+ So(query.Result.Used, ShouldEqual, 0)
+ })
// related: https://github.com/grafana/grafana/issues/14342
Convey("Should org quota updating is successful even if it called multiple time", func() {
diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go
index bf36b35a010..7b31ebb7926 100644
--- a/pkg/services/sqlstore/sqlstore.go
+++ b/pkg/services/sqlstore/sqlstore.go
@@ -158,7 +158,6 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser() error {
// ensure admin user
if !ss.Cfg.DisableInitAdminCreation {
- ss.log.Debug("Creating default admin user")
ss.log.Debug("Creating default admin user")
if _, err := ss.createUser(ctx, sess, userCreationArgs{
Login: ss.Cfg.AdminUser,
diff --git a/pkg/setting/setting_quota.go b/pkg/setting/setting_quota.go
index be562a4c0e2..d62e5727d5c 100644
--- a/pkg/setting/setting_quota.go
+++ b/pkg/setting/setting_quota.go
@@ -9,6 +9,7 @@ type OrgQuota struct {
DataSource int64 `target:"data_source"`
Dashboard int64 `target:"dashboard"`
ApiKey int64 `target:"api_key"`
+ AlertRule int64 `target:"alert_rule"`
}
type UserQuota struct {
@@ -22,6 +23,7 @@ type GlobalQuota struct {
Dashboard int64 `target:"dashboard"`
ApiKey int64 `target:"api_key"`
Session int64 `target:"-"`
+ AlertRule int64 `target:"alert_rule"`
}
func (q *OrgQuota) ToMap() map[string]int64 {
@@ -64,12 +66,19 @@ func (cfg *Cfg) readQuotaSettings() {
quota := cfg.Raw.Section("quota")
Quota.Enabled = quota.Key("enabled").MustBool(false)
+ var alertOrgQuota int64
+ var alertGlobalQuota int64
+ if cfg.IsNgAlertEnabled() {
+ alertOrgQuota = quota.Key("org_alert_rule").MustInt64(100)
+ alertGlobalQuota = quota.Key("global_alert_rule").MustInt64(-1)
+ }
// per ORG Limits
Quota.Org = &OrgQuota{
User: quota.Key("org_user").MustInt64(10),
DataSource: quota.Key("org_data_source").MustInt64(10),
Dashboard: quota.Key("org_dashboard").MustInt64(10),
ApiKey: quota.Key("org_api_key").MustInt64(10),
+ AlertRule: alertOrgQuota,
}
// per User limits
@@ -85,6 +94,7 @@ func (cfg *Cfg) readQuotaSettings() {
Dashboard: quota.Key("global_dashboard").MustInt64(-1),
ApiKey: quota.Key("global_api_key").MustInt64(-1),
Session: quota.Key("global_session").MustInt64(-1),
+ AlertRule: alertGlobalQuota,
}
cfg.Quota = Quota
diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go
index ccc2018330b..c7ea7a2f928 100644
--- a/pkg/tests/api/alerting/api_alertmanager_test.go
+++ b/pkg/tests/api/alerting/api_alertmanager_test.go
@@ -2,6 +2,7 @@ package alerting
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -10,6 +11,8 @@ import (
"testing"
"time"
+ "github.com/grafana/grafana/pkg/bus"
+
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -26,15 +29,52 @@ import (
func TestAlertAndGroupsQuery(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
- AnonymousUserRole: models.ROLE_EDITOR,
+ DisableAnonymous: true,
})
store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
+ // unauthenticated request to get the alerts should fail
+ {
+ alertsURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
+ // nolint:gosec
+ resp, err := http.Get(alertsURL)
+ require.NoError(t, err)
+ t.Cleanup(func() {
+ err := resp.Body.Close()
+ require.NoError(t, err)
+ })
+ b, err := ioutil.ReadAll(resp.Body)
+ require.NoError(t, err)
+ require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ require.JSONEq(t, `{"message": "Unauthorized"}`, string(b))
+ }
+
+ // Create a user to make authenticated requests
+ require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
+
+ // invalid credentials request to get the alerts should fail
+ {
+ alertsURL := fmt.Sprintf("http://grafana:invalid@%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
+ // nolint:gosec
+ resp, err := http.Get(alertsURL)
+ require.NoError(t, err)
+ t.Cleanup(func() {
+ err := resp.Body.Close()
+ require.NoError(t, err)
+ })
+ b, err := ioutil.ReadAll(resp.Body)
+ require.NoError(t, err)
+ require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ require.JSONEq(t, `{"error": "invalid username or password","message": "invalid username or password"}`, string(b))
+ }
+
// When there are no alerts available, it returns an empty list.
{
- alertsURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
+ alertsURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(alertsURL)
require.NoError(t, err)
@@ -50,7 +90,7 @@ func TestAlertAndGroupsQuery(t *testing.T) {
// When are there no alerts available, it returns an empty list of groups.
{
- alertsURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/api/v2/alerts/groups", grafanaListedAddr)
+ alertsURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts/groups", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(alertsURL)
require.NoError(t, err)
@@ -106,7 +146,7 @@ func TestAlertAndGroupsQuery(t *testing.T) {
err = enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
t.Cleanup(func() {
@@ -119,7 +159,7 @@ func TestAlertAndGroupsQuery(t *testing.T) {
// Eventually, we'll get an alert with its state being active.
{
- alertsURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
+ alertsURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts", grafanaListedAddr)
// nolint:gosec
require.Eventually(t, func() bool {
resp, err := http.Get(alertsURL)
@@ -150,11 +190,18 @@ func TestAlertRuleCRUD(t *testing.T) {
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
- AnonymousUserRole: models.ROLE_EDITOR,
+ EnableQuota: true,
+ DisableAnonymous: true,
})
+
store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
+ err := createUser(t, store, models.ROLE_EDITOR, "grafana", "password")
+ require.NoError(t, err)
+
// Create the namespace we'll save our alerts to.
require.NoError(t, createFolder(t, store, 0, "default"))
@@ -229,7 +276,7 @@ func TestAlertRuleCRUD(t *testing.T) {
Annotations: map[string]string{"annotation1": "val1"},
},
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
- Title: getLongString(ngstore.AlertRuleMaxTitleLength + 1),
+ Title: getLongString(t, ngstore.AlertRuleMaxTitleLength+1),
Condition: "A",
Data: []ngmodels.AlertQuery{
{
@@ -251,7 +298,7 @@ func TestAlertRuleCRUD(t *testing.T) {
},
{
desc: "alert rule with too long rulegroup",
- rulegroup: getLongString(ngstore.AlertRuleMaxTitleLength + 1),
+ rulegroup: getLongString(t, ngstore.AlertRuleMaxTitleLength+1),
rule: apimodels.PostableExtendedRuleNode{
ApiRuleNode: &apimodels.ApiRuleNode{
For: interval,
@@ -386,7 +433,7 @@ func TestAlertRuleCRUD(t *testing.T) {
err := enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
@@ -466,7 +513,7 @@ func TestAlertRuleCRUD(t *testing.T) {
err := enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
@@ -483,7 +530,7 @@ func TestAlertRuleCRUD(t *testing.T) {
// With the rules created, let's make sure that rule definition is stored correctly.
{
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(u)
require.NoError(t, err)
@@ -523,7 +570,7 @@ func TestAlertRuleCRUD(t *testing.T) {
},
"grafana_alert":{
"id":1,
- "orgId":2,
+ "orgId":1,
"title":"AlwaysFiring",
"condition":"A",
"data":[
@@ -558,7 +605,7 @@ func TestAlertRuleCRUD(t *testing.T) {
"expr":"",
"grafana_alert":{
"id":2,
- "orgId":2,
+ "orgId":1,
"title":"AlwaysFiringButSilenced",
"condition":"A",
"data":[
@@ -645,7 +692,7 @@ func TestAlertRuleCRUD(t *testing.T) {
err = enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
@@ -660,7 +707,7 @@ func TestAlertRuleCRUD(t *testing.T) {
require.JSONEq(t, `{"error":"failed to get alert rule unknown: could not find alert rule", "message": "failed to update rule group"}`, string(b))
// let's make sure that rule definitions are not affected by the failed POST request.
- u = fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err = http.Get(u)
require.NoError(t, err)
@@ -729,7 +776,7 @@ func TestAlertRuleCRUD(t *testing.T) {
err = enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
@@ -744,7 +791,7 @@ func TestAlertRuleCRUD(t *testing.T) {
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
// let's make sure that rule definitions are updated correctly.
- u = fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err = http.Get(u)
require.NoError(t, err)
@@ -782,7 +829,7 @@ func TestAlertRuleCRUD(t *testing.T) {
},
"grafana_alert":{
"id":1,
- "orgId":2,
+ "orgId":1,
"title":"AlwaysNormal",
"condition":"A",
"data":[
@@ -823,7 +870,7 @@ func TestAlertRuleCRUD(t *testing.T) {
// Finally, make sure we can delete it.
{
t.Run("fail if he rule group name does not exists", func(t *testing.T) {
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default/groupnotexist", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/groupnotexist", grafanaListedAddr)
req, err := http.NewRequest(http.MethodDelete, u, nil)
require.NoError(t, err)
resp, err := client.Do(req)
@@ -840,7 +887,7 @@ func TestAlertRuleCRUD(t *testing.T) {
})
t.Run("succeed if the rule group name does exist", func(t *testing.T) {
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default/arulegroup", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/arulegroup", grafanaListedAddr)
req, err := http.NewRequest(http.MethodDelete, u, nil)
require.NoError(t, err)
resp, err := client.Do(req)
@@ -856,6 +903,124 @@ func TestAlertRuleCRUD(t *testing.T) {
require.JSONEq(t, `{"message":"rule group deleted"}`, string(b))
})
}
+}
+
+func TestQuota(t *testing.T) {
+ // Setup Grafana and its Database
+ dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
+ EnableFeatureToggles: []string{"ngalert"},
+ EnableQuota: true,
+ DisableAnonymous: true,
+ })
+
+ store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
+ grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
+
+ // Create the namespace we'll save our alerts to.
+ require.NoError(t, createFolder(t, store, 0, "default"))
+
+ // Create a user to make authenticated requests
+ require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
+
+ interval, err := model.ParseDuration("1m")
+ require.NoError(t, err)
+
+ // check quota limits
+ t.Run("when quota limit exceed", func(t *testing.T) {
+ // get existing org quota
+ query := models.GetOrgQuotaByTargetQuery{OrgId: 1, Target: "alert_rule"}
+ err = sqlstore.GetOrgQuotaByTarget(&query)
+ require.NoError(t, err)
+ used := query.Result.Used
+ limit := query.Result.Limit
+
+ // set org quota limit to equal used
+ orgCmd := models.UpdateOrgQuotaCmd{
+ OrgId: 1,
+ Target: "alert_rule",
+ Limit: used,
+ }
+ err := sqlstore.UpdateOrgQuota(&orgCmd)
+ require.NoError(t, err)
+
+ t.Cleanup(func() {
+ // reset org quota to original value
+ orgCmd := models.UpdateOrgQuotaCmd{
+ OrgId: 1,
+ Target: "alert_rule",
+ Limit: limit,
+ }
+ err := sqlstore.UpdateOrgQuota(&orgCmd)
+ require.NoError(t, err)
+ })
+
+ // try to create an alert rule
+ rules := apimodels.PostableRuleGroupConfig{
+ Name: "arulegroup",
+ Interval: interval,
+ Rules: []apimodels.PostableExtendedRuleNode{
+ {
+ GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
+ Title: "One more alert rule",
+ Condition: "A",
+ Data: []ngmodels.AlertQuery{
+ {
+ RefID: "A",
+ RelativeTimeRange: ngmodels.RelativeTimeRange{
+ From: ngmodels.Duration(time.Duration(5) * time.Hour),
+ To: ngmodels.Duration(time.Duration(3) * time.Hour),
+ },
+ Model: json.RawMessage(`{
+ "datasourceUid": "-100",
+ "type": "math",
+ "expression": "2 + 3 > 1"
+ }`),
+ },
+ },
+ },
+ },
+ },
+ }
+ buf := bytes.Buffer{}
+ enc := json.NewEncoder(&buf)
+ err = enc.Encode(&rules)
+ require.NoError(t, err)
+
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ // nolint:gosec
+ resp, err := http.Post(u, "application/json", &buf)
+ require.NoError(t, err)
+ t.Cleanup(func() {
+ err := resp.Body.Close()
+ require.NoError(t, err)
+ })
+ b, err := ioutil.ReadAll(resp.Body)
+ require.NoError(t, err)
+
+ assert.Equal(t, http.StatusForbidden, resp.StatusCode)
+ require.JSONEq(t, `{"message":"quota reached"}`, string(b))
+ })
+}
+
+func TestEval(t *testing.T) {
+ // Setup Grafana and its Database
+ dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
+ EnableFeatureToggles: []string{"ngalert"},
+ EnableQuota: true,
+ DisableAnonymous: true,
+ })
+
+ store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
+ grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
+
+ require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
+
+ // Create the namespace we'll save our alerts to.
+ require.NoError(t, createFolder(t, store, 0, "default"))
// test eval conditions
testCases := []struct {
@@ -1022,7 +1187,7 @@ func TestAlertRuleCRUD(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
- u := fmt.Sprintf("http://%s/api/v1/rule/test/grafana", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/v1/rule/test/grafana", grafanaListedAddr)
r := strings.NewReader(tc.payload)
// nolint:gosec
resp, err := http.Post(u, "application/json", r)
@@ -1178,7 +1343,7 @@ func TestAlertRuleCRUD(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
- u := fmt.Sprintf("http://%s/api/v1/eval", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/v1/eval", grafanaListedAddr)
r := strings.NewReader(tc.payload)
// nolint:gosec
resp, err := http.Post(u, "application/json", r)
@@ -1202,7 +1367,7 @@ func createFolder(t *testing.T, store *sqlstore.SQLStore, folderID int64, folder
t.Helper()
cmd := models.SaveDashboardCommand{
- OrgId: 2, // This is the orgID of the anonymous user.
+ OrgId: 1, // default organisation
FolderId: folderID,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -1244,7 +1409,21 @@ func rulesNamespaceWithoutVariableValues(t *testing.T, b []byte) (string, map[st
return string(json), m
}
-func getLongString(n int) string {
+func createUser(t *testing.T, store *sqlstore.SQLStore, role models.RoleType, username, password string) error {
+ t.Helper()
+
+ cmd := models.CreateUserCommand{
+ Login: username,
+ Password: password,
+ DefaultOrgRole: string(role),
+ }
+ _, err := store.CreateUser(context.Background(), cmd)
+ return err
+}
+
+func getLongString(t *testing.T, n int) string {
+ t.Helper()
+
b := make([]rune, n)
for i := range b {
b[i] = 'a'
diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go
index 027c531cbce..81b49aad636 100644
--- a/pkg/tests/api/alerting/api_prometheus_test.go
+++ b/pkg/tests/api/alerting/api_prometheus_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "github.com/grafana/grafana/pkg/bus"
"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"
@@ -21,18 +22,23 @@ import (
func TestPrometheusRules(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
- AnonymousUserRole: models.ROLE_EDITOR,
+ DisableAnonymous: true,
})
store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
- // Create the namespace we'll save our alerts to.
+ // Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
require.NoError(t, createFolder(t, store, 0, "default"))
+ // Create a user to make authenticated requests
+ require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
+
interval, err := model.ParseDuration("10s")
require.NoError(t, err)
- // When we have no alerting rules, it returns an empty list.
+ // an unauthenticated request to get rules should fail
{
promRulesURL := fmt.Sprintf("http://%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
@@ -42,6 +48,20 @@ func TestPrometheusRules(t *testing.T) {
err := resp.Body.Close()
require.NoError(t, err)
})
+ require.NoError(t, err)
+ assert.Equal(t, 401, resp.StatusCode)
+ }
+
+ // When we have no alerting rules, it returns an empty list.
+ {
+ promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
+ // nolint:gosec
+ resp, err := http.Get(promRulesURL)
+ require.NoError(t, err)
+ t.Cleanup(func() {
+ err := resp.Body.Close()
+ require.NoError(t, err)
+ })
b, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, 200, resp.StatusCode)
@@ -109,7 +129,7 @@ func TestPrometheusRules(t *testing.T) {
err := enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
@@ -126,7 +146,7 @@ func TestPrometheusRules(t *testing.T) {
// Now, let's see how this looks like.
{
- promRulesURL := fmt.Sprintf("http://%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
+ promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(promRulesURL)
require.NoError(t, err)
@@ -181,7 +201,7 @@ func TestPrometheusRules(t *testing.T) {
}
{
- promRulesURL := fmt.Sprintf("http://%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
+ promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
require.Eventually(t, func() bool {
resp, err := http.Get(promRulesURL)
diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go
index 6b4750e88a8..cdac017299f 100644
--- a/pkg/tests/api/alerting/api_ruler_test.go
+++ b/pkg/tests/api/alerting/api_ruler_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "github.com/grafana/grafana/pkg/bus"
"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"
@@ -22,11 +23,16 @@ func TestAlertRulePermissions(t *testing.T) {
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
- AnonymousUserRole: models.ROLE_EDITOR,
+ DisableAnonymous: true,
})
store := testinfra.SetUpDatabase(t, dir)
+ // override bus to get the GetSignedInUserQuery handler
+ store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
+ // Create a user to make authenticated requests
+ require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
+
// Create the namespace we'll save our alerts to.
require.NoError(t, createFolder(t, store, 0, "folder1"))
@@ -41,7 +47,7 @@ func TestAlertRulePermissions(t *testing.T) {
// With the rules created, let's make sure that rule definitions are stored.
{
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(u)
require.NoError(t, err)
@@ -73,7 +79,7 @@ func TestAlertRulePermissions(t *testing.T) {
},
"grafana_alert":{
"id":1,
- "orgId":2,
+ "orgId":1,
"title":"rule under folder folder1",
"condition":"A",
"data":[
@@ -123,7 +129,7 @@ func TestAlertRulePermissions(t *testing.T) {
},
"grafana_alert":{
"id":2,
- "orgId":2,
+ "orgId":1,
"title":"rule under folder folder2",
"condition":"A",
"data":[
@@ -195,7 +201,7 @@ func TestAlertRulePermissions(t *testing.T) {
},
"grafana_alert":{
"id":1,
- "orgId":2,
+ "orgId":1,
"title":"rule under folder folder1",
"condition":"A",
"data":[
@@ -276,7 +282,7 @@ func createRule(t *testing.T, grafanaListedAddr string, folder string) {
err = enc.Encode(&rules)
require.NoError(t, err)
- u := fmt.Sprintf("http://%s/api/ruler/grafana/api/v1/rules/%s", grafanaListedAddr, folder)
+ u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/%s", grafanaListedAddr, folder)
// nolint:gosec
resp, err := http.Post(u, "application/json", &buf)
require.NoError(t, err)
diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go
index 8d25914490b..106f3dc05b5 100644
--- a/pkg/tests/testinfra/testinfra.go
+++ b/pkg/tests/testinfra/testinfra.go
@@ -219,6 +219,18 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
_, err = anonSect.NewKey("org_role", string(o.AnonymousUserRole))
require.NoError(t, err)
}
+ if o.EnableQuota {
+ quotaSection, err := cfg.NewSection("quota")
+ require.NoError(t, err)
+ _, err = quotaSection.NewKey("enabled", "true")
+ require.NoError(t, err)
+ }
+ if o.DisableAnonymous {
+ anonSect, err := cfg.GetSection("auth.anonymous")
+ require.NoError(t, err)
+ _, err = anonSect.NewKey("enabled", "false")
+ require.NoError(t, err)
+ }
}
cfgPath := filepath.Join(cfgDir, "test.ini")
@@ -235,4 +247,6 @@ type GrafanaOpts struct {
EnableCSP bool
EnableFeatureToggles []string
AnonymousUserRole models.RoleType
+ EnableQuota bool
+ DisableAnonymous bool
}