From 9023171940e17c44072a86ee40705964473485ef Mon Sep 17 00:00:00 2001 From: woodsaj Date: Mon, 20 Jul 2015 20:51:27 +0800 Subject: [PATCH] inital backend suport for quotas. issue #321 Conflicts: conf/defaults.ini main.go pkg/services/sqlstore/migrations/migrations.go --- conf/defaults.ini | 7 +- main.go | 3 + pkg/api/api.go | 2 + pkg/api/quota.go | 26 ++++++ pkg/models/quotas.go | 63 +++++++++++++ .../sqlstore/migrations/migrations.go | 1 + pkg/services/sqlstore/migrations/quota_mig.go | 27 ++++++ pkg/services/sqlstore/quota.go | 90 +++++++++++++++++++ 8 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 pkg/api/quota.go create mode 100644 pkg/models/quotas.go create mode 100644 pkg/services/sqlstore/migrations/quota_mig.go create mode 100644 pkg/services/sqlstore/quota.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 941813fb4c6..bfa13c231e5 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -253,4 +253,9 @@ exchange = grafana_events enabled = false path = /var/lib/grafana/dashboards - +[quota] +user = 10 +dashboard = 100 +data_source = 10 +endpoint = 10 +collector = 10 diff --git a/main.go b/main.go index 045527a6811..cdbf248358c 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/eventpublisher" "github.com/grafana/grafana/pkg/services/notifications" @@ -56,6 +57,8 @@ func main() { eventpublisher.Init() plugins.Init() + models.InitQuotaDefaults() + if err := notifications.Init(); err != nil { log.Fatal(3, "Notification service failed to initialize", err) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 6c56e6aa9f1..fce99e733f5 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -122,6 +122,8 @@ func Register(r *macaron.Macaron) { r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser)) r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser)) r.Delete("/users/:userId", wrap(RemoveOrgUser)) + r.Get("/quotas", wrap(GetOrgQuotas)) + r.Put("/quotas/:target", bind(m.UpdateQuotaCmd{}), wrap(UpdateOrgQuota)) }, reqGrafanaAdmin) // auth api keys diff --git a/pkg/api/quota.go b/pkg/api/quota.go new file mode 100644 index 00000000000..13e0e8c9781 --- /dev/null +++ b/pkg/api/quota.go @@ -0,0 +1,26 @@ +package api + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" +) + +func GetOrgQuotas(c *middleware.Context) Response { + query := m.GetQuotasQuery{OrgId: c.ParamsInt64(":orgId")} + + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to get org quotas", err) + } + + return Json(200, query.Result) +} + +func UpdateOrgQuota(c *middleware.Context, cmd m.UpdateQuotaCmd) Response { + cmd.OrgId = c.ParamsInt64(":orgId") + cmd.Target = m.QuotaTarget(c.Params(":target")) + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to update org quotas", err) + } + return ApiSuccess("Organization quota updated") +} diff --git a/pkg/models/quotas.go b/pkg/models/quotas.go new file mode 100644 index 00000000000..e296ddedb8e --- /dev/null +++ b/pkg/models/quotas.go @@ -0,0 +1,63 @@ +package models + +import ( + "github.com/grafana/grafana/pkg/setting" + "time" +) + +type QuotaTarget string + +const ( + QUOTA_USER QuotaTarget = "user" //SQL table to query. ie. "select count(*) from user where org_id=?" + QUOTA_DATASOURCE QuotaTarget = "data_source" + QUOTA_DASHBOARD QuotaTarget = "dashboard" + QUOTA_ENDPOINT QuotaTarget = "endpoint" + QUOTA_COLLECTOR QuotaTarget = "collector" +) + +// defaults are set from settings package. +var DefaultQuotas map[QuotaTarget]int64 + +func InitQuotaDefaults() { + // set global defaults. + DefaultQuotas = make(map[QuotaTarget]int64) + quota := setting.Cfg.Section("quota") + DefaultQuotas[QUOTA_USER] = quota.Key("user").MustInt64(10) + DefaultQuotas[QUOTA_DATASOURCE] = quota.Key("data_source").MustInt64(10) + DefaultQuotas[QUOTA_DASHBOARD] = quota.Key("dashboard").MustInt64(10) + DefaultQuotas[QUOTA_ENDPOINT] = quota.Key("endpoint").MustInt64(10) + DefaultQuotas[QUOTA_COLLECTOR] = quota.Key("collector").MustInt64(10) +} + +type Quota struct { + Id int64 + OrgId int64 + Target QuotaTarget + Limit int64 + Created time.Time + Updated time.Time +} + +type QuotaDTO struct { + OrgId int64 `json:"org_id"` + Target QuotaTarget `json:"target"` + Limit int64 `json:"limit"` + Used int64 `json:"used"` +} + +type GetQuotaByTargetQuery struct { + Target QuotaTarget + OrgId int64 + Result *QuotaDTO +} + +type GetQuotasQuery struct { + OrgId int64 + Result []*QuotaDTO +} + +type UpdateQuotaCmd struct { + Target QuotaTarget `json:"target"` + Limit int64 `json:"limit"` + OrgId int64 `json:"-"` +} diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 70acc9ef736..8f7054d3959 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -17,6 +17,7 @@ func AddMigrations(mg *Migrator) { addDataSourceMigration(mg) addApiKeyMigrations(mg) addDashboardSnapshotMigrations(mg) + addQuotaMigration(mg) } func addMigrationLogMigrations(mg *Migrator) { diff --git a/pkg/services/sqlstore/migrations/quota_mig.go b/pkg/services/sqlstore/migrations/quota_mig.go new file mode 100644 index 00000000000..d0b3b14fd5a --- /dev/null +++ b/pkg/services/sqlstore/migrations/quota_mig.go @@ -0,0 +1,27 @@ +package migrations + +import ( + . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +func addQuotaMigration(mg *Migrator) { + + var quotaV1 = Table{ + Name: "quota", + Columns: []*Column{ + {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "org_id", Type: DB_BigInt, Nullable: false}, + {Name: "target", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "limit", Type: DB_BigInt, Nullable: false}, + {Name: "created", Type: DB_DateTime, Nullable: false}, + {Name: "updated", Type: DB_DateTime, Nullable: false}, + }, + Indices: []*Index{ + {Cols: []string{"org_id", "target"}, Type: UniqueIndex}, + }, + } + mg.AddMigration("create quota table v1", NewAddTableMigration(quotaV1)) + + //------- indexes ------------------ + addTableIndicesMigrations(mg, "v1", quotaV1) +} diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go new file mode 100644 index 00000000000..47b7d253ffc --- /dev/null +++ b/pkg/services/sqlstore/quota.go @@ -0,0 +1,90 @@ +package sqlstore + +import ( + "fmt" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func init() { + bus.AddHandler("sql", GetQuotaByTarget) + bus.AddHandler("sql", GetQuotas) + bus.AddHandler("sql", UpdateQuota) +} + +type targetCount struct { + Count int64 +} + +func GetQuotaByTarget(query *m.GetQuotaByTargetQuery) error { + quota := m.Quota{ + Target: query.Target, + OrgId: query.OrgId, + } + has, err := x.Get(quota) + if err != nil { + return err + } else if has == false { + quota.Limit = m.DefaultQuotas[query.Target] + } + + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", string(query.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, query.OrgId).Find(&resp); err != nil { + return err + } + + query.Result = &m.QuotaDTO{ + Target: query.Target, + Limit: quota.Limit, + OrgId: query.OrgId, + Used: resp[0].Count, + } + + return nil +} + +func GetQuotas(query *m.GetQuotasQuery) error { + quotas := make([]*m.Quota, 0) + sess := x.Table("quota") + if err := sess.Where("org_id=?", query.OrgId).Find("as); err != nil { + return err + } + + seenTargets := make(map[m.QuotaTarget]bool) + for _, q := range quotas { + seenTargets[q.Target] = true + } + + for t, v := range m.DefaultQuotas { + if _, ok := seenTargets[t]; !ok { + quotas = append(quotas, &m.Quota{ + OrgId: query.OrgId, + Target: t, + Limit: v, + }) + } + } + result := make([]*m.QuotaDTO, len(quotas)) + for i, q := range quotas { + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", string(q.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, q.OrgId).Find(&resp); err != nil { + return err + } + result[i] = &m.QuotaDTO{ + Target: q.Target, + Limit: q.Limit, + OrgId: q.OrgId, + Used: resp[0].Count, + } + } + query.Result = result + return nil +} + +func UpdateQuota(cmd *m.UpdateQuotaCmd) error { + return nil +}