From 96cdf77995c6fcd47256e19dccece145ebad59aa Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:52:07 +0200 Subject: [PATCH] Revert "Chore: Refactor quota service (#57586)" (#58394) This reverts commit 326ea86a579ed927b1999bba5f2c0a35e26506d9. --- pkg/api/admin_users.go | 2 +- pkg/api/api.go | 22 +- pkg/api/common_test.go | 18 +- pkg/api/dashboard.go | 2 +- pkg/api/dashboard_test.go | 26 +- pkg/api/folder_test.go | 2 +- pkg/api/metrics_test.go | 8 +- pkg/api/org_test.go | 15 +- pkg/api/org_users_test.go | 60 +-- pkg/api/plugin_dashboards_test.go | 2 +- pkg/api/pluginproxy/ds_proxy_test.go | 118 ++--- pkg/api/plugins_test.go | 2 +- pkg/api/quota.go | 88 ++-- pkg/api/quota_test.go | 31 +- pkg/api/user_test.go | 4 +- pkg/cmd/grafana-cli/runner/wire.go | 2 +- pkg/middleware/quota.go | 6 +- pkg/middleware/quota_test.go | 71 ++- pkg/models/quotas.go | 91 ++++ pkg/models/user_token.go | 4 + pkg/server/wire.go | 2 +- .../resourcepermissions/service_test.go | 4 +- .../annotationsimpl/xorm_store_test.go | 9 +- pkg/services/apikey/apikeyimpl/apikey.go | 54 +- pkg/services/apikey/apikeyimpl/sqlx_store.go | 33 -- pkg/services/apikey/apikeyimpl/store.go | 3 - pkg/services/apikey/apikeyimpl/xorm_store.go | 46 -- pkg/services/apikey/model.go | 6 - pkg/services/auth/auth_token.go | 51 +- pkg/services/auth/auth_token_test.go | 13 +- pkg/services/auth/model.go | 6 - pkg/services/dashboardimport/api/api.go | 11 +- pkg/services/dashboardimport/api/api_test.go | 5 +- pkg/services/dashboards/dashboard.go | 2 - pkg/services/dashboards/database/acl_test.go | 6 +- pkg/services/dashboards/database/database.go | 86 +--- .../database/database_folder_test.go | 26 +- .../database/database_provisioning_test.go | 5 +- .../dashboards/database/database_test.go | 20 +- pkg/services/dashboards/models.go | 6 - .../dashboard_service_integration_test.go | 75 ++- pkg/services/dashboards/store_mock.go | 5 - pkg/services/datasources/models.go | 6 - .../datasources/service/datasource.go | 43 +- .../datasources/service/datasource_test.go | 45 +- pkg/services/datasources/service/store.go | 48 -- .../folder/folderimpl/sqlstore_test.go | 4 +- .../guardian/accesscontrol_guardian_test.go | 8 +- .../libraryelements/libraryelements_test.go | 15 +- .../librarypanels/librarypanels_test.go | 15 +- .../login/loginservice/loginservice.go | 18 +- .../login/loginservice/loginservice_test.go | 8 +- pkg/services/ngalert/api/api.go | 25 - pkg/services/ngalert/api/api_ruler.go | 2 +- pkg/services/ngalert/api/persist.go | 2 - pkg/services/ngalert/models/alert_rule.go | 6 - pkg/services/ngalert/ngalert.go | 42 -- pkg/services/ngalert/provisioning/persist.go | 2 +- .../provisioning/quota_checker_mock.go | 29 +- pkg/services/ngalert/store/alert_rule.go | 24 - pkg/services/ngalert/tests/fakes/rules.go | 4 - pkg/services/ngalert/tests/util.go | 7 +- pkg/services/org/model.go | 6 - pkg/services/org/orgimpl/org.go | 51 +- pkg/services/org/orgimpl/org_test.go | 5 - pkg/services/org/orgimpl/store.go | 70 --- .../publicdashboards/api/query_test.go | 4 +- .../database/database_test.go | 48 +- .../publicdashboards/service/query_test.go | 13 +- .../publicdashboards/service/service_test.go | 30 +- pkg/services/query/query_test.go | 5 +- pkg/services/quota/context.go | 42 -- pkg/services/quota/model.go | 210 +------- pkg/services/quota/quota.go | 23 +- pkg/services/quota/quotaimpl/quota.go | 452 ++++++----------- pkg/services/quota/quotaimpl/quota_test.go | 469 +----------------- pkg/services/quota/quotaimpl/store.go | 115 +---- pkg/services/quota/quotaimpl/store_test.go | 4 +- pkg/services/quota/quotatest/fake.go | 38 +- .../kvstore/migrations/datasource_mig_test.go | 6 +- pkg/services/serviceaccounts/api/api_test.go | 26 +- .../serviceaccounts/api/token_test.go | 9 +- .../serviceaccounts/database/database_test.go | 8 +- pkg/services/serviceaccounts/tests/common.go | 7 +- pkg/services/sqlstore/mockstore/mockstore.go | 28 ++ pkg/services/sqlstore/quota.go | 315 ++++++++++++ pkg/services/sqlstore/quota_test.go | 301 +++++++++++ pkg/services/sqlstore/store.go | 7 + pkg/services/store/service.go | 68 +-- pkg/services/store/service_test.go | 4 +- pkg/services/user/model.go | 5 - pkg/services/user/userimpl/store.go | 19 - pkg/services/user/userimpl/user.go | 50 +- pkg/services/user/userimpl/user_test.go | 4 - pkg/setting/setting.go | 10 +- pkg/setting/setting_quota.go | 48 +- .../api/alerting/api_alertmanager_test.go | 29 +- pkg/tests/api/alerting/testing.go | 55 -- pkg/tsdb/legacydata/service/service_test.go | 9 +- 99 files changed, 1398 insertions(+), 2596 deletions(-) create mode 100644 pkg/models/quotas.go delete mode 100644 pkg/services/quota/context.go create mode 100644 pkg/services/sqlstore/quota.go create mode 100644 pkg/services/sqlstore/quota_test.go diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index e164cc49d63..7daf81be95c 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -245,7 +245,7 @@ func (hs *HTTPServer) AdminDeleteUser(c *models.ReqContext) response.Response { return nil }) g.Go(func() error { - if err := hs.QuotaService.DeleteQuotaForUser(ctx, cmd.UserID); err != nil { + if err := hs.QuotaService.DeleteByUser(ctx, cmd.UserID); err != nil { return err } return nil diff --git a/pkg/api/api.go b/pkg/api/api.go index 0a76dfab147..5692a904322 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -36,16 +36,12 @@ 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" ) @@ -73,8 +69,8 @@ func (hs *HTTPServer) registerRoutes() { // not logged in views r.Get("/logout", hs.Logout) - r.Post("/login", quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginPost)) - r.Get("/login/:name", quota(string(auth.QuotaTargetSrv)), hs.OAuthLogin) + r.Post("/login", quota("session"), routing.Wrap(hs.LoginPost)) + r.Get("/login/:name", quota("session"), hs.OAuthLogin) r.Get("/login", hs.LoginView) r.Get("/invite/:code", hs.Index) @@ -177,7 +173,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.QuotaTargetSrv), quota(org.QuotaTargetSrv), routing.Wrap(hs.SignUp)) + r.Post("/api/user/signup", quota("user"), routing.Wrap(hs.SignUp)) r.Post("/api/user/signup/step2", routing.Wrap(hs.SignUpStep2)) // invited @@ -196,7 +192,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/dashboard/snapshots/", reqSignedIn, hs.Index) // api renew session based on cookie - r.Get("/api/login/ping", quota(string(auth.QuotaTargetSrv)), routing.Wrap(hs.LoginAPIPing)) + r.Get("/api/login/ping", quota("session"), routing.Wrap(hs.LoginAPIPing)) // expose plugin file system assets r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets) @@ -302,13 +298,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.QuotaTargetSrv), quota(org.QuotaTargetSrv), routing.Wrap(hs.AddOrgUserToCurrentOrg)) + orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), 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.QuotaTargetSrv), quota(user.QuotaTargetSrv), routing.Wrap(hs.AddOrgInvite)) + orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), quota("user"), routing.Wrap(hs.AddOrgInvite)) orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.RevokeInvite)) // prefs @@ -335,7 +331,7 @@ func (hs *HTTPServer) registerRoutes() { }) // create new org - apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsCreate)), quota(org.QuotaTargetSrv), routing.Wrap(hs.CreateOrg)) + apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsCreate)), quota("org"), routing.Wrap(hs.CreateOrg)) // search all orgs apiRoute.Get("/orgs", authorizeInOrg(reqGrafanaAdmin, ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsRead)), routing.Wrap(hs.SearchOrgs)) @@ -362,7 +358,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(string(apikey.QuotaTargetSrv)), routing.Wrap(hs.AddAPIKey)) + keysRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyCreate)), quota("api_key"), routing.Wrap(hs.AddAPIKey)) keysRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyDelete, apikeyIDScope)), routing.Wrap(hs.DeleteAPIKey)) }) @@ -377,7 +373,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(string(datasources.QuotaTargetSrv)), routing.Wrap(hs.AddDataSource)) + datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(datasources.ActionCreate)), quota("data_source"), 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)) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 0ecb6a38188..8ca9c240b8e 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -45,6 +45,7 @@ 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" @@ -248,13 +249,15 @@ func (s *fakeRenderService) Init() error { } func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []accesscontrol.Permission) (*scenarioContext, *HTTPServer) { - store := sqlstore.InitTestDB(t) + cfg.Quota.Enabled = false + + store := db.InitTestDB(t) hs := &HTTPServer{ Cfg: cfg, Live: newTestLive(t, store), License: &licensing.OSSLicensingService{}, Features: featuremgmt.WithFeatures(), - QuotaService: quotatest.New(false, nil), + QuotaService: "aimpl.Service{Cfg: cfg}, RouteRegister: routing.NewRouteRegister(), AccessControl: accesscontrolmock.New().WithPermissions(permissions), searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()), @@ -373,9 +376,7 @@ func setupHTTPServerWithCfgDb( routeRegister := routing.NewRouteRegister() teamService := teamimpl.ProvideService(db, cfg) cfg.IsFeatureToggleEnabled = features.IsEnabled - quotaService := quotatest.New(false, nil) - dashboardsStore, err := dashboardsstore.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, cfg), quotaService) - require.NoError(t, err) + dashboardsStore := dashboardsstore.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, cfg)) var acmock *accesscontrolmock.Mock var ac accesscontrol.AccessControl @@ -401,8 +402,7 @@ func setupHTTPServerWithCfgDb( acService, err = acimpl.ProvideService(cfg, db, routeRegister, localcache.ProvideService(), featuremgmt.WithFeatures()) require.NoError(t, err) ac = acimpl.ProvideAccessControl(cfg) - userSvc, err = userimpl.ProvideService(db, nil, cfg, teamimpl.ProvideService(db, cfg), localcache.ProvideService(), quotatest.New(false, nil)) - require.NoError(t, err) + userSvc = userimpl.ProvideService(db, nil, cfg, teamimpl.ProvideService(db, cfg), localcache.ProvideService()) } 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: quotaService, + QuotaService: "aimpl.Service{Cfg: cfg}, 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.New(false, nil), + QuotaService: quotatest.NewQuotaServiceFake(), searchUsersService: &searchusers.OSSService{}, } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index f8d47c8866e..2f3f335d389 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -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, dashboards.QuotaTargetSrv) + limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard") if err != nil { return response.Error(500, "failed to get quota", err) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 6c506ee49a1..b4fc7c52e3c 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -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/quotatest" + "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -150,7 +150,6 @@ func TestDashboardAPIEndpoint(t *testing.T) { DashboardService: dashboardService, dashboardVersionService: fakeDashboardVersionService, Coremodels: registry.NewBase(nil), - QuotaService: quotatest.New(false, nil), } setUp := func() { @@ -991,12 +990,9 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr provisioningService = provisioning.NewProvisioningServiceMock(context.Background()) } - var err error if dashboardStore == nil { sql := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err = database.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService) - require.NoError(t, err) + dashboardStore = database.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg)) } libraryPanelsService := mockLibraryPanelService{} @@ -1035,7 +1031,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 @@ -1081,10 +1077,12 @@ 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: quotatest.New(false, nil), + Cfg: cfg, + ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), + Live: newTestLive(t, db.InitTestDB(t)), + QuotaService: "aimpl.Service{ + Cfg: cfg, + }, pluginStore: &plugins.FakePluginStore{}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, @@ -1118,7 +1116,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: quotatest.New(false, nil), + QuotaService: "aimpl.Service{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, SQLStore: sqlmock, @@ -1154,7 +1152,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: quotatest.New(false, nil), + QuotaService: "aimpl.Service{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, SQLStore: sqlmock, @@ -1192,7 +1190,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: quotatest.New(false, nil), + QuotaService: "aimpl.Service{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, DashboardService: mock, diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 1d759717a91..bc7ace258a9 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -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.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) t.Run("Should attach access control metadata to multiple folders", func(t *testing.T) { diff --git a/pkg/api/metrics_test.go b/pkg/api/metrics_test.go index 3992a5cac1d..8f7961a9daf 100644 --- a/pkg/api/metrics_test.go +++ b/pkg/api/metrics_test.go @@ -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.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) serverFeatureDisabled := SetupAPITestServer(t, func(hs *HTTPServer) { hs.queryDataService = qds hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, false) - hs.QuotaService = quotatest.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) 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.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) 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.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) req := srv.NewPostRequest("/api/ds/query", strings.NewReader(tc.request)) webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer}) diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index 49ed8000aae..e97d330e312 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -11,7 +11,6 @@ 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" @@ -105,8 +104,7 @@ func TestAPIEndpoint_PutCurrentOrg_LegacyAccessControl(t *testing.T) { }) setInitCtxSignedInOrgAdmin(sc.initCtx) - sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil)) - require.NoError(t, err) + sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) 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) @@ -120,8 +118,7 @@ func TestAPIEndpoint_PutCurrentOrg_AccessControl(t *testing.T) { _, err := sc.db.CreateOrgWithMember("TestOrg", sc.initCtx.UserID) require.NoError(t, err) - sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil)) - require.NoError(t, err) + sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) input := strings.NewReader(testUpdateOrgNameForm) t.Run("AccessControl allows updating current org with correct permissions", func(t *testing.T) { @@ -439,9 +436,7 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) { cfg.RBACEnabled = false sc := setupHTTPServerWithCfg(t, true, cfg) setInitCtxSignedInViewer(sc.initCtx) - var err error - sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil)) - require.NoError(t, err) + sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) // Create two orgs, to update another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) @@ -461,9 +456,7 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_PutOrg_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - var err error - sc.hs.orgService, err = orgimpl.ProvideService(sc.db, sc.cfg, quotatest.New(false, nil)) - require.NoError(t, err) + sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) // Create two orgs, to update another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 3dbe71300ae..71a6b00db7d 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -22,7 +22,6 @@ 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" @@ -390,13 +389,11 @@ 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, 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) + hs.userService = userimpl.ProvideService( + hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), + ) + hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg) }) setupOrgUsersDBForAccessControlTests(t, sc.db) setInitCtxSignedInUser(sc.initCtx, tc.user) @@ -406,7 +403,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 { @@ -496,14 +493,11 @@ 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) { - 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) + hs.userService = userimpl.ProvideService( + hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), + ) + hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg) }) setInitCtxSignedInUser(sc.initCtx, tc.user) setupOrgUsersDBForAccessControlTests(t, sc.db) @@ -604,11 +598,10 @@ 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, 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.userService = userimpl.ProvideService( + hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), + ) }) setupOrgUsersDBForAccessControlTests(t, sc.db) @@ -723,12 +716,11 @@ 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, 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) + hs.userService = userimpl.ProvideService( + hs.SQLStore, nil, setting.NewCfg(), teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), setting.NewCfg()), localcache.ProvideService(), + ) }) setInitCtxSignedInViewer(sc.initCtx) setupOrgUsersDBForAccessControlTests(t, sc.db) @@ -843,14 +835,11 @@ 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) { - 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) + hs.userService = userimpl.ProvideService( + hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), + ) + hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg) }) setupOrgUsersDBForAccessControlTests(t, sc.db) setInitCtxSignedInUser(sc.initCtx, tc.user) @@ -973,14 +962,11 @@ 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) { - 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) + hs.userService = userimpl.ProvideService( + hs.SQLStore, nil, cfg, teamimpl.ProvideService(hs.SQLStore.(*sqlstore.SQLStore), cfg), localcache.ProvideService(), + ) + hs.orgService = orgimpl.ProvideService(hs.SQLStore, cfg) }) setupOrgUsersDBForAccessControlTests(t, sc.db) setInitCtxSignedInUser(sc.initCtx, tc.user) diff --git a/pkg/api/plugin_dashboards_test.go b/pkg/api/plugin_dashboards_test.go index 6ad7abfcc95..e98116f96f4 100644 --- a/pkg/api/plugin_dashboards_test.go +++ b/pkg/api/plugin_dashboards_test.go @@ -41,7 +41,7 @@ func TestGetPluginDashboards(t *testing.T) { s := SetupAPITestServer(t, func(hs *HTTPServer) { hs.pluginDashboardService = pluginDashboardService - hs.QuotaService = quotatest.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) t.Run("Not signed in should return 404 Not Found", func(t *testing.T) { diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index af7ec30e5ac..e31a16c07c1 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -32,7 +32,6 @@ 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" @@ -139,9 +138,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path", func(t *testing.T) { ctx, req := setUp() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -154,9 +151,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path and has dynamic url", func(t *testing.T) { ctx, req := setUp() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) proxy.matchedRoute = routes[3] @@ -168,9 +163,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path with no url", func(t *testing.T) { ctx, req := setUp() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) proxy.matchedRoute = routes[4] @@ -181,9 +174,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path and has dynamic body", func(t *testing.T) { ctx, req := setUp() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) proxy.matchedRoute = routes[5] @@ -197,9 +188,7 @@ 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() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) err = proxy.validateRequest() @@ -208,9 +197,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("plugin route with admin role and user is editor", func(t *testing.T) { ctx, _ := setUp() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) err = proxy.validateRequest() @@ -220,9 +207,7 @@ 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 - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) err = proxy.validateRequest() @@ -313,9 +298,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { }, } - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -331,9 +314,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { req, err := http.NewRequest("GET", "http://localhost/asd", nil) require.NoError(t, err) client = newFakeHTTPClient(t, json2) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -350,9 +331,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { require.NoError(t, err) client = newFakeHTTPClient(t, []byte{}) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -376,9 +355,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -405,9 +382,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -433,9 +408,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -465,9 +438,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, pluginRoutes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -492,9 +463,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -545,9 +514,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -684,9 +651,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -706,9 +671,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -724,9 +687,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -750,9 +711,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -779,9 +738,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -807,9 +764,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -835,11 +790,8 @@ 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")) - 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) + 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) require.Error(t, err) assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`)) } @@ -860,10 +812,8 @@ 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")) - 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) + 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) require.NoError(t, err) } @@ -906,9 +856,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer) if tc.err == nil { require.NoError(t, err) @@ -936,9 +884,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) 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) @@ -1055,9 +1001,7 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, secrets tracer := tracing.InitializeTracerForTest() var routes []*plugins.Route - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) @@ -1101,9 +1045,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) proxy, err := NewDataSourceProxy(&datasources.DataSource{}, routes, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer) require.NoError(t, err) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 0afe045c0a8..ab9a5f977c2 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -60,7 +60,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) { PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled, } hs.pluginInstaller = inst - hs.QuotaService = quotatest.New(false, nil) + hs.QuotaService = quotatest.NewQuotaServiceFake() }) t.Run(testName("Install", tc), func(t *testing.T) { diff --git a/pkg/api/quota.go b/pkg/api/quota.go index dd0ee9f538d..9d3fa2a5c0b 100644 --- a/pkg/api/quota.go +++ b/pkg/api/quota.go @@ -6,22 +6,10 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota" + "github.com/grafana/grafana/pkg/setting" "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) } @@ -41,17 +29,22 @@ 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.Err(quota.ErrBadRequest.Errorf("orgId is invalid: %w", err)) + return response.Error(http.StatusBadRequest, "orgId is invalid", err) } return hs.getOrgQuotasHelper(c, orgId) } func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) response.Response { - q, err := hs.QuotaService.GetQuotasByScope(c.Req.Context(), quota.OrgScope, orgID) - if err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "failed to get quota", err) + if !hs.Cfg.Quota.Enabled { + return response.Error(404, "Quotas not enabled", nil) } - return response.JSON(http.StatusOK, q) + 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) } // swagger:route PUT /orgs/{org_id}/quotas/{quota_target} orgs updateOrgQuota @@ -70,19 +63,26 @@ func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) resp // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response { - cmd := quota.UpdateQuotaCmd{} + cmd := models.UpdateOrgQuotaCmd{} var err error if err := web.Bind(c.Req, &cmd); err != nil { - return response.Err(quota.ErrBadRequest.Errorf("bad request data: %w", err)) + return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.OrgID, err = strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64) + 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) if err != nil { - return response.Err(quota.ErrBadRequest.Errorf("orgId is invalid: %w", err)) + return response.Error(http.StatusBadRequest, "orgId is invalid", err) } cmd.Target = web.Params(c.Req)[":target"] - if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err) + 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) } return response.Success("Organization quota updated") } @@ -114,17 +114,22 @@ 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.Err(quota.ErrBadRequest.Errorf("id is invalid: %w", err)) + return response.Error(http.StatusBadRequest, "id is invalid", 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) + 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) } - return response.JSON(http.StatusOK, q) + return response.JSON(http.StatusOK, query.Result) } // swagger:route PUT /admin/users/{user_id}/quotas/{quota_target} admin_users updateUserQuota @@ -143,19 +148,26 @@ func (hs *HTTPServer) GetUserQuotas(c *models.ReqContext) response.Response { // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response { - cmd := quota.UpdateQuotaCmd{} + cmd := models.UpdateUserQuotaCmd{} var err error if err := web.Bind(c.Req, &cmd); err != nil { - return response.Err(quota.ErrBadRequest.Errorf("bad request data: %w", err)) + return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.UserID, err = strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64) + if !setting.Quota.Enabled { + return response.Error(404, "Quotas not enabled", nil) + } + cmd.UserId, err = strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64) if err != nil { - return response.Err(quota.ErrBadRequest.Errorf("id is invalid: %w", err)) + return response.Error(http.StatusBadRequest, "id is invalid", err) } cmd.Target = web.Params(c.Req)[":target"] - if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err) + 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) } return response.Success("Organization quota updated") } @@ -164,7 +176,7 @@ func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response { type UpdateUserQuotaParams struct { // in:body // required:true - Body quota.UpdateQuotaCmd `json:"body"` + Body models.UpdateUserQuotaCmd `json:"body"` // in:path // required:true QuotaTarget string `json:"quota_target"` @@ -191,7 +203,7 @@ type GetOrgQuotaParams struct { type UpdateOrgQuotaParam struct { // in:body // required:true - Body quota.UpdateQuotaCmd `json:"body"` + Body models.UpdateOrgQuotaCmd `json:"body"` // in:path // required:true QuotaTarget string `json:"quota_target"` @@ -203,5 +215,5 @@ type UpdateOrgQuotaParam struct { // swagger:response getQuotaResponse type GetQuotaResponseResponse struct { // in:body - Body []*quota.QuotaDTO `json:"body"` + Body []*models.UserQuotaDTO `json:"body"` } diff --git a/pkg/api/quota_test.go b/pkg/api/quota_test.go index 36e128f9124..51a6806a35f 100644 --- a/pkg/api/quota_test.go +++ b/pkg/api/quota_test.go @@ -32,13 +32,17 @@ 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) @@ -58,9 +62,7 @@ func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) { } func TestAPIEndpoint_GetCurrentOrgQuotas_AccessControl(t *testing.T) { - cfg := setting.NewCfg() - cfg.Quota.Enabled = true - sc := setupHTTPServerWithCfg(t, true, cfg) + sc := setupHTTPServer(t, true) setInitCtxSignedInViewer(sc.initCtx) setupDBAndSettingsForAccessControlQuotaTests(t, sc) @@ -84,7 +86,6 @@ 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) @@ -104,9 +105,7 @@ func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) { } func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) { - cfg := setting.NewCfg() - cfg.Quota.Enabled = true - sc := setupHTTPServerWithCfg(t, true, cfg) + sc := setupHTTPServer(t, true) setupDBAndSettingsForAccessControlQuotaTests(t, sc) t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) { @@ -131,7 +130,6 @@ 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) @@ -153,20 +151,7 @@ func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) { } func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) { - 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) + sc := setupHTTPServer(t, true) setupDBAndSettingsForAccessControlQuotaTests(t, sc) input := strings.NewReader(testUpdateOrgQuotaCmd) diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index 5108aebabc9..0998a71687c 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -20,7 +20,6 @@ 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" @@ -69,8 +68,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { } user, err := sqlStore.CreateUser(context.Background(), createUserCmd) require.Nil(t, err) - hs.userService, err = userimpl.ProvideService(sqlStore, nil, sc.cfg, nil, nil, quotatest.New(false, nil)) - require.NoError(t, err) + hs.userService = userimpl.ProvideService(sqlStore, nil, sc.cfg, nil, nil) sc.handlerFunc = hs.GetUserByID diff --git a/pkg/cmd/grafana-cli/runner/wire.go b/pkg/cmd/grafana-cli/runner/wire.go index 8d0d85fda7a..49819799069 100644 --- a/pkg/cmd/grafana-cli/runner/wire.go +++ b/pkg/cmd/grafana-cli/runner/wire.go @@ -254,7 +254,7 @@ var wireSet = wire.NewSet( wire.Bind(new(social.Service), new(*social.SocialService)), oauthtoken.ProvideService, auth.ProvideActiveAuthTokenService, - wire.Bind(new(auth.ActiveTokenService), new(*auth.ActiveAuthTokenService)), + wire.Bind(new(models.ActiveTokenService), new(*auth.ActiveAuthTokenService)), wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), tempo.ProvideService, loki.ProvideService, diff --git a/pkg/middleware/quota.go b/pkg/middleware/quota.go index 7a0689ff11d..57533436ebe 100644 --- a/pkg/middleware/quota.go +++ b/pkg/middleware/quota.go @@ -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(targetSrv string) web.Handler { + return func(target string) web.Handler { return func(c *models.ReqContext) { - limitReached, err := quotaService.QuotaReached(c, quota.TargetSrv(targetSrv)) + limitReached, err := quotaService.QuotaReached(c, target) if err != nil { c.JsonApiErr(500, "Failed to get quota", err) return } if limitReached { - c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", targetSrv), nil) + c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", target), nil) return } } diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go index 446b7842933..3f0aacd89ab 100644 --- a/pkg/middleware/quota_test.go +++ b/pkg/middleware/quota_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota/quotatest" + "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/web" @@ -30,6 +30,8 @@ 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) { @@ -39,6 +41,8 @@ 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) { @@ -48,10 +52,13 @@ 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} @@ -72,6 +79,8 @@ 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) { @@ -84,6 +93,8 @@ 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) { @@ -95,6 +106,8 @@ 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) { @@ -106,6 +119,8 @@ 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) { @@ -117,6 +132,8 @@ 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) { @@ -128,6 +145,9 @@ 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) { @@ -142,6 +162,7 @@ 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) { @@ -156,6 +177,7 @@ 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) { @@ -168,6 +190,8 @@ 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) { @@ -179,15 +203,58 @@ 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 := quotatest.New(reached, nil) + qs := &mockQuotaService{ + reached: reached, + } 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 } diff --git a/pkg/models/quotas.go b/pkg/models/quotas.go new file mode 100644 index 00000000000..26a63a92423 --- /dev/null +++ b/pkg/models/quotas.go @@ -0,0 +1,91 @@ +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:"-"` +} diff --git a/pkg/models/user_token.go b/pkg/models/user_token.go index 6c92a40d86b..6ce74c004f3 100644 --- a/pkg/models/user_token.go +++ b/pkg/models/user_token.go @@ -76,6 +76,10 @@ 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 } diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 90fbdd0a9d8..1123c997893 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -272,7 +272,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(social.Service), new(*social.SocialService)), oauthtoken.ProvideService, auth.ProvideActiveAuthTokenService, - wire.Bind(new(auth.ActiveTokenService), new(*auth.ActiveAuthTokenService)), + wire.Bind(new(models.ActiveTokenService), new(*auth.ActiveAuthTokenService)), wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), tempo.ProvideService, loki.ProvideService, diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index c1352b89f9b..7c033d2f4a8 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -12,7 +12,6 @@ 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" @@ -226,8 +225,7 @@ func setupTestEnvironment(t *testing.T, permissions []accesscontrol.Permission, sql := db.InitTestDB(t) cfg := setting.NewCfg() teamSvc := teamimpl.ProvideService(sql, cfg) - userSvc, err := userimpl.ProvideService(sql, nil, cfg, teamimpl.ProvideService(sql, cfg), nil, quotatest.New(false, nil)) - require.NoError(t, err) + userSvc := userimpl.ProvideService(sql, nil, cfg, teamimpl.ProvideService(sql, cfg), nil) license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() mock := accesscontrolmock.New().WithPermissions(permissions) diff --git a/pkg/services/annotations/annotationsimpl/xorm_store_test.go b/pkg/services/annotations/annotationsimpl/xorm_store_test.go index 44239e50f95..e7615243fdc 100644 --- a/pkg/services/annotations/annotationsimpl/xorm_store_test.go +++ b/pkg/services/annotations/annotationsimpl/xorm_store_test.go @@ -20,7 +20,6 @@ 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" @@ -57,9 +56,7 @@ func TestIntegrationAnnotations(t *testing.T) { assert.NoError(t, err) }) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg)) testDashboard1 := models.SaveDashboardCommand{ UserId: 1, @@ -456,9 +453,7 @@ 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} - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sql, sql.Cfg)) testDashboard1 := models.SaveDashboardCommand{ UserId: 1, diff --git a/pkg/services/apikey/apikeyimpl/apikey.go b/pkg/services/apikey/apikeyimpl/apikey.go index 2a09d26319f..4b2af715707 100644 --- a/pkg/services/apikey/apikeyimpl/apikey.go +++ b/pkg/services/apikey/apikeyimpl/apikey.go @@ -6,7 +6,6 @@ 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" ) @@ -14,34 +13,16 @@ type Service struct { store store } -func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (apikey.Service, error) { - s := &Service{} +func ProvideService(db db.DB, cfg *setting.Cfg) apikey.Service { if cfg.IsFeatureToggleEnabled(featuremgmt.FlagNewDBLibrary) { - s.store = &sqlxStore{ - sess: db.GetSqlxSession(), - cfg: cfg, + return &Service{ + store: &sqlxStore{ + sess: db.GetSqlxSession(), + 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) + return &Service{store: &sqlStore{db: db, cfg: cfg}} } func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) error { @@ -68,24 +49,3 @@ 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 -} diff --git a/pkg/services/apikey/apikeyimpl/sqlx_store.go b/pkg/services/apikey/apikeyimpl/sqlx_store.go index b9935a58123..9401a975931 100644 --- a/pkg/services/apikey/apikeyimpl/sqlx_store.go +++ b/pkg/services/apikey/apikeyimpl/sqlx_store.go @@ -10,7 +10,6 @@ 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" ) @@ -143,35 +142,3 @@ 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 -} diff --git a/pkg/services/apikey/apikeyimpl/store.go b/pkg/services/apikey/apikeyimpl/store.go index 54988660d08..33b8159e7cc 100644 --- a/pkg/services/apikey/apikeyimpl/store.go +++ b/pkg/services/apikey/apikeyimpl/store.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana/pkg/services/apikey" - "github.com/grafana/grafana/pkg/services/quota" ) type store interface { @@ -16,6 +15,4 @@ 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) } diff --git a/pkg/services/apikey/apikeyimpl/xorm_store.go b/pkg/services/apikey/apikeyimpl/xorm_store.go index bf2ba4ce6d4..fad3bb89401 100644 --- a/pkg/services/apikey/apikeyimpl/xorm_store.go +++ b/pkg/services/apikey/apikeyimpl/xorm_store.go @@ -11,8 +11,6 @@ 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" ) @@ -176,47 +174,3 @@ 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 -} diff --git a/pkg/services/apikey/model.go b/pkg/services/apikey/model.go index 9563377760b..82acaf3b77e 100644 --- a/pkg/services/apikey/model.go +++ b/pkg/services/apikey/model.go @@ -5,7 +5,6 @@ import ( "time" "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/user" ) @@ -65,8 +64,3 @@ type GetByIDQuery struct { ApiKeyId int64 Result *APIKey } - -const ( - QuotaTargetSrv quota.TargetSrv = "api_key" - QuotaTarget quota.Target = "api_key" -) diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go index f261e33bcd1..dfbd80c8064 100644 --- a/pkg/services/auth/auth_token.go +++ b/pkg/services/auth/auth_token.go @@ -12,7 +12,6 @@ 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" @@ -42,38 +41,19 @@ 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, quotaService quota.Service) (*ActiveAuthTokenService, error) { - s := &ActiveAuthTokenService{ +func ProvideActiveAuthTokenService(cfg *setting.Cfg, sqlStore db.DB) *ActiveAuthTokenService { + return &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, _ *quota.ScopeParameters) (*quota.Map, error) { +func (a *ActiveAuthTokenService) ActiveTokenCount(ctx context.Context) (int64, error) { var count int64 var err error err = a.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { @@ -86,14 +66,7 @@ func (a *ActiveAuthTokenService) ActiveTokenCount(ctx context.Context, _ *quota. return 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 + return count, err } func (s *UserAuthTokenService) CreateToken(ctx context.Context, user *user.User, clientIP net.IP, userAgent string) (*models.UserToken, error) { @@ -499,19 +472,3 @@ 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 -} diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go index 16886d7b439..a2e86b79e42 100644 --- a/pkg/services/auth/auth_token_test.go +++ b/pkg/services/auth/auth_token_test.go @@ -14,7 +14,6 @@ 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" ) @@ -41,12 +40,8 @@ func TestUserAuthToken(t *testing.T) { userToken := createToken() t.Run("Can count active tokens", func(t *testing.T) { - m, err := ctx.activeTokenService.ActiveTokenCount(context.Background(), "a.ScopeParameters{}) + count, err := ctx.activeTokenService.ActiveTokenCount(context.Background()) 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) }) @@ -213,12 +208,8 @@ func TestUserAuthToken(t *testing.T) { require.Nil(t, notGood) t.Run("should not find active token when expired", func(t *testing.T) { - m, err := ctx.activeTokenService.ActiveTokenCount(context.Background(), "a.ScopeParameters{}) + count, err := ctx.activeTokenService.ActiveTokenCount(context.Background()) 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) }) }) diff --git a/pkg/services/auth/model.go b/pkg/services/auth/model.go index afc5b566c48..799b3e68b16 100644 --- a/pkg/services/auth/model.go +++ b/pkg/services/auth/model.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota" ) type userAuthToken struct { @@ -72,8 +71,3 @@ func (uat *userAuthToken) toUserToken(ut *models.UserToken) error { return nil } - -const ( - QuotaTargetSrv quota.TargetSrv = "auth" - QuotaTarget quota.Target = "session" -) diff --git a/pkg/services/dashboardimport/api/api.go b/pkg/services/dashboardimport/api/api.go index f491d645bdc..12691f8ed5e 100644 --- a/pkg/services/dashboardimport/api/api.go +++ b/pkg/services/dashboardimport/api/api.go @@ -12,7 +12,6 @@ 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" ) @@ -65,9 +64,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, dashboards.QuotaTargetSrv) + limitReached, err := api.quotaService.QuotaReached(c, "dashboard") if err != nil { - return response.Err(err) + return response.Error(500, "failed to get quota", err) } if limitReached { @@ -84,12 +83,12 @@ func (api *ImportDashboardAPI) ImportDashboard(c *models.ReqContext) response.Re } type QuotaService interface { - QuotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) + QuotaReached(c *models.ReqContext, target string) (bool, error) } -type quotaServiceFunc func(c *models.ReqContext, target quota.TargetSrv) (bool, error) +type quotaServiceFunc func(c *models.ReqContext, target string) (bool, error) -func (fn quotaServiceFunc) QuotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) { +func (fn quotaServiceFunc) QuotaReached(c *models.ReqContext, target string) (bool, error) { return fn(c, target) } diff --git a/pkg/services/dashboardimport/api/api_test.go b/pkg/services/dashboardimport/api/api_test.go index d688e019109..77085c0c01a 100644 --- a/pkg/services/dashboardimport/api/api_test.go +++ b/pkg/services/dashboardimport/api/api_test.go @@ -12,7 +12,6 @@ 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" @@ -166,10 +165,10 @@ func (s *serviceMock) ImportDashboard(ctx context.Context, req *dashboardimport. return nil, nil } -func quotaReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) { +func quotaReached(c *models.ReqContext, target string) (bool, error) { return true, nil } -func quotaNotReached(c *models.ReqContext, target quota.TargetSrv) (bool, error) { +func quotaNotReached(c *models.ReqContext, target string) (bool, error) { return false, nil } diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 78428d39700..82f4eaa0850 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota" ) // DashboardService is a service for operating on dashboards. @@ -78,7 +77,6 @@ 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) diff --git a/pkg/services/dashboards/database/acl_test.go b/pkg/services/dashboards/database/acl_test.go index 86ef2df3e3a..3836bfdb01e 100644 --- a/pkg/services/dashboards/database/acl_test.go +++ b/pkg/services/dashboards/database/acl_test.go @@ -9,7 +9,6 @@ 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" @@ -27,10 +26,7 @@ func TestIntegrationDashboardACLDataAccess(t *testing.T) { setup := func(t *testing.T) { sqlStore = db.InitTestDB(t) - 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) + dashboardStore = ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) 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") diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 2cfc1d0299e..321e7eab7c0 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -16,8 +16,6 @@ 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" @@ -44,23 +42,8 @@ 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, 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 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 (d *DashboardStore) emitEntityEvent() bool { @@ -308,50 +291,6 @@ 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 @@ -1079,27 +1018,6 @@ 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. diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index 97b12665d52..8f104a670ff 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -12,7 +12,6 @@ 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" @@ -34,10 +33,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { setup := func() { sqlStore = db.InitTestDB(t) sqlStore.Cfg.RBACEnabled = false - 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) + dashboardStore = ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) 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") @@ -190,9 +186,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { setup2 := func() { sqlStore = db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) 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") @@ -297,9 +291,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { setup3 := func() { sqlStore = db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) 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") @@ -481,9 +473,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { var sqlStore *sqlstore.SQLStore var folder1, folder2 *models.Dashboard sqlStore = db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) 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") @@ -498,9 +488,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("GetFolderByUID", func(t *testing.T) { var orgId int64 = 1 sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod") dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.Id, false, "prod") @@ -524,9 +512,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("GetFolderByID", func(t *testing.T) { var orgId int64 = 1 sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod") dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.Id, false, "prod") diff --git a/pkg/services/dashboards/database/database_provisioning_test.go b/pkg/services/dashboards/database/database_provisioning_test.go index 2bfd0feb0cf..35e7d8e18de 100644 --- a/pkg/services/dashboards/database/database_provisioning_test.go +++ b/pkg/services/dashboards/database/database_provisioning_test.go @@ -10,7 +10,6 @@ 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" ) @@ -19,9 +18,7 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) { t.Skip("skipping integration test") } sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) folderCmd := models.SaveDashboardCommand{ OrgId: 1, diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 4f63c4aef3d..5163e0d8d90 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -18,7 +18,6 @@ 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" @@ -43,10 +42,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t) starService = starimpl.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) + dashboardStore = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg)) 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") @@ -589,9 +585,7 @@ func TestIntegrationDashboardDataAccessGivenPluginWithImportedDashboards(t *test sqlStore := db.InitTestDB(t) cfg := setting.NewCfg() cfg.IsFeatureToggleEnabled = func(key string) bool { return false } - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg)) pluginId := "test-app" appFolder := insertTestDashboardForPlugin(t, dashboardStore, "app-test", 1, 0, true, pluginId) @@ -603,7 +597,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) } @@ -615,9 +609,7 @@ func TestIntegrationDashboard_SortingOptions(t *testing.T) { sqlStore := db.InitTestDB(t) cfg := setting.NewCfg() cfg.IsFeatureToggleEnabled = func(key string) bool { return false } - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg)) dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false) dashA := insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false) @@ -668,9 +660,7 @@ func TestIntegrationDashboard_Filter(t *testing.T) { sqlStore := db.InitTestDB(t) cfg := setting.NewCfg() cfg.IsFeatureToggleEnabled = func(key string) bool { return false } - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, cfg)) insertTestDashboard(t, dashboardStore, "Alfa", 1, 0, false) dashB := insertTestDashboard(t, dashboardStore, "Beta", 1, 0, false) qNoFilter := &models.FindPersistedDashboardsQuery{ diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 4c88e2669db..21c184cff5c 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -4,7 +4,6 @@ import ( "time" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/user" ) @@ -31,11 +30,6 @@ type DashboardSearchProjection struct { SortMeta int64 } -const ( - QuotaTargetSrv quota.TargetSrv = "dashboard" - QuotaTarget quota.Target = "dashboard" -) - type CountDashboardsInFolderQuery struct { FolderUID string } diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 210085b565d..5c5c369d864 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -17,7 +17,6 @@ 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" @@ -43,7 +42,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { }), } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardNotFound, err) }) @@ -63,7 +62,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: false, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardNotFound, err) }) @@ -105,7 +104,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sqlStore) + err := callSaveWithError(cmd, sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId) @@ -125,7 +124,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId) @@ -145,7 +144,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) @@ -166,7 +165,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) @@ -187,7 +186,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) @@ -208,7 +207,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) @@ -229,7 +228,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) @@ -250,7 +249,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) @@ -271,7 +270,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) @@ -292,7 +291,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: true, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) @@ -433,7 +432,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardFolderNotFound, err) }) @@ -449,7 +448,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) }) @@ -489,7 +488,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err) }) @@ -528,7 +527,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) @@ -544,7 +543,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) @@ -560,7 +559,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err) }) }) @@ -648,7 +647,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err) }) @@ -712,7 +711,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) @@ -728,7 +727,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) @@ -744,7 +743,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) @@ -760,7 +759,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err) }) @@ -775,7 +774,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardWithSameNameAsFolder, err) }) @@ -790,7 +789,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { Overwrite: shouldOverwrite, } - err := callSaveWithError(t, cmd, sc.sqlStore) + err := callSaveWithError(cmd, sc.sqlStore) assert.Equal(t, dashboards.ErrDashboardFolderWithSameNameAsDashboard, err) }) }) @@ -822,9 +821,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) service := ProvideDashboardService( cfg, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), @@ -881,9 +878,7 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore cfg := setting.NewCfg() cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) service := ProvideDashboardService( cfg, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), @@ -897,14 +892,12 @@ func callSaveWithResult(t *testing.T, cmd models.SaveDashboardCommand, sqlStore return res } -func callSaveWithError(t *testing.T, cmd models.SaveDashboardCommand, sqlStore db.DB) error { +func callSaveWithError(cmd models.SaveDashboardCommand, sqlStore db.DB) error { dto := toSaveDashboardDto(cmd) cfg := setting.NewCfg() cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) service := ProvideDashboardService( cfg, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), @@ -912,7 +905,7 @@ func callSaveWithError(t *testing.T, cmd models.SaveDashboardCommand, sqlStore d accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.New(), ) - _, err = service.SaveDashboard(context.Background(), &dto, false) + _, err := service.SaveDashboard(context.Background(), &dto, false) return err } @@ -941,9 +934,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto cfg := setting.NewCfg() cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) service := ProvideDashboardService( cfg, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), @@ -981,9 +972,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *mo cfg := setting.NewCfg() cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) service := ProvideDashboardService( cfg, dashboardStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 2bd9ff1284c..5824d5332db 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -6,7 +6,6 @@ import ( context "context" models "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/quota" mock "github.com/stretchr/testify/mock" ) @@ -474,10 +473,6 @@ 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{} diff --git a/pkg/services/datasources/models.go b/pkg/services/datasources/models.go index fec4db4ade7..9697c739cbc 100644 --- a/pkg/services/datasources/models.go +++ b/pkg/services/datasources/models.go @@ -4,7 +4,6 @@ import ( "time" "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/user" ) @@ -194,8 +193,3 @@ type DatasourcesPermissionFilterQuery struct { Datasources []*DataSource Result []*DataSource } - -const ( - QuotaTargetSrv quota.TargetSrv = "data_source" - QuotaTarget quota.Target = "data_source" -) diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index 3b4bb78e01e..064a3431029 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -20,7 +20,6 @@ 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" @@ -53,8 +52,7 @@ 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, - quotaService quota.Service, -) (*Service, error) { +) *Service { dslogger := log.New("datasources") store := &SqlStore{db: db, logger: dslogger} s := &Service{ @@ -75,23 +73,7 @@ func ProvideService( ac.RegisterScopeAttributeResolver(NewNameScopeResolver(store)) ac.RegisterScopeAttributeResolver(NewIDScopeResolver(store)) - 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) + return s } // DataSourceRetriever interface for retrieving a datasource. @@ -609,24 +591,3 @@ 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 -} diff --git a/pkg/services/datasources/service/datasource_test.go b/pkg/services/datasources/service/datasource_test.go index b06cb9913fa..e12e4c3ac56 100644 --- a/pkg/services/datasources/service/datasource_test.go +++ b/pkg/services/datasources/service/datasource_test.go @@ -21,7 +21,6 @@ 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" @@ -201,9 +200,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider) require.NoError(t, err) @@ -238,9 +235,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, @@ -289,9 +284,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, @@ -337,9 +330,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, @@ -382,9 +373,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, @@ -417,9 +406,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, @@ -486,9 +473,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Id: 1, Url: "http://k8s:8001", @@ -522,9 +507,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) ds := datasources.DataSource{ Type: datasources.DS_ES, @@ -561,9 +544,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) for _, tc := range testCases { ds := &datasources.DataSource{ @@ -584,9 +565,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) jsonData := map[string]string{ "password": "securePassword", @@ -612,9 +591,7 @@ 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")) - quotaService := quotatest.New(false, nil) - dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) jsonData := map[string]string{ "password": "securePassword", diff --git a/pkg/services/datasources/service/store.go b/pkg/services/datasources/service/store.go index 9737da64622..2074889ce64 100644 --- a/pkg/services/datasources/service/store.go +++ b/pkg/services/datasources/service/store.go @@ -16,8 +16,6 @@ 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" ) @@ -31,8 +29,6 @@ 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 { @@ -175,50 +171,6 @@ 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} diff --git a/pkg/services/folder/folderimpl/sqlstore_test.go b/pkg/services/folder/folderimpl/sqlstore_test.go index d79a838535a..85c1dfaf04e 100644 --- a/pkg/services/folder/folderimpl/sqlstore_test.go +++ b/pkg/services/folder/folderimpl/sqlstore_test.go @@ -11,7 +11,6 @@ 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" @@ -584,8 +583,7 @@ func TestIntegrationGetChildren(t *testing.T) { func CreateOrg(t *testing.T, db *sqlstore.SQLStore) int64 { t.Helper() - orgService, err := orgimpl.ProvideService(db, db.Cfg, quotatest.New(false, nil)) - require.NoError(t, err) + orgService := orgimpl.ProvideService(db, db.Cfg) orgID, err := orgService.GetOrCreate(context.Background(), "test-org") require.NoError(t, err) t.Cleanup(func() { diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index 39c0496a19c..8660e1cf2b5 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -19,7 +19,6 @@ 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" @@ -592,9 +591,7 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce toSave.SetUid(uid) // seed dashboard - quotaService := quotatest.New(false, nil) - dashStore, err := dashdb.ProvideDashboardStore(store, store.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(store, store.Cfg), quotaService) - require.NoError(t, err) + dashStore := dashdb.ProvideDashboardStore(store, store.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(store, store.Cfg)) dash, err := dashStore.SaveDashboard(context.Background(), models.SaveDashboardCommand{ Dashboard: toSave.Data, UserId: 1, @@ -606,8 +603,7 @@ 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, err := userimpl.ProvideService(store, nil, store.Cfg, nil, nil, quotatest.New(false, nil)) - require.NoError(t, err) + userSvc := userimpl.ProvideService(store, nil, store.Cfg, nil, nil) folderPermissions, err := ossaccesscontrol.ProvideFolderPermissions( setting.NewCfg(), routing.NewRouteRegister(), store, ac, license, &dashboards.FakeDashboardStore{}, ac, teamSvc, userSvc) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 8ecc4399794..3b6a3975ed5 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -28,7 +28,6 @@ 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" @@ -279,9 +278,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash cfg.RBACEnabled = false features := featuremgmt.WithFeatures() cfg.IsFeatureToggleEnabled = features.IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) dashAlertExtractor := alerting.ProvideDashAlertExtractorService(nil, nil, nil) ac := acmock.New() folderPermissions := acmock.NewMockedPermissionsService() @@ -307,9 +304,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S ac := acmock.New() folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) d := dashboardservice.ProvideDashboardService( cfg, dashboardStore, nil, @@ -410,9 +405,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo orgID := int64(1) role := org.RoleAdmin sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) features := featuremgmt.WithFeatures() ac := acmock.New().WithDisabled() // TODO: Update tests to work with rbac @@ -449,7 +442,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{ diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index e4ca222f89d..a422dc729a2 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -26,7 +26,6 @@ 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" @@ -692,9 +691,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash cfg := setting.NewCfg() cfg.RBACEnabled = false cfg.IsFeatureToggleEnabled = featuremgmt.WithFeatures().IsEnabled - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil) ac := acmock.New() service := dashboardservice.ProvideDashboardService( @@ -718,9 +715,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user. features := featuremgmt.WithFeatures() folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil) @@ -813,9 +808,7 @@ 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) - quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) features := featuremgmt.WithFeatures() ac := acmock.New() @@ -854,7 +847,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{ diff --git a/pkg/services/login/loginservice/loginservice.go b/pkg/services/login/loginservice/loginservice.go index 1c28ac1423c..6e68e00c0a5 100644 --- a/pkg/services/login/loginservice/loginservice.go +++ b/pkg/services/login/loginservice/loginservice.go @@ -71,17 +71,13 @@ func (ls *Implementation) UpsertUser(ctx context.Context, cmd *models.UpsertUser return login.ErrSignupNotAllowed } - // 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 - } + 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 } result, errCreateUser := ls.createUser(extUser) diff --git a/pkg/services/login/loginservice/loginservice_test.go b/pkg/services/login/loginservice/loginservice_test.go index edd9bade8d6..2655a7d5c3a 100644 --- a/pkg/services/login/loginservice/loginservice_test.go +++ b/pkg/services/login/loginservice/loginservice_test.go @@ -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/quotatest" + "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "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: quotatest.New(false, nil), + QuotaService: "aimpl.Service{}, AuthInfoService: authInfoMock, SQLStore: nil, userService: usertest.NewUserServiceFake(), @@ -51,7 +51,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) { orgService.ExpectedOrgListResponse = createResponseWithOneErrLastOrgAdminItem() login := Implementation{ - QuotaService: quotatest.New(false, nil), + QuotaService: "aimpl.Service{}, 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: quotatest.New(false, nil), + QuotaService: "aimpl.Service{}, AuthInfoService: authInfoMock, } diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index ffca5f0c293..bf0c3953c8f 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -145,28 +145,3 @@ 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 -} diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 433d3160ae5..9229bd40f78 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -393,7 +393,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, groupKey ngmod } if len(finalChanges.New) > 0 { - limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, ngmodels.QuotaTargetSrv, "a.ScopeParameters{ + limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, "alert_rule", "a.ScopeParameters{ OrgID: c.OrgID, UserID: c.UserID, }) // alert rule is table name diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index 60341ae2594..bb8f59c7412 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -23,6 +23,4 @@ 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) } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 83ee27f2a74..1572cd7b850 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -12,7 +12,6 @@ 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" ) @@ -461,11 +460,6 @@ 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 { diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 12cc93d6ca2..d61b07fe9c0 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -239,19 +239,6 @@ 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 { @@ -321,32 +308,3 @@ 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 -} diff --git a/pkg/services/ngalert/provisioning/persist.go b/pkg/services/ngalert/provisioning/persist.go index bfbbadb3646..97d406a8214 100644 --- a/pkg/services/ngalert/provisioning/persist.go +++ b/pkg/services/ngalert/provisioning/persist.go @@ -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 quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) + CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error) } // PersistConfig validates to config before eventually persisting it if no error occurs diff --git a/pkg/services/ngalert/provisioning/quota_checker_mock.go b/pkg/services/ngalert/provisioning/quota_checker_mock.go index 1dac163c33d..f545dd1b5ec 100644 --- a/pkg/services/ngalert/provisioning/quota_checker_mock.go +++ b/pkg/services/ngalert/provisioning/quota_checker_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.12.0. DO NOT EDIT. package provisioning @@ -7,6 +7,8 @@ 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 @@ -23,18 +25,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 quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) { +func (_m *MockQuotaChecker) CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error) { ret := _m.Called(ctx, target, scopeParams) var r0 bool - if rf, ok := ret.Get(0).(func(context.Context, quota.TargetSrv, *quota.ScopeParameters) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, *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, quota.TargetSrv, *quota.ScopeParameters) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, string, *quota.ScopeParameters) error); ok { r1 = rf(ctx, target, scopeParams) } else { r1 = ret.Error(1) @@ -49,16 +51,16 @@ type MockQuotaChecker_CheckQuotaReached_Call struct { } // CheckQuotaReached is a helper method to define mock.On call -// - ctx context.Context -// - target quota.TargetSrv -// - scopeParams *quota.ScopeParameters +// - ctx context.Context +// - target string +// - 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 quota.TargetSrv, scopeParams *quota.ScopeParameters)) *MockQuotaChecker_CheckQuotaReached_Call { +func (_c *MockQuotaChecker_CheckQuotaReached_Call) Run(run func(ctx context.Context, target string, scopeParams *quota.ScopeParameters)) *MockQuotaChecker_CheckQuotaReached_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(quota.TargetSrv), args[2].(*quota.ScopeParameters)) + run(args[0].(context.Context), args[1].(string), args[2].(*quota.ScopeParameters)) }) return _c } @@ -68,13 +70,8 @@ func (_c *MockQuotaChecker_CheckQuotaReached_Call) Return(_a0 bool, _a1 error) * return _c } -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 { +// 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 { mock := &MockQuotaChecker{} mock.Mock.Test(t) diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 3a9cbac848d..1d2084857e3 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -10,7 +10,6 @@ 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" @@ -269,29 +268,6 @@ 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 { diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index c2139c5d2bf..3cb2f1b7210 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -339,7 +339,3 @@ func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, org } return result, nil } - -func (f *RuleStore) Count(ctx context.Context, orgID int64) (int64, error) { - return 0, nil -} diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index d8083bdb2e7..6862ed83361 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -31,7 +31,6 @@ 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" @@ -76,9 +75,7 @@ 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)) - quotaService := quotatest.New(false, nil) - dashboardStore, err := databasestore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(tb, err) + dashboardStore := databasestore.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) ac := acmock.New() features := featuremgmt.WithFeatures() @@ -95,7 +92,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, quotatest.New(false, nil), + cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil, secretsService, nil, m, folderService, ac, &dashboards.FakeDashboardService{}, nil, bus, ac, annotationstest.NewFakeAnnotationsRepo(), ) require.NoError(tb, err) diff --git a/pkg/services/org/model.go b/pkg/services/org/model.go index d6956ce2384..4dc0e2a0a2b 100644 --- a/pkg/services/org/model.go +++ b/pkg/services/org/model.go @@ -204,9 +204,3 @@ 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" -) diff --git a/pkg/services/org/orgimpl/org.go b/pkg/services/org/orgimpl/org.go index ed6c3bc506a..ca5539eb9a5 100644 --- a/pkg/services/org/orgimpl/org.go +++ b/pkg/services/org/orgimpl/org.go @@ -8,7 +8,6 @@ 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" ) @@ -19,9 +18,9 @@ type Service struct { log log.Logger } -func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (org.Service, error) { +func ProvideService(db db.DB, cfg *setting.Cfg) org.Service { log := log.New("org service") - s := &Service{ + return &Service{ store: &sqlStore{ db: db, dialect: db.GetDialect(), @@ -31,24 +30,6 @@ func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (org 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) { @@ -198,31 +179,3 @@ 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 -} diff --git a/pkg/services/org/orgimpl/org_test.go b/pkg/services/org/orgimpl/org_test.go index 410bbf5a255..9d9b48c862c 100644 --- a/pkg/services/org/orgimpl/org_test.go +++ b/pkg/services/org/orgimpl/org_test.go @@ -5,7 +5,6 @@ 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" ) @@ -136,7 +135,3 @@ 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 -} diff --git a/pkg/services/org/orgimpl/store.go b/pkg/services/org/orgimpl/store.go index 09936a3195d..afc900223b5 100644 --- a/pkg/services/org/orgimpl/store.go +++ b/pkg/services/org/orgimpl/store.go @@ -14,8 +14,6 @@ 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" @@ -44,8 +42,6 @@ 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 { @@ -399,72 +395,6 @@ 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, diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index 4a1eb4ff142..c5aa5caa787 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -28,7 +28,6 @@ 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" @@ -301,8 +300,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) } // create dashboard - dashboardStoreService, err := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStoreService := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db, db.Cfg)) dashboard, err := dashboardStoreService.SaveDashboard(context.Background(), saveDashboardCmd) require.NoError(t, err) diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index b217e324a6c..6c66764b304 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -13,7 +13,6 @@ 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" @@ -36,9 +35,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { t.Skip("skipping integration test") } sqlStore, cfg := db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore := ProvideStore(sqlStore) var orgId int64 = 1 @@ -81,10 +78,7 @@ func TestIntegrationFindDashboard(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t) - quotaService := quotatest.New(false, nil) - store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) - dashboardStore = store + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -111,10 +105,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t) - quotaService := quotatest.New(false, nil) - store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) - dashboardStore = store + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -184,10 +175,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t) - quotaService := quotatest.New(false, nil) - store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) - dashboardStore = store + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -249,10 +237,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t) - quotaService := quotatest.New(false, nil) - store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) - dashboardStore = store + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -314,12 +299,10 @@ 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, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -386,10 +369,7 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagPublicDashboards}}) - quotaService := quotatest.New(false, nil) - store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) - dashboardStore = store + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) savedDashboard2 = insertTestDashboard(t, dashboardStore, "testDashie2", 1, 0, true) @@ -456,13 +436,10 @@ 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}}) - quotaService := quotatest.New(false, nil) - dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) anotherSavedDashboard = insertTestDashboard(t, dashboardStore, "test another Dashie", 1, 0, true) @@ -552,13 +529,10 @@ 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) - quotaService := quotatest.New(false, nil) - dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) - require.NoError(t, err) + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) } @@ -625,12 +599,10 @@ 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, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) publicdashboardStore = ProvideStore(sqlStore) savedDashboard = insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true) savedPublicDashboard = insertPublicDashboard(t, publicdashboardStore, savedDashboard.Uid, savedDashboard.OrgId, true) diff --git a/pkg/services/publicdashboards/service/query_test.go b/pkg/services/publicdashboards/service/query_test.go index 79baf710054..884b82d8d59 100644 --- a/pkg/services/publicdashboards/service/query_test.go +++ b/pkg/services/publicdashboards/service/query_test.go @@ -20,7 +20,6 @@ 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" @@ -356,8 +355,7 @@ const ( func TestGetQueryDataResponse(t *testing.T) { sqlStore := sqlstore.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) service := &PublicDashboardServiceImpl{ @@ -740,8 +738,7 @@ func TestGetAnnotations(t *testing.T) { func TestGetMetricRequest(t *testing.T) { sqlStore := db.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) publicDashboard := &PublicDashboard{ @@ -814,8 +811,7 @@ func TestGetUniqueDashboardDatasourceUids(t *testing.T) { func TestBuildMetricRequest(t *testing.T) { sqlStore := db.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) publicDashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) @@ -1026,8 +1022,7 @@ func TestBuildMetricRequest(t *testing.T) { func TestBuildAnonymousUser(t *testing.T) { sqlStore := db.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) //publicdashboardStore := database.ProvideStore(sqlStore) //service := &PublicDashboardServiceImpl{ diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index fcf700b36c4..caafbad220e 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -21,7 +21,6 @@ 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" @@ -126,9 +125,7 @@ func TestGetPublicDashboard(t *testing.T) { func TestCreatePublicDashboard(t *testing.T) { t.Run("Create public dashboard", func(t *testing.T) { sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) @@ -150,7 +147,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) @@ -174,9 +171,7 @@ func TestCreatePublicDashboard(t *testing.T) { t.Run("Validate pubdash has default time setting value", func(t *testing.T) { sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) @@ -196,7 +191,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) @@ -206,9 +201,7 @@ func TestCreatePublicDashboard(t *testing.T) { t.Run("Validate pubdash whose dashboard has template variables returns error", func(t *testing.T) { sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) templateVars := make([]map[string]interface{}, 1) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, templateVars, nil) @@ -229,7 +222,7 @@ func TestCreatePublicDashboard(t *testing.T) { }, } - _, err = service.Create(context.Background(), SignedInUser, dto) + _, err := service.Create(context.Background(), SignedInUser, dto) require.Error(t, err) }) @@ -272,8 +265,7 @@ func TestCreatePublicDashboard(t *testing.T) { t.Run("Returns error if public dashboard exists", func(t *testing.T) { sqlStore := db.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotatest.New(false, nil)) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) @@ -324,9 +316,7 @@ func TestCreatePublicDashboard(t *testing.T) { func TestUpdatePublicDashboard(t *testing.T) { t.Run("Updating public dashboard", func(t *testing.T) { sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) @@ -388,9 +378,7 @@ func TestUpdatePublicDashboard(t *testing.T) { t.Run("Updating set empty time settings", func(t *testing.T) { sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) + dashboardStore := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, sqlStore.Cfg)) publicdashboardStore := database.ProvideStore(sqlStore) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, true, []map[string]interface{}{}, nil) diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index af1d87c9263..32a17fcc787 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -23,7 +23,6 @@ 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" @@ -390,9 +389,7 @@ 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()) - quotaService := quotatest.New(false, nil) - ds, err := dsSvc.ProvideService(nil, ssvc, ss, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + ds := dsSvc.ProvideService(nil, ssvc, ss, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService()) fakeDatasourceService := &fakeDatasources.FakeDataSourceService{ DataSources: nil, SimulatePluginFailure: false, diff --git a/pkg/services/quota/context.go b/pkg/services/quota/context.go deleted file mode 100644 index 2342d53d476..00000000000 --- a/pkg/services/quota/context.go +++ /dev/null @@ -1,42 +0,0 @@ -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 -} diff --git a/pkg/services/quota/model.go b/pkg/services/quota/model.go index 091c60c4f36..d0e69700f68 100644 --- a/pkg/services/quota/model.go +++ b/pkg/services/quota/model.go @@ -1,216 +1,10 @@ package quota -import ( - "strings" - "sync" - "time" +import "errors" - "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") +var ErrInvalidQuotaTarget = errors.New("invalid quota target") 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 :: -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 ^(?\\w):(?\\w):(?\\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 -} diff --git a/pkg/services/quota/quota.go b/pkg/services/quota/quota.go index 13045f41de2..90cc46c878b 100644 --- a/pkg/services/quota/quota.go +++ b/pkg/services/quota/quota.go @@ -7,24 +7,7 @@ import ( ) type Service interface { - // 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 + QuotaReached(c *models.ReqContext, target string) (bool, error) + CheckQuotaReached(ctx context.Context, target string, scopeParams *ScopeParameters) (bool, error) + DeleteByUser(context.Context, int64) error } - -type UsageReporterFunc func(ctx context.Context, scopeParams *ScopeParameters) (*Map, error) diff --git a/pkg/services/quota/quotaimpl/quota.go b/pkg/services/quota/quotaimpl/quota.go index e435989fbfb..fb7f9fd6fc1 100644 --- a/pkg/services/quota/quotaimpl/quota.go +++ b/pkg/services/quota/quotaimpl/quota.go @@ -2,81 +2,38 @@ 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 serviceDisabled struct { +type Service struct { + store store + authTokenService models.ActiveTokenService + Cfg *setting.Cfg + SQLStore sqlstore.Store + Logger log.Logger } -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(), +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"), } - - 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, targetSrv quota.TargetSrv) (bool, error) { +func (s *Service) QuotaReached(c *models.ReqContext, target string) (bool, error) { + if !s.Cfg.Quota.Enabled { + return false, nil + } // No request context means this is a background service, like LDAP Background Sync if c == nil { return false, nil @@ -89,129 +46,91 @@ func (s *service) QuotaReached(c *models.ReqContext, targetSrv quota.TargetSrv) UserID: c.UserID, } } - 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) + return s.CheckQuotaReached(c.Req.Context(), target, params) } // 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, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) { - targetSrvLimits, err := s.getOverridenLimits(ctx, targetSrv, scopeParams) +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) if err != nil { return false, err } + for _, scope := range scopes { + s.Logger.Debug("Checking quota", "target", target, "scope", scope) - usageReporterFunc, ok := s.getReporter(targetSrv) - if !ok { - return false, quota.ErrInvalidTargetSrv - } - targetUsage, err := usageReporterFunc(ctx, scopeParams) - if err != nil { - return false, err - } - - 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) + switch scope.Name { + case "global": + if scope.DefaultLimit < 0 { + continue } - if u >= limit { + if scope.DefaultLimit == 0 { + return true, nil + } + if target == "session" { + usedSessions, err := s.authTokenService.ActiveTokenCount(ctx) + 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 + } + 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 { return true, nil } } @@ -219,127 +138,68 @@ func (s *service) CheckQuotaReached(ctx context.Context, targetSrv quota.TargetS return false, nil } -func (s *service) DeleteQuotaForUser(ctx context.Context, userID int64) error { - c, err := s.getContext(ctx) - if err != nil { - return err +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 } - return s.store.DeleteByUser(c, 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 +func (s *Service) DeleteByUser(ctx context.Context, userID int64) error { + return s.store.DeleteByUser(ctx, userID) } diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 17164adc785..c2cdfd5edda 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -3,481 +3,26 @@ 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 := "atest.FakeQuotaStore{} - quotaService := service{ + quotaStore := &FakeQuotaStore{} + quotaService := Service{ store: quotaStore, } t.Run("delete quota", func(t *testing.T) { - err := quotaService.DeleteQuotaForUser(context.Background(), 1) + err := quotaService.DeleteByUser(context.Background(), 1) require.NoError(t, err) }) } -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 +type FakeQuotaStore struct { + ExpectedError error } -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) +func (f *FakeQuotaStore) DeleteByUser(ctx context.Context, userID int64) error { + return f.ExpectedError } diff --git a/pkg/services/quota/quotaimpl/store.go b/pkg/services/quota/quotaimpl/store.go index d6111580f28..6b3a32bdb91 100644 --- a/pkg/services/quota/quotaimpl/store.go +++ b/pkg/services/quota/quotaimpl/store.go @@ -1,130 +1,23 @@ package quotaimpl import ( - "time" + "context" "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 { - Get(ctx quota.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) - Update(ctx quota.Context, cmd *quota.UpdateQuotaCmd) error - DeleteByUser(quota.Context, int64) error + DeleteByUser(context.Context, int64) error } type sqlStore struct { - db db.DB - logger log.Logger + db db.DB } -func (ss *sqlStore) DeleteByUser(ctx quota.Context, userID int64) error { +func (ss *sqlStore) DeleteByUser(ctx context.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 -} diff --git a/pkg/services/quota/quotaimpl/store_test.go b/pkg/services/quota/quotaimpl/store_test.go index d332ab97851..f9f7a184456 100644 --- a/pkg/services/quota/quotaimpl/store_test.go +++ b/pkg/services/quota/quotaimpl/store_test.go @@ -7,7 +7,6 @@ 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) { @@ -21,8 +20,7 @@ func TestIntegrationQuotaDataAccess(t *testing.T) { } t.Run("quota deleted", func(t *testing.T) { - ctx := quota.FromContext(context.Background(), "a.TargetToSrv{}) - err := quotaStore.DeleteByUser(ctx, 1) + err := quotaStore.DeleteByUser(context.Background(), 1) require.NoError(t, err) }) } diff --git a/pkg/services/quota/quotatest/fake.go b/pkg/services/quota/quotatest/fake.go index d62267d9276..00eae845789 100644 --- a/pkg/services/quota/quotatest/fake.go +++ b/pkg/services/quota/quotatest/fake.go @@ -12,46 +12,18 @@ type FakeQuotaService struct { err error } -func New(reached bool, err error) *FakeQuotaService { - return &FakeQuotaService{reached, err} +func NewQuotaServiceFake() *FakeQuotaService { + return &FakeQuotaService{} } -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) { +func (f *FakeQuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) { return f.reached, f.err } -func (f *FakeQuotaService) CheckQuotaReached(c context.Context, target quota.TargetSrv, params *quota.ScopeParameters) (bool, error) { +func (f *FakeQuotaService) CheckQuotaReached(c context.Context, target string, params *quota.ScopeParameters) (bool, error) { return f.reached, f.err } -func (f *FakeQuotaService) DeleteQuotaForUser(c context.Context, userID int64) error { +func (f *FakeQuotaService) DeleteByUser(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 -} diff --git a/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go b/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go index ded9d12412f..a8e8ef8bcaa 100644 --- a/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go +++ b/pkg/services/secrets/kvstore/migrations/datasource_mig_test.go @@ -5,7 +5,6 @@ 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" @@ -14,7 +13,6 @@ 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" @@ -29,9 +27,7 @@ func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvS features = featuremgmt.WithFeatures(featuremgmt.FlagDisableSecretsCompatibility, true) } secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) - quotaService := quotatest.New(false, nil) - dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New().WithDisabled(), acmock.NewMockedPermissionsService(), quotaService) - require.NoError(t, err) + dsService := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New().WithDisabled(), acmock.NewMockedPermissionsService()) migService := ProvideDataSourceMigrationService(dsService, kvStore, features) return migService } diff --git a/pkg/services/serviceaccounts/api/api_test.go b/pkg/services/serviceaccounts/api/api_test.go index 8abeeb43789..0e87ed07c82 100644 --- a/pkg/services/serviceaccounts/api/api_test.go +++ b/pkg/services/serviceaccounts/api/api_test.go @@ -27,7 +27,6 @@ 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" @@ -45,12 +44,9 @@ var ( func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { store := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) kvStore := kvstore.ProvideService(store) - orgService, err := orgimpl.ProvideService(store, setting.NewCfg(), quotaService) - require.NoError(t, err) + orgService := orgimpl.ProvideService(store, setting.NewCfg()) saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, orgService) svcmock := tests.ServiceAccountMock{} @@ -61,7 +57,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 { @@ -216,9 +212,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) { store := db.InitTestDB(t) kvStore := kvstore.ProvideService(store) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil) svcmock := tests.ServiceAccountMock{} @@ -290,9 +284,7 @@ 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, err := userimpl.ProvideService(sqlStore, nil, cfg, teamimpl.ProvideService(sqlStore, cfg), nil, quotatest.New(false, nil)) - require.NoError(t, err) + userSvc := userimpl.ProvideService(sqlStore, nil, cfg, teamimpl.ProvideService(sqlStore, cfg), nil) saPermissionService, err := ossaccesscontrol.ProvideServiceAccountPermissions( cfg, routing.NewRouteRegister(), sqlStore, acmock, &licensing.OSSLicensingService{}, saStore, acmock, teamSvc, userSvc) require.NoError(t, err) @@ -324,9 +316,7 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { store := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) kvStore := kvstore.ProvideService(store) saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil) svcmock := tests.ServiceAccountMock{} @@ -418,9 +408,7 @@ func newString(s string) *string { func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) { store := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) kvStore := kvstore.ProvideService(store) saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil) svcmock := tests.ServiceAccountMock{} diff --git a/pkg/services/serviceaccounts/api/token_test.go b/pkg/services/serviceaccounts/api/token_test.go index 90b234d24d2..9e9e91f4d98 100644 --- a/pkg/services/serviceaccounts/api/token_test.go +++ b/pkg/services/serviceaccounts/api/token_test.go @@ -23,7 +23,6 @@ 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" @@ -55,9 +54,7 @@ func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string, func TestServiceAccountsAPI_CreateToken(t *testing.T) { store := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) kvStore := kvstore.ProvideService(store) saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil) svcmock := tests.ServiceAccountMock{} @@ -174,9 +171,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) { func TestServiceAccountsAPI_DeleteToken(t *testing.T) { store := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(store, store.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(store, store.Cfg) kvStore := kvstore.ProvideService(store) svcMock := &tests.ServiceAccountMock{} saStore := database.ProvideServiceAccountsStore(store, apiKeyService, kvStore, nil) diff --git a/pkg/services/serviceaccounts/database/database_test.go b/pkg/services/serviceaccounts/database/database_test.go index be9011ed9bc..a6aba5f1367 100644 --- a/pkg/services/serviceaccounts/database/database_test.go +++ b/pkg/services/serviceaccounts/database/database_test.go @@ -14,7 +14,6 @@ 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" @@ -113,12 +112,9 @@ func TestStore_DeleteServiceAccount(t *testing.T) { func setupTestDatabase(t *testing.T) (*sqlstore.SQLStore, *ServiceAccountsStoreImpl) { t.Helper() db := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(db, db.Cfg, quotaService) - require.NoError(t, err) + apiKeyService := apikeyimpl.ProvideService(db, db.Cfg) kvStore := kvstore.ProvideService(db) - orgService, err := orgimpl.ProvideService(db, setting.NewCfg(), quotaService) - require.NoError(t, err) + orgService := orgimpl.ProvideService(db, setting.NewCfg()) return db, ProvideServiceAccountsStore(db, apiKeyService, kvStore, orgService) } diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index d8b5dea247b..2671cc065e3 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -12,7 +12,6 @@ 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" @@ -71,10 +70,8 @@ func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey) addKeyCmd.Key = "secret" } - quotaService := quotatest.New(false, nil) - apiKeyService, err := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) - require.NoError(t, err) - err = apiKeyService.AddAPIKey(context.Background(), addKeyCmd) + apiKeyService := apikeyimpl.ProvideService(sqlStore, sqlStore.Cfg) + err := apiKeyService.AddAPIKey(context.Background(), addKeyCmd) require.NoError(t, err) if testKey.IsExpired { diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index 1b4757b2e93..bf23c79e8a2 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -98,6 +98,34 @@ 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 } diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go new file mode 100644 index 00000000000..a28dba881d7 --- /dev/null +++ b/pkg/services/sqlstore/quota.go @@ -0,0 +1,315 @@ +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 + }) +} diff --git a/pkg/services/sqlstore/quota_test.go b/pkg/services/sqlstore/quota_test.go new file mode 100644 index 00000000000..e58b42adf8d --- /dev/null +++ b/pkg/services/sqlstore/quota_test.go @@ -0,0 +1,301 @@ +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) + }) +} diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 16ca3e885aa..463ad05b919 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -23,6 +23,13 @@ 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 diff --git a/pkg/services/store/service.go b/pkg/services/store/service.go index cc3beab7d10..61eb97c4239 100644 --- a/pkg/services/store/service.go +++ b/pkg/services/store/service.go @@ -18,7 +18,6 @@ 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" ) @@ -59,11 +58,6 @@ type CreateFolderCmd struct { Path string `json:"path"` } -const ( - QuotaTargetSrv quota.TargetSrv = "store" - QuotaTarget quota.Target = "file" -) - type StorageService interface { registry.BackgroundService @@ -103,7 +97,7 @@ func ProvideService( features featuremgmt.FeatureToggles, cfg *setting.Cfg, quotaService quota.Service, -) (StorageService, error) { +) StorageService { settings, err := LoadStorageConfig(cfg, features) if err != nil { grafanaStorageLogger.Warn("error loading storage config", "error", err) @@ -265,37 +259,7 @@ func ProvideService( s := newStandardStorageService(sql, globalRoots, initializeOrgStorages, authService, cfg) s.quotaService = quotaService s.cfg = settings - - 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 + return s } func createSystemBrandingPathFilter() filestorage.PathFilter { @@ -365,32 +329,6 @@ 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 @@ -457,7 +395,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, QuotaTargetSrv, nil) + quotaReached, err := s.quotaService.CheckQuotaReached(ctx, "file", nil) if err != nil { grafanaStorageLogger.Error("failed while checking upload quota", "path", path, "error", err) return ErrUploadInternalError diff --git a/pkg/services/store/service_test.go b/pkg/services/store/service_test.go index c74b744af16..650c3dceefc 100644 --- a/pkg/services/store/service_test.go +++ b/pkg/services/store/service_test.go @@ -118,7 +118,7 @@ func setupUploadStore(t *testing.T, authService storageAuthService) (StorageServ store.cfg = &GlobalStorageConfig{ AllowUnsanitizedSvgUpload: true, } - store.quotaService = quotatest.New(false, nil) + store.quotaService = quotatest.NewQuotaServiceFake() return store, mockStorage, storageName } @@ -297,7 +297,7 @@ func TestContentRootWithNestedStorage(t *testing.T) { store.cfg = &GlobalStorageConfig{ AllowUnsanitizedSvgUpload: true, } - store.quotaService = quotatest.New(false, nil) + store.quotaService = quotatest.NewQuotaServiceFake() fileName := "file.jpg" tests := []struct { diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 88951c1cc5a..b5d66f1b360 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -357,8 +357,3 @@ type SearchUserFilter interface { } type FilterHandler func(params []string) (Filter, error) - -const ( - QuotaTargetSrv string = "user" - QuotaTarget string = "user" -) diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index 53deaf17c41..c368be1389c 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -11,7 +11,6 @@ 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" @@ -38,8 +37,6 @@ 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 { @@ -464,22 +461,6 @@ 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{}) diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index f2250a5245d..96dab700d94 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -12,7 +12,6 @@ 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" @@ -33,44 +32,15 @@ func ProvideService( cfg *setting.Cfg, teamService team.Service, cacheService *localcache.CacheService, - quotaService quota.Service, -) (user.Service, error) { +) user.Service { store := ProvideStore(db, cfg) - s := &Service{ + return &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) { @@ -334,19 +304,3 @@ 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 -} diff --git a/pkg/services/user/userimpl/user_test.go b/pkg/services/user/userimpl/user_test.go index aadd510e2ad..a371c74789d 100644 --- a/pkg/services/user/userimpl/user_test.go +++ b/pkg/services/user/userimpl/user_test.go @@ -252,7 +252,3 @@ 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 -} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 44a79754cfd..cf48a800f44 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -153,6 +153,9 @@ var ( LDAPAllowSignup bool LDAPActiveSyncEnabled bool + // Quota + Quota QuotaSettings + // Alerting AlertingEnabled *bool ExecuteAlerts bool @@ -419,12 +422,12 @@ type Cfg struct { LDAPSkipOrgRoleSync bool LDAPAllowSignup bool + Quota QuotaSettings + DefaultTheme string DefaultLocale string HomePage string - Quota QuotaSettings - AutoAssignOrg bool AutoAssignOrgId int AutoAssignOrgRole string @@ -1050,12 +1053,11 @@ 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 diff --git a/pkg/setting/setting_quota.go b/pkg/setting/setting_quota.go index 053adb74662..b3cd6d01115 100644 --- a/pkg/setting/setting_quota.go +++ b/pkg/setting/setting_quota.go @@ -1,5 +1,9 @@ package setting +import ( + "reflect" +) + type OrgQuota struct { User int64 `target:"org_user"` DataSource int64 `target:"data_source"` @@ -23,17 +27,45 @@ 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") - cfg.Quota.Enabled = quota.Key("enabled").MustBool(false) + Quota.Enabled = quota.Key("enabled").MustBool(false) var alertOrgQuota int64 var alertGlobalQuota int64 @@ -42,7 +74,7 @@ func (cfg *Cfg) readQuotaSettings() { alertGlobalQuota = quota.Key("global_alert_rule").MustInt64(-1) } // per ORG Limits - cfg.Quota.Org = OrgQuota{ + 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), @@ -51,12 +83,12 @@ func (cfg *Cfg) readQuotaSettings() { } // per User limits - cfg.Quota.User = UserQuota{ + Quota.User = &UserQuota{ Org: quota.Key("user_org").MustInt64(10), } // Global Limits - cfg.Quota.Global = GlobalQuota{ + 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), @@ -66,4 +98,6 @@ func (cfg *Cfg) readQuotaSettings() { File: quota.Key("global_file").MustInt64(-1), AlertRule: alertGlobalQuota, } + + cfg.Quota = Quota } diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index 5755d9b6497..aaf7afb0693 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -16,6 +16,7 @@ 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" @@ -1877,8 +1878,6 @@ 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", @@ -1919,10 +1918,30 @@ 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 - limit, used := apiClient.GetOrgQuotaLimits(t, 1) - apiClient.UpdateAlertRuleOrgQuota(t, 1, used) + 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) + t.Cleanup(func() { - apiClient.UpdateAlertRuleOrgQuota(t, 1, limit) + // 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) }) // try to create an alert rule diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index f13443b5d2d..0b2540a7628 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -16,7 +16,6 @@ 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" ) @@ -203,60 +202,6 @@ 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{} diff --git a/pkg/tsdb/legacydata/service/service_test.go b/pkg/tsdb/legacydata/service/service_test.go index beee540e81b..263fc24a828 100644 --- a/pkg/tsdb/legacydata/service/service_test.go +++ b/pkg/tsdb/legacydata/service/service_test.go @@ -16,14 +16,16 @@ 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 { @@ -44,10 +46,7 @@ func TestHandleRequest(t *testing.T) { secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) datasourcePermissions := acmock.NewMockedPermissionsService() - quotaService := quotatest.New(false, nil) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService) - require.NoError(t, err) - + dsService := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions) s := ProvideService(client, nil, dsService) ds := &datasources.DataSource{Id: 12, Type: "unregisteredType", JsonData: simplejson.New()}