mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
[Alerting]: Extend quota service to optionally set limits on alerts (#33283)
* Quota: Extend service to set limit on alerts * Add test for applying quota to alert rules * Apply suggestions from code review Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com> * Get used alert quota only if naglert is enabled * Set alert limit to zero if nglalert is not enabled Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
parent
985331e813
commit
540f110220
@ -651,6 +651,9 @@ org_data_source = 10
|
|||||||
# limit number of api_keys per Org.
|
# limit number of api_keys per Org.
|
||||||
org_api_key = 10
|
org_api_key = 10
|
||||||
|
|
||||||
|
# limit number of alerts per Org.
|
||||||
|
org_alert_rule = 100
|
||||||
|
|
||||||
# limit number of orgs a user can create.
|
# limit number of orgs a user can create.
|
||||||
user_org = 10
|
user_org = 10
|
||||||
|
|
||||||
@ -669,6 +672,9 @@ global_api_key = -1
|
|||||||
# global limit on number of logged in users.
|
# global limit on number of logged in users.
|
||||||
global_session = -1
|
global_session = -1
|
||||||
|
|
||||||
|
# global limit of alerts
|
||||||
|
global_alert_rule = -1
|
||||||
|
|
||||||
#################################### Alerting ############################
|
#################################### Alerting ############################
|
||||||
[alerting]
|
[alerting]
|
||||||
# Disable alerting engine & UI features
|
# Disable alerting engine & UI features
|
||||||
|
@ -640,6 +640,9 @@
|
|||||||
# limit number of api_keys per Org.
|
# limit number of api_keys per Org.
|
||||||
; org_api_key = 10
|
; org_api_key = 10
|
||||||
|
|
||||||
|
# limit number of alerts per Org.
|
||||||
|
;org_alert_rule = 100
|
||||||
|
|
||||||
# limit number of orgs a user can create.
|
# limit number of orgs a user can create.
|
||||||
; user_org = 10
|
; user_org = 10
|
||||||
|
|
||||||
@ -658,6 +661,9 @@
|
|||||||
# global limit on number of logged in users.
|
# global limit on number of logged in users.
|
||||||
; global_session = -1
|
; global_session = -1
|
||||||
|
|
||||||
|
# global limit of alerts
|
||||||
|
;global_alert_rule = -1
|
||||||
|
|
||||||
#################################### Alerting ############################
|
#################################### Alerting ############################
|
||||||
[alerting]
|
[alerting]
|
||||||
# Disable alerting engine & UI features
|
# Disable alerting engine & UI features
|
||||||
|
@ -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.
|
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
|
### user_org
|
||||||
|
|
||||||
Limit the number of organizations a user can create. Default is 10.
|
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).
|
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).
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
## [alerting]
|
## [alerting]
|
||||||
|
@ -208,6 +208,61 @@ func TestMiddlewareQuota(t *testing.T) {
|
|||||||
cfg.Quota.Org.Dashboard = quotaUsed
|
cfg.Quota.Org.Dashboard = quotaUsed
|
||||||
cfg.Quota.Enabled = false
|
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,
|
Dashboard: 5,
|
||||||
DataSource: 5,
|
DataSource: 5,
|
||||||
ApiKey: 5,
|
ApiKey: 5,
|
||||||
|
AlertRule: 5,
|
||||||
},
|
},
|
||||||
User: &setting.UserQuota{
|
User: &setting.UserQuota{
|
||||||
Org: 5,
|
Org: 5,
|
||||||
@ -241,6 +297,7 @@ func configure(cfg *setting.Cfg) {
|
|||||||
DataSource: 5,
|
DataSource: 5,
|
||||||
ApiKey: 5,
|
ApiKey: 5,
|
||||||
Session: 5,
|
Session: 5,
|
||||||
|
AlertRule: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,33 +44,38 @@ type GlobalQuotaDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetOrgQuotaByTargetQuery struct {
|
type GetOrgQuotaByTargetQuery struct {
|
||||||
Target string
|
Target string
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Default int64
|
Default int64
|
||||||
Result *OrgQuotaDTO
|
IsNgAlertEnabled bool
|
||||||
|
Result *OrgQuotaDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetOrgQuotasQuery struct {
|
type GetOrgQuotasQuery struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Result []*OrgQuotaDTO
|
IsNgAlertEnabled bool
|
||||||
|
Result []*OrgQuotaDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserQuotaByTargetQuery struct {
|
type GetUserQuotaByTargetQuery struct {
|
||||||
Target string
|
Target string
|
||||||
UserId int64
|
UserId int64
|
||||||
Default int64
|
Default int64
|
||||||
Result *UserQuotaDTO
|
IsNgAlertEnabled bool
|
||||||
|
Result *UserQuotaDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserQuotasQuery struct {
|
type GetUserQuotasQuery struct {
|
||||||
UserId int64
|
UserId int64
|
||||||
Result []*UserQuotaDTO
|
IsNgAlertEnabled bool
|
||||||
|
Result []*UserQuotaDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetGlobalQuotaByTargetQuery struct {
|
type GetGlobalQuotaByTargetQuery struct {
|
||||||
Target string
|
Target string
|
||||||
Default int64
|
Default int64
|
||||||
Result *GlobalQuotaDTO
|
IsNgAlertEnabled bool
|
||||||
|
Result *GlobalQuotaDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateOrgQuotaCmd struct {
|
type UpdateOrgQuotaCmd struct {
|
||||||
|
@ -3,6 +3,8 @@ package api
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||||
|
|
||||||
@ -41,6 +43,7 @@ type API struct {
|
|||||||
DatasourceCache datasources.CacheService
|
DatasourceCache datasources.CacheService
|
||||||
RouteRegister routing.RouteRegister
|
RouteRegister routing.RouteRegister
|
||||||
DataService *tsdb.Service
|
DataService *tsdb.Service
|
||||||
|
QuotaService *quota.QuotaService
|
||||||
Schedule schedule.ScheduleService
|
Schedule schedule.ScheduleService
|
||||||
RuleStore store.RuleStore
|
RuleStore store.RuleStore
|
||||||
InstanceStore store.InstanceStore
|
InstanceStore store.InstanceStore
|
||||||
@ -73,7 +76,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.Metrics) {
|
|||||||
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
||||||
api.DatasourceCache,
|
api.DatasourceCache,
|
||||||
NewLotexRuler(proxy, logger),
|
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)
|
), m)
|
||||||
api.RegisterTestingApiEndpoints(TestingApiSrv{
|
api.RegisterTestingApiEndpoints(TestingApiSrv{
|
||||||
AlertingProxy: proxy,
|
AlertingProxy: proxy,
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
|
|
||||||
coreapi "github.com/grafana/grafana/pkg/api"
|
coreapi "github.com/grafana/grafana/pkg/api"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
@ -23,6 +24,7 @@ import (
|
|||||||
type RulerSrv struct {
|
type RulerSrv struct {
|
||||||
store store.RuleStore
|
store store.RuleStore
|
||||||
DatasourceCache datasources.CacheService
|
DatasourceCache datasources.CacheService
|
||||||
|
QuotaService *quota.QuotaService
|
||||||
manager *state.Manager
|
manager *state.Manager
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
@ -209,8 +211,18 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
|||||||
return toNamespaceErrorResponse(err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check permissions
|
// quotas are checked in advanced
|
||||||
// TODO check quota
|
// 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 validate UID uniqueness in the payload
|
||||||
|
|
||||||
//TODO: Should this belong in alerting-api?
|
//TODO: Should this belong in alerting-api?
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ type AlertNG struct {
|
|||||||
DataService *tsdb.Service `inject:""`
|
DataService *tsdb.Service `inject:""`
|
||||||
Alertmanager *notifier.Alertmanager `inject:""`
|
Alertmanager *notifier.Alertmanager `inject:""`
|
||||||
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
|
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
|
||||||
|
QuotaService *quota.QuotaService `inject:""`
|
||||||
Metrics *metrics.Metrics `inject:""`
|
Metrics *metrics.Metrics `inject:""`
|
||||||
Log log.Logger
|
Log log.Logger
|
||||||
schedule schedule.ScheduleService
|
schedule schedule.ScheduleService
|
||||||
@ -83,6 +86,7 @@ func (ng *AlertNG) Init() error {
|
|||||||
DataService: ng.DataService,
|
DataService: ng.DataService,
|
||||||
Schedule: ng.schedule,
|
Schedule: ng.schedule,
|
||||||
DataProxy: ng.DataProxy,
|
DataProxy: ng.DataProxy,
|
||||||
|
QuotaService: ng.QuotaService,
|
||||||
InstanceStore: store,
|
InstanceStore: store,
|
||||||
RuleStore: store,
|
RuleStore: store,
|
||||||
AlertingStore: store,
|
AlertingStore: store,
|
||||||
|
@ -64,7 +64,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target}
|
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, IsNgAlertEnabled: qs.Cfg.IsNgAlertEnabled()}
|
||||||
if err := bus.Dispatch(&query); err != nil {
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
|||||||
if !c.IsSignedIn {
|
if !c.IsSignedIn {
|
||||||
continue
|
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 {
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool,
|
|||||||
if !c.IsSignedIn || c.UserId == 0 {
|
if !c.IsSignedIn || c.UserId == 0 {
|
||||||
continue
|
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 {
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
return true, err
|
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},
|
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.Session},
|
||||||
)
|
)
|
||||||
return scopes, nil
|
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:
|
default:
|
||||||
return scopes, ErrInvalidQuotaTarget
|
return scopes, ErrInvalidQuotaTarget
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const ALERT_RULE_TARGET = "alert_rule"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
bus.AddHandler("sql", GetOrgQuotaByTarget)
|
bus.AddHandler("sql", GetOrgQuotaByTarget)
|
||||||
bus.AddHandler("sql", GetOrgQuotas)
|
bus.AddHandler("sql", GetOrgQuotas)
|
||||||
@ -35,18 +37,22 @@ func GetOrgQuotaByTarget(query *models.GetOrgQuotaByTargetQuery) error {
|
|||||||
quota.Limit = query.Default
|
quota.Limit = query.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
// get quota used.
|
var used int64
|
||||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target))
|
if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
|
||||||
resp := make([]*targetCount, 0)
|
// get quota used.
|
||||||
if err := x.SQL(rawSQL, query.OrgId).Find(&resp); err != nil {
|
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target))
|
||||||
return err
|
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{
|
query.Result = &models.OrgQuotaDTO{
|
||||||
Target: query.Target,
|
Target: query.Target,
|
||||||
Limit: quota.Limit,
|
Limit: quota.Limit,
|
||||||
OrgId: query.OrgId,
|
OrgId: query.OrgId,
|
||||||
Used: resp[0].Count,
|
Used: used,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -78,17 +84,21 @@ func GetOrgQuotas(query *models.GetOrgQuotasQuery) error {
|
|||||||
|
|
||||||
result := make([]*models.OrgQuotaDTO, len(quotas))
|
result := make([]*models.OrgQuotaDTO, len(quotas))
|
||||||
for i, q := range quotas {
|
for i, q := range quotas {
|
||||||
// get quota used.
|
var used int64
|
||||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
|
if q.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
|
||||||
resp := make([]*targetCount, 0)
|
// get quota used.
|
||||||
if err := x.SQL(rawSQL, q.OrgId).Find(&resp); err != nil {
|
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
|
||||||
return err
|
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{
|
result[i] = &models.OrgQuotaDTO{
|
||||||
Target: q.Target,
|
Target: q.Target,
|
||||||
Limit: q.Limit,
|
Limit: q.Limit,
|
||||||
OrgId: q.OrgId,
|
OrgId: q.OrgId,
|
||||||
Used: resp[0].Count,
|
Used: used,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query.Result = result
|
query.Result = result
|
||||||
@ -138,18 +148,22 @@ func GetUserQuotaByTarget(query *models.GetUserQuotaByTargetQuery) error {
|
|||||||
quota.Limit = query.Default
|
quota.Limit = query.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
// get quota used.
|
var used int64
|
||||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
|
if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
|
||||||
resp := make([]*targetCount, 0)
|
// get quota used.
|
||||||
if err := x.SQL(rawSQL, query.UserId).Find(&resp); err != nil {
|
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
|
||||||
return err
|
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{
|
query.Result = &models.UserQuotaDTO{
|
||||||
Target: query.Target,
|
Target: query.Target,
|
||||||
Limit: quota.Limit,
|
Limit: quota.Limit,
|
||||||
UserId: query.UserId,
|
UserId: query.UserId,
|
||||||
Used: resp[0].Count,
|
Used: used,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -181,17 +195,21 @@ func GetUserQuotas(query *models.GetUserQuotasQuery) error {
|
|||||||
|
|
||||||
result := make([]*models.UserQuotaDTO, len(quotas))
|
result := make([]*models.UserQuotaDTO, len(quotas))
|
||||||
for i, q := range quotas {
|
for i, q := range quotas {
|
||||||
// get quota used.
|
var used int64
|
||||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
|
if q.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
|
||||||
resp := make([]*targetCount, 0)
|
// get quota used.
|
||||||
if err := x.SQL(rawSQL, q.UserId).Find(&resp); err != nil {
|
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
|
||||||
return err
|
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{
|
result[i] = &models.UserQuotaDTO{
|
||||||
Target: q.Target,
|
Target: q.Target,
|
||||||
Limit: q.Limit,
|
Limit: q.Limit,
|
||||||
UserId: q.UserId,
|
UserId: q.UserId,
|
||||||
Used: resp[0].Count,
|
Used: used,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
query.Result = result
|
query.Result = result
|
||||||
@ -230,17 +248,22 @@ func UpdateUserQuota(cmd *models.UpdateUserQuotaCmd) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalQuotaByTarget(query *models.GetGlobalQuotaByTargetQuery) error {
|
func GetGlobalQuotaByTarget(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||||
// get quota used.
|
var used int64
|
||||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target))
|
if query.Target != ALERT_RULE_TARGET || query.IsNgAlertEnabled {
|
||||||
resp := make([]*targetCount, 0)
|
// get quota used.
|
||||||
if err := x.SQL(rawSQL).Find(&resp); err != nil {
|
|
||||||
return err
|
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{
|
query.Result = &models.GlobalQuotaDTO{
|
||||||
Target: query.Target,
|
Target: query.Target,
|
||||||
Limit: query.Default,
|
Limit: query.Default,
|
||||||
Used: resp[0].Count,
|
Used: used,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -24,6 +24,7 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
|
|||||||
Dashboard: 5,
|
Dashboard: 5,
|
||||||
DataSource: 5,
|
DataSource: 5,
|
||||||
ApiKey: 5,
|
ApiKey: 5,
|
||||||
|
AlertRule: 5,
|
||||||
},
|
},
|
||||||
User: &setting.UserQuota{
|
User: &setting.UserQuota{
|
||||||
Org: 5,
|
Org: 5,
|
||||||
@ -35,6 +36,7 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
|
|||||||
DataSource: 5,
|
DataSource: 5,
|
||||||
ApiKey: 5,
|
ApiKey: 5,
|
||||||
Session: 5,
|
Session: 5,
|
||||||
|
AlertRule: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +89,19 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(query.Result.Used, ShouldEqual, 0)
|
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() {
|
Convey("Should be able to quota list for org", func() {
|
||||||
query := models.GetOrgQuotasQuery{OrgId: orgId}
|
query := models.GetOrgQuotasQuery{OrgId: orgId}
|
||||||
err = GetOrgQuotas(&query)
|
err = GetOrgQuotas(&query)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(query.Result), ShouldEqual, 4)
|
So(len(query.Result), ShouldEqual, 5)
|
||||||
for _, res := range query.Result {
|
for _, res := range query.Result {
|
||||||
limit := 5 // default quota limit
|
limit := 5 // default quota limit
|
||||||
used := 0
|
used := 0
|
||||||
@ -169,6 +178,14 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
|
|||||||
So(query.Result.Limit, ShouldEqual, 5)
|
So(query.Result.Limit, ShouldEqual, 5)
|
||||||
So(query.Result.Used, ShouldEqual, 1)
|
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
|
// related: https://github.com/grafana/grafana/issues/14342
|
||||||
Convey("Should org quota updating is successful even if it called multiple time", func() {
|
Convey("Should org quota updating is successful even if it called multiple time", func() {
|
||||||
|
@ -158,7 +158,6 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser() error {
|
|||||||
|
|
||||||
// ensure admin user
|
// ensure admin user
|
||||||
if !ss.Cfg.DisableInitAdminCreation {
|
if !ss.Cfg.DisableInitAdminCreation {
|
||||||
ss.log.Debug("Creating default admin user")
|
|
||||||
ss.log.Debug("Creating default admin user")
|
ss.log.Debug("Creating default admin user")
|
||||||
if _, err := ss.createUser(ctx, sess, userCreationArgs{
|
if _, err := ss.createUser(ctx, sess, userCreationArgs{
|
||||||
Login: ss.Cfg.AdminUser,
|
Login: ss.Cfg.AdminUser,
|
||||||
|
@ -9,6 +9,7 @@ type OrgQuota struct {
|
|||||||
DataSource int64 `target:"data_source"`
|
DataSource int64 `target:"data_source"`
|
||||||
Dashboard int64 `target:"dashboard"`
|
Dashboard int64 `target:"dashboard"`
|
||||||
ApiKey int64 `target:"api_key"`
|
ApiKey int64 `target:"api_key"`
|
||||||
|
AlertRule int64 `target:"alert_rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserQuota struct {
|
type UserQuota struct {
|
||||||
@ -22,6 +23,7 @@ type GlobalQuota struct {
|
|||||||
Dashboard int64 `target:"dashboard"`
|
Dashboard int64 `target:"dashboard"`
|
||||||
ApiKey int64 `target:"api_key"`
|
ApiKey int64 `target:"api_key"`
|
||||||
Session int64 `target:"-"`
|
Session int64 `target:"-"`
|
||||||
|
AlertRule int64 `target:"alert_rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *OrgQuota) ToMap() map[string]int64 {
|
func (q *OrgQuota) ToMap() map[string]int64 {
|
||||||
@ -64,12 +66,19 @@ func (cfg *Cfg) readQuotaSettings() {
|
|||||||
quota := cfg.Raw.Section("quota")
|
quota := cfg.Raw.Section("quota")
|
||||||
Quota.Enabled = quota.Key("enabled").MustBool(false)
|
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
|
// per ORG Limits
|
||||||
Quota.Org = &OrgQuota{
|
Quota.Org = &OrgQuota{
|
||||||
User: quota.Key("org_user").MustInt64(10),
|
User: quota.Key("org_user").MustInt64(10),
|
||||||
DataSource: quota.Key("org_data_source").MustInt64(10),
|
DataSource: quota.Key("org_data_source").MustInt64(10),
|
||||||
Dashboard: quota.Key("org_dashboard").MustInt64(10),
|
Dashboard: quota.Key("org_dashboard").MustInt64(10),
|
||||||
ApiKey: quota.Key("org_api_key").MustInt64(10),
|
ApiKey: quota.Key("org_api_key").MustInt64(10),
|
||||||
|
AlertRule: alertOrgQuota,
|
||||||
}
|
}
|
||||||
|
|
||||||
// per User limits
|
// per User limits
|
||||||
@ -85,6 +94,7 @@ func (cfg *Cfg) readQuotaSettings() {
|
|||||||
Dashboard: quota.Key("global_dashboard").MustInt64(-1),
|
Dashboard: quota.Key("global_dashboard").MustInt64(-1),
|
||||||
ApiKey: quota.Key("global_api_key").MustInt64(-1),
|
ApiKey: quota.Key("global_api_key").MustInt64(-1),
|
||||||
Session: quota.Key("global_session").MustInt64(-1),
|
Session: quota.Key("global_session").MustInt64(-1),
|
||||||
|
AlertRule: alertGlobalQuota,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Quota = Quota
|
cfg.Quota = Quota
|
||||||
|
@ -2,6 +2,7 @@ package alerting
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,6 +11,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -26,15 +29,52 @@ import (
|
|||||||
func TestAlertAndGroupsQuery(t *testing.T) {
|
func TestAlertAndGroupsQuery(t *testing.T) {
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
EnableFeatureToggles: []string{"ngalert"},
|
EnableFeatureToggles: []string{"ngalert"},
|
||||||
AnonymousUserRole: models.ROLE_EDITOR,
|
DisableAnonymous: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
store := testinfra.SetUpDatabase(t, dir)
|
store := testinfra.SetUpDatabase(t, dir)
|
||||||
|
// override bus to get the GetSignedInUserQuery handler
|
||||||
|
store.Bus = bus.GetBus()
|
||||||
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
|
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.
|
// 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
|
// nolint:gosec
|
||||||
resp, err := http.Get(alertsURL)
|
resp, err := http.Get(alertsURL)
|
||||||
require.NoError(t, err)
|
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.
|
// 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
|
// nolint:gosec
|
||||||
resp, err := http.Get(alertsURL)
|
resp, err := http.Get(alertsURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -106,7 +146,7 @@ func TestAlertAndGroupsQuery(t *testing.T) {
|
|||||||
err = enc.Encode(&rules)
|
err = enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
@ -119,7 +159,7 @@ func TestAlertAndGroupsQuery(t *testing.T) {
|
|||||||
|
|
||||||
// Eventually, we'll get an alert with its state being active.
|
// 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
|
// nolint:gosec
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
resp, err := http.Get(alertsURL)
|
resp, err := http.Get(alertsURL)
|
||||||
@ -150,11 +190,18 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
// Setup Grafana and its Database
|
// Setup Grafana and its Database
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
EnableFeatureToggles: []string{"ngalert"},
|
EnableFeatureToggles: []string{"ngalert"},
|
||||||
AnonymousUserRole: models.ROLE_EDITOR,
|
EnableQuota: true,
|
||||||
|
DisableAnonymous: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
store := testinfra.SetUpDatabase(t, dir)
|
store := testinfra.SetUpDatabase(t, dir)
|
||||||
|
// override bus to get the GetSignedInUserQuery handler
|
||||||
|
store.Bus = bus.GetBus()
|
||||||
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
|
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.
|
// Create the namespace we'll save our alerts to.
|
||||||
require.NoError(t, createFolder(t, store, 0, "default"))
|
require.NoError(t, createFolder(t, store, 0, "default"))
|
||||||
|
|
||||||
@ -229,7 +276,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
Annotations: map[string]string{"annotation1": "val1"},
|
Annotations: map[string]string{"annotation1": "val1"},
|
||||||
},
|
},
|
||||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||||
Title: getLongString(ngstore.AlertRuleMaxTitleLength + 1),
|
Title: getLongString(t, ngstore.AlertRuleMaxTitleLength+1),
|
||||||
Condition: "A",
|
Condition: "A",
|
||||||
Data: []ngmodels.AlertQuery{
|
Data: []ngmodels.AlertQuery{
|
||||||
{
|
{
|
||||||
@ -251,7 +298,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "alert rule with too long rulegroup",
|
desc: "alert rule with too long rulegroup",
|
||||||
rulegroup: getLongString(ngstore.AlertRuleMaxTitleLength + 1),
|
rulegroup: getLongString(t, ngstore.AlertRuleMaxTitleLength+1),
|
||||||
rule: apimodels.PostableExtendedRuleNode{
|
rule: apimodels.PostableExtendedRuleNode{
|
||||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||||
For: interval,
|
For: interval,
|
||||||
@ -386,7 +433,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
err := enc.Encode(&rules)
|
err := enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -466,7 +513,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
err := enc.Encode(&rules)
|
err := enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
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.
|
// 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
|
// nolint:gosec
|
||||||
resp, err := http.Get(u)
|
resp, err := http.Get(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -523,7 +570,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":1,
|
"id":1,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"AlwaysFiring",
|
"title":"AlwaysFiring",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -558,7 +605,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
"expr":"",
|
"expr":"",
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":2,
|
"id":2,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"AlwaysFiringButSilenced",
|
"title":"AlwaysFiringButSilenced",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -645,7 +692,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
err = enc.Encode(&rules)
|
err = enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
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))
|
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.
|
// 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
|
// nolint:gosec
|
||||||
resp, err = http.Get(u)
|
resp, err = http.Get(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -729,7 +776,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
err = enc.Encode(&rules)
|
err = enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -744,7 +791,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
||||||
|
|
||||||
// let's make sure that rule definitions are updated correctly.
|
// 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
|
// nolint:gosec
|
||||||
resp, err = http.Get(u)
|
resp, err = http.Get(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -782,7 +829,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":1,
|
"id":1,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"AlwaysNormal",
|
"title":"AlwaysNormal",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -823,7 +870,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
// Finally, make sure we can delete it.
|
// Finally, make sure we can delete it.
|
||||||
{
|
{
|
||||||
t.Run("fail if he rule group name does not exists", func(t *testing.T) {
|
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)
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
resp, err := client.Do(req)
|
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) {
|
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)
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@ -856,6 +903,124 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
require.JSONEq(t, `{"message":"rule group deleted"}`, string(b))
|
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
|
// test eval conditions
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -1022,7 +1187,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
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)
|
r := strings.NewReader(tc.payload)
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", r)
|
resp, err := http.Post(u, "application/json", r)
|
||||||
@ -1178,7 +1343,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
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)
|
r := strings.NewReader(tc.payload)
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", r)
|
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()
|
t.Helper()
|
||||||
|
|
||||||
cmd := models.SaveDashboardCommand{
|
cmd := models.SaveDashboardCommand{
|
||||||
OrgId: 2, // This is the orgID of the anonymous user.
|
OrgId: 1, // default organisation
|
||||||
FolderId: folderID,
|
FolderId: folderID,
|
||||||
IsFolder: true,
|
IsFolder: true,
|
||||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||||
@ -1244,7 +1409,21 @@ func rulesNamespaceWithoutVariableValues(t *testing.T, b []byte) (string, map[st
|
|||||||
return string(json), m
|
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)
|
b := make([]rune, n)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = 'a'
|
b[i] = 'a'
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
@ -21,18 +22,23 @@ import (
|
|||||||
func TestPrometheusRules(t *testing.T) {
|
func TestPrometheusRules(t *testing.T) {
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
EnableFeatureToggles: []string{"ngalert"},
|
EnableFeatureToggles: []string{"ngalert"},
|
||||||
AnonymousUserRole: models.ROLE_EDITOR,
|
DisableAnonymous: true,
|
||||||
})
|
})
|
||||||
store := testinfra.SetUpDatabase(t, dir)
|
store := testinfra.SetUpDatabase(t, dir)
|
||||||
|
// override bus to get the GetSignedInUserQuery handler
|
||||||
|
store.Bus = bus.GetBus()
|
||||||
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
|
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"))
|
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")
|
interval, err := model.ParseDuration("10s")
|
||||||
require.NoError(t, err)
|
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)
|
promRulesURL := fmt.Sprintf("http://%s/api/prometheus/grafana/api/v1/rules", grafanaListedAddr)
|
||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
@ -42,6 +48,20 @@ func TestPrometheusRules(t *testing.T) {
|
|||||||
err := resp.Body.Close()
|
err := resp.Body.Close()
|
||||||
require.NoError(t, err)
|
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)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 200, resp.StatusCode)
|
assert.Equal(t, 200, resp.StatusCode)
|
||||||
@ -109,7 +129,7 @@ func TestPrometheusRules(t *testing.T) {
|
|||||||
err := enc.Encode(&rules)
|
err := enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -126,7 +146,7 @@ func TestPrometheusRules(t *testing.T) {
|
|||||||
|
|
||||||
// Now, let's see how this looks like.
|
// 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
|
// nolint:gosec
|
||||||
resp, err := http.Get(promRulesURL)
|
resp, err := http.Get(promRulesURL)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
resp, err := http.Get(promRulesURL)
|
resp, err := http.Get(promRulesURL)
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
@ -22,11 +23,16 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
// Setup Grafana and its Database
|
// Setup Grafana and its Database
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
EnableFeatureToggles: []string{"ngalert"},
|
EnableFeatureToggles: []string{"ngalert"},
|
||||||
AnonymousUserRole: models.ROLE_EDITOR,
|
DisableAnonymous: true,
|
||||||
})
|
})
|
||||||
store := testinfra.SetUpDatabase(t, dir)
|
store := testinfra.SetUpDatabase(t, dir)
|
||||||
|
// override bus to get the GetSignedInUserQuery handler
|
||||||
|
store.Bus = bus.GetBus()
|
||||||
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
|
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.
|
// Create the namespace we'll save our alerts to.
|
||||||
require.NoError(t, createFolder(t, store, 0, "folder1"))
|
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.
|
// 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
|
// nolint:gosec
|
||||||
resp, err := http.Get(u)
|
resp, err := http.Get(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -73,7 +79,7 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":1,
|
"id":1,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"rule under folder folder1",
|
"title":"rule under folder folder1",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -123,7 +129,7 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":2,
|
"id":2,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"rule under folder folder2",
|
"title":"rule under folder folder2",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -195,7 +201,7 @@ func TestAlertRulePermissions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"grafana_alert":{
|
"grafana_alert":{
|
||||||
"id":1,
|
"id":1,
|
||||||
"orgId":2,
|
"orgId":1,
|
||||||
"title":"rule under folder folder1",
|
"title":"rule under folder folder1",
|
||||||
"condition":"A",
|
"condition":"A",
|
||||||
"data":[
|
"data":[
|
||||||
@ -276,7 +282,7 @@ func createRule(t *testing.T, grafanaListedAddr string, folder string) {
|
|||||||
err = enc.Encode(&rules)
|
err = enc.Encode(&rules)
|
||||||
require.NoError(t, err)
|
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
|
// nolint:gosec
|
||||||
resp, err := http.Post(u, "application/json", &buf)
|
resp, err := http.Post(u, "application/json", &buf)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -219,6 +219,18 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
|||||||
_, err = anonSect.NewKey("org_role", string(o.AnonymousUserRole))
|
_, err = anonSect.NewKey("org_role", string(o.AnonymousUserRole))
|
||||||
require.NoError(t, err)
|
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")
|
cfgPath := filepath.Join(cfgDir, "test.ini")
|
||||||
@ -235,4 +247,6 @@ type GrafanaOpts struct {
|
|||||||
EnableCSP bool
|
EnableCSP bool
|
||||||
EnableFeatureToggles []string
|
EnableFeatureToggles []string
|
||||||
AnonymousUserRole models.RoleType
|
AnonymousUserRole models.RoleType
|
||||||
|
EnableQuota bool
|
||||||
|
DisableAnonymous bool
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user