From 310ab323c8e51455a753b4fc5bd8bbec068da4e2 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 17 May 2021 13:05:15 +0200 Subject: [PATCH] Quota: Do not count folders towards dashboard quota (#32519) (#34025) --- pkg/services/quota/quota.go | 12 +- pkg/services/sqlstore/quota.go | 18 +- pkg/services/sqlstore/quota_test.go | 460 +++++++++++++++------------- 3 files changed, 274 insertions(+), 216 deletions(-) diff --git a/pkg/services/quota/quota.go b/pkg/services/quota/quota.go index 7b9b49c074b..43c4ead43a5 100644 --- a/pkg/services/quota/quota.go +++ b/pkg/services/quota/quota.go @@ -130,8 +130,16 @@ func (qs *QuotaService) getQuotaScopes(target string) ([]models.QuotaScope, erro return scopes, nil case "dashboard": scopes = append(scopes, - models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.Dashboard}, - models.QuotaScope{Name: "org", Target: target, DefaultLimit: qs.Cfg.Quota.Org.Dashboard}, + models.QuotaScope{ + Name: "global", + Target: target, + DefaultLimit: qs.Cfg.Quota.Global.Dashboard, + }, + models.QuotaScope{ + Name: "org", + Target: target, + DefaultLimit: qs.Cfg.Quota.Org.Dashboard, + }, ) return scopes, nil case "data_source": diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go index ed8c2c72d4a..c48080ead86 100644 --- a/pkg/services/sqlstore/quota.go +++ b/pkg/services/sqlstore/quota.go @@ -9,6 +9,8 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +const dashboardTarget = "dashboard" + func init() { bus.AddHandler("sql", GetOrgQuotaByTarget) bus.AddHandler("sql", GetOrgQuotas) @@ -36,7 +38,13 @@ func GetOrgQuotaByTarget(query *models.GetOrgQuotaByTargetQuery) error { } // get quota used. - rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target)) + 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)) + } + resp := make([]*targetCount, 0) if err := x.SQL(rawSQL, query.OrgId).Find(&resp); err != nil { return err @@ -231,7 +239,13 @@ func UpdateUserQuota(cmd *models.UpdateUserQuotaCmd) error { func GetGlobalQuotaByTarget(query *models.GetGlobalQuotaByTargetQuery) error { // get quota used. - rawSQL := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target)) + 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)) + } + resp := make([]*targetCount, 0) if err := x.SQL(rawSQL).Find(&resp); err != nil { return err diff --git a/pkg/services/sqlstore/quota_test.go b/pkg/services/sqlstore/quota_test.go index 0fefb9ea2d4..2cfbf5c92e6 100644 --- a/pkg/services/sqlstore/quota_test.go +++ b/pkg/services/sqlstore/quota_test.go @@ -8,230 +8,266 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" ) func TestQuotaCommandsAndQueries(t *testing.T) { - Convey("Testing Quota commands & queries", t, func() { - InitTestDB(t) - userId := int64(1) - orgId := int64(0) + 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, - }, - User: &setting.UserQuota{ - Org: 5, - }, - Global: &setting.GlobalQuota{ - Org: 5, - User: 5, - Dashboard: 5, - DataSource: 5, - ApiKey: 5, - Session: 5, - }, + setting.Quota = setting.QuotaSettings{ + Enabled: true, + Org: &setting.OrgQuota{ + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + }, + User: &setting.UserQuota{ + Org: 5, + }, + Global: &setting.GlobalQuota{ + Org: 5, + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + Session: 5, + }, + } + + // 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: 1, + } + + err := CreateOrg(&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 := UpdateOrgQuota(&orgCmd) + 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: 1, - } - - err := CreateOrg(&userCmd) - So(err, ShouldBeNil) - orgId = userCmd.Result.Id - - Convey("Given saved org quota for users", func() { - orgCmd := models.UpdateOrgQuotaCmd{ - OrgId: orgId, - Target: "org_user", - Limit: 10, - } - err := UpdateOrgQuota(&orgCmd) - So(err, ShouldBeNil) - - Convey("Should be able to get saved quota by org id and target", func() { - query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1} - err = GetOrgQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 10) - }) - Convey("Should be able to get default quota by org id and target", func() { - query := models.GetOrgQuotaByTargetQuery{OrgId: 123, Target: "org_user", Default: 11} - err = GetOrgQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 11) - }) - Convey("Should be able to get used org quota when rows exist", func() { - query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 11} - err = GetOrgQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Used, ShouldEqual, 1) - }) - Convey("Should be able to get used org quota when no rows exist", func() { - query := models.GetOrgQuotaByTargetQuery{OrgId: 2, Target: "org_user", Default: 11} - err = GetOrgQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Used, ShouldEqual, 0) - }) - Convey("Should be able to quota list for org", func() { - query := models.GetOrgQuotasQuery{OrgId: orgId} - err = GetOrgQuotas(&query) - - So(err, ShouldBeNil) - So(len(query.Result), ShouldEqual, 4) - for _, res := range query.Result { - limit := 5 // default quota limit - used := 0 - if res.Target == "org_user" { - limit = 10 // customized quota limit. - used = 1 - } - So(res.Limit, ShouldEqual, limit) - So(res.Used, ShouldEqual, used) - } - }) - }) - Convey("Given saved user quota for org", func() { - userQuotaCmd := models.UpdateUserQuotaCmd{ - UserId: userId, - Target: "org_user", - Limit: 10, - } - err := UpdateUserQuota(&userQuotaCmd) - So(err, ShouldBeNil) - - Convey("Should be able to get saved quota by user id and target", func() { - query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} - err = GetUserQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 10) - }) - Convey("Should be able to get default quota by user id and target", func() { - query := models.GetUserQuotaByTargetQuery{UserId: 9, Target: "org_user", Default: 11} - err = GetUserQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 11) - }) - Convey("Should be able to get used user quota when rows exist", func() { - query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 11} - err = GetUserQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Used, ShouldEqual, 1) - }) - Convey("Should be able to get used user quota when no rows exist", func() { - query := models.GetUserQuotaByTargetQuery{UserId: 2, Target: "org_user", Default: 11} - err = GetUserQuotaByTarget(&query) - - So(err, ShouldBeNil) - So(query.Result.Used, ShouldEqual, 0) - }) - Convey("Should be able to quota list for user", func() { - query := models.GetUserQuotasQuery{UserId: userId} - err = GetUserQuotas(&query) - - So(err, ShouldBeNil) - So(len(query.Result), ShouldEqual, 1) - So(query.Result[0].Limit, ShouldEqual, 10) - So(query.Result[0].Used, ShouldEqual, 1) - }) - }) - - Convey("Should be able to global user quota", func() { - query := models.GetGlobalQuotaByTargetQuery{Target: "user", Default: 5} - err = GetGlobalQuotaByTarget(&query) - So(err, ShouldBeNil) - - So(query.Result.Limit, ShouldEqual, 5) - So(query.Result.Used, ShouldEqual, 0) - }) - Convey("Should be able to global org quota", func() { - query := models.GetGlobalQuotaByTargetQuery{Target: "org", Default: 5} - err = GetGlobalQuotaByTarget(&query) - So(err, ShouldBeNil) - - So(query.Result.Limit, ShouldEqual, 5) - So(query.Result.Used, ShouldEqual, 1) - }) - - // related: https://github.com/grafana/grafana/issues/14342 - Convey("Should org quota updating is successful even if it called multiple time", func() { - orgCmd := models.UpdateOrgQuotaCmd{ - OrgId: orgId, - Target: "org_user", - Limit: 5, - } - err := UpdateOrgQuota(&orgCmd) - So(err, ShouldBeNil) - + 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 = GetOrgQuotaByTarget(&query) - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 5) - // 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 = UpdateOrgQuota(&orgCmd) - So(err, ShouldBeNil) - - query = models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1} - err = GetOrgQuotaByTarget(&query) - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 10) + require.NoError(t, err) + require.Equal(t, int64(10), query.Result.Limit) }) - // related: https://github.com/grafana/grafana/issues/14342 - Convey("Should user quota updating is successful even if it called multiple time", func() { - userQuotaCmd := models.UpdateUserQuotaCmd{ - UserId: userId, - Target: "org_user", - Limit: 5, + 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 = GetOrgQuotaByTarget(&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 = GetOrgQuotaByTarget(&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 = GetOrgQuotaByTarget(&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 = GetOrgQuotas(&query) + + require.NoError(t, err) + require.Len(t, query.Result, 4) + 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) } - err := UpdateUserQuota(&userQuotaCmd) - So(err, ShouldBeNil) - - query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} - err = GetUserQuotaByTarget(&query) - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 5) - - // 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 = UpdateUserQuota(&userQuotaCmd) - So(err, ShouldBeNil) - - query = models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} - err = GetUserQuotaByTarget(&query) - So(err, ShouldBeNil) - So(query.Result.Limit, ShouldEqual, 10) }) }) + + t.Run("Given saved org quota for dashboards", func(t *testing.T) { + orgCmd := models.UpdateOrgQuotaCmd{ + OrgId: orgId, + Target: dashboardTarget, + Limit: 10, + } + err := UpdateOrgQuota(&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 = GetOrgQuotaByTarget(&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 := UpdateUserQuota(&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 = GetUserQuotaByTarget(&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 = GetUserQuotaByTarget(&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 = GetUserQuotaByTarget(&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 = GetUserQuotaByTarget(&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 = GetUserQuotas(&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 = GetGlobalQuotaByTarget(&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 org quota", func(t *testing.T) { + query := models.GetGlobalQuotaByTargetQuery{Target: "org", Default: 5} + err = GetGlobalQuotaByTarget(&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 dashboard quota", func(t *testing.T) { + query := models.GetGlobalQuotaByTargetQuery{Target: dashboardTarget, Default: 5} + err = GetGlobalQuotaByTarget(&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 := UpdateOrgQuota(&orgCmd) + require.NoError(t, err) + + query := models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1} + err = GetOrgQuotaByTarget(&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 = UpdateOrgQuota(&orgCmd) + require.NoError(t, err) + + query = models.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1} + err = GetOrgQuotaByTarget(&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 := UpdateUserQuota(&userQuotaCmd) + require.NoError(t, err) + + query := models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} + err = GetUserQuotaByTarget(&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 = UpdateUserQuota(&userQuotaCmd) + require.NoError(t, err) + + query = models.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} + err = GetUserQuotaByTarget(&query) + require.NoError(t, err) + require.Equal(t, int64(10), query.Result.Limit) + }) }