mirror of
https://github.com/grafana/grafana.git
synced 2024-12-28 18:01:40 -06:00
Chore: Refactor quota service (#57586)
* Chore: refactore quota service * Apply suggestions from code review
This commit is contained in:
parent
faa0fda6eb
commit
326ea86a57
@ -245,7 +245,7 @@ func (hs *HTTPServer) AdminDeleteUser(c *models.ReqContext) response.Response {
|
||||
return nil
|
||||
})
|
||||
g.Go(func() error {
|
||||
if err := hs.QuotaService.DeleteByUser(ctx, cmd.UserID); err != nil {
|
||||
if err := hs.QuotaService.DeleteQuotaForUser(ctx, cmd.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -36,12 +36,16 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/correlations"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -69,8 +73,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// not logged in views
|
||||
r.Get("/logout", hs.Logout)
|
||||
r.Post("/login", quota("session"), routing.Wrap(hs.LoginPost))
|
||||
r.Get("/login/:name", quota("session"), hs.OAuthLogin)
|
||||
r.Post("/login", quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginPost))
|
||||
r.Get("/login/:name", quota(string(auth.QuotaTargetSrv)), hs.OAuthLogin)
|
||||
r.Get("/login", hs.LoginView)
|
||||
r.Get("/invite/:code", hs.Index)
|
||||
|
||||
@ -173,7 +177,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/verify", hs.Index)
|
||||
r.Get("/signup", hs.Index)
|
||||
r.Get("/api/user/signup/options", routing.Wrap(GetSignUpOptions))
|
||||
r.Post("/api/user/signup", quota("user"), routing.Wrap(hs.SignUp))
|
||||
r.Post("/api/user/signup", quota(user.QuotaTargetSrv), quota(org.QuotaTargetSrv), routing.Wrap(hs.SignUp))
|
||||
r.Post("/api/user/signup/step2", routing.Wrap(hs.SignUpStep2))
|
||||
|
||||
// invited
|
||||
@ -192,7 +196,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index)
|
||||
|
||||
// api renew session based on cookie
|
||||
r.Get("/api/login/ping", quota("session"), routing.Wrap(hs.LoginAPIPing))
|
||||
r.Get("/api/login/ping", quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginAPIPing))
|
||||
|
||||
// expose plugin file system assets
|
||||
r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets)
|
||||
@ -298,13 +302,13 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgsWrite)), routing.Wrap(hs.UpdateCurrentOrgAddress))
|
||||
orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.SearchOrgUsersWithPaging))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), routing.Wrap(hs.AddOrgUserToCurrentOrg))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota(user.QuotaTargetSrv), quota(org.QuotaTargetSrv), routing.Wrap(hs.AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersWrite, userIDScope)), routing.Wrap(hs.UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg))
|
||||
|
||||
// invites
|
||||
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), quota("user"), routing.Wrap(hs.AddOrgInvite))
|
||||
orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), quota(user.QuotaTargetSrv), quota(user.QuotaTargetSrv), routing.Wrap(hs.AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.RevokeInvite))
|
||||
|
||||
// prefs
|
||||
@ -331,7 +335,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
})
|
||||
|
||||
// create new org
|
||||
apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsCreate)), quota("org"), routing.Wrap(hs.CreateOrg))
|
||||
apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsCreate)), quota(org.QuotaTargetSrv), routing.Wrap(hs.CreateOrg))
|
||||
|
||||
// search all orgs
|
||||
apiRoute.Get("/orgs", authorizeInOrg(reqGrafanaAdmin, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsRead)), routing.Wrap(hs.SearchOrgs))
|
||||
@ -358,7 +362,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
apikeyIDScope := ac.Scope("apikeys", "id", ac.Parameter(":id"))
|
||||
keysRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyRead)), routing.Wrap(hs.GetAPIKeys))
|
||||
keysRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyCreate)), quota("api_key"), routing.Wrap(hs.AddAPIKey))
|
||||
keysRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyCreate)), quota(string(apikey.QuotaTargetSrv)), routing.Wrap(hs.AddAPIKey))
|
||||
keysRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyDelete, apikeyIDScope)), routing.Wrap(hs.DeleteAPIKey))
|
||||
})
|
||||
|
||||
@ -373,7 +377,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
uidScope := datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":uid"))
|
||||
nameScope := datasources.ScopeProvider.GetResourceScopeName(ac.Parameter(":name"))
|
||||
datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionRead)), routing.Wrap(hs.GetDataSources))
|
||||
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionCreate)), quota("data_source"), routing.Wrap(hs.AddDataSource))
|
||||
datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionCreate)), quota(string(datasources.QuotaTargetSrv)), routing.Wrap(hs.AddDataSource))
|
||||
datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, idScope)), routing.Wrap(hs.UpdateDataSourceByID))
|
||||
datasourceRoute.Put("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(hs.UpdateDataSourceByUID))
|
||||
datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionDelete, idScope)), routing.Wrap(hs.DeleteDataSourceById))
|
||||
|
@ -45,7 +45,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/preference/preftest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
@ -249,15 +248,13 @@ func (s *fakeRenderService) Init() error {
|
||||
}
|
||||
|
||||
func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []accesscontrol.Permission) (*scenarioContext, *HTTPServer) {
|
||||
cfg.Quota.Enabled = false
|
||||
|
||||
store := db.InitTestDB(t)
|
||||
store := sqlstore.InitTestDB(t)
|
||||
hs := &HTTPServer{
|
||||
Cfg: cfg,
|
||||
Live: newTestLive(t, store),
|
||||
License: &licensing.OSSLicensingService{},
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
QuotaService: "aimpl.Service{Cfg: cfg},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
RouteRegister: routing.NewRouteRegister(),
|
||||
AccessControl: accesscontrolmock.New().WithPermissions(permissions),
|
||||
searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()),
|
||||
@ -376,7 +373,9 @@ func setupHTTPServerWithCfgDb(
|
||||
routeRegister := routing.NewRouteRegister()
|
||||
teamService := teamimpl.ProvideService(db, cfg)
|
||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||
dashboardsStore := dashboardsstore.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardsStore, err := dashboardsstore.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
var acmock *accesscontrolmock.Mock
|
||||
var ac accesscontrol.AccessControl
|
||||
@ -402,7 +401,8 @@ func setupHTTPServerWithCfgDb(
|
||||
acService, err = acimpl.ProvideService(cfg, db, routeRegister, localcache.ProvideService(), featuremgmt.WithFeatures())
|
||||
require.NoError(t, err)
|
||||
ac = acimpl.ProvideAccessControl(cfg)
|
||||
userSvc = userimpl.ProvideService(db, nil, cfg, teamimpl.ProvideService(db, cfg), localcache.ProvideService())
|
||||
userSvc, err = userimpl.ProvideService(db, nil, cfg, teamimpl.ProvideService(db, cfg), localcache.ProvideService(), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, license, acService, teamService, userSvc)
|
||||
require.NoError(t, err)
|
||||
@ -412,7 +412,7 @@ func setupHTTPServerWithCfgDb(
|
||||
Cfg: cfg,
|
||||
Features: features,
|
||||
Live: newTestLive(t, db),
|
||||
QuotaService: "aimpl.Service{Cfg: cfg},
|
||||
QuotaService: quotaService,
|
||||
RouteRegister: routeRegister,
|
||||
SQLStore: store,
|
||||
License: &licensing.OSSLicensingService{},
|
||||
@ -497,7 +497,7 @@ func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Serv
|
||||
RouteRegister: routing.NewRouteRegister(),
|
||||
License: &licensing.OSSLicensingService{},
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
QuotaService: quotatest.NewQuotaServiceFake(),
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
searchUsersService: &searchusers.OSSService{},
|
||||
}
|
||||
|
||||
|
@ -407,7 +407,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
|
||||
dash := cmd.GetDashboardModel()
|
||||
newDashboard := dash.Id == 0
|
||||
if newDashboard {
|
||||
limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard")
|
||||
limitReached, err := hs.QuotaService.QuotaReached(c, dashboards.QuotaTargetSrv)
|
||||
if err != nil {
|
||||
return response.Error(500, "failed to get quota", err)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import (
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
"github.com/grafana/grafana/pkg/services/preference/preftest"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
@ -150,6 +150,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
DashboardService: dashboardService,
|
||||
dashboardVersionService: fakeDashboardVersionService,
|
||||
Coremodels: registry.NewBase(nil),
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
}
|
||||
|
||||
setUp := func() {
|
||||
@ -990,9 +991,12 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
provisioningService = provisioning.NewProvisioningServiceMock(context.Background())
|
||||
}
|
||||
|
||||
var err error
|
||||
if dashboardStore == nil {
|
||||
sql := db.InitTestDB(t)
|
||||
dashboardStore = database.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err = database.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
libraryPanelsService := mockLibraryPanelService{}
|
||||
@ -1031,7 +1035,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
require.Equal(sc.t, 200, sc.resp.Code)
|
||||
|
||||
dash := dtos.DashboardFullWithMeta{}
|
||||
err := json.NewDecoder(sc.resp.Body).Decode(&dash)
|
||||
err = json.NewDecoder(sc.resp.Body).Decode(&dash)
|
||||
require.NoError(sc.t, err)
|
||||
|
||||
return dash
|
||||
@ -1077,12 +1081,10 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
hs := HTTPServer{
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: "aimpl.Service{
|
||||
Cfg: cfg,
|
||||
},
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
pluginStore: &plugins.FakePluginStore{},
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
@ -1116,7 +1118,7 @@ func postValidateScenario(t *testing.T, desc string, url string, routePattern st
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: "aimpl.Service{Cfg: cfg},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
SQLStore: sqlmock,
|
||||
@ -1152,7 +1154,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: "aimpl.Service{Cfg: cfg},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
SQLStore: sqlmock,
|
||||
@ -1190,7 +1192,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
|
||||
Cfg: cfg,
|
||||
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
|
||||
Live: newTestLive(t, db.InitTestDB(t)),
|
||||
QuotaService: "aimpl.Service{Cfg: cfg},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
DashboardService: mock,
|
||||
|
@ -146,7 +146,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.folderService = folderService
|
||||
hs.AccessControl = acmock.New()
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
|
||||
t.Run("Should attach access control metadata to multiple folders", func(t *testing.T) {
|
||||
|
@ -94,12 +94,12 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
|
||||
serverFeatureEnabled := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.queryDataService = qds
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, true)
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
serverFeatureDisabled := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.queryDataService = qds
|
||||
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, false)
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
|
||||
t.Run("Status code is 400 when data source response has an error and feature toggle is disabled", func(t *testing.T) {
|
||||
@ -142,7 +142,7 @@ func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
|
||||
)
|
||||
httpServer := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.queryDataService = qds
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
|
||||
t.Run("Status code is 500 and a secrets plugin error is returned if there is a problem getting secrets from the remote plugin", func(t *testing.T) {
|
||||
@ -294,7 +294,7 @@ func TestDataSourceQueryError(t *testing.T) {
|
||||
pluginClient.ProvideService(r, &config.Cfg{}),
|
||||
&fakeOAuthTokenService{},
|
||||
)
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
req := srv.NewPostRequest("/api/ds/query", strings.NewReader(tc.request))
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer})
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -104,7 +105,8 @@ func TestAPIEndpoint_PutCurrentOrg_LegacyAccessControl(t *testing.T) {
|
||||
})
|
||||
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg)
|
||||
sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
t.Run("Admin can update current org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
@ -118,7 +120,8 @@ func TestAPIEndpoint_PutCurrentOrg_AccessControl(t *testing.T) {
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", sc.initCtx.UserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg)
|
||||
sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating current org with correct permissions", func(t *testing.T) {
|
||||
@ -436,7 +439,9 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) {
|
||||
cfg.RBACEnabled = false
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg)
|
||||
var err error
|
||||
sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
|
||||
|
||||
@ -456,7 +461,9 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) {
|
||||
|
||||
func TestAPIEndpoint_PutOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg)
|
||||
var err error
|
||||
sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
|
||||
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
@ -389,11 +390,13 @@ func TestGetOrgUsersAPIEndpoint_AccessControlMetadata(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = tc.enableAccessControl
|
||||
var err error
|
||||
sc := setupHTTPServerWithCfg(t, false, cfg, func(hs *HTTPServer) {
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(),
|
||||
)
|
||||
hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
hs.orgService, err = orgimpl.ProvideService(hs.SQLStore, cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
@ -403,7 +406,7 @@ func TestGetOrgUsersAPIEndpoint_AccessControlMetadata(t *testing.T) {
|
||||
require.Equal(t, tc.expectedCode, response.Code)
|
||||
|
||||
var userList []*models.OrgUserDTO
|
||||
err := json.NewDecoder(response.Body).Decode(&userList)
|
||||
err = json.NewDecoder(response.Body).Decode(&userList)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expectedMetadata != nil {
|
||||
@ -493,11 +496,14 @@ func TestGetOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = tc.enableAccessControl
|
||||
var err error
|
||||
sc := setupHTTPServerWithCfg(t, false, cfg, func(hs *HTTPServer) {
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(),
|
||||
)
|
||||
hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
hs.orgService, err = orgimpl.ProvideService(hs.SQLStore, cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
@ -598,10 +604,11 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = tc.enableAccessControl
|
||||
var err error
|
||||
sc := setupHTTPServerWithCfg(t, false, cfg, func(hs *HTTPServer) {
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(),
|
||||
)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
@ -716,11 +723,12 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
var err error
|
||||
sc := setupHTTPServer(t, true, func(hs *HTTPServer) {
|
||||
hs.tempUserService = tempuserimpl.ProvideService(hs.SQLStore)
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, setting.NewCfg(), teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), setting.NewCfg()), localcache.ProvideService(),
|
||||
)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, setting.NewCfg(), teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), setting.NewCfg()), localcache.ProvideService(), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
@ -835,11 +843,14 @@ func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = tc.enableAccessControl
|
||||
var err error
|
||||
sc := setupHTTPServerWithCfg(t, false, cfg, func(hs *HTTPServer) {
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(),
|
||||
)
|
||||
hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
hs.orgService, err = orgimpl.ProvideService(hs.SQLStore, cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
@ -962,11 +973,14 @@ func TestDeleteOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = tc.enableAccessControl
|
||||
var err error
|
||||
sc := setupHTTPServerWithCfg(t, false, cfg, func(hs *HTTPServer) {
|
||||
hs.userService = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(),
|
||||
)
|
||||
hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
hs.userService, err = userimpl.ProvideService(
|
||||
hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
hs.orgService, err = orgimpl.ProvideService(hs.SQLStore, cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
setupOrgUsersDBForAccessControlTests(t, sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
|
@ -41,7 +41,7 @@ func TestGetPluginDashboards(t *testing.T) {
|
||||
|
||||
s := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.pluginDashboardService = pluginDashboardService
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
|
||||
t.Run("Not signed in should return 404 Not Found", func(t *testing.T) {
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
@ -138,7 +139,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider,
|
||||
&oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
@ -151,7 +154,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path and has dynamic url", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[3]
|
||||
@ -163,7 +168,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path with no url", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[4]
|
||||
@ -174,7 +181,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path and has dynamic body", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[5]
|
||||
@ -188,7 +197,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
t.Run("Validating request", func(t *testing.T) {
|
||||
t.Run("plugin route with valid role", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@ -197,7 +208,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@ -207,7 +220,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgRole = org.RoleAdmin
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@ -298,7 +313,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
|
||||
@ -314,7 +331,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
|
||||
require.NoError(t, err)
|
||||
client = newFakeHTTPClient(t, json2)
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[1], dsInfo, cfg)
|
||||
@ -331,7 +350,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
client = newFakeHTTPClient(t, []byte{})
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
|
||||
@ -355,7 +376,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@ -382,7 +405,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -408,7 +433,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -438,7 +465,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, pluginRoutes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -463,7 +492,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@ -514,7 +545,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@ -651,7 +684,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -671,7 +706,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -687,7 +724,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -711,7 +750,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -738,7 +779,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -764,7 +807,9 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -790,8 +835,11 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
_, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
var err error
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
require.Error(t, err)
|
||||
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
|
||||
}
|
||||
@ -812,8 +860,10 @@ func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
_, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@ -856,7 +906,9 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
if tc.err == nil {
|
||||
require.NoError(t, err)
|
||||
@ -884,7 +936,9 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@ -1001,7 +1055,9 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, secrets
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
|
||||
var routes []*plugins.Route
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -1045,7 +1101,9 @@ func Test_PathCheck(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(&datasources.DataSource{}, routes, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -60,7 +60,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled,
|
||||
}
|
||||
hs.pluginInstaller = inst
|
||||
hs.QuotaService = quotatest.NewQuotaServiceFake()
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
})
|
||||
|
||||
t.Run(testName("Install", tc), func(t *testing.T) {
|
||||
|
@ -6,10 +6,22 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
// swagger:route GET /org/quotas getCurrentOrg getCurrentOrgQuota
|
||||
//
|
||||
// Fetch Organization quota.
|
||||
//
|
||||
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `orgs.quotas:read` and scope `org:id:1` (orgIDScope).
|
||||
//
|
||||
// Responses:
|
||||
// 200: getQuotaResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetCurrentOrgQuotas(c *models.ReqContext) response.Response {
|
||||
return hs.getOrgQuotasHelper(c, c.OrgID)
|
||||
}
|
||||
@ -29,22 +41,17 @@ func (hs *HTTPServer) GetCurrentOrgQuotas(c *models.ReqContext) response.Respons
|
||||
func (hs *HTTPServer) GetOrgQuotas(c *models.ReqContext) response.Response {
|
||||
orgId, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("orgId is invalid: %w", err))
|
||||
}
|
||||
return hs.getOrgQuotasHelper(c, orgId)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) response.Response {
|
||||
if !hs.Cfg.Quota.Enabled {
|
||||
return response.Error(404, "Quotas not enabled", nil)
|
||||
q, err := hs.QuotaService.GetQuotasByScope(c.Req.Context(), quota.OrgScope, orgID)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get quota", err)
|
||||
}
|
||||
query := models.GetOrgQuotasQuery{OrgId: orgID}
|
||||
|
||||
if err := hs.SQLStore.GetOrgQuotas(c.Req.Context(), &query); err != nil {
|
||||
return response.Error(500, "Failed to get org quotas", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, query.Result)
|
||||
return response.JSON(http.StatusOK, q)
|
||||
}
|
||||
|
||||
// swagger:route PUT /orgs/{org_id}/quotas/{quota_target} orgs updateOrgQuota
|
||||
@ -63,26 +70,19 @@ func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) resp
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response {
|
||||
cmd := models.UpdateOrgQuotaCmd{}
|
||||
cmd := quota.UpdateQuotaCmd{}
|
||||
var err error
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("bad request data: %w", err))
|
||||
}
|
||||
if !hs.Cfg.Quota.Enabled {
|
||||
return response.Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
cmd.OrgId, err = strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
||||
cmd.OrgID, err = strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("orgId is invalid: %w", err))
|
||||
}
|
||||
cmd.Target = web.Params(c.Req)[":target"]
|
||||
|
||||
if _, ok := hs.Cfg.Quota.Org.ToMap()[cmd.Target]; !ok {
|
||||
return response.Error(404, "Invalid quota target", nil)
|
||||
}
|
||||
|
||||
if err := hs.SQLStore.UpdateOrgQuota(c.Req.Context(), &cmd); err != nil {
|
||||
return response.Error(500, "Failed to update org quotas", err)
|
||||
if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err)
|
||||
}
|
||||
return response.Success("Organization quota updated")
|
||||
}
|
||||
@ -114,22 +114,17 @@ func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetUserQuotas(c *models.ReqContext) response.Response {
|
||||
if !setting.Quota.Enabled {
|
||||
return response.Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "id is invalid", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("id is invalid: %w", err))
|
||||
}
|
||||
|
||||
query := models.GetUserQuotasQuery{UserId: id}
|
||||
|
||||
if err := hs.SQLStore.GetUserQuotas(c.Req.Context(), &query); err != nil {
|
||||
return response.Error(500, "Failed to get org quotas", err)
|
||||
q, err := hs.QuotaService.GetQuotasByScope(c.Req.Context(), quota.UserScope, id)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to get org quotas", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, query.Result)
|
||||
return response.JSON(http.StatusOK, q)
|
||||
}
|
||||
|
||||
// swagger:route PUT /admin/users/{user_id}/quotas/{quota_target} admin_users updateUserQuota
|
||||
@ -148,26 +143,19 @@ func (hs *HTTPServer) GetUserQuotas(c *models.ReqContext) response.Response {
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response {
|
||||
cmd := models.UpdateUserQuotaCmd{}
|
||||
cmd := quota.UpdateQuotaCmd{}
|
||||
var err error
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("bad request data: %w", err))
|
||||
}
|
||||
if !setting.Quota.Enabled {
|
||||
return response.Error(404, "Quotas not enabled", nil)
|
||||
}
|
||||
cmd.UserId, err = strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
||||
cmd.UserID, err = strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "id is invalid", err)
|
||||
return response.Err(quota.ErrBadRequest.Errorf("id is invalid: %w", err))
|
||||
}
|
||||
cmd.Target = web.Params(c.Req)[":target"]
|
||||
|
||||
if _, ok := setting.Quota.User.ToMap()[cmd.Target]; !ok {
|
||||
return response.Error(404, "Invalid quota target", nil)
|
||||
}
|
||||
|
||||
if err := hs.SQLStore.UpdateUserQuota(c.Req.Context(), &cmd); err != nil {
|
||||
return response.Error(500, "Failed to update org quotas", err)
|
||||
if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err)
|
||||
}
|
||||
return response.Success("Organization quota updated")
|
||||
}
|
||||
@ -176,7 +164,7 @@ func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response {
|
||||
type UpdateUserQuotaParams struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body models.UpdateUserQuotaCmd `json:"body"`
|
||||
Body quota.UpdateQuotaCmd `json:"body"`
|
||||
// in:path
|
||||
// required:true
|
||||
QuotaTarget string `json:"quota_target"`
|
||||
@ -203,7 +191,7 @@ type GetOrgQuotaParams struct {
|
||||
type UpdateOrgQuotaParam struct {
|
||||
// in:body
|
||||
// required:true
|
||||
Body models.UpdateOrgQuotaCmd `json:"body"`
|
||||
Body quota.UpdateQuotaCmd `json:"body"`
|
||||
// in:path
|
||||
// required:true
|
||||
QuotaTarget string `json:"quota_target"`
|
||||
@ -215,5 +203,5 @@ type UpdateOrgQuotaParam struct {
|
||||
// swagger:response getQuotaResponse
|
||||
type GetQuotaResponseResponse struct {
|
||||
// in:body
|
||||
Body []*models.UserQuotaDTO `json:"body"`
|
||||
Body []*quota.QuotaDTO `json:"body"`
|
||||
}
|
||||
|
@ -32,17 +32,13 @@ var testOrgQuota = setting.OrgQuota{
|
||||
func setupDBAndSettingsForAccessControlQuotaTests(t *testing.T, sc accessControlScenarioContext) {
|
||||
t.Helper()
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
// Required while sqlstore quota.go relies on setting global variables
|
||||
setting.Quota = sc.hs.Cfg.Quota
|
||||
|
||||
// Create two orgs with the context user
|
||||
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota.Enabled = true
|
||||
cfg.RBACEnabled = false
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
@ -62,7 +58,9 @@ func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota.Enabled = true
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
@ -86,6 +84,7 @@ func TestAPIEndpoint_GetCurrentOrgQuotas_AccessControl(t *testing.T) {
|
||||
|
||||
func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota.Enabled = true
|
||||
cfg.RBACEnabled = false
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
@ -105,7 +104,9 @@ func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota.Enabled = true
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) {
|
||||
@ -130,6 +131,7 @@ func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) {
|
||||
|
||||
func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota.Enabled = true
|
||||
cfg.RBACEnabled = false
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
@ -151,7 +153,20 @@ func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Global: setting.GlobalQuota{
|
||||
Org: 5,
|
||||
},
|
||||
Org: setting.OrgQuota{
|
||||
User: 5,
|
||||
},
|
||||
User: setting.UserQuota{
|
||||
Org: 5,
|
||||
},
|
||||
}
|
||||
sc := setupHTTPServerWithCfg(t, true, cfg)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
authinfostore "github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
@ -68,7 +69,8 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
}
|
||||
user, err := sqlStore.CreateUser(context.Background(), createUserCmd)
|
||||
require.Nil(t, err)
|
||||
hs.userService = userimpl.ProvideService(sqlStore, nil, sc.cfg, nil, nil)
|
||||
hs.userService, err = userimpl.ProvideService(sqlStore, nil, sc.cfg, nil, nil, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.handlerFunc = hs.GetUserByID
|
||||
|
||||
|
@ -254,7 +254,7 @@ var wireSet = wire.NewSet(
|
||||
wire.Bind(new(social.Service), new(*social.SocialService)),
|
||||
oauthtoken.ProvideService,
|
||||
auth.ProvideActiveAuthTokenService,
|
||||
wire.Bind(new(models.ActiveTokenService), new(*auth.ActiveAuthTokenService)),
|
||||
wire.Bind(new(auth.ActiveTokenService), new(*auth.ActiveAuthTokenService)),
|
||||
wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
|
||||
tempo.ProvideService,
|
||||
loki.ProvideService,
|
||||
|
@ -14,15 +14,15 @@ func Quota(quotaService quota.Service) func(string) web.Handler {
|
||||
panic("quotaService is nil")
|
||||
}
|
||||
//https://open.spotify.com/track/7bZSoBEAEEUsGEuLOf94Jm?si=T1Tdju5qRSmmR0zph_6RBw fuuuuunky
|
||||
return func(target string) web.Handler {
|
||||
return func(targetSrv string) web.Handler {
|
||||
return func(c *models.ReqContext) {
|
||||
limitReached, err := quotaService.QuotaReached(c, target)
|
||||
limitReached, err := quotaService.QuotaReached(c, quota.TargetSrv(targetSrv))
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get quota", err)
|
||||
return
|
||||
}
|
||||
if limitReached {
|
||||
c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", target), nil)
|
||||
c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", targetSrv), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@ -30,8 +30,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.User = 4
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -41,8 +39,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.Session = 10
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -52,13 +48,10 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.Session = 1
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with user logged in", func(t *testing.T) {
|
||||
const quotaUsed = 4
|
||||
setUp := func(sc *scenarioContext) {
|
||||
sc.withTokenSessionCookie("token")
|
||||
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: 12}
|
||||
@ -79,8 +72,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.DataSource = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "user Org quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -93,8 +84,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.User.Org = quotaUsed + 1
|
||||
})
|
||||
|
||||
middlewareScenario(t, "user Org quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -106,8 +95,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.User.Org = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -119,8 +106,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed + 1
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -132,8 +117,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota reached, but quotas disabled", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -145,9 +128,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed
|
||||
cfg.Quota.Enabled = false
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org alert quota reached and unified alerting is enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -162,7 +142,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
|
||||
cfg.UnifiedAlerting.Enabled = new(bool)
|
||||
*cfg.UnifiedAlerting.Enabled = true
|
||||
cfg.Quota.Org.AlertRule = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org alert quota not reached and unified alerting is enabled", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -177,7 +156,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
|
||||
cfg.UnifiedAlerting.Enabled = new(bool)
|
||||
*cfg.UnifiedAlerting.Enabled = true
|
||||
cfg.Quota.Org.AlertRule = quotaUsed + 1
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org alert quota reached but ngalert disabled", func(t *testing.T, sc *scenarioContext) {
|
||||
@ -190,8 +168,6 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
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) {
|
||||
@ -203,58 +179,15 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.AlertRule = quotaUsed + 1
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getQuotaHandler(reached bool, target string) web.Handler {
|
||||
qs := &mockQuotaService{
|
||||
reached: reached,
|
||||
}
|
||||
qs := quotatest.New(reached, nil)
|
||||
return Quota(qs)(target)
|
||||
}
|
||||
|
||||
func configure(cfg *setting.Cfg) {
|
||||
cfg.AnonymousEnabled = false
|
||||
cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Org: &setting.OrgQuota{
|
||||
User: 5,
|
||||
Dashboard: 5,
|
||||
DataSource: 5,
|
||||
ApiKey: 5,
|
||||
AlertRule: 5,
|
||||
},
|
||||
User: &setting.UserQuota{
|
||||
Org: 5,
|
||||
},
|
||||
Global: &setting.GlobalQuota{
|
||||
Org: 5,
|
||||
User: 5,
|
||||
Dashboard: 5,
|
||||
DataSource: 5,
|
||||
ApiKey: 5,
|
||||
Session: 5,
|
||||
AlertRule: 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mockQuotaService struct {
|
||||
reached bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockQuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) {
|
||||
return m.reached, m.err
|
||||
}
|
||||
|
||||
func (m *mockQuotaService) CheckQuotaReached(c context.Context, target string, params *quota.ScopeParameters) (bool, error) {
|
||||
return m.reached, m.err
|
||||
}
|
||||
|
||||
func (m *mockQuotaService) DeleteByUser(c context.Context, userID int64) error {
|
||||
return m.err
|
||||
}
|
||||
|
@ -1,91 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidQuotaTarget = errors.New("invalid quota target")
|
||||
|
||||
type Quota struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
UserId int64
|
||||
Target string
|
||||
Limit int64
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type QuotaScope struct {
|
||||
Name string
|
||||
Target string
|
||||
DefaultLimit int64
|
||||
}
|
||||
|
||||
type OrgQuotaDTO struct {
|
||||
OrgId int64 `json:"org_id"`
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
Used int64 `json:"used"`
|
||||
}
|
||||
|
||||
type UserQuotaDTO struct {
|
||||
UserId int64 `json:"user_id"`
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
Used int64 `json:"used"`
|
||||
}
|
||||
|
||||
type GlobalQuotaDTO struct {
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
Used int64 `json:"used"`
|
||||
}
|
||||
|
||||
type GetOrgQuotaByTargetQuery struct {
|
||||
Target string
|
||||
OrgId int64
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *OrgQuotaDTO
|
||||
}
|
||||
|
||||
type GetOrgQuotasQuery struct {
|
||||
OrgId int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result []*OrgQuotaDTO
|
||||
}
|
||||
|
||||
type GetUserQuotaByTargetQuery struct {
|
||||
Target string
|
||||
UserId int64
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *UserQuotaDTO
|
||||
}
|
||||
|
||||
type GetUserQuotasQuery struct {
|
||||
UserId int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result []*UserQuotaDTO
|
||||
}
|
||||
|
||||
type GetGlobalQuotaByTargetQuery struct {
|
||||
Target string
|
||||
Default int64
|
||||
UnifiedAlertingEnabled bool
|
||||
Result *GlobalQuotaDTO
|
||||
}
|
||||
|
||||
type UpdateOrgQuotaCmd struct {
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
OrgId int64 `json:"-"`
|
||||
}
|
||||
|
||||
type UpdateUserQuotaCmd struct {
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
UserId int64 `json:"-"`
|
||||
}
|
@ -76,10 +76,6 @@ type UserTokenService interface {
|
||||
GetUserRevokedTokens(ctx context.Context, userId int64) ([]*UserToken, error)
|
||||
}
|
||||
|
||||
type ActiveTokenService interface {
|
||||
ActiveTokenCount(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
type UserTokenBackgroundService interface {
|
||||
registry.BackgroundService
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ var wireBasicSet = wire.NewSet(
|
||||
wire.Bind(new(social.Service), new(*social.SocialService)),
|
||||
oauthtoken.ProvideService,
|
||||
auth.ProvideActiveAuthTokenService,
|
||||
wire.Bind(new(models.ActiveTokenService), new(*auth.ActiveAuthTokenService)),
|
||||
wire.Bind(new(auth.ActiveTokenService), new(*auth.ActiveAuthTokenService)),
|
||||
wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
|
||||
tempo.ProvideService,
|
||||
loki.ProvideService,
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
@ -225,7 +226,8 @@ func setupTestEnvironment(t *testing.T, permissions []accesscontrol.Permission,
|
||||
sql := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
teamSvc := teamimpl.ProvideService(sql, cfg)
|
||||
userSvc := userimpl.ProvideService(sql, nil, cfg, teamimpl.ProvideService(sql, cfg), nil)
|
||||
userSvc, err := userimpl.ProvideService(sql, nil, cfg, teamimpl.ProvideService(sql, cfg), nil, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
license := licensingtest.NewFakeLicensing()
|
||||
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
|
||||
mock := accesscontrolmock.New().WithPermissions(permissions)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -56,7 +57,9 @@ func TestIntegrationAnnotations(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
dashboardStore := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDashboard1 := models.SaveDashboardCommand{
|
||||
UserId: 1,
|
||||
@ -453,7 +456,9 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
|
||||
|
||||
var maximumTagsLength int64 = 60
|
||||
repo := xormRepositoryImpl{db: sql, cfg: setting.NewCfg(), log: log.New("annotation.test"), tagService: tagimpl.ProvideService(sql, sql.Cfg), maximumTagsLength: maximumTagsLength}
|
||||
dashboardStore := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDashboard1 := models.SaveDashboardCommand{
|
||||
UserId: 1,
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -13,16 +14,34 @@ type Service struct {
|
||||
store store
|
||||
}
|
||||
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg) apikey.Service {
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (apikey.Service, error) {
|
||||
s := &Service{}
|
||||
if cfg.IsFeatureToggleEnabled(featuremgmt.FlagNewDBLibrary) {
|
||||
return &Service{
|
||||
store: &sqlxStore{
|
||||
sess: db.GetSqlxSession(),
|
||||
cfg: cfg,
|
||||
},
|
||||
s.store = &sqlxStore{
|
||||
sess: db.GetSqlxSession(),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
return &Service{store: &sqlStore{db: db, cfg: cfg}}
|
||||
s.store = &sqlStore{db: db, cfg: cfg}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: apikey.QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Usage,
|
||||
}); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) Usage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return s.store.Count(ctx, scopeParams)
|
||||
}
|
||||
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error {
|
||||
@ -49,3 +68,24 @@ func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) error {
|
||||
func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
|
||||
return s.store.UpdateAPIKeyLastUsedDate(ctx, tokenID)
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
orgQuotaTag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.ApiKey)
|
||||
limits.Set(orgQuotaTag, cfg.Quota.Org.ApiKey)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@ -142,3 +143,35 @@ func (ss *sqlxStore) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64
|
||||
_, err := ss.sess.Exec(ctx, `UPDATE api_key SET last_used_at=? WHERE id=?`, &now, tokenID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ss *sqlxStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
if err := ss.sess.Get(ctx, &r, `SELECT COUNT(*) AS count FROM api_key`); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
|
||||
if scopeParams.OrgID != 0 {
|
||||
if err := ss.sess.Get(ctx, &r, `SELECT COUNT(*) AS count FROM api_key WHERE org_id = ?`, scopeParams.OrgID); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
)
|
||||
|
||||
type store interface {
|
||||
@ -15,4 +16,6 @@ type store interface {
|
||||
GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) error
|
||||
GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error)
|
||||
UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error
|
||||
|
||||
Count(context.Context, *quota.ScopeParameters) (*quota.Map, error)
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -174,3 +176,47 @@ func (ss *sqlStore) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM api_key"
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
|
||||
if scopeParams.OrgID != 0 {
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM api_key WHERE org_id = ?"
|
||||
if _, err := sess.SQL(rawSQL, scopeParams.OrgID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
@ -64,3 +65,8 @@ type GetByIDQuery struct {
|
||||
ApiKeyId int64
|
||||
Result *APIKey
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "api_key"
|
||||
QuotaTarget quota.Target = "api_key"
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -41,19 +42,38 @@ type UserAuthTokenService struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
type ActiveTokenService interface {
|
||||
ActiveTokenCount(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error)
|
||||
}
|
||||
|
||||
type ActiveAuthTokenService struct {
|
||||
cfg *setting.Cfg
|
||||
sqlStore db.DB
|
||||
}
|
||||
|
||||
func ProvideActiveAuthTokenService(cfg *setting.Cfg, sqlStore db.DB) *ActiveAuthTokenService {
|
||||
return &ActiveAuthTokenService{
|
||||
func ProvideActiveAuthTokenService(cfg *setting.Cfg, sqlStore db.DB, quotaService quota.Service) (*ActiveAuthTokenService, error) {
|
||||
s := &ActiveAuthTokenService{
|
||||
cfg: cfg,
|
||||
sqlStore: sqlStore,
|
||||
}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.ActiveTokenCount,
|
||||
}); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (a *ActiveAuthTokenService) ActiveTokenCount(ctx context.Context) (int64, error) {
|
||||
func (a *ActiveAuthTokenService) ActiveTokenCount(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) {
|
||||
var count int64
|
||||
var err error
|
||||
err = a.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
@ -66,7 +86,14 @@ func (a *ActiveAuthTokenService) ActiveTokenCount(ctx context.Context) (int64, e
|
||||
return err
|
||||
})
|
||||
|
||||
return count, err
|
||||
tag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := "a.Map{}
|
||||
u.Set(tag, count)
|
||||
|
||||
return u, err
|
||||
}
|
||||
|
||||
func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*models.UserToken, error) {
|
||||
@ -472,3 +499,19 @@ func hashToken(token string) string {
|
||||
hashBytes := sha256.Sum256([]byte(token + setting.SecretKey))
|
||||
return hex.EncodeToString(hashBytes[:])
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.Session)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@ -40,8 +41,12 @@ func TestUserAuthToken(t *testing.T) {
|
||||
userToken := createToken()
|
||||
|
||||
t.Run("Can count active tokens", func(t *testing.T) {
|
||||
count, err := ctx.activeTokenService.ActiveTokenCount(context.Background())
|
||||
m, err := ctx.activeTokenService.ActiveTokenCount(context.Background(), "a.ScopeParameters{})
|
||||
require.Nil(t, err)
|
||||
tag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
require.NoError(t, err)
|
||||
count, ok := m.Get(tag)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int64(1), count)
|
||||
})
|
||||
|
||||
@ -208,8 +213,12 @@ func TestUserAuthToken(t *testing.T) {
|
||||
require.Nil(t, notGood)
|
||||
|
||||
t.Run("should not find active token when expired", func(t *testing.T) {
|
||||
count, err := ctx.activeTokenService.ActiveTokenCount(context.Background())
|
||||
m, err := ctx.activeTokenService.ActiveTokenCount(context.Background(), "a.ScopeParameters{})
|
||||
require.Nil(t, err)
|
||||
tag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
require.NoError(t, err)
|
||||
count, ok := m.Get(tag)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int64(0), count)
|
||||
})
|
||||
})
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
)
|
||||
|
||||
type userAuthToken struct {
|
||||
@ -71,3 +72,8 @@ func (uat *userAuthToken) toUserToken(ut *models.UserToken) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "auth"
|
||||
QuotaTarget quota.Target = "session"
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
@ -64,9 +65,9 @@ func (api *ImportDashboardAPI) ImportDashboard(c *models.ReqContext) response.Re
|
||||
return response.Error(http.StatusUnprocessableEntity, "Dashboard must be set", nil)
|
||||
}
|
||||
|
||||
limitReached, err := api.quotaService.QuotaReached(c, "dashboard")
|
||||
limitReached, err := api.quotaService.QuotaReached(c, dashboards.QuotaTargetSrv)
|
||||
if err != nil {
|
||||
return response.Error(500, "failed to get quota", err)
|
||||
return response.Err(err)
|
||||
}
|
||||
|
||||
if limitReached {
|
||||
@ -83,12 +84,12 @@ func (api *ImportDashboardAPI) ImportDashboard(c *models.ReqContext) response.Re
|
||||
}
|
||||
|
||||
type QuotaService interface {
|
||||
QuotaReached(c *models.ReqContext, target string) (bool, error)
|
||||
QuotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error)
|
||||
}
|
||||
|
||||
type quotaServiceFunc func(c *models.ReqContext, target string) (bool, error)
|
||||
type quotaServiceFunc func(c *models.ReqContext, target quota.TargetSrv) (bool, error)
|
||||
|
||||
func (fn quotaServiceFunc) QuotaReached(c *models.ReqContext, target string) (bool, error) {
|
||||
func (fn quotaServiceFunc) QuotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) {
|
||||
return fn(c, target)
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -165,10 +166,10 @@ func (s *serviceMock) ImportDashboard(ctx context.Context, req *dashboardimport.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func quotaReached(c *models.ReqContext, target string) (bool, error) {
|
||||
func quotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func quotaNotReached(c *models.ReqContext, target string) (bool, error) {
|
||||
func quotaNotReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
)
|
||||
|
||||
// DashboardService is a service for operating on dashboards.
|
||||
@ -77,6 +78,7 @@ type Store interface {
|
||||
ValidateDashboardBeforeSave(ctx context.Context, dashboard *models.Dashboard, overwrite bool) (bool, error)
|
||||
DeleteACLByUser(context.Context, int64) error
|
||||
|
||||
Count(context.Context, *quota.ScopeParameters) (*quota.Map, error)
|
||||
// CountDashboardsInFolder returns the number of dashboards associated with
|
||||
// the given parent folder ID.
|
||||
CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
@ -26,7 +27,10 @@ func TestIntegrationDashboardACLDataAccess(t *testing.T) {
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
sqlStore = db.InitTestDB(t)
|
||||
dashboardStore = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
currentUser = createUser(t, sqlStore, "viewer", "Viewer", false)
|
||||
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
childDash = insertTestDashboard(t, dashboardStore, "2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
@ -42,8 +44,23 @@ type DashboardTag struct {
|
||||
// DashboardStore implements the Store interface
|
||||
var _ dashboards.Store = (*DashboardStore)(nil)
|
||||
|
||||
func ProvideDashboardStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tagService tag.Service) *DashboardStore {
|
||||
return &DashboardStore{store: sqlStore, cfg: cfg, log: log.New("dashboard-store"), features: features, tagService: tagService}
|
||||
func ProvideDashboardStore(sqlStore db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tagService tag.Service, quotaService quota.Service) (*DashboardStore, error) {
|
||||
s := &DashboardStore{store: sqlStore, cfg: cfg, log: log.New("dashboard-store"), features: features, tagService: tagService}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: dashboards.QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Count,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (d *DashboardStore) emitEntityEvent() bool {
|
||||
@ -291,6 +308,50 @@ func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DashboardStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
if err := d.store.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM dashboard WHERE is_folder=%s", d.store.GetDialect().BooleanStr(false))
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
|
||||
if scopeParams.OrgID != 0 {
|
||||
if err := d.store.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM dashboard WHERE org_id=? AND is_folder=%s", d.store.GetDialect().BooleanStr(false))
|
||||
if _, err := sess.SQL(rawSQL, scopeParams.OrgID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func getExistingDashboardByIdOrUidForUpdate(sess *db.Session, dash *models.Dashboard, dialect migrator.Dialect, overwrite bool) (bool, error) {
|
||||
dashWithIdExists := false
|
||||
isParentFolderChanged := false
|
||||
@ -1018,6 +1079,27 @@ func (d *DashboardStore) GetDashboardTags(ctx context.Context, query *models.Get
|
||||
})
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return "a.Map{}, err
|
||||
}
|
||||
orgQuotaTag, err := quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return "a.Map{}, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.Dashboard)
|
||||
limits.Set(orgQuotaTag, cfg.Quota.Org.Dashboard)
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
// This will be updated to take CountDashboardsInFolderQuery as an argument and
|
||||
// lookup dashboards using the ParentFolderUID when the NestedFolder
|
||||
// implementation is complete.
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -33,7 +34,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
setup := func() {
|
||||
sqlStore = db.InitTestDB(t)
|
||||
sqlStore.Cfg.RBACEnabled = false
|
||||
dashboardStore = ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp")
|
||||
childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, folder.Id, false, "prod", "webapp")
|
||||
@ -186,7 +190,9 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
setup2 := func() {
|
||||
sqlStore = db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod")
|
||||
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod")
|
||||
dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod")
|
||||
@ -291,7 +297,9 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
|
||||
setup3 := func() {
|
||||
sqlStore = db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder1 = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod")
|
||||
folder2 = insertTestDashboard(t, dashboardStore, "2 test dash folder", 1, 0, true, "prod")
|
||||
insertTestDashboard(t, dashboardStore, "folder in another org", 2, 0, true, "prod")
|
||||
@ -473,7 +481,9 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
var sqlStore *sqlstore.SQLStore
|
||||
var folder1, folder2 *models.Dashboard
|
||||
sqlStore = db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder2 = insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod")
|
||||
_ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.Id, false, "prod")
|
||||
folder1 = insertTestDashboard(t, dashboardStore, title, orgId, 0, true, "prod")
|
||||
@ -488,7 +498,9 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
t.Run("GetFolderByUID", func(t *testing.T) {
|
||||
var orgId int64 = 1
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod")
|
||||
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.Id, false, "prod")
|
||||
|
||||
@ -512,7 +524,9 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
||||
t.Run("GetFolderByID", func(t *testing.T) {
|
||||
var orgId int64 = 1
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod")
|
||||
dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.Id, false, "prod")
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
)
|
||||
|
||||
@ -18,7 +19,9 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
folderCmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
publicDashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/star"
|
||||
@ -42,7 +43,10 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
starService = starimpl.ProvideService(sqlStore, cfg)
|
||||
dashboardStore = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
var err error
|
||||
dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
savedFolder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
savedDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard(t, dashboardStore, "test dash 45", 1, savedFolder.Id, false, "prod")
|
||||
@ -585,7 +589,9 @@ func TestIntegrationDashboardDataAccessGivenPluginWithImportedDashboards(t *test
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool { return false }
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
pluginId := "test-app"
|
||||
|
||||
appFolder := insertTestDashboardForPlugin(t, dashboardStore, "app-test", 1, 0, true, pluginId)
|
||||
@ -597,7 +603,7 @@ func TestIntegrationDashboardDataAccessGivenPluginWithImportedDashboards(t *test
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := dashboardStore.GetDashboardsByPluginID(context.Background(), &query)
|
||||
err = dashboardStore.GetDashboardsByPluginID(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(query.Result), 2)
|
||||
}
|
||||
@ -609,7 +615,9 @@ func TestIntegrationDashboard_SortingOptions(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool { return false }
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
|
||||
dashA := insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
|
||||
@ -660,7 +668,9 @@ func TestIntegrationDashboard_Filter(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.IsFeatureToggleEnabled = func(key string) bool { return false }
|
||||
dashboardStore := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false)
|
||||
dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false)
|
||||
qNoFilter := &models.FindPersistedDashboardsQuery{
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
@ -30,6 +31,11 @@ type DashboardSearchProjection struct {
|
||||
SortMeta int64
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "dashboard"
|
||||
QuotaTarget quota.Target = "dashboard"
|
||||
)
|
||||
|
||||
type CountDashboardsInFolderQuery struct {
|
||||
FolderUID string
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -42,7 +43,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
}),
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardNotFound, err)
|
||||
})
|
||||
|
||||
@ -62,7 +63,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: false,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardNotFound, err)
|
||||
})
|
||||
|
||||
@ -104,7 +105,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sqlStore)
|
||||
err := callSaveWithError(t, cmd, sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId)
|
||||
@ -124,7 +125,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -144,7 +145,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -165,7 +166,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -186,7 +187,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -207,7 +208,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -228,7 +229,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -249,7 +250,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -270,7 +271,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -291,7 +292,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
|
||||
|
||||
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
|
||||
@ -432,7 +433,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardFolderNotFound, err)
|
||||
})
|
||||
|
||||
@ -448,7 +449,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err)
|
||||
})
|
||||
|
||||
@ -488,7 +489,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err)
|
||||
})
|
||||
|
||||
@ -527,7 +528,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
|
||||
})
|
||||
|
||||
@ -543,7 +544,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
|
||||
})
|
||||
|
||||
@ -559,7 +560,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
|
||||
})
|
||||
})
|
||||
@ -647,7 +648,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err)
|
||||
})
|
||||
|
||||
@ -711,7 +712,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
|
||||
})
|
||||
|
||||
@ -727,7 +728,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
|
||||
})
|
||||
|
||||
@ -743,7 +744,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
|
||||
})
|
||||
|
||||
@ -759,7 +760,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
|
||||
})
|
||||
|
||||
@ -774,7 +775,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardWithSameNameAsFolder, err)
|
||||
})
|
||||
|
||||
@ -789,7 +790,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
|
||||
Overwrite: shouldOverwrite,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd, sc.sqlStore)
|
||||
err := callSaveWithError(t, cmd, sc.sqlStore)
|
||||
assert.Equal(t, dashboards.ErrDashboardFolderWithSameNameAsDashboard, err)
|
||||
})
|
||||
})
|
||||
@ -821,7 +822,9 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
service := ProvideDashboardService(
|
||||
cfg, dashboardStore, &dummyDashAlertExtractor{},
|
||||
featuremgmt.WithFeatures(),
|
||||
@ -878,7 +881,9 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
service := ProvideDashboardService(
|
||||
cfg, dashboardStore, &dummyDashAlertExtractor{},
|
||||
featuremgmt.WithFeatures(),
|
||||
@ -892,12 +897,14 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore
|
||||
return res
|
||||
}
|
||||
|
||||
func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore db.DB) error {
|
||||
func callSaveWithError(t *testing.T, cmd models.SaveDashboardCommand, sqlStore db.DB) error {
|
||||
dto := toSaveDashboardDto(cmd)
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
service := ProvideDashboardService(
|
||||
cfg, dashboardStore, &dummyDashAlertExtractor{},
|
||||
featuremgmt.WithFeatures(),
|
||||
@ -905,7 +912,7 @@ func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore db.DB) error {
|
||||
accesscontrolmock.NewMockedPermissionsService(),
|
||||
accesscontrolmock.New(),
|
||||
)
|
||||
_, err := service.SaveDashboard(context.Background(), &dto, false)
|
||||
_, err = service.SaveDashboard(context.Background(), &dto, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -934,7 +941,9 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
service := ProvideDashboardService(
|
||||
cfg, dashboardStore, &dummyDashAlertExtractor{},
|
||||
featuremgmt.WithFeatures(),
|
||||
@ -972,7 +981,9 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *mo
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
service := ProvideDashboardService(
|
||||
cfg, dashboardStore, &dummyDashAlertExtractor{},
|
||||
featuremgmt.WithFeatures(),
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
context "context"
|
||||
|
||||
models "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@ -473,6 +474,10 @@ type mockConstructorTestingTNewFakeDashboardStore interface {
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
func (_m *FakeDashboardStore) Count(context.Context, *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewFakeDashboardStore creates a new instance of FakeDashboardStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewFakeDashboardStore(t mockConstructorTestingTNewFakeDashboardStore) *FakeDashboardStore {
|
||||
mock := &FakeDashboardStore{}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
@ -193,3 +194,8 @@ type DatasourcesPermissionFilterQuery struct {
|
||||
Datasources []*DataSource
|
||||
Result []*DataSource
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "data_source"
|
||||
QuotaTarget quota.Target = "data_source"
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -52,7 +53,8 @@ type cachedRoundTripper struct {
|
||||
func ProvideService(
|
||||
db db.DB, secretsService secrets.Service, secretsStore kvstore.SecretsKVStore, cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, datasourcePermissionsService accesscontrol.DatasourcePermissionsService,
|
||||
) *Service {
|
||||
quotaService quota.Service,
|
||||
) (*Service, error) {
|
||||
dslogger := log.New("datasources")
|
||||
store := &SqlStore{db: db, logger: dslogger}
|
||||
s := &Service{
|
||||
@ -73,7 +75,23 @@ func ProvideService(
|
||||
ac.RegisterScopeAttributeResolver(NewNameScopeResolver(store))
|
||||
ac.RegisterScopeAttributeResolver(NewIDScopeResolver(store))
|
||||
|
||||
return s
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: datasources.QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Usage,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) Usage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return s.SQLStore.Count(ctx, scopeParams)
|
||||
}
|
||||
|
||||
// DataSourceRetriever interface for retrieving a datasource.
|
||||
@ -591,3 +609,24 @@ func (s *Service) fillWithSecureJSONData(ctx context.Context, cmd *datasources.U
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
orgQuotaTag, err := quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.DataSource)
|
||||
limits.Set(orgQuotaTag, cfg.Quota.Org.DataSource)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
@ -200,7 +201,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
|
||||
require.NoError(t, err)
|
||||
@ -235,7 +238,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
@ -284,7 +289,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
@ -330,7 +337,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
@ -373,7 +382,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
@ -406,7 +417,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
@ -473,7 +486,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
ds := datasources.DataSource{
|
||||
Id: 1,
|
||||
Url: "http://k8s:8001",
|
||||
@ -507,7 +522,9 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
Type: datasources.DS_ES,
|
||||
@ -544,7 +561,9 @@ func TestService_getTimeout(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testCases {
|
||||
ds := &datasources.DataSource{
|
||||
@ -565,7 +584,9 @@ func TestService_GetDecryptedValues(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonData := map[string]string{
|
||||
"password": "securePassword",
|
||||
@ -591,7 +612,9 @@ func TestService_GetDecryptedValues(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
dsService := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonData := map[string]string{
|
||||
"password": "securePassword",
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -29,6 +31,8 @@ type Store interface {
|
||||
AddDataSource(context.Context, *datasources.AddDataSourceCommand) error
|
||||
UpdateDataSource(context.Context, *datasources.UpdateDataSourceCommand) error
|
||||
GetAllDataSources(ctx context.Context, query *datasources.GetAllDataSourcesQuery) error
|
||||
|
||||
Count(context.Context, *quota.ScopeParameters) (*quota.Map, error)
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@ -171,6 +175,50 @@ func (ss *SqlStore) DeleteDataSource(ctx context.Context, cmd *datasources.Delet
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SqlStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM data_source"
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
|
||||
if scopeParams.OrgID != 0 {
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM data_source WHERE org_id=?"
|
||||
if _, err := sess.SQL(rawSQL, scopeParams.OrgID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (ss *SqlStore) AddDataSource(ctx context.Context, cmd *datasources.AddDataSourceCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
existing := datasources.DataSource{OrgId: cmd.OrgId, Name: cmd.Name}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -583,7 +584,8 @@ func TestIntegrationGetChildren(t *testing.T) {
|
||||
func CreateOrg(t *testing.T, db *sqlstore.SQLStore) int64 {
|
||||
t.Helper()
|
||||
|
||||
orgService := orgimpl.ProvideService(db, db.Cfg)
|
||||
orgService, err := orgimpl.ProvideService(db, db.Cfg, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
orgID, err := orgService.GetOrCreate(context.Background(), "test-org")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -591,7 +592,9 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce
|
||||
toSave.SetUid(uid)
|
||||
|
||||
// seed dashboard
|
||||
dashStore := dashdb.ProvideDashboardStore(store, store.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(store, store.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashStore, err := dashdb.ProvideDashboardStore(store, store.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(store, store.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dash, err := dashStore.SaveDashboard(context.Background(), models.SaveDashboardCommand{
|
||||
Dashboard: toSave.Data,
|
||||
UserId: 1,
|
||||
@ -603,7 +606,8 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce
|
||||
license := licensingtest.NewFakeLicensing()
|
||||
license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe()
|
||||
teamSvc := teamimpl.ProvideService(store, store.Cfg)
|
||||
userSvc := userimpl.ProvideService(store, nil, store.Cfg, nil, nil)
|
||||
userSvc, err := userimpl.ProvideService(store, nil, store.Cfg, nil, nil, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions(
|
||||
setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, ac, teamSvc, userSvc)
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -278,7 +279,9 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
|
||||
cfg.RBACEnabled = false
|
||||
features := featuremgmt.WithFeatures()
|
||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
@ -304,7 +307,9 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S
|
||||
ac := acmock.New()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
d := dashboardservice.ProvideDashboardService(
|
||||
cfg, dashboardStore, nil,
|
||||
@ -405,7 +410,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
orgID := int64(1)
|
||||
role := org.RoleAdmin
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
features := featuremgmt.WithFeatures()
|
||||
ac := acmock.New().WithDisabled()
|
||||
// TODO: Update tests to work with rbac
|
||||
@ -442,7 +449,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
Login: userInDbName,
|
||||
}
|
||||
|
||||
_, err := sqlStore.CreateUser(context.Background(), cmd)
|
||||
_, err = sqlStore.CreateUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc := scenarioContext{
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -691,7 +692,9 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
|
||||
cfg := setting.NewCfg()
|
||||
cfg.RBACEnabled = false
|
||||
cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil)
|
||||
ac := acmock.New()
|
||||
service := dashboardservice.ProvideDashboardService(
|
||||
@ -715,7 +718,9 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.
|
||||
features := featuremgmt.WithFeatures()
|
||||
folderPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardPermissions := acmock.NewMockedPermissionsService()
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac)
|
||||
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil)
|
||||
|
||||
@ -808,7 +813,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
orgID := int64(1)
|
||||
role := org.RoleAdmin
|
||||
sqlStore, cfg := db.InitTestDBwithCfg(t)
|
||||
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
features := featuremgmt.WithFeatures()
|
||||
ac := acmock.New()
|
||||
@ -847,7 +854,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
||||
Login: userInDbName,
|
||||
}
|
||||
|
||||
_, err := sqlStore.CreateUser(context.Background(), cmd)
|
||||
_, err = sqlStore.CreateUser(context.Background(), cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc := scenarioContext{
|
||||
|
@ -71,13 +71,17 @@ func (ls *Implementation) UpsertUser(ctx context.Context, cmd *models.UpsertUser
|
||||
return login.ErrSignupNotAllowed
|
||||
}
|
||||
|
||||
limitReached, errLimit := ls.QuotaService.QuotaReached(cmd.ReqContext, "user")
|
||||
if errLimit != nil {
|
||||
cmd.ReqContext.Logger.Warn("Error getting user quota.", "error", errLimit)
|
||||
return login.ErrGettingUserQuota
|
||||
}
|
||||
if limitReached {
|
||||
return login.ErrUsersQuotaReached
|
||||
// we may insert in both user and org_user tables
|
||||
// therefore we need to query check quota for both user and org services
|
||||
for _, srv := range []string{user.QuotaTargetSrv, org.QuotaTargetSrv} {
|
||||
limitReached, errLimit := ls.QuotaService.QuotaReached(cmd.ReqContext, quota.TargetSrv(srv))
|
||||
if errLimit != nil {
|
||||
cmd.ReqContext.Logger.Warn("Error getting user quota.", "error", errLimit)
|
||||
return login.ErrGettingUserQuota
|
||||
}
|
||||
if limitReached {
|
||||
return login.ErrUsersQuotaReached
|
||||
}
|
||||
}
|
||||
|
||||
result, errCreateUser := ls.createUser(extUser)
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -26,7 +26,7 @@ func Test_syncOrgRoles_doesNotBreakWhenTryingToRemoveLastOrgAdmin(t *testing.T)
|
||||
authInfoMock := &logintest.AuthInfoServiceFake{}
|
||||
|
||||
login := Implementation{
|
||||
QuotaService: "aimpl.Service{},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
AuthInfoService: authInfoMock,
|
||||
SQLStore: nil,
|
||||
userService: usertest.NewUserServiceFake(),
|
||||
@ -51,7 +51,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
|
||||
orgService.ExpectedOrgListResponse = createResponseWithOneErrLastOrgAdminItem()
|
||||
|
||||
login := Implementation{
|
||||
QuotaService: "aimpl.Service{},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
AuthInfoService: authInfoMock,
|
||||
SQLStore: nil,
|
||||
userService: usertest.NewUserServiceFake(),
|
||||
@ -66,7 +66,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
|
||||
func Test_teamSync(t *testing.T) {
|
||||
authInfoMock := &logintest.AuthInfoServiceFake{}
|
||||
login := Implementation{
|
||||
QuotaService: "aimpl.Service{},
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
AuthInfoService: authInfoMock,
|
||||
}
|
||||
|
||||
|
@ -145,3 +145,28 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
alertRules: api.AlertRules,
|
||||
}), m)
|
||||
}
|
||||
|
||||
func (api *API) Usage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
if orgUsage, err := api.RuleStore.Count(ctx, scopeParams.OrgID); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(models.QuotaTargetSrv, models.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, orgUsage)
|
||||
}
|
||||
|
||||
if globalUsage, err := api.RuleStore.Count(ctx, 0); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(models.QuotaTargetSrv, models.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, globalUsage)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, groupKey ngmod
|
||||
}
|
||||
|
||||
if len(finalChanges.New) > 0 {
|
||||
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, "alert_rule", "a.ScopeParameters{
|
||||
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, "a.ScopeParameters{
|
||||
OrgID: c.OrgID,
|
||||
UserID: c.UserID,
|
||||
}) // alert rule is table name
|
||||
|
@ -23,4 +23,6 @@ type RuleStore interface {
|
||||
|
||||
// IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace
|
||||
IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error)
|
||||
|
||||
Count(ctx context.Context, orgID int64) (int64, error)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/util/cmputil"
|
||||
)
|
||||
|
||||
@ -460,6 +461,11 @@ func (g RulesGroup) SortByGroupIndex() {
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "ngalert"
|
||||
QuotaTarget quota.Target = "alert_rule"
|
||||
)
|
||||
|
||||
type ruleKeyContextKey struct{}
|
||||
|
||||
func WithRuleKey(ctx context.Context, ruleKey AlertRuleKey) context.Context {
|
||||
|
@ -239,6 +239,19 @@ func (ng *AlertNG) init() error {
|
||||
}
|
||||
api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
||||
|
||||
defaultLimits, err := readQuotaConfig(ng.Cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ng.QuotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: models.QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: api.Usage,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.RegisterContextualLogProvider(func(ctx context.Context) ([]interface{}, bool) {
|
||||
key, ok := models.RuleKeyFromContext(ctx)
|
||||
if !ok {
|
||||
@ -308,3 +321,32 @@ func (ng *AlertNG) IsDisabled() bool {
|
||||
}
|
||||
return !ng.Cfg.UnifiedAlerting.IsEnabled()
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
var alertOrgQuota int64
|
||||
var alertGlobalQuota int64
|
||||
|
||||
if cfg.UnifiedAlerting.IsEnabled() {
|
||||
alertOrgQuota = cfg.Quota.Org.AlertRule
|
||||
alertGlobalQuota = cfg.Quota.Global.AlertRule
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(models.QuotaTargetSrv, models.QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
orgQuotaTag, err := quota.NewTag(models.QuotaTargetSrv, models.QuotaTarget, quota.OrgScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, alertGlobalQuota)
|
||||
limits.Set(orgQuotaTag, alertOrgQuota)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ type RuleStore interface {
|
||||
//
|
||||
//go:generate mockery --name QuotaChecker --structname MockQuotaChecker --inpackage --filename quota_checker_mock.go --with-expecter
|
||||
type QuotaChecker interface {
|
||||
CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error)
|
||||
CheckQuotaReached(ctx context.Context, target quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error)
|
||||
}
|
||||
|
||||
// PersistConfig validates to config before eventually persisting it if no error occurs
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Code generated by mockery v2.12.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.14.0. DO NOT EDIT.
|
||||
|
||||
package provisioning
|
||||
|
||||
@ -7,8 +7,6 @@ import (
|
||||
|
||||
quota "github.com/grafana/grafana/pkg/services/quota"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
testing "testing"
|
||||
)
|
||||
|
||||
// MockQuotaChecker is an autogenerated mock type for the QuotaChecker type
|
||||
@ -25,18 +23,18 @@ func (_m *MockQuotaChecker) EXPECT() *MockQuotaChecker_Expecter {
|
||||
}
|
||||
|
||||
// CheckQuotaReached provides a mock function with given fields: ctx, target, scopeParams
|
||||
func (_m *MockQuotaChecker) CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error) {
|
||||
func (_m *MockQuotaChecker) CheckQuotaReached(ctx context.Context, target quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) {
|
||||
ret := _m.Called(ctx, target, scopeParams)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *quota.ScopeParameters) bool); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, quota.TargetSrv, *quota.ScopeParameters) bool); ok {
|
||||
r0 = rf(ctx, target, scopeParams)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, *quota.ScopeParameters) error); ok {
|
||||
if rf, ok := ret.Get(1).(func(context.Context, quota.TargetSrv, *quota.ScopeParameters) error); ok {
|
||||
r1 = rf(ctx, target, scopeParams)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
@ -51,16 +49,16 @@ type MockQuotaChecker_CheckQuotaReached_Call struct {
|
||||
}
|
||||
|
||||
// CheckQuotaReached is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - target string
|
||||
// - scopeParams *quota.ScopeParameters
|
||||
// - ctx context.Context
|
||||
// - target quota.TargetSrv
|
||||
// - scopeParams *quota.ScopeParameters
|
||||
func (_e *MockQuotaChecker_Expecter) CheckQuotaReached(ctx interface{}, target interface{}, scopeParams interface{}) *MockQuotaChecker_CheckQuotaReached_Call {
|
||||
return &MockQuotaChecker_CheckQuotaReached_Call{Call: _e.mock.On("CheckQuotaReached", ctx, target, scopeParams)}
|
||||
}
|
||||
|
||||
func (_c *MockQuotaChecker_CheckQuotaReached_Call) Run(run func(ctx context.Context, target string, scopeParams *quota.ScopeParameters)) *MockQuotaChecker_CheckQuotaReached_Call {
|
||||
func (_c *MockQuotaChecker_CheckQuotaReached_Call) Run(run func(ctx context.Context, target quota.TargetSrv, scopeParams *quota.ScopeParameters)) *MockQuotaChecker_CheckQuotaReached_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(*quota.ScopeParameters))
|
||||
run(args[0].(context.Context), args[1].(quota.TargetSrv), args[2].(*quota.ScopeParameters))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@ -70,8 +68,13 @@ func (_c *MockQuotaChecker_CheckQuotaReached_Call) Return(_a0 bool, _a1 error) *
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockQuotaChecker creates a new instance of MockQuotaChecker. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewMockQuotaChecker(t testing.TB) *MockQuotaChecker {
|
||||
type mockConstructorTestingTNewMockQuotaChecker interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewMockQuotaChecker creates a new instance of MockQuotaChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewMockQuotaChecker(t mockConstructorTestingTNewMockQuotaChecker) *MockQuotaChecker {
|
||||
mock := &MockQuotaChecker{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -268,6 +269,29 @@ func (st DBstore) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertR
|
||||
})
|
||||
}
|
||||
|
||||
// Count returns either the number of the alert rules under a specific org (if orgID is not zero)
|
||||
// or the number of all the alert rules
|
||||
func (st DBstore) Count(ctx context.Context, orgID int64) (int64, error) {
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
err := st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) as count from alert_rule"
|
||||
args := make([]interface{}, 0)
|
||||
if orgID != 0 {
|
||||
rawSQL += " WHERE org_id=?"
|
||||
args = append(args, orgID)
|
||||
}
|
||||
if _, err := sess.SQL(rawSQL, args...).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return r.Count, err
|
||||
}
|
||||
|
||||
func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) {
|
||||
var interval int64 = 0
|
||||
return interval, st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
|
@ -339,3 +339,7 @@ func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, org
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) Count(ctx context.Context, orgID int64) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
@ -75,7 +76,9 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
sqlStore := db.InitTestDB(tb)
|
||||
secretsService := secretsManager.SetupTestService(tb, database.ProvideSecretsStore(sqlStore))
|
||||
dashboardStore := databasestore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := databasestore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(tb, err)
|
||||
|
||||
ac := acmock.New()
|
||||
features := featuremgmt.WithFeatures()
|
||||
@ -92,7 +95,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
|
||||
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardService, dashboardStore, features, folderPermissions, nil)
|
||||
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil,
|
||||
cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),
|
||||
secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac, annotationstest.NewFakeAnnotationsRepo(),
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
@ -204,3 +204,9 @@ func (o ByOrgName) Less(i, j int) bool {
|
||||
|
||||
return o[i].Name < o[j].Name
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv string = "org"
|
||||
OrgQuotaTarget string = "org"
|
||||
OrgUserQuotaTarget string = "org_user"
|
||||
)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -18,9 +19,9 @@ type Service struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg) org.Service {
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (org.Service, error) {
|
||||
log := log.New("org service")
|
||||
return &Service{
|
||||
s := &Service{
|
||||
store: &sqlStore{
|
||||
db: db,
|
||||
dialect: db.GetDialect(),
|
||||
@ -30,6 +31,24 @@ func ProvideService(db db.DB, cfg *setting.Cfg) org.Service {
|
||||
cfg: cfg,
|
||||
log: log,
|
||||
}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: quota.TargetSrv(org.QuotaTargetSrv),
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Usage,
|
||||
}); err != nil {
|
||||
return s, nil
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) Usage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return s.store.Count(ctx, scopeParams)
|
||||
}
|
||||
|
||||
func (s *Service) GetIDForNewUser(ctx context.Context, cmd org.GetOrgIDForNewUserCommand) (int64, error) {
|
||||
@ -179,3 +198,31 @@ func (s *Service) GetOrgUsers(ctx context.Context, query *org.GetOrgUsersQuery)
|
||||
func (s *Service) SearchOrgUsers(ctx context.Context, query *org.SearchOrgUsersQuery) (*org.SearchOrgUsersQueryResult, error) {
|
||||
return s.store.SearchOrgUsers(ctx, query)
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgQuotaTarget), quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
orgQuotaTag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
userTag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.Org)
|
||||
// users per org
|
||||
limits.Set(orgQuotaTag, cfg.Quota.Org.User)
|
||||
// orgs per user
|
||||
limits.Set(userTag, cfg.Quota.User.Org)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -135,3 +136,7 @@ func (f *FakeOrgStore) SearchOrgUsers(ctx context.Context, query *org.SearchOrgU
|
||||
func (f *FakeOrgStore) RemoveOrgUser(ctx context.Context, cmd *org.RemoveOrgUserCommand) error {
|
||||
return f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeOrgStore) Count(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -42,6 +44,8 @@ type store interface {
|
||||
GetByName(context.Context, *org.GetOrgByNameQuery) (*org.Org, error)
|
||||
SearchOrgUsers(context.Context, *org.SearchOrgUsersQuery) (*org.SearchOrgUsersQueryResult, error)
|
||||
RemoveOrgUser(context.Context, *org.RemoveOrgUserCommand) error
|
||||
|
||||
Count(context.Context, *quota.ScopeParameters) (*quota.Map, error)
|
||||
}
|
||||
|
||||
type sqlStore struct {
|
||||
@ -395,6 +399,72 @@ func (ss *sqlStore) AddOrgUser(ctx context.Context, cmd *org.AddOrgUserCommand)
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) as count from org"
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgQuotaTarget), quota.GlobalScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
|
||||
if scopeParams.OrgID != 0 {
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM (SELECT user_id FROM org_user WHERE org_id=? AND user_id IN (SELECT id AS user_id FROM %s WHERE is_service_account=%s)) as subq",
|
||||
ss.db.GetDialect().Quote("user"),
|
||||
ss.db.GetDialect().BooleanStr(false),
|
||||
)
|
||||
if _, err := sess.SQL(rawSQL, scopeParams.OrgID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
if scopeParams.UserID != 0 {
|
||||
if err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// should we exclude service accounts?
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM org_user WHERE user_id=?"
|
||||
if _, err := sess.SQL(rawSQL, scopeParams.UserID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
}
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func setUsingOrgInTransaction(sess *db.Session, userID int64, orgID int64) error {
|
||||
user := user.User{
|
||||
ID: userID,
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -300,7 +301,8 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
|
||||
}
|
||||
|
||||
// create dashboard
|
||||
dashboardStoreService := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg))
|
||||
dashboardStoreService, err := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
dashboard, err := dashboardStoreService.SaveDashboard(context.Background(), saveDashboardCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -35,7 +36,9 @@ func TestIntegrationListPublicDashboard(t *testing.T) {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore, cfg := db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := ProvideStore(sqlStore)
|
||||
|
||||
var orgId int64 = 1
|
||||
@ -78,7 +81,10 @@ func TestIntegrationFindDashboard(t *testing.T) {
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -105,7 +111,10 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) {
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -175,7 +184,10 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) {
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -237,7 +249,10 @@ func TestIntegrationFindByDashboardUid(t *testing.T) {
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -299,10 +314,12 @@ func TestIntegrationFindByAccessToken(t *testing.T) {
|
||||
var dashboardStore *dashboardsDB.DashboardStore
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
var savedDashboard *models.Dashboard
|
||||
var err error
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -369,7 +386,10 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) {
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
dashboardStore = store
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true)
|
||||
@ -436,10 +456,13 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) {
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
var savedDashboard *models.Dashboard
|
||||
var anotherSavedDashboard *models.Dashboard
|
||||
var err error
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}})
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, true)
|
||||
@ -529,10 +552,13 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
||||
var dashboardStore *dashboardsDB.DashboardStore
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
var savedDashboard *models.Dashboard
|
||||
var err error
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
}
|
||||
@ -599,10 +625,12 @@ func TestIntegrationDelete(t *testing.T) {
|
||||
var publicdashboardStore *PublicDashboardStoreImpl
|
||||
var savedDashboard *models.Dashboard
|
||||
var savedPublicDashboard *PublicDashboard
|
||||
var err error
|
||||
|
||||
setup := func() {
|
||||
sqlStore, cfg = db.InitTestDBwithCfg(t)
|
||||
dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
|
||||
dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore = ProvideStore(sqlStore)
|
||||
savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true)
|
||||
savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.Uid, savedDashboard.OrgId, true)
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/internal"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -355,7 +356,8 @@ const (
|
||||
|
||||
func TestGetQueryDataResponse(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
|
||||
service := &PublicDashboardServiceImpl{
|
||||
@ -738,7 +740,8 @@ func TestGetAnnotations(t *testing.T) {
|
||||
|
||||
func TestGetMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
publicDashboard := &PublicDashboard{
|
||||
@ -811,7 +814,8 @@ func TestGetUniqueDashboardDatasourceUids(t *testing.T) {
|
||||
|
||||
func TestBuildMetricRequest(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
|
||||
publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
@ -1022,7 +1026,8 @@ func TestBuildMetricRequest(t *testing.T) {
|
||||
|
||||
func TestBuildAnonymousUser(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
//publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
//service := &PublicDashboardServiceImpl{
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/internal/tokens"
|
||||
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -125,7 +126,9 @@ func TestGetPublicDashboard(t *testing.T) {
|
||||
func TestCreatePublicDashboard(t *testing.T) {
|
||||
t.Run("Create public dashboard", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -147,7 +150,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
_, err = service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
@ -171,7 +174,9 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("Validate pubdash has default time setting value", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -191,7 +196,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
_, err = service.Create(context.Background(), SignedInUser, dto)
|
||||
require.NoError(t, err)
|
||||
|
||||
pubdash, err := service.FindByDashboardUid(context.Background(), dashboard.OrgId, dashboard.Uid)
|
||||
@ -201,7 +206,9 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("Validate pubdash whose dashboard has template variables returns error", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
templateVars := make([]map[string]interface{}, 1)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars, nil)
|
||||
@ -222,7 +229,7 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := service.Create(context.Background(), SignedInUser, dto)
|
||||
_, err = service.Create(context.Background(), SignedInUser, dto)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@ -265,7 +272,8 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("Returns error if public dashboard exists", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -316,7 +324,9 @@ func TestCreatePublicDashboard(t *testing.T) {
|
||||
func TestUpdatePublicDashboard(t *testing.T) {
|
||||
t.Run("Updating public dashboard", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
@ -378,7 +388,9 @@ func TestUpdatePublicDashboard(t *testing.T) {
|
||||
|
||||
t.Run("Updating set empty time settings", func(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
publicdashboardStore := database.ProvideStore(sqlStore)
|
||||
dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil)
|
||||
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
dsSvc "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
@ -389,7 +390,9 @@ func setup(t *testing.T) *testContext {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
ss := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
ssvc := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
ds := dsSvc.ProvideService(nil, ssvc, ss, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
ds, err := dsSvc.ProvideService(nil, ssvc, ss, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
fakeDatasourceService := &fakeDatasources.FakeDataSourceService{
|
||||
DataSources: nil,
|
||||
SimulatePluginFailure: false,
|
||||
|
42
pkg/services/quota/context.go
Normal file
42
pkg/services/quota/context.go
Normal file
@ -0,0 +1,42 @@
|
||||
package quota
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
context.Context
|
||||
TargetToSrv *TargetToSrv
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context, targetToSrv *TargetToSrv) Context {
|
||||
if targetToSrv == nil {
|
||||
targetToSrv = NewTargetToSrv()
|
||||
}
|
||||
return Context{Context: ctx, TargetToSrv: targetToSrv}
|
||||
}
|
||||
|
||||
type TargetToSrv struct {
|
||||
mutex sync.RWMutex
|
||||
m map[Target]TargetSrv
|
||||
}
|
||||
|
||||
func NewTargetToSrv() *TargetToSrv {
|
||||
return &TargetToSrv{m: make(map[Target]TargetSrv)}
|
||||
}
|
||||
|
||||
func (m *TargetToSrv) Get(target Target) (TargetSrv, bool) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
srv, ok := m.m[target]
|
||||
return srv, ok
|
||||
}
|
||||
|
||||
func (m *TargetToSrv) Set(target Target, srv TargetSrv) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.m[target] = srv
|
||||
}
|
@ -1,10 +1,216 @@
|
||||
package quota
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
var ErrInvalidQuotaTarget = errors.New("invalid quota target")
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
var ErrBadRequest = errutil.NewBase(errutil.StatusBadRequest, "quota.bad-request")
|
||||
var ErrInvalidTargetSrv = errutil.NewBase(errutil.StatusBadRequest, "quota.invalid-target")
|
||||
var ErrInvalidScope = errutil.NewBase(errutil.StatusBadRequest, "quota.invalid-scope")
|
||||
var ErrInvalidTarget = errutil.NewBase(errutil.StatusInternal, "quota.invalid-target-table")
|
||||
var ErrTargetSrvConflict = errutil.NewBase(errutil.StatusBadRequest, "quota.target-srv-conflict")
|
||||
var ErrDisabled = errutil.NewBase(errutil.StatusForbidden, "quota.disabled", errutil.WithPublicMessage("Quotas not enabled"))
|
||||
var ErrInvalidTagFormat = errutil.NewBase(errutil.StatusInternal, "quota.invalid-invalid-tag-format")
|
||||
|
||||
type ScopeParameters struct {
|
||||
OrgID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
type Scope string
|
||||
|
||||
const (
|
||||
GlobalScope Scope = "global"
|
||||
OrgScope Scope = "org"
|
||||
UserScope Scope = "user"
|
||||
)
|
||||
|
||||
func (s Scope) Validate() error {
|
||||
switch s {
|
||||
case GlobalScope, OrgScope, UserScope:
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidScope.Errorf("bad scope: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
type TargetSrv string
|
||||
|
||||
type Target string
|
||||
|
||||
const delimiter = ":"
|
||||
|
||||
// Tag is a string with the format <srv>:<target>:<scope>
|
||||
type Tag string
|
||||
|
||||
func NewTag(srv TargetSrv, t Target, scope Scope) (Tag, error) {
|
||||
if err := scope.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tag := Tag(strings.Join([]string{string(srv), string(t), string(scope)}, delimiter))
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func (t Tag) split() ([]string, error) {
|
||||
parts := strings.SplitN(string(t), delimiter, -1)
|
||||
if len(parts) != 3 {
|
||||
return nil, ErrInvalidTagFormat.Errorf("tag format should be ^(?<srv>\\w):(?<target>\\w):(?<scope>\\w)$")
|
||||
}
|
||||
|
||||
return parts, nil
|
||||
}
|
||||
|
||||
func (t Tag) GetSrv() (TargetSrv, error) {
|
||||
parts, err := t.split()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return TargetSrv(parts[0]), nil
|
||||
}
|
||||
|
||||
func (t Tag) GetTarget() (Target, error) {
|
||||
parts, err := t.split()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Target(parts[1]), nil
|
||||
}
|
||||
|
||||
func (t Tag) GetScope() (Scope, error) {
|
||||
parts, err := t.split()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Scope(parts[2]), nil
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Tag Tag
|
||||
Value int64
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
mutex sync.RWMutex
|
||||
m map[Tag]int64
|
||||
}
|
||||
|
||||
func (m *Map) Set(tag Tag, limit int64) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if len(m.m) == 0 {
|
||||
m.m = make(map[Tag]int64, 0)
|
||||
}
|
||||
m.m[tag] = limit
|
||||
}
|
||||
|
||||
func (m *Map) Get(tag Tag) (int64, bool) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
limit, ok := m.m[tag]
|
||||
return limit, ok
|
||||
}
|
||||
|
||||
func (m *Map) Merge(l2 *Map) {
|
||||
l2.mutex.RLock()
|
||||
defer l2.mutex.RUnlock()
|
||||
|
||||
for k, v := range l2.m {
|
||||
// TODO check for conflicts?
|
||||
m.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map) Iter() <-chan Item {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
ch := make(chan Item)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
for t, v := range m.m {
|
||||
ch <- Item{Tag: t, Value: v}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (m *Map) Scopes() (map[Scope]struct{}, error) {
|
||||
res := make(map[Scope]struct{})
|
||||
for item := range m.Iter() {
|
||||
scope, err := item.Tag.GetScope()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[scope] = struct{}{}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (m *Map) Services() (map[TargetSrv]struct{}, error) {
|
||||
res := make(map[TargetSrv]struct{})
|
||||
for item := range m.Iter() {
|
||||
srv, err := item.Tag.GetSrv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[srv] = struct{}{}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (m *Map) Targets() (map[Target]struct{}, error) {
|
||||
res := make(map[Target]struct{})
|
||||
for item := range m.Iter() {
|
||||
target, err := item.Tag.GetTarget()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[target] = struct{}{}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type Quota struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
UserId int64
|
||||
Target string
|
||||
Limit int64
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type QuotaDTO struct {
|
||||
OrgId int64 `json:"org_id,omitempty"`
|
||||
UserId int64 `json:"user_id,omitempty"`
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
Used int64 `json:"used"`
|
||||
Service string `json:"-"`
|
||||
Scope string `json:"-"`
|
||||
}
|
||||
|
||||
func (dto QuotaDTO) Tag() (Tag, error) {
|
||||
return NewTag(TargetSrv(dto.Service), Target(dto.Target), Scope(dto.Scope))
|
||||
}
|
||||
|
||||
type UpdateQuotaCmd struct {
|
||||
Target string `json:"target"`
|
||||
Limit int64 `json:"limit"`
|
||||
OrgID int64 `json:"-"`
|
||||
UserID int64 `json:"-"`
|
||||
}
|
||||
|
||||
type NewUsageReporter struct {
|
||||
TargetSrv TargetSrv
|
||||
DefaultLimits *Map
|
||||
Reporter UsageReporterFunc
|
||||
}
|
||||
|
@ -7,7 +7,24 @@ import (
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
QuotaReached(c *models.ReqContext, target string) (bool, error)
|
||||
CheckQuotaReached(ctx context.Context, target string, scopeParams *ScopeParameters) (bool, error)
|
||||
DeleteByUser(context.Context, int64) error
|
||||
// GetQuotasByScope returns the quota for the specific scope (global, organization, user)
|
||||
// If the scope is organization, the ID is expected to be the organisation ID.
|
||||
// If the scope is user, the id is expected to be the user ID.
|
||||
GetQuotasByScope(ctx context.Context, scope Scope, ID int64) ([]QuotaDTO, error)
|
||||
// Update overrides the quota for a specific scope (global, organization, user).
|
||||
// If the cmd.OrgID is set, then the organization quota are updated.
|
||||
// If the cmd.UseID is set, then the user quota are updated.
|
||||
Update(ctx context.Context, cmd *UpdateQuotaCmd) error
|
||||
// QuotaReached is called by the quota middleware for applying quota enforcement to API handlers
|
||||
QuotaReached(c *models.ReqContext, targetSrv TargetSrv) (bool, error)
|
||||
// CheckQuotaReached checks if the quota limitations have been reached for a specific service
|
||||
CheckQuotaReached(ctx context.Context, targetSrv TargetSrv, scopeParams *ScopeParameters) (bool, error)
|
||||
// DeleteQuotaForUser deletes custom quota limitations for the user
|
||||
DeleteQuotaForUser(ctx context.Context, userID int64) error
|
||||
// DeleteByOrg(ctx context.Context, orgID int64) error
|
||||
|
||||
// RegisterQuotaReporter registers a service UsageReporterFunc, targets and their default limits
|
||||
RegisterQuotaReporter(e *NewUsageReporter) error
|
||||
}
|
||||
|
||||
type UsageReporterFunc func(ctx context.Context, scopeParams *ScopeParameters) (*Map, error)
|
||||
|
@ -2,38 +2,81 @@ package quotaimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store store
|
||||
authTokenService models.ActiveTokenService
|
||||
Cfg *setting.Cfg
|
||||
SQLStore sqlstore.Store
|
||||
Logger log.Logger
|
||||
type serviceDisabled struct {
|
||||
}
|
||||
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg, tokenService models.ActiveTokenService, ss *sqlstore.SQLStore) quota.Service {
|
||||
return &Service{
|
||||
store: &sqlStore{db: db},
|
||||
Cfg: cfg,
|
||||
authTokenService: tokenService,
|
||||
SQLStore: ss,
|
||||
Logger: log.New("quota_service"),
|
||||
func (s *serviceDisabled) QuotaReached(c *models.ReqContext, targetSrv quota.TargetSrv) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *serviceDisabled) GetQuotasByScope(ctx context.Context, scope quota.Scope, id int64) ([]quota.QuotaDTO, error) {
|
||||
return nil, quota.ErrDisabled
|
||||
}
|
||||
|
||||
func (s *serviceDisabled) Update(ctx context.Context, cmd *quota.UpdateQuotaCmd) error {
|
||||
return quota.ErrDisabled
|
||||
}
|
||||
|
||||
func (s *serviceDisabled) CheckQuotaReached(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *serviceDisabled) DeleteQuotaForUser(ctx context.Context, userID int64) error {
|
||||
return quota.ErrDisabled
|
||||
}
|
||||
|
||||
func (s *serviceDisabled) RegisterQuotaReporter(e *quota.NewUsageReporter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type service struct {
|
||||
store store
|
||||
Cfg *setting.Cfg
|
||||
Logger log.Logger
|
||||
|
||||
mutex sync.RWMutex
|
||||
reporters map[quota.TargetSrv]quota.UsageReporterFunc
|
||||
|
||||
defaultLimits *quota.Map
|
||||
|
||||
targetToSrv *quota.TargetToSrv
|
||||
}
|
||||
|
||||
func ProvideService(db db.DB, cfg *setting.Cfg) quota.Service {
|
||||
logger := log.New("quota_service")
|
||||
s := service{
|
||||
store: &sqlStore{db: db, logger: logger},
|
||||
Cfg: cfg,
|
||||
Logger: logger,
|
||||
reporters: make(map[quota.TargetSrv]quota.UsageReporterFunc),
|
||||
defaultLimits: "a.Map{},
|
||||
targetToSrv: quota.NewTargetToSrv(),
|
||||
}
|
||||
|
||||
if s.IsDisabled() {
|
||||
return &serviceDisabled{}
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *service) IsDisabled() bool {
|
||||
return !s.Cfg.Quota.Enabled
|
||||
}
|
||||
|
||||
// QuotaReached checks that quota is reached for a target. Runs CheckQuotaReached and take context and scope parameters from the request context
|
||||
func (s *Service) QuotaReached(c *models.ReqContext, target string) (bool, error) {
|
||||
if !s.Cfg.Quota.Enabled {
|
||||
return false, nil
|
||||
}
|
||||
func (s *service) QuotaReached(c *models.ReqContext, targetSrv quota.TargetSrv) (bool, error) {
|
||||
// No request context means this is a background service, like LDAP Background Sync
|
||||
if c == nil {
|
||||
return false, nil
|
||||
@ -46,91 +89,129 @@ func (s *Service) QuotaReached(c *models.ReqContext, target string) (bool, error
|
||||
UserID: c.UserID,
|
||||
}
|
||||
}
|
||||
return s.CheckQuotaReached(c.Req.Context(), target, params)
|
||||
return s.CheckQuotaReached(c.Req.Context(), targetSrv, params)
|
||||
}
|
||||
|
||||
func (s *service) GetQuotasByScope(ctx context.Context, scope quota.Scope, id int64) ([]quota.QuotaDTO, error) {
|
||||
if err := scope.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := make([]quota.QuotaDTO, 0)
|
||||
|
||||
scopeParams := quota.ScopeParameters{}
|
||||
if scope == quota.OrgScope {
|
||||
scopeParams.OrgID = id
|
||||
} else if scope == quota.UserScope {
|
||||
scopeParams.UserID = id
|
||||
}
|
||||
|
||||
c, err := s.getContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customLimits, err := s.store.Get(c, &scopeParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u, err := s.getUsage(ctx, &scopeParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for item := range s.defaultLimits.Iter() {
|
||||
limit := item.Value
|
||||
|
||||
scp, err := item.Tag.GetScope()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if scp != scope {
|
||||
continue
|
||||
}
|
||||
|
||||
if targetCustomLimit, ok := customLimits.Get(item.Tag); ok {
|
||||
limit = targetCustomLimit
|
||||
}
|
||||
|
||||
target, err := item.Tag.GetTarget()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv, err := item.Tag.GetSrv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
used, _ := u.Get(item.Tag)
|
||||
q = append(q, quota.QuotaDTO{
|
||||
Target: string(target),
|
||||
Limit: limit,
|
||||
OrgId: scopeParams.OrgID,
|
||||
UserId: scopeParams.UserID,
|
||||
Used: used,
|
||||
Service: string(srv),
|
||||
Scope: string(scope),
|
||||
})
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, cmd *quota.UpdateQuotaCmd) error {
|
||||
targetFound := false
|
||||
knownTargets, err := s.defaultLimits.Targets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for t := range knownTargets {
|
||||
if t == quota.Target(cmd.Target) {
|
||||
targetFound = true
|
||||
}
|
||||
}
|
||||
if !targetFound {
|
||||
return quota.ErrInvalidTarget.Errorf("unknown quota target: %s", cmd.Target)
|
||||
}
|
||||
|
||||
c, err := s.getContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.store.Update(c, cmd)
|
||||
}
|
||||
|
||||
// CheckQuotaReached check that quota is reached for a target. If ScopeParameters are not defined, only global scope is checked
|
||||
func (s *Service) CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error) {
|
||||
if !s.Cfg.Quota.Enabled {
|
||||
return false, nil
|
||||
}
|
||||
// get the list of scopes that this target is valid for. Org, User, Global
|
||||
scopes, err := s.getQuotaScopes(target)
|
||||
func (s *service) CheckQuotaReached(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) {
|
||||
targetSrvLimits, err := s.getOverridenLimits(ctx, targetSrv, scopeParams)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, scope := range scopes {
|
||||
s.Logger.Debug("Checking quota", "target", target, "scope", scope)
|
||||
|
||||
switch scope.Name {
|
||||
case "global":
|
||||
if scope.DefaultLimit < 0 {
|
||||
continue
|
||||
}
|
||||
if scope.DefaultLimit == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if target == "session" {
|
||||
usedSessions, err := s.authTokenService.ActiveTokenCount(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
usageReporterFunc, ok := s.getReporter(targetSrv)
|
||||
if !ok {
|
||||
return false, quota.ErrInvalidTargetSrv
|
||||
}
|
||||
targetUsage, err := usageReporterFunc(ctx, scopeParams)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if usedSessions > scope.DefaultLimit {
|
||||
s.Logger.Debug("Sessions limit reached", "active", usedSessions, "limit", scope.DefaultLimit)
|
||||
return true, nil
|
||||
}
|
||||
continue
|
||||
for t, limit := range targetSrvLimits {
|
||||
switch {
|
||||
case limit < 0:
|
||||
continue
|
||||
case limit == 0:
|
||||
return true, nil
|
||||
default:
|
||||
u, ok := targetUsage.Get(t)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("no usage for target:%s", t)
|
||||
}
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled()}
|
||||
// TODO : move GetGlobalQuotaByTarget to a global quota service
|
||||
if err := s.SQLStore.GetGlobalQuotaByTarget(ctx, &query); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if query.Result.Used >= scope.DefaultLimit {
|
||||
return true, nil
|
||||
}
|
||||
case "org":
|
||||
if scopeParams == nil {
|
||||
continue
|
||||
}
|
||||
query := models.GetOrgQuotaByTargetQuery{
|
||||
OrgId: scopeParams.OrgID,
|
||||
Target: scope.Target,
|
||||
Default: scope.DefaultLimit,
|
||||
UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled(),
|
||||
}
|
||||
// TODO: move GetOrgQuotaByTarget from sqlstore to quota store
|
||||
if err := s.SQLStore.GetOrgQuotaByTarget(ctx, &query); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if query.Result.Limit < 0 {
|
||||
continue
|
||||
}
|
||||
if query.Result.Limit == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if query.Result.Used >= query.Result.Limit {
|
||||
return true, nil
|
||||
}
|
||||
case "user":
|
||||
if scopeParams == nil || scopeParams.UserID == 0 {
|
||||
continue
|
||||
}
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: scopeParams.UserID, Target: scope.Target, Default: scope.DefaultLimit, UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled()}
|
||||
// TODO: move GetUserQuotaByTarget from sqlstore to quota store
|
||||
if err := s.SQLStore.GetUserQuotaByTarget(ctx, &query); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if query.Result.Limit < 0 {
|
||||
continue
|
||||
}
|
||||
if query.Result.Limit == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if query.Result.Used >= query.Result.Limit {
|
||||
if u >= limit {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
@ -138,68 +219,127 @@ func (s *Service) CheckQuotaReached(ctx context.Context, target string, scopePar
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Service) getQuotaScopes(target string) ([]models.QuotaScope, error) {
|
||||
scopes := make([]models.QuotaScope, 0)
|
||||
switch target {
|
||||
case "user":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.User},
|
||||
models.QuotaScope{Name: "org", Target: "org_user", DefaultLimit: s.Cfg.Quota.Org.User},
|
||||
)
|
||||
return scopes, nil
|
||||
case "org":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.Org},
|
||||
models.QuotaScope{Name: "user", Target: "org_user", DefaultLimit: s.Cfg.Quota.User.Org},
|
||||
)
|
||||
return scopes, nil
|
||||
case "dashboard":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{
|
||||
Name: "global",
|
||||
Target: target,
|
||||
DefaultLimit: s.Cfg.Quota.Global.Dashboard,
|
||||
},
|
||||
models.QuotaScope{
|
||||
Name: "org",
|
||||
Target: target,
|
||||
DefaultLimit: s.Cfg.Quota.Org.Dashboard,
|
||||
},
|
||||
)
|
||||
return scopes, nil
|
||||
case "data_source":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.DataSource},
|
||||
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.DataSource},
|
||||
)
|
||||
return scopes, nil
|
||||
case "api_key":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.ApiKey},
|
||||
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.ApiKey},
|
||||
)
|
||||
return scopes, nil
|
||||
case "session":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.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: s.Cfg.Quota.Global.AlertRule},
|
||||
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.AlertRule},
|
||||
)
|
||||
return scopes, nil
|
||||
case "file":
|
||||
scopes = append(scopes,
|
||||
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.File},
|
||||
)
|
||||
return scopes, nil
|
||||
default:
|
||||
return scopes, quota.ErrInvalidQuotaTarget
|
||||
func (s *service) DeleteQuotaForUser(ctx context.Context, userID int64) error {
|
||||
c, err := s.getContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.store.DeleteByUser(c, userID)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
|
||||
return s.store.DeleteByUser(ctx, userID)
|
||||
func (s *service) RegisterQuotaReporter(e *quota.NewUsageReporter) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
_, ok := s.reporters[e.TargetSrv]
|
||||
if ok {
|
||||
return quota.ErrTargetSrvConflict.Errorf("target service: %s already exists", e.TargetSrv)
|
||||
}
|
||||
|
||||
s.reporters[e.TargetSrv] = e.Reporter
|
||||
|
||||
for item := range e.DefaultLimits.Iter() {
|
||||
target, err := item.Tag.GetTarget()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srv, err := item.Tag.GetSrv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.targetToSrv.Set(target, srv)
|
||||
s.defaultLimits.Set(item.Tag, item.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) getReporter(target quota.TargetSrv) (quota.UsageReporterFunc, bool) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
r, ok := s.reporters[target]
|
||||
return r, ok
|
||||
}
|
||||
|
||||
type reporter struct {
|
||||
target quota.TargetSrv
|
||||
reporterFunc quota.UsageReporterFunc
|
||||
}
|
||||
|
||||
func (s *service) getReporters() <-chan reporter {
|
||||
ch := make(chan reporter)
|
||||
go func() {
|
||||
s.mutex.RLock()
|
||||
defer func() {
|
||||
s.mutex.RUnlock()
|
||||
close(ch)
|
||||
}()
|
||||
for t, r := range s.reporters {
|
||||
ch <- reporter{target: t, reporterFunc: r}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (s *service) getOverridenLimits(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (map[quota.Tag]int64, error) {
|
||||
targetSrvLimits := make(map[quota.Tag]int64)
|
||||
|
||||
c, err := s.getContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customLimits, err := s.store.Get(c, scopeParams)
|
||||
if err != nil {
|
||||
return targetSrvLimits, err
|
||||
}
|
||||
|
||||
for item := range s.defaultLimits.Iter() {
|
||||
srv, err := item.Tag.GetSrv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if srv != targetSrv {
|
||||
continue
|
||||
}
|
||||
|
||||
defaultLimit := item.Value
|
||||
|
||||
if customLimit, ok := customLimits.Get(item.Tag); ok {
|
||||
targetSrvLimits[item.Tag] = customLimit
|
||||
} else {
|
||||
targetSrvLimits[item.Tag] = defaultLimit
|
||||
}
|
||||
}
|
||||
|
||||
return targetSrvLimits, nil
|
||||
}
|
||||
|
||||
func (s *service) getUsage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
usage := "a.Map{}
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for r := range s.getReporters() {
|
||||
r := r
|
||||
g.Go(func() error {
|
||||
u, err := r.reporterFunc(ctx, scopeParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Merge(u)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
func (s *service) getContext(ctx context.Context) (quota.Context, error) {
|
||||
return quota.FromContext(ctx, s.targetToSrv), nil
|
||||
}
|
||||
|
@ -3,26 +3,481 @@ package quotaimpl
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
dsservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
ngalertmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
ngalerttests "github.com/grafana/grafana/pkg/services/ngalert/tests"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
storesrv "github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xorcare/pointer"
|
||||
)
|
||||
|
||||
func TestQuotaService(t *testing.T) {
|
||||
quotaStore := &FakeQuotaStore{}
|
||||
quotaService := Service{
|
||||
quotaStore := "atest.FakeQuotaStore{}
|
||||
quotaService := service{
|
||||
store: quotaStore,
|
||||
}
|
||||
|
||||
t.Run("delete quota", func(t *testing.T) {
|
||||
err := quotaService.DeleteByUser(context.Background(), 1)
|
||||
err := quotaService.DeleteQuotaForUser(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
type FakeQuotaStore struct {
|
||||
ExpectedError error
|
||||
func TestIntegrationQuotaCommandsAndQueries(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
sqlStore.Cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
|
||||
Org: setting.OrgQuota{
|
||||
User: 2,
|
||||
Dashboard: 3,
|
||||
DataSource: 4,
|
||||
ApiKey: 5,
|
||||
AlertRule: 6,
|
||||
},
|
||||
User: setting.UserQuota{
|
||||
Org: 7,
|
||||
},
|
||||
Global: setting.GlobalQuota{
|
||||
Org: 8,
|
||||
User: 9,
|
||||
Dashboard: 10,
|
||||
DataSource: 11,
|
||||
ApiKey: 12,
|
||||
Session: 13,
|
||||
AlertRule: 14,
|
||||
File: 15,
|
||||
},
|
||||
}
|
||||
|
||||
b := bus.ProvideBus(tracing.InitializeTracerForTest())
|
||||
quotaService := ProvideService(sqlStore, sqlStore.Cfg)
|
||||
orgService, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
userService, err := userimpl.ProvideService(sqlStore, orgService, sqlStore.Cfg, nil, nil, quotaService)
|
||||
require.NoError(t, err)
|
||||
setupEnv(t, sqlStore, b, quotaService)
|
||||
|
||||
u, err := userService.Create(context.Background(), &user.CreateUserCommand{
|
||||
Name: "TestUser",
|
||||
SkipOrgSetup: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
o, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{
|
||||
Name: "TestOrg",
|
||||
UserID: u.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// fetch global default limit/usage
|
||||
defaultGlobalLimits := make(map[quota.Tag]int64)
|
||||
existingGlobalUsage := make(map[quota.Tag]int64)
|
||||
scope := quota.GlobalScope
|
||||
result, err := quotaService.GetQuotasByScope(context.Background(), scope, 0)
|
||||
require.NoError(t, err)
|
||||
for _, r := range result {
|
||||
tag, err := r.Tag()
|
||||
require.NoError(t, err)
|
||||
defaultGlobalLimits[tag] = r.Limit
|
||||
existingGlobalUsage[tag] = r.Used
|
||||
}
|
||||
tag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgQuotaTarget), scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.Org, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.User, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.Dashboard, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.DataSource, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.ApiKey, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(auth.QuotaTargetSrv, auth.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.Session, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.AlertRule, defaultGlobalLimits[tag])
|
||||
tag, err = quota.NewTag(storesrv.QuotaTargetSrv, storesrv.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Global.File, defaultGlobalLimits[tag])
|
||||
|
||||
// fetch default limit/usage for org
|
||||
defaultOrgLimits := make(map[quota.Tag]int64)
|
||||
existingOrgUsage := make(map[quota.Tag]int64)
|
||||
scope = quota.OrgScope
|
||||
result, err = quotaService.GetQuotasByScope(context.Background(), scope, o.ID)
|
||||
require.NoError(t, err)
|
||||
for _, r := range result {
|
||||
tag, err := r.Tag()
|
||||
require.NoError(t, err)
|
||||
defaultOrgLimits[tag] = r.Limit
|
||||
existingOrgUsage[tag] = r.Used
|
||||
}
|
||||
tag, err = quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Org.User, defaultOrgLimits[tag])
|
||||
tag, err = quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Org.Dashboard, defaultOrgLimits[tag])
|
||||
tag, err = quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Org.DataSource, defaultOrgLimits[tag])
|
||||
tag, err = quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Org.ApiKey, defaultOrgLimits[tag])
|
||||
tag, err = quota.NewTag(ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.Org.AlertRule, defaultOrgLimits[tag])
|
||||
|
||||
// fetch default limit/usage for user
|
||||
defaultUserLimits := make(map[quota.Tag]int64)
|
||||
existingUserUsage := make(map[quota.Tag]int64)
|
||||
scope = quota.UserScope
|
||||
result, err = quotaService.GetQuotasByScope(context.Background(), scope, u.ID)
|
||||
require.NoError(t, err)
|
||||
for _, r := range result {
|
||||
tag, err := r.Tag()
|
||||
require.NoError(t, err)
|
||||
defaultUserLimits[tag] = r.Limit
|
||||
existingUserUsage[tag] = r.Used
|
||||
}
|
||||
tag, err = quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), scope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sqlStore.Cfg.Quota.User.Org, defaultUserLimits[tag])
|
||||
|
||||
t.Run("Given saved org quota for users", func(t *testing.T) {
|
||||
// update quota for the created org and limit users to 1
|
||||
var customOrgUserLimit int64 = 1
|
||||
orgCmd := quota.UpdateQuotaCmd{
|
||||
OrgID: o.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customOrgUserLimit,
|
||||
}
|
||||
err := quotaService.Update(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved limit/usage for org users", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope, "a.ScopeParameters{OrgID: o.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, customOrgUserLimit, q.Limit)
|
||||
require.Equal(t, int64(1), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get default org users limit/usage for unknown org", func(t *testing.T) {
|
||||
unknownOrgID := -1
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope, "a.ScopeParameters{OrgID: int64(unknownOrgID)})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultOrgLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(0), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get zero used org alert quota when table does not exist (ngalert is not enabled - default case)", func(t *testing.T) {
|
||||
// disable Grafana Alerting
|
||||
cfg := *sqlStore.Cfg
|
||||
cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{Enabled: pointer.Bool(false)}
|
||||
|
||||
quotaSrv := ProvideService(sqlStore, &cfg)
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaSrv, ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, quota.OrgScope, "a.ScopeParameters{OrgID: o.ID})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), q.Limit)
|
||||
})
|
||||
|
||||
t.Run("Should be able to quota list for org", func(t *testing.T) {
|
||||
result, err := quotaService.GetQuotasByScope(context.Background(), quota.OrgScope, o.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, 5)
|
||||
|
||||
require.NoError(t, err)
|
||||
for _, res := range result {
|
||||
tag, err := res.Tag()
|
||||
require.NoError(t, err)
|
||||
limit := defaultOrgLimits[tag]
|
||||
used := existingOrgUsage[tag]
|
||||
if res.Target == org.OrgUserQuotaTarget {
|
||||
limit = customOrgUserLimit
|
||||
used = 1 // one user in the created org
|
||||
}
|
||||
require.Equal(t, limit, res.Limit)
|
||||
require.Equal(t, used, res.Used)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given saved org quota for dashboards", func(t *testing.T) {
|
||||
// update quota for the created org and limit dashboards to 1
|
||||
var customOrgDashboardLimit int64 = 1
|
||||
orgCmd := quota.UpdateQuotaCmd{
|
||||
OrgID: o.ID,
|
||||
Target: string(dashboards.QuotaTarget),
|
||||
Limit: customOrgDashboardLimit,
|
||||
}
|
||||
err := quotaService.Update(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved quota by org id and target", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.OrgScope, "a.ScopeParameters{OrgID: o.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, customOrgDashboardLimit, q.Limit)
|
||||
require.Equal(t, existingOrgUsage[tag], q.Used)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given saved user quota for org", func(t *testing.T) {
|
||||
// update quota for the created user and limit orgs to 1
|
||||
var customUserOrgsLimit int64 = 1
|
||||
userQuotaCmd := quota.UpdateQuotaCmd{
|
||||
UserID: u.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customUserOrgsLimit,
|
||||
}
|
||||
err := quotaService.Update(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved limit/usage for user orgs", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope, "a.ScopeParameters{UserID: u.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, customUserOrgsLimit, q.Limit)
|
||||
require.Equal(t, int64(1), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get default user orgs limit/usage for unknown user", func(t *testing.T) {
|
||||
var unknownUserID int64 = -1
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope, "a.ScopeParameters{UserID: unknownUserID})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultUserLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(0), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to quota list for user", func(t *testing.T) {
|
||||
result, err = quotaService.GetQuotasByScope(context.Background(), quota.UserScope, u.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, result, 1)
|
||||
for _, res := range result {
|
||||
tag, err := res.Tag()
|
||||
require.NoError(t, err)
|
||||
limit := defaultUserLimits[tag]
|
||||
used := existingUserUsage[tag]
|
||||
if res.Target == org.OrgUserQuotaTarget {
|
||||
limit = customUserOrgsLimit // customized quota limit.
|
||||
used = 1 // one user in the created org
|
||||
}
|
||||
require.Equal(t, limit, res.Limit)
|
||||
require.Equal(t, used, res.Used)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Should be able to global user quota", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope, "a.ScopeParameters{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultGlobalLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(1), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to global org quota", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgQuotaTarget), quota.GlobalScope, "a.ScopeParameters{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultGlobalLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(1), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get zero used global alert quota when table does not exist (ngalert is not enabled - default case)", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, quota.GlobalScope, "a.ScopeParameters{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultGlobalLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(0), q.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to global dashboard quota", func(t *testing.T) {
|
||||
q, err := getQuotaBySrvTargetScope(t, quotaService, dashboards.QuotaTargetSrv, dashboards.QuotaTarget, quota.GlobalScope, "a.ScopeParameters{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tag, err := q.Tag()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultGlobalLimits[tag], q.Limit)
|
||||
require.Equal(t, int64(0), q.Used)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
t.Run("Should org quota updating is successful even if it called multiple time", func(t *testing.T) {
|
||||
// update quota for the created org and limit users to 1
|
||||
var customOrgUserLimit int64 = 1
|
||||
orgCmd := quota.UpdateQuotaCmd{
|
||||
OrgID: o.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customOrgUserLimit,
|
||||
}
|
||||
err := quotaService.Update(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope, "a.ScopeParameters{OrgID: o.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, customOrgUserLimit, query.Limit)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
customOrgUserLimit = 2
|
||||
orgCmd = quota.UpdateQuotaCmd{
|
||||
OrgID: o.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customOrgUserLimit,
|
||||
}
|
||||
err = quotaService.Update(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query, err = getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.OrgScope, "a.ScopeParameters{OrgID: o.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, customOrgUserLimit, query.Limit)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
t.Run("Should user quota updating is successful even if it called multiple time", func(t *testing.T) {
|
||||
// update quota for the created org and limit users to 1
|
||||
var customUserOrgLimit int64 = 1
|
||||
userQuotaCmd := quota.UpdateQuotaCmd{
|
||||
UserID: u.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customUserOrgLimit,
|
||||
}
|
||||
err := quotaService.Update(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query, err := getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope, "a.ScopeParameters{UserID: u.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, customUserOrgLimit, query.Limit)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
customUserOrgLimit = 10
|
||||
userQuotaCmd = quota.UpdateQuotaCmd{
|
||||
UserID: u.ID,
|
||||
Target: org.OrgUserQuotaTarget,
|
||||
Limit: customUserOrgLimit,
|
||||
}
|
||||
err = quotaService.Update(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query, err = getQuotaBySrvTargetScope(t, quotaService, quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), quota.UserScope, "a.ScopeParameters{UserID: u.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, customUserOrgLimit, query.Limit)
|
||||
})
|
||||
|
||||
// TODO data_source, file
|
||||
}
|
||||
|
||||
func (f *FakeQuotaStore) DeleteByUser(ctx context.Context, userID int64) error {
|
||||
return f.ExpectedError
|
||||
func getQuotaBySrvTargetScope(t *testing.T, quotaService quota.Service, srv quota.TargetSrv, target quota.Target, scope quota.Scope, scopeParams *quota.ScopeParameters) (quota.QuotaDTO, error) {
|
||||
t.Helper()
|
||||
|
||||
var id int64 = 0
|
||||
switch {
|
||||
case scope == quota.OrgScope:
|
||||
id = scopeParams.OrgID
|
||||
case scope == quota.UserScope:
|
||||
id = scopeParams.UserID
|
||||
}
|
||||
|
||||
result, err := quotaService.GetQuotasByScope(context.Background(), scope, id)
|
||||
require.NoError(t, err)
|
||||
for _, r := range result {
|
||||
if r.Target != string(target) {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Service != string(srv) {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Scope != string(scope) {
|
||||
continue
|
||||
}
|
||||
|
||||
require.Equal(t, r.OrgId, scopeParams.OrgID)
|
||||
require.Equal(t, r.UserId, scopeParams.UserID)
|
||||
return r, nil
|
||||
}
|
||||
return quota.QuotaDTO{}, err
|
||||
}
|
||||
|
||||
func setupEnv(t *testing.T, sqlStore *sqlstore.SQLStore, b bus.Bus, quotaService quota.Service) {
|
||||
_, err := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
_, err = auth.ProvideActiveAuthTokenService(sqlStore.Cfg, sqlStore, quotaService)
|
||||
require.NoError(t, err)
|
||||
_, err = dashboardStore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService)
|
||||
require.NoError(t, err)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New().WithDisabled(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
_, err = ngalert.ProvideService(
|
||||
sqlStore.Cfg, &ngalerttests.FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotaService,
|
||||
secretsService, nil, m, &foldertest.FakeService{}, &acmock.Mock{}, &dashboards.FakeDashboardService{}, nil, b, &acmock.Mock{}, annotationstest.NewFakeAnnotationsRepo(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = storesrv.ProvideService(sqlStore, featuremgmt.WithFeatures(), sqlStore.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
@ -1,23 +1,130 @@
|
||||
package quotaimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type store interface {
|
||||
DeleteByUser(context.Context, int64) error
|
||||
Get(ctx quota.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error)
|
||||
Update(ctx quota.Context, cmd *quota.UpdateQuotaCmd) error
|
||||
DeleteByUser(quota.Context, int64) error
|
||||
}
|
||||
|
||||
type sqlStore struct {
|
||||
db db.DB
|
||||
db db.DB
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (ss *sqlStore) DeleteByUser(ctx context.Context, userID int64) error {
|
||||
func (ss *sqlStore) DeleteByUser(ctx quota.Context, userID int64) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
var rawSQL = "DELETE FROM quota WHERE user_id = ?"
|
||||
_, err := sess.Exec(rawSQL, userID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) Get(ctx quota.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
limits := quota.Map{}
|
||||
if scopeParams.OrgID != 0 {
|
||||
orgLimits, err := ss.getOrgScopeQuota(ctx, scopeParams.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limits.Merge(orgLimits)
|
||||
}
|
||||
|
||||
if scopeParams.UserID != 0 {
|
||||
userLimits, err := ss.getUserScopeQuota(ctx, scopeParams.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limits.Merge(userLimits)
|
||||
}
|
||||
|
||||
return &limits, nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) Update(ctx quota.Context, cmd *quota.UpdateQuotaCmd) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
// Check if quota is already defined in the DB
|
||||
quota := quota.Quota{
|
||||
Target: cmd.Target,
|
||||
UserId: cmd.UserID,
|
||||
OrgId: cmd.OrgID,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Updated = time.Now()
|
||||
quota.Limit = cmd.Limit
|
||||
if !has {
|
||||
quota.Created = time.Now()
|
||||
// No quota in the DB for this target, so create a new one.
|
||||
if _, err := sess.Insert("a); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// update existing quota entry in the DB.
|
||||
_, err := sess.ID(quota.Id).Update("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) getUserScopeQuota(ctx quota.Context, userID int64) (*quota.Map, error) {
|
||||
r := quota.Map{}
|
||||
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
quotas := make([]*quota.Quota, 0)
|
||||
if err := sess.Table("quota").Where("user_id=? AND org_id=0", userID).Find("as); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, q := range quotas {
|
||||
srv, ok := ctx.TargetToSrv.Get(quota.Target(q.Target))
|
||||
if !ok {
|
||||
ss.logger.Info("failed to get service for target", "target", q.Target)
|
||||
}
|
||||
tag, err := quota.NewTag(srv, quota.Target(q.Target), quota.UserScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(tag, q.Limit)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &r, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) getOrgScopeQuota(ctx quota.Context, OrgID int64) (*quota.Map, error) {
|
||||
r := quota.Map{}
|
||||
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
quotas := make([]*quota.Quota, 0)
|
||||
if err := sess.Table("quota").Where("user_id=0 AND org_id=?", OrgID).Find("as); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, q := range quotas {
|
||||
srv, ok := ctx.TargetToSrv.Get(quota.Target(q.Target))
|
||||
if !ok {
|
||||
ss.logger.Info("failed to get service for target", "target", q.Target)
|
||||
}
|
||||
tag, err := quota.NewTag(srv, quota.Target(q.Target), quota.OrgScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(tag, q.Limit)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &r, err
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
)
|
||||
|
||||
func TestIntegrationQuotaDataAccess(t *testing.T) {
|
||||
@ -20,7 +21,8 @@ func TestIntegrationQuotaDataAccess(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("quota deleted", func(t *testing.T) {
|
||||
err := quotaStore.DeleteByUser(context.Background(), 1)
|
||||
ctx := quota.FromContext(context.Background(), "a.TargetToSrv{})
|
||||
err := quotaStore.DeleteByUser(ctx, 1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -12,18 +12,46 @@ type FakeQuotaService struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewQuotaServiceFake() *FakeQuotaService {
|
||||
return &FakeQuotaService{}
|
||||
func New(reached bool, err error) *FakeQuotaService {
|
||||
return &FakeQuotaService{reached, err}
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) {
|
||||
func (f *FakeQuotaService) GetQuotasByScope(ctx context.Context, scope quota.Scope, id int64) ([]quota.QuotaDTO, error) {
|
||||
return []quota.QuotaDTO{}, nil
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) Update(ctx context.Context, cmd *quota.UpdateQuotaCmd) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) QuotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) {
|
||||
return f.reached, f.err
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) CheckQuotaReached(c context.Context, target string, params *quota.ScopeParameters) (bool, error) {
|
||||
func (f *FakeQuotaService) CheckQuotaReached(c context.Context, target quota.TargetSrv, params *quota.ScopeParameters) (bool, error) {
|
||||
return f.reached, f.err
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) DeleteByUser(c context.Context, userID int64) error {
|
||||
func (f *FakeQuotaService) DeleteQuotaForUser(c context.Context, userID int64) error {
|
||||
return f.err
|
||||
}
|
||||
|
||||
func (f *FakeQuotaService) RegisterQuotaReporter(e *quota.NewUsageReporter) error {
|
||||
return f.err
|
||||
}
|
||||
|
||||
type FakeQuotaStore struct {
|
||||
ExpectedError error
|
||||
}
|
||||
|
||||
func (f *FakeQuotaStore) DeleteByUser(ctx quota.Context, userID int64) error {
|
||||
return f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeQuotaStore) Get(ctx quota.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
|
||||
return nil, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeQuotaStore) Update(ctx quota.Context, cmd *quota.UpdateQuotaCmd) error {
|
||||
return f.ExpectedError
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
dsservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
@ -27,7 +29,9 @@ func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvS
|
||||
features = featuremgmt.WithFeatures(featuremgmt.FlagDisableSecretsCompatibility, true)
|
||||
}
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New().WithDisabled(), acmock.NewMockedPermissionsService())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New().WithDisabled(), acmock.NewMockedPermissionsService(), quotaService)
|
||||
require.NoError(t, err)
|
||||
migService := ProvideDataSourceMigrationService(dsService, kvStore, features)
|
||||
return migService
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
@ -44,9 +45,12 @@ var (
|
||||
|
||||
func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
orgService := orgimpl.ProvideService(store, setting.NewCfg())
|
||||
orgService, err := orgimpl.ProvideService(store, setting.NewCfg(), quotaService)
|
||||
require.NoError(t, err)
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, orgService)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
|
||||
@ -57,7 +61,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
}()
|
||||
|
||||
orgCmd := &models.CreateOrgCommand{Name: "Some Test Org"}
|
||||
err := store.CreateOrg(context.Background(), orgCmd)
|
||||
err = store.CreateOrg(context.Background(), orgCmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
type testCreateSATestCase struct {
|
||||
@ -212,7 +216,9 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
|
||||
func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
|
||||
@ -284,7 +290,9 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
|
||||
sqlStore db.DB, saStore serviceaccounts.Store) (*web.Mux, *ServiceAccountsAPI) {
|
||||
cfg := setting.NewCfg()
|
||||
teamSvc := teamimpl.ProvideService(sqlStore, cfg)
|
||||
userSvc := userimpl.ProvideService(sqlStore, nil, cfg, teamimpl.ProvideService(sqlStore, cfg), nil)
|
||||
|
||||
userSvc, err := userimpl.ProvideService(sqlStore, nil, cfg, teamimpl.ProvideService(sqlStore, cfg), nil, quotatest.New(false, nil))
|
||||
require.NoError(t, err)
|
||||
saPermissionService, err := ossaccesscontrol.ProvideServiceAccountPermissions(
|
||||
cfg, routing.NewRouteRegister(), sqlStore, acmock, &licensing.OSSLicensingService{}, saStore, acmock, teamSvc, userSvc)
|
||||
require.NoError(t, err)
|
||||
@ -316,7 +324,9 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
|
||||
|
||||
func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
@ -408,7 +418,9 @@ func newString(s string) *string {
|
||||
|
||||
func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
@ -54,7 +55,9 @@ func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string,
|
||||
|
||||
func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil)
|
||||
svcmock := tests.ServiceAccountMock{}
|
||||
@ -171,7 +174,9 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
||||
|
||||
func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
|
||||
store := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(store, store.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(store)
|
||||
svcMock := &tests.ServiceAccountMock{}
|
||||
saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil)
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
@ -112,9 +113,12 @@ func TestStore_DeleteServiceAccount(t *testing.T) {
|
||||
func setupTestDatabase(t *testing.T) (*sqlstore.SQLStore, *ServiceAccountsStoreImpl) {
|
||||
t.Helper()
|
||||
db := db.InitTestDB(t)
|
||||
apiKeyService := apikeyimpl.ProvideService(db, db.Cfg)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(db, db.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
kvStore := kvstore.ProvideService(db)
|
||||
orgService := orgimpl.ProvideService(db, setting.NewCfg())
|
||||
orgService, err := orgimpl.ProvideService(db, setting.NewCfg(), quotaService)
|
||||
require.NoError(t, err)
|
||||
return db, ProvideServiceAccountsStore(db, apiKeyService, kvStore, orgService)
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -70,8 +71,10 @@ func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey)
|
||||
addKeyCmd.Key = "secret"
|
||||
}
|
||||
|
||||
apiKeyService := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg)
|
||||
err := apiKeyService.AddAPIKey(context.Background(), addKeyCmd)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
apiKeyService, err := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService)
|
||||
require.NoError(t, err)
|
||||
err = apiKeyService.AddAPIKey(context.Background(), addKeyCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
if testKey.IsExpired {
|
||||
|
@ -98,34 +98,6 @@ func (m *SQLStoreMock) WithNewDbSession(ctx context.Context, callback sqlstore.D
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotasQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) UpdateOrgQuota(ctx context.Context, cmd *models.UpdateOrgQuotaCmd) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetUserQuotaByTarget(ctx context.Context, query *models.GetUserQuotaByTargetQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetUserQuotas(ctx context.Context, query *models.GetUserQuotasQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) UpdateUserQuota(ctx context.Context, cmd *models.UpdateUserQuotaCmd) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
@ -1,315 +0,0 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
alertRuleTarget = "alert_rule"
|
||||
dashboardTarget = "dashboard"
|
||||
filesTarget = "file"
|
||||
)
|
||||
|
||||
type targetCount struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
quota := models.Quota{
|
||||
Target: query.Target,
|
||||
OrgId: query.OrgId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
quota.Limit = query.Default
|
||||
}
|
||||
|
||||
var used int64
|
||||
if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM %s WHERE org_id=?",
|
||||
dialect.Quote(query.Target))
|
||||
|
||||
if query.Target == dashboardTarget {
|
||||
rawSQL += fmt.Sprintf(" AND is_folder=%s", dialect.BooleanStr(false))
|
||||
}
|
||||
// need to account for removing service accounts from the user table
|
||||
if query.Target == "org_user" {
|
||||
rawSQL = fmt.Sprintf("SELECT COUNT(*) as count from (select user_id from %s where org_id=? AND user_id IN (SELECT id as user_id FROM %s WHERE is_service_account=%s)) as subq",
|
||||
dialect.Quote(query.Target),
|
||||
dialect.Quote("user"),
|
||||
dialect.BooleanStr(false),
|
||||
)
|
||||
}
|
||||
resp := make([]*targetCount, 0)
|
||||
if err := sess.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: used,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotasQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
quotas := make([]*models.Quota, 0)
|
||||
if err := sess.Table("quota").Where("org_id=? AND user_id=0", query.OrgId).Find("as); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultQuotas := setting.Quota.Org.ToMap()
|
||||
|
||||
seenTargets := make(map[string]bool)
|
||||
for _, q := range quotas {
|
||||
seenTargets[q.Target] = true
|
||||
}
|
||||
|
||||
for t, v := range defaultQuotas {
|
||||
if _, ok := seenTargets[t]; !ok {
|
||||
quotas = append(quotas, &models.Quota{
|
||||
OrgId: query.OrgId,
|
||||
Target: t,
|
||||
Limit: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]*models.OrgQuotaDTO, len(quotas))
|
||||
for i, q := range quotas {
|
||||
var used int64
|
||||
var rawSQL string
|
||||
if q.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL = fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
|
||||
|
||||
// need to account for removing service accounts from the user table
|
||||
if q.Target == "org_user" {
|
||||
rawSQL = fmt.Sprintf("SELECT COUNT(*) as count from (select user_id from %s where org_id=? AND user_id IN (SELECT id as user_id FROM %s WHERE is_service_account=%s)) as subq",
|
||||
dialect.Quote(q.Target),
|
||||
dialect.Quote("user"),
|
||||
dialect.BooleanStr(false),
|
||||
)
|
||||
}
|
||||
resp := make([]*targetCount, 0)
|
||||
if err := sess.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: used,
|
||||
}
|
||||
}
|
||||
query.Result = result
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) UpdateOrgQuota(ctx context.Context, cmd *models.UpdateOrgQuotaCmd) error {
|
||||
return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
|
||||
// Check if quota is already defined in the DB
|
||||
quota := models.Quota{
|
||||
Target: cmd.Target,
|
||||
OrgId: cmd.OrgId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Updated = time.Now()
|
||||
quota.Limit = cmd.Limit
|
||||
if !has {
|
||||
quota.Created = time.Now()
|
||||
// No quota in the DB for this target, so create a new one.
|
||||
if _, err := sess.Insert("a); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// update existing quota entry in the DB.
|
||||
_, err := sess.ID(quota.Id).Update("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetUserQuotaByTarget(ctx context.Context, query *models.GetUserQuotaByTargetQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
quota := models.Quota{
|
||||
Target: query.Target,
|
||||
UserId: query.UserId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
quota.Limit = query.Default
|
||||
}
|
||||
|
||||
var used int64
|
||||
if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// 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 := sess.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: used,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetUserQuotas(ctx context.Context, query *models.GetUserQuotasQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
quotas := make([]*models.Quota, 0)
|
||||
if err := sess.Table("quota").Where("user_id=? AND org_id=0", query.UserId).Find("as); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultQuotas := setting.Quota.User.ToMap()
|
||||
|
||||
seenTargets := make(map[string]bool)
|
||||
for _, q := range quotas {
|
||||
seenTargets[q.Target] = true
|
||||
}
|
||||
|
||||
for t, v := range defaultQuotas {
|
||||
if _, ok := seenTargets[t]; !ok {
|
||||
quotas = append(quotas, &models.Quota{
|
||||
UserId: query.UserId,
|
||||
Target: t,
|
||||
Limit: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]*models.UserQuotaDTO, len(quotas))
|
||||
for i, q := range quotas {
|
||||
var used int64
|
||||
if q.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// 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 := sess.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: used,
|
||||
}
|
||||
}
|
||||
query.Result = result
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) UpdateUserQuota(ctx context.Context, cmd *models.UpdateUserQuotaCmd) error {
|
||||
return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
|
||||
// Check if quota is already defined in the DB
|
||||
quota := models.Quota{
|
||||
Target: cmd.Target,
|
||||
UserId: cmd.UserId,
|
||||
}
|
||||
has, err := sess.Get("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Updated = time.Now()
|
||||
quota.Limit = cmd.Limit
|
||||
if !has {
|
||||
quota.Created = time.Now()
|
||||
// No quota in the DB for this target, so create a new one.
|
||||
if _, err := sess.Insert("a); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// update existing quota entry in the DB.
|
||||
_, err := sess.ID(quota.Id).Update("a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SQLStore) GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
return ss.WithDbSession(ctx, func(sess *DBSession) error {
|
||||
var used int64
|
||||
|
||||
if query.Target == filesTarget {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM %s",
|
||||
dialect.Quote("file"))
|
||||
|
||||
notFolderCondition := fmt.Sprintf(" WHERE path NOT LIKE '%s'", "%/")
|
||||
resp := make([]*targetCount, 0)
|
||||
if err := sess.SQL(rawSQL + notFolderCondition).Find(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
used = resp[0].Count
|
||||
} else if query.Target != alertRuleTarget || query.UnifiedAlertingEnabled {
|
||||
// get quota used.
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM %s",
|
||||
dialect.Quote(query.Target))
|
||||
|
||||
if query.Target == dashboardTarget {
|
||||
rawSQL += fmt.Sprintf(" WHERE is_folder=%s", dialect.BooleanStr(false))
|
||||
}
|
||||
// removing service accounts from count
|
||||
if query.Target == dialect.Quote("user") {
|
||||
rawSQL += fmt.Sprintf(" WHERE is_service_account=%s", dialect.BooleanStr(false))
|
||||
}
|
||||
resp := make([]*targetCount, 0)
|
||||
if err := sess.SQL(rawSQL).Find(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
used = resp[0].Count
|
||||
}
|
||||
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: used,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIntegrationQuotaCommandsAndQueries(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
sqlStore := InitTestDB(t)
|
||||
userId := int64(1)
|
||||
orgId := int64(0)
|
||||
|
||||
setting.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Org: &setting.OrgQuota{
|
||||
User: 5,
|
||||
Dashboard: 5,
|
||||
DataSource: 5,
|
||||
ApiKey: 5,
|
||||
AlertRule: 5,
|
||||
},
|
||||
User: &setting.UserQuota{
|
||||
Org: 5,
|
||||
},
|
||||
Global: &setting.GlobalQuota{
|
||||
Org: 5,
|
||||
User: 5,
|
||||
Dashboard: 5,
|
||||
DataSource: 5,
|
||||
ApiKey: 5,
|
||||
Session: 5,
|
||||
AlertRule: 5,
|
||||
},
|
||||
}
|
||||
createUserCmd := user.CreateUserCommand{
|
||||
Name: "TestUser",
|
||||
OrgID: orgId,
|
||||
SkipOrgSetup: true,
|
||||
}
|
||||
user, err := sqlStore.CreateUser(context.Background(), createUserCmd)
|
||||
require.NoError(t, err)
|
||||
// create a new org and add user_id 1 as admin.
|
||||
// we will then have an org with 1 user. and a user
|
||||
// with 1 org.
|
||||
userCmd := models.CreateOrgCommand{
|
||||
Name: "TestOrg",
|
||||
UserId: user.ID,
|
||||
}
|
||||
|
||||
err = sqlStore.CreateOrg(context.Background(), &userCmd)
|
||||
require.NoError(t, err)
|
||||
orgId = userCmd.Result.Id
|
||||
|
||||
t.Run("Given saved org quota for users", func(t *testing.T) {
|
||||
orgCmd := models.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err := sqlStore.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved quota by org id and target", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), query.Result.Limit)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get default quota by org id and target", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: 123, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(11), query.Result.Limit)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get used org quota when rows exist", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get used org quota when no rows exist", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: 2, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get zero used org alert quota when table does not exist (ngalert is not enabled - default case)", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: 2, Target: "alert", Default: 11}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to quota list for org", func(t *testing.T) {
|
||||
query := models.GetOrgQuotasQuery{OrgId: orgId}
|
||||
err = sqlStore.GetOrgQuotas(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, query.Result, 5)
|
||||
for _, res := range query.Result {
|
||||
limit := int64(5) // default quota limit
|
||||
used := int64(0)
|
||||
if res.Target == "org_user" {
|
||||
limit = 10 // customized quota limit.
|
||||
used = 1
|
||||
}
|
||||
require.Equal(t, limit, res.Limit)
|
||||
require.Equal(t, used, res.Used)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given saved org quota for dashboards", func(t *testing.T) {
|
||||
orgCmd := models.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: dashboardTarget,
|
||||
Limit: 10,
|
||||
}
|
||||
err := sqlStore.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved quota by org id and target", func(t *testing.T) {
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: dashboardTarget, Default: 1}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), query.Result.Limit)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given saved user quota for org", func(t *testing.T) {
|
||||
userQuotaCmd := models.UpdateUserQuotaCmd{
|
||||
UserId: userId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err := sqlStore.UpdateUserQuota(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should be able to get saved quota by user id and target", func(t *testing.T) {
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), query.Result.Limit)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get default quota by user id and target", func(t *testing.T) {
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: 9, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(11), query.Result.Limit)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get used user quota when rows exist", func(t *testing.T) {
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get used user quota when no rows exist", func(t *testing.T) {
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: 2, Target: "org_user", Default: 11}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to quota list for user", func(t *testing.T) {
|
||||
query := models.GetUserQuotasQuery{UserId: userId}
|
||||
err = sqlStore.GetUserQuotas(context.Background(), &query)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, query.Result, 1)
|
||||
require.Equal(t, int64(10), query.Result[0].Limit)
|
||||
require.Equal(t, int64(1), query.Result[0].Used)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Should be able to global user quota", func(t *testing.T) {
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: "user", Default: 5}
|
||||
err = sqlStore.GetGlobalQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
require.Equal(t, int64(1), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to global org quota", func(t *testing.T) {
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: "org", Default: 5}
|
||||
err = sqlStore.GetGlobalQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
require.Equal(t, int64(1), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to get zero used global alert quota when table does not exist (ngalert is not enabled - default case)", func(t *testing.T) {
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: "alert_rule", Default: 5}
|
||||
err = sqlStore.GetGlobalQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
|
||||
t.Run("Should be able to global dashboard quota", func(t *testing.T) {
|
||||
query := models.GetGlobalQuotaByTargetQuery{Target: dashboardTarget, Default: 5}
|
||||
err = sqlStore.GetGlobalQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
require.Equal(t, int64(0), query.Result.Used)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
t.Run("Should org quota updating is successful even if it called multiple time", func(t *testing.T) {
|
||||
orgCmd := models.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: "org_user",
|
||||
Limit: 5,
|
||||
}
|
||||
err := sqlStore.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
orgCmd = models.UpdateOrgQuotaCmd{
|
||||
OrgId: orgId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err = sqlStore.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query = models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetOrgQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), query.Result.Limit)
|
||||
})
|
||||
|
||||
// related: https://github.com/grafana/grafana/issues/14342
|
||||
t.Run("Should user quota updating is successful even if it called multiple time", func(t *testing.T) {
|
||||
userQuotaCmd := models.UpdateUserQuotaCmd{
|
||||
UserId: userId,
|
||||
Target: "org_user",
|
||||
Limit: 5,
|
||||
}
|
||||
err := sqlStore.UpdateUserQuota(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(5), query.Result.Limit)
|
||||
|
||||
// XXX: resolution of `Updated` column is 1sec, so this makes delay
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
userQuotaCmd = models.UpdateUserQuotaCmd{
|
||||
UserId: userId,
|
||||
Target: "org_user",
|
||||
Limit: 10,
|
||||
}
|
||||
err = sqlStore.UpdateUserQuota(context.Background(), &userQuotaCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
query = models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
|
||||
err = sqlStore.GetUserQuotaByTarget(context.Background(), &query)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(10), query.Result.Limit)
|
||||
})
|
||||
}
|
@ -23,13 +23,6 @@ type Store interface {
|
||||
GetSignedInUser(ctx context.Context, query *models.GetSignedInUserQuery) error
|
||||
WithDbSession(ctx context.Context, callback DBTransactionFunc) error
|
||||
WithNewDbSession(ctx context.Context, callback DBTransactionFunc) error
|
||||
GetOrgQuotaByTarget(ctx context.Context, query *models.GetOrgQuotaByTargetQuery) error
|
||||
GetOrgQuotas(ctx context.Context, query *models.GetOrgQuotasQuery) error
|
||||
UpdateOrgQuota(ctx context.Context, cmd *models.UpdateOrgQuotaCmd) error
|
||||
GetUserQuotaByTarget(ctx context.Context, query *models.GetUserQuotaByTargetQuery) error
|
||||
GetUserQuotas(ctx context.Context, query *models.GetUserQuotasQuery) error
|
||||
UpdateUserQuota(ctx context.Context, cmd *models.UpdateUserQuotaCmd) error
|
||||
GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error
|
||||
WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error
|
||||
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||
Migrate(bool) error
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@ -58,6 +59,11 @@ type CreateFolderCmd struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
const (
|
||||
QuotaTargetSrv quota.TargetSrv = "store"
|
||||
QuotaTarget quota.Target = "file"
|
||||
)
|
||||
|
||||
type StorageService interface {
|
||||
registry.BackgroundService
|
||||
|
||||
@ -97,7 +103,7 @@ func ProvideService(
|
||||
features featuremgmt.FeatureToggles,
|
||||
cfg *setting.Cfg,
|
||||
quotaService quota.Service,
|
||||
) StorageService {
|
||||
) (StorageService, error) {
|
||||
settings, err := LoadStorageConfig(cfg, features)
|
||||
if err != nil {
|
||||
grafanaStorageLogger.Warn("error loading storage config", "error", err)
|
||||
@ -259,7 +265,37 @@ func ProvideService(
|
||||
s := newStandardStorageService(sql, globalRoots, initializeOrgStorages, authService, cfg)
|
||||
s.quotaService = quotaService
|
||||
s.cfg = settings
|
||||
return s
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: QuotaTargetSrv,
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Usage,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.File)
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
func createSystemBrandingPathFilter() filestorage.PathFilter {
|
||||
@ -329,6 +365,32 @@ func (s *standardStorageService) Read(ctx context.Context, user *user.SignedInUs
|
||||
return s.tree.GetFile(ctx, getOrgId(user), path)
|
||||
}
|
||||
|
||||
func (s *standardStorageService) Usage(ctx context.Context, ScopeParameters *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
|
||||
err := s.sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
r := result{}
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) AS count FROM file WHERE path NOT LIKE '%s'", "%/")
|
||||
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tag, err := quota.NewTag(QuotaTargetSrv, QuotaTarget, quota.GlobalScope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Set(tag, r.Count)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return u, err
|
||||
}
|
||||
|
||||
type UploadRequest struct {
|
||||
Contents []byte
|
||||
Path string
|
||||
@ -395,7 +457,7 @@ func (s *standardStorageService) Upload(ctx context.Context, user *user.SignedIn
|
||||
|
||||
func (s *standardStorageService) checkFileQuota(ctx context.Context, path string) error {
|
||||
// assumes we are only uploading to the SQL database - TODO: refactor once we introduce object stores
|
||||
quotaReached, err := s.quotaService.CheckQuotaReached(ctx, "file", nil)
|
||||
quotaReached, err := s.quotaService.CheckQuotaReached(ctx, QuotaTargetSrv, nil)
|
||||
if err != nil {
|
||||
grafanaStorageLogger.Error("failed while checking upload quota", "path", path, "error", err)
|
||||
return ErrUploadInternalError
|
||||
|
@ -118,7 +118,7 @@ func setupUploadStore(t *testing.T, authService storageAuthService) (StorageServ
|
||||
store.cfg = &GlobalStorageConfig{
|
||||
AllowUnsanitizedSvgUpload: true,
|
||||
}
|
||||
store.quotaService = quotatest.NewQuotaServiceFake()
|
||||
store.quotaService = quotatest.New(false, nil)
|
||||
|
||||
return store, mockStorage, storageName
|
||||
}
|
||||
@ -297,7 +297,7 @@ func TestContentRootWithNestedStorage(t *testing.T) {
|
||||
store.cfg = &GlobalStorageConfig{
|
||||
AllowUnsanitizedSvgUpload: true,
|
||||
}
|
||||
store.quotaService = quotatest.NewQuotaServiceFake()
|
||||
store.quotaService = quotatest.New(false, nil)
|
||||
fileName := "file.jpg"
|
||||
|
||||
tests := []struct {
|
||||
|
@ -357,3 +357,8 @@ type SearchUserFilter interface {
|
||||
}
|
||||
|
||||
type FilterHandler func(params []string) (Filter, error)
|
||||
|
||||
const (
|
||||
QuotaTargetSrv string = "user"
|
||||
QuotaTarget string = "user"
|
||||
)
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -37,6 +38,8 @@ type store interface {
|
||||
BatchDisableUsers(context.Context, *user.BatchDisableUsersCommand) error
|
||||
Disable(context.Context, *user.DisableUserCommand) error
|
||||
Search(context.Context, *user.SearchUsersQuery) (*user.SearchUserQueryResult, error)
|
||||
|
||||
Count(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
type sqlStore struct {
|
||||
@ -461,6 +464,22 @@ func (ss *sqlStore) UpdatePermissions(ctx context.Context, userID int64, isAdmin
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) Count(ctx context.Context) (int64, error) {
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s WHERE is_service_account=%s", ss.db.GetDialect().Quote("user"), ss.db.GetDialect().BooleanStr(false))
|
||||
if _, err := sess.SQL(rawSQL).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return r.Count, err
|
||||
}
|
||||
|
||||
// validateOneAdminLeft validate that there is an admin user left
|
||||
func validateOneAdminLeft(ctx context.Context, sess *db.Session) error {
|
||||
count, err := sess.Where("is_admin=?", true).Count(&user.User{})
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -32,15 +33,44 @@ func ProvideService(
|
||||
cfg *setting.Cfg,
|
||||
teamService team.Service,
|
||||
cacheService *localcache.CacheService,
|
||||
) user.Service {
|
||||
quotaService quota.Service,
|
||||
) (user.Service, error) {
|
||||
store := ProvideStore(db, cfg)
|
||||
return &Service{
|
||||
s := &Service{
|
||||
store: &store,
|
||||
orgService: orgService,
|
||||
cfg: cfg,
|
||||
teamService: teamService,
|
||||
cacheService: cacheService,
|
||||
}
|
||||
|
||||
defaultLimits, err := readQuotaConfig(cfg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{
|
||||
TargetSrv: quota.TargetSrv(user.QuotaTargetSrv),
|
||||
DefaultLimits: defaultLimits,
|
||||
Reporter: s.Usage,
|
||||
}); err != nil {
|
||||
return s, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Service) Usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) {
|
||||
u := "a.Map{}
|
||||
if used, err := s.store.Count(ctx); err != nil {
|
||||
return u, err
|
||||
} else {
|
||||
tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.Set(tag, used)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) {
|
||||
@ -304,3 +334,19 @@ func (s *Service) GetProfile(ctx context.Context, query *user.GetUserProfileQuer
|
||||
result, err := s.store.GetProfile(ctx, query)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
if cfg == nil {
|
||||
return limits, nil
|
||||
}
|
||||
|
||||
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
|
||||
if err != nil {
|
||||
return limits, err
|
||||
}
|
||||
|
||||
limits.Set(globalQuotaTag, cfg.Quota.Global.User)
|
||||
return limits, nil
|
||||
}
|
||||
|
@ -252,3 +252,7 @@ func (f *FakeUserStore) Disable(ctx context.Context, cmd *user.DisableUserComman
|
||||
func (f *FakeUserStore) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
|
||||
return f.ExpectedSearchUserQueryResult, f.ExpectedError
|
||||
}
|
||||
|
||||
func (f *FakeUserStore) Count(ctx context.Context) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -153,9 +153,6 @@ var (
|
||||
LDAPAllowSignup bool
|
||||
LDAPActiveSyncEnabled bool
|
||||
|
||||
// Quota
|
||||
Quota QuotaSettings
|
||||
|
||||
// Alerting
|
||||
AlertingEnabled *bool
|
||||
ExecuteAlerts bool
|
||||
@ -422,12 +419,12 @@ type Cfg struct {
|
||||
LDAPSkipOrgRoleSync bool
|
||||
LDAPAllowSignup bool
|
||||
|
||||
Quota QuotaSettings
|
||||
|
||||
DefaultTheme string
|
||||
DefaultLocale string
|
||||
HomePage string
|
||||
|
||||
Quota QuotaSettings
|
||||
|
||||
AutoAssignOrg bool
|
||||
AutoAssignOrgId int
|
||||
AutoAssignOrgRole string
|
||||
@ -1053,11 +1050,12 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
||||
cfg.readAzureSettings()
|
||||
cfg.readSessionConfig()
|
||||
cfg.readSmtpSettings()
|
||||
cfg.readQuotaSettings()
|
||||
if err := cfg.readAnnotationSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.readQuotaSettings()
|
||||
|
||||
cfg.readExpressionsSettings()
|
||||
if err := cfg.readGrafanaEnvironmentMetrics(); err != nil {
|
||||
return err
|
||||
|
@ -1,9 +1,5 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type OrgQuota struct {
|
||||
User int64 `target:"org_user"`
|
||||
DataSource int64 `target:"data_source"`
|
||||
@ -27,45 +23,17 @@ type GlobalQuota struct {
|
||||
File int64 `target:"file"`
|
||||
}
|
||||
|
||||
func (q *OrgQuota) ToMap() map[string]int64 {
|
||||
return quotaToMap(*q)
|
||||
}
|
||||
|
||||
func (q *UserQuota) ToMap() map[string]int64 {
|
||||
return quotaToMap(*q)
|
||||
}
|
||||
|
||||
func quotaToMap(q interface{}) map[string]int64 {
|
||||
qMap := make(map[string]int64)
|
||||
typ := reflect.TypeOf(q)
|
||||
val := reflect.ValueOf(q)
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
name := field.Tag.Get("target")
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
value := val.Field(i)
|
||||
qMap[name] = value.Int()
|
||||
}
|
||||
return qMap
|
||||
}
|
||||
|
||||
type QuotaSettings struct {
|
||||
Enabled bool
|
||||
Org *OrgQuota
|
||||
User *UserQuota
|
||||
Global *GlobalQuota
|
||||
Org OrgQuota
|
||||
User UserQuota
|
||||
Global GlobalQuota
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readQuotaSettings() {
|
||||
// set global defaults.
|
||||
quota := cfg.Raw.Section("quota")
|
||||
Quota.Enabled = quota.Key("enabled").MustBool(false)
|
||||
cfg.Quota.Enabled = quota.Key("enabled").MustBool(false)
|
||||
|
||||
var alertOrgQuota int64
|
||||
var alertGlobalQuota int64
|
||||
@ -74,7 +42,7 @@ func (cfg *Cfg) readQuotaSettings() {
|
||||
alertGlobalQuota = quota.Key("global_alert_rule").MustInt64(-1)
|
||||
}
|
||||
// per ORG Limits
|
||||
Quota.Org = &OrgQuota{
|
||||
cfg.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),
|
||||
@ -83,12 +51,12 @@ func (cfg *Cfg) readQuotaSettings() {
|
||||
}
|
||||
|
||||
// per User limits
|
||||
Quota.User = &UserQuota{
|
||||
cfg.Quota.User = UserQuota{
|
||||
Org: quota.Key("user_org").MustInt64(10),
|
||||
}
|
||||
|
||||
// Global Limits
|
||||
Quota.Global = &GlobalQuota{
|
||||
cfg.Quota.Global = GlobalQuota{
|
||||
User: quota.Key("global_user").MustInt64(-1),
|
||||
Org: quota.Key("global_org").MustInt64(-1),
|
||||
DataSource: quota.Key("global_data_source").MustInt64(-1),
|
||||
@ -98,6 +66,4 @@ func (cfg *Cfg) readQuotaSettings() {
|
||||
File: quota.Key("global_file").MustInt64(-1),
|
||||
AlertRule: alertGlobalQuota,
|
||||
}
|
||||
|
||||
cfg.Quota = Quota
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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"
|
||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@ -1878,6 +1877,8 @@ func TestQuota(t *testing.T) {
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
createUser(t, store, user.CreateUserCommand{
|
||||
// needs permission to update org quota
|
||||
IsAdmin: true,
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
@ -1918,30 +1919,10 @@ func TestQuota(t *testing.T) {
|
||||
// check quota limits
|
||||
t.Run("when quota limit exceed creating new rule should fail", func(t *testing.T) {
|
||||
// get existing org quota
|
||||
query := models.GetOrgQuotaByTargetQuery{OrgId: 1, Target: "alert_rule"}
|
||||
err = store.GetOrgQuotaByTarget(context.Background(), &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 := store.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
limit, used := apiClient.GetOrgQuotaLimits(t, 1)
|
||||
apiClient.UpdateAlertRuleOrgQuota(t, 1, used)
|
||||
t.Cleanup(func() {
|
||||
// reset org quota to original value
|
||||
orgCmd := models.UpdateOrgQuotaCmd{
|
||||
OrgId: 1,
|
||||
Target: "alert_rule",
|
||||
Limit: limit,
|
||||
}
|
||||
err := store.UpdateOrgQuota(context.Background(), &orgCmd)
|
||||
require.NoError(t, err)
|
||||
apiClient.UpdateAlertRuleOrgQuota(t, 1, limit)
|
||||
})
|
||||
|
||||
// try to create an alert rule
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -202,6 +203,60 @@ func (a apiClient) CreateFolder(t *testing.T, uID string, title string) {
|
||||
a.ReloadCachedPermissions(t)
|
||||
}
|
||||
|
||||
func (a apiClient) GetOrgQuotaLimits(t *testing.T, orgID int64) (int64, int64) {
|
||||
t.Helper()
|
||||
|
||||
u := fmt.Sprintf("%s/api/orgs/%d/quotas", a.url, orgID)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
results := []quota.QuotaDTO{}
|
||||
require.NoError(t, json.Unmarshal(b, &results))
|
||||
|
||||
var limit int64 = 0
|
||||
var used int64 = 0
|
||||
for _, q := range results {
|
||||
if q.Target != string(ngmodels.QuotaTargetSrv) {
|
||||
continue
|
||||
}
|
||||
limit = q.Limit
|
||||
used = q.Used
|
||||
}
|
||||
return limit, used
|
||||
}
|
||||
|
||||
func (a apiClient) UpdateAlertRuleOrgQuota(t *testing.T, orgID int64, limit int64) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode("a.UpdateQuotaCmd{
|
||||
Target: "alert_rule",
|
||||
Limit: limit,
|
||||
OrgID: orgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u := fmt.Sprintf("%s/api/orgs/%d/quotas/alert_rule", a.url, orgID)
|
||||
// nolint:gosec
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest(http.MethodPut, u, &buf)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) (int, string) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
|
@ -16,16 +16,14 @@ import (
|
||||
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
)
|
||||
|
||||
func TestHandleRequest(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
|
||||
t.Run("Should invoke plugin manager QueryData when handling request for query", func(t *testing.T) {
|
||||
origOAuthIsOAuthPassThruEnabledFunc := oAuthIsOAuthPassThruEnabledFunc
|
||||
oAuthIsOAuthPassThruEnabledFunc = func(oAuthTokenService oauthtoken.OAuthTokenService, ds *datasources.DataSource) bool {
|
||||
@ -46,7 +44,10 @@ func TestHandleRequest(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
datasourcePermissions := acmock.NewMockedPermissionsService()
|
||||
dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions)
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := ProvideService(client, nil, dsService)
|
||||
|
||||
ds := &datasources.DataSource{Id: 12, Type: "unregisteredType", JsonData: simplejson.New()}
|
||||
|
Loading…
Reference in New Issue
Block a user