From d1346771aaa2e38ef8d561ff6ee062bf967ef478 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Wed, 1 Apr 2020 20:02:25 +0800 Subject: [PATCH] feature: domain resource quota --- cmd/climc/shell/domains.go | 10 + cmd/climc/shell/projects.go | 14 +- cmd/climc/shell/quotas.go | 144 +++++++++ pkg/apis/identity/domain.go | 14 + pkg/apis/identity/input.go | 49 +++ pkg/cloudcommon/db/namevalidator.go | 14 + pkg/cloudcommon/db/quotas/handler.go | 96 ++++-- pkg/cloudcommon/db/quotas/models.go | 15 +- pkg/cloudcommon/db/quotas/quotakeys.go | 144 +++++++-- pkg/cloudcommon/db/quotas/quotakeys_test.go | 74 +++-- pkg/cloudcommon/db/quotas/quotas.go | 2 +- pkg/cloudcommon/db/quotas/register.go | 3 + pkg/compute/guestdrivers/aliyun.go | 2 +- pkg/compute/guestdrivers/aws.go | 2 +- pkg/compute/guestdrivers/azure.go | 2 +- pkg/compute/guestdrivers/baremetals.go | 2 +- pkg/compute/guestdrivers/container.go | 2 +- pkg/compute/guestdrivers/ctyun.go | 2 +- pkg/compute/guestdrivers/esxi.go | 2 +- pkg/compute/guestdrivers/google.go | 2 +- pkg/compute/guestdrivers/huawei.go | 2 +- pkg/compute/guestdrivers/kvm.go | 2 +- pkg/compute/guestdrivers/openstack.go | 2 +- pkg/compute/guestdrivers/qcloud.go | 2 +- pkg/compute/guestdrivers/ucloud.go | 2 +- pkg/compute/guestdrivers/zstack.go | 2 +- pkg/compute/models/cloudaccounts.go | 43 +++ pkg/compute/models/domainquota.go | 193 +++++++++++ pkg/compute/models/globalvpcs.go | 49 +++ pkg/compute/models/guests.go | 1 + pkg/compute/models/hosts.go | 62 ++++ pkg/compute/models/infrasquota.go | 227 +++++++++++++ pkg/compute/models/projectquota.go | 13 +- pkg/compute/models/quotas.go | 5 +- pkg/compute/models/quotas_doc.go | 118 +++++++ pkg/compute/models/regionquota.go | 3 + pkg/compute/models/secgroups.go | 4 +- pkg/compute/models/vpcs.go | 90 +++++- pkg/compute/models/zonequota.go | 4 + pkg/compute/options/options.go | 6 + pkg/compute/policy/defaults.go | 12 + pkg/compute/service/handlers.go | 8 + pkg/compute/service/service.go | 2 + pkg/compute/usages/handler.go | 30 +- pkg/image/models/images.go | 4 +- pkg/image/models/quotas.go | 24 +- pkg/keystone/models/domains.go | 26 +- pkg/keystone/models/groups.go | 58 ++++ pkg/keystone/models/identitybase.go | 10 + pkg/keystone/models/identityquota.go | 342 ++++++++++++++++++++ pkg/keystone/models/policies.go | 71 ++-- pkg/keystone/models/projects.go | 52 ++- pkg/keystone/models/roles.go | 60 +++- pkg/keystone/models/users.go | 45 ++- pkg/keystone/options/options.go | 6 + pkg/keystone/service/handlers.go | 8 +- pkg/keystone/service/service.go | 1 + pkg/mcclient/modules/mod_domains.go | 2 +- pkg/mcclient/modules/mod_projects.go | 2 +- pkg/mcclient/modules/mod_quotas.go | 19 ++ 60 files changed, 2004 insertions(+), 203 deletions(-) create mode 100644 pkg/compute/models/domainquota.go create mode 100644 pkg/compute/models/infrasquota.go create mode 100644 pkg/keystone/models/identityquota.go diff --git a/cmd/climc/shell/domains.go b/cmd/climc/shell/domains.go index 2a0d2a4da3..037cde91e3 100644 --- a/cmd/climc/shell/domains.go +++ b/cmd/climc/shell/domains.go @@ -83,6 +83,8 @@ func init() { Desc string `help:"Description"` Enabled bool `help:"Set the domain enabled"` Disabled bool `help:"Set the domain disabled"` + + Displayname string `help:"display name"` } R(&DomainCreateOptions{}, "domain-create", "Create a new domain", func(s *mcclient.ClientSession, args *DomainCreateOptions) error { params := jsonutils.NewDict() @@ -95,6 +97,9 @@ func init() { } else if !args.Enabled && args.Disabled { params.Add(jsonutils.JSONFalse, "enabled") } + if len(args.Displayname) > 0 { + params.Add(jsonutils.NewString(args.Displayname), "displayname") + } result, err := modules.Domains.Create(s, params) if err != nil { return err @@ -110,6 +115,8 @@ func init() { Enabled bool `help:"Set the domain enabled"` Disabled bool `help:"Set the domain disabled"` Driver string `help:"Set the domain Driver"` + + Displayname string `help:"display name"` } R(&DomainUpdateOptions{}, "domain-update", "Update a domain", func(s *mcclient.ClientSession, args *DomainUpdateOptions) error { obj, err := modules.Domains.Get(s, args.ID, nil) @@ -136,6 +143,9 @@ func init() { } else if !args.Enabled && args.Disabled { params.Add(jsonutils.JSONFalse, "enabled") } + if len(args.Displayname) > 0 { + params.Add(jsonutils.NewString(args.Displayname), "displayname") + } result, err := modules.Domains.Patch(s, objId, params) if err != nil { return err diff --git a/cmd/climc/shell/projects.go b/cmd/climc/shell/projects.go index d1e748251b..0840b79a44 100644 --- a/cmd/climc/shell/projects.go +++ b/cmd/climc/shell/projects.go @@ -82,11 +82,12 @@ func init() { }) type ProjectCreateOptions struct { - NAME string `help:"Name of new project"` - Domain string `help:"Domain"` - Desc string `help:"Description"` - Enabled bool `help:"Project is enabled"` - Disabled bool `help:"Project is disabled"` + NAME string `help:"Name of new project"` + Displayname string `help:"display name"` + Domain string `help:"Domain"` + Desc string `help:"Description"` + Enabled bool `help:"Project is enabled"` + Disabled bool `help:"Project is disabled"` } R(&ProjectCreateOptions{}, "project-create", "Create a project", func(s *mcclient.ClientSession, args *ProjectCreateOptions) error { params := jsonutils.NewDict() @@ -106,6 +107,9 @@ func init() { if len(args.Desc) > 0 { params.Add(jsonutils.NewString(args.Desc), "description") } + if len(args.Displayname) > 0 { + params.Add(jsonutils.NewString(args.Displayname), "displayname") + } result, err := modules.Projects.Create(s, params) if err != nil { return err diff --git a/cmd/climc/shell/quotas.go b/cmd/climc/shell/quotas.go index aa2ea432b4..d9ca671d30 100644 --- a/cmd/climc/shell/quotas.go +++ b/cmd/climc/shell/quotas.go @@ -94,6 +94,25 @@ type ImageQuotaOptions struct { Image int64 `help:"Template count" json:"image,omitzero"` } +type DomainQuotaOptions struct { + Cloudaccount int64 `help:"cloudaccount count" json:"cloudaccount,omitzero"` +} + +type InfrasQuotaOptions struct { + RegionQuotaKeys + + Host int64 `help:"host count" json:"host,omitzero"` + Vpc int64 `help:"vpc count" json:"vpc,omitzero"` +} + +type IdentityQuotaOptions struct { + User int64 `help:"user count" json:"user,omitzero"` + Group int64 `help:"group count" json:"group,omitzero"` + Project int64 `help:"project count" json:"project,omitzero"` + Role int64 `help:"role count" json:"role,omitzero"` + Policy int64 `help:"policy count" json:"policy,omitzero"` +} + type QuotaSetBaseOptions struct { Project string `help:"Tenant name or ID to set quota" json:"tenant,omitempty"` Domain string `help:"Domain name or ID to set quota" json:"domain,omitempty"` @@ -167,6 +186,33 @@ func init() { printQuotaList(quotas) return nil }) + R(&QuotaOptions{}, "domain-quota", "Show domain-quota for current domain", func(s *mcclient.ClientSession, args *QuotaOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.DomainQuotas.GetQuota(s, params) + if err != nil { + return err + } + printQuotaList(result) + return nil + }) + R(&QuotaOptions{}, "infras-quota", "Show infras-quota for current domain", func(s *mcclient.ClientSession, args *QuotaOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.InfrasQuotas.GetQuota(s, params) + if err != nil { + return err + } + printQuotaList(result) + return nil + }) + R(&QuotaOptions{}, "identity-quota", "Show identity-quota for current domain", func(s *mcclient.ClientSession, args *QuotaOptions) error { + params := jsonutils.Marshal(args) + result, err := modules.IdentityQuotas.GetQuota(s, params) + if err != nil { + return err + } + printQuotaList(result) + return nil + }) type ComputeQuotaSetOptions struct { QuotaSetBaseOptions @@ -238,6 +284,48 @@ func init() { return nil }) + type DomainQuotaSetOptions struct { + QuotaSetBaseOptions + DomainQuotaOptions + } + R(&DomainQuotaSetOptions{}, "domain-quota-set", "Set domain quota for domain", func(s *mcclient.ClientSession, args *DomainQuotaSetOptions) error { + params := jsonutils.Marshal(args) + quotas, e := modules.DomainQuotas.DoQuotaSet(s, params) + if e != nil { + return e + } + printQuotaList(quotas) + return nil + }) + + type InfrasQuotaSetOptions struct { + QuotaSetBaseOptions + InfrasQuotaOptions + } + R(&InfrasQuotaSetOptions{}, "infras-quota-set", "Set infrastructure quota for domain", func(s *mcclient.ClientSession, args *InfrasQuotaSetOptions) error { + params := jsonutils.Marshal(args) + quotas, e := modules.InfrasQuotas.DoQuotaSet(s, params) + if e != nil { + return e + } + printQuotaList(quotas) + return nil + }) + + type IdentityQuotaSetOptions struct { + QuotaSetBaseOptions + IdentityQuotaOptions + } + R(&IdentityQuotaSetOptions{}, "identity-quota-set", "Set identity quota for domain", func(s *mcclient.ClientSession, args *InfrasQuotaSetOptions) error { + params := jsonutils.Marshal(args) + quotas, e := modules.IdentityQuotas.DoQuotaSet(s, params) + if e != nil { + return e + } + printQuotaList(quotas) + return nil + }) + type QuotaListOptions struct { ProjectDomain string `help:"domain name or ID to query project quotas"` Refresh bool `help:"refresh" json:"refresh,omitfalse"` @@ -293,6 +381,36 @@ func init() { return nil }) + R(&QuotaListOptions{}, "domain-quota-list", "List domain quota of domains", func(s *mcclient.ClientSession, args *QuotaListOptions) error { + params := jsonutils.Marshal(args) + result, e := modules.DomainQuotas.GetQuotaList(s, params) + if e != nil { + return e + } + printQuotaList(result) + return nil + }) + + R(&QuotaListOptions{}, "infras-quota-list", "List infrastructure quota of domains", func(s *mcclient.ClientSession, args *QuotaListOptions) error { + params := jsonutils.Marshal(args) + result, e := modules.InfrasQuotas.GetQuotaList(s, params) + if e != nil { + return e + } + printQuotaList(result) + return nil + }) + + R(&QuotaListOptions{}, "identity-quota-list", "List identity quota of domains", func(s *mcclient.ClientSession, args *QuotaListOptions) error { + params := jsonutils.Marshal(args) + result, e := modules.IdentityQuotas.GetQuotaList(s, params) + if e != nil { + return e + } + printQuotaList(result) + return nil + }) + type CleanPendingUsageOptions struct { Scope string `help:"scope" choices:"domain|project"` Project string `help:"Tenant name or ID" json:"tenant"` @@ -343,4 +461,30 @@ func init() { return nil }) + R(&CleanPendingUsageOptions{}, "clean-domain-pending-usage", "Clean pending usage for project or domain", func(s *mcclient.ClientSession, args *CleanPendingUsageOptions) error { + params := jsonutils.Marshal(args) + _, err := modules.DomainQuotas.DoCleanPendingUsage(s, params) + if err != nil { + return err + } + return nil + }) + + R(&CleanPendingUsageOptions{}, "clean-infras-pending-usage", "Clean pending usage for project or domain", func(s *mcclient.ClientSession, args *CleanPendingUsageOptions) error { + params := jsonutils.Marshal(args) + _, err := modules.InfrasQuotas.DoCleanPendingUsage(s, params) + if err != nil { + return err + } + return nil + }) + + R(&CleanPendingUsageOptions{}, "clean-identity-pending-usage", "Clean pending usage for project or domain", func(s *mcclient.ClientSession, args *CleanPendingUsageOptions) error { + params := jsonutils.Marshal(args) + _, err := modules.IdentityQuotas.DoCleanPendingUsage(s, params) + if err != nil { + return err + } + return nil + }) } diff --git a/pkg/apis/identity/domain.go b/pkg/apis/identity/domain.go index 73248672e9..95b93032e1 100644 --- a/pkg/apis/identity/domain.go +++ b/pkg/apis/identity/domain.go @@ -35,5 +35,19 @@ type DomainDetails struct { type DomainUpdateInput struct { apis.StandaloneResourceBaseUpdateInput + // 显示名 + Displayname string `json:"displayname"` + + // 是否启用 + Enabled *bool `json:"enabled"` +} + +type DomainCreateInput struct { + apis.StandaloneResourceCreateInput + + // 显示名 + Displayname string `json:"displayname"` + + // 是否启用 Enabled *bool `json:"enabled"` } diff --git a/pkg/apis/identity/input.go b/pkg/apis/identity/input.go index 88c70cc4f7..497de73b33 100644 --- a/pkg/apis/identity/input.go +++ b/pkg/apis/identity/input.go @@ -375,6 +375,9 @@ type PolicyUpdateInput struct { type ProjectUpdateInput struct { IdentityBaseUpdateInput + + // 显示名称 + Displayname string `json:"displayname"` } type RoleUpdateInput struct { @@ -398,3 +401,49 @@ type UserUpdateInput struct { Password string `json:"password"` } + +type UserCreateInput struct { + EnabledIdentityBaseResourceCreateInput + + Email string `json:"email"` + + Mobile string `json:"mobule"` + + Displayname string `json:"displayname"` + + IsSystemAccount *bool `json:"is_system_account"` + + AllowWebConsole *bool `json:"allow_web_console"` + + EnableMfa *bool `json:"enable_mfa"` + + Password string `json:"password"` + + SkipPasswordComplexityCheck *bool `json:"skip_password_complexity_check"` +} + +type ProjectCreateInput struct { + IdentityBaseResourceCreateInput + + // 显示名称 + Displayname string `json:"displayname"` +} + +type GroupCreateInput struct { + IdentityBaseResourceCreateInput + + // display name + Displayname string `json:"displayname"` +} + +type PolicyCreateInput struct { + EnabledIdentityBaseResourceCreateInput + + Type string `json:"type"` + + Blob jsonutils.JSONObject `json:"blob"` +} + +type RoleCreateInput struct { + IdentityBaseResourceCreateInput +} diff --git a/pkg/cloudcommon/db/namevalidator.go b/pkg/cloudcommon/db/namevalidator.go index 25dfd47a9a..97a41c31a6 100644 --- a/pkg/cloudcommon/db/namevalidator.go +++ b/pkg/cloudcommon/db/namevalidator.go @@ -16,6 +16,7 @@ package db import ( "fmt" + "regexp" "yunion.io/x/pkg/util/stringutils" @@ -112,3 +113,16 @@ func GenerateName2(manager IModelManager, ownerId mcclient.IIdentityProvider, hi baseIndex += 1 } } + +var ( + dnsNameREG = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]*$`) +) + +type SDnsNameValidatorManager struct{} + +func (manager *SDnsNameValidatorManager) ValidateName(name string) error { + if dnsNameREG.MatchString(name) { + return nil + } + return httperrors.NewInputParameterError("name starts with letter, and contains letter, number and - only") +} diff --git a/pkg/cloudcommon/db/quotas/handler.go b/pkg/cloudcommon/db/quotas/handler.go index ad88adf2a0..7f06a4c24e 100644 --- a/pkg/cloudcommon/db/quotas/handler.go +++ b/pkg/cloudcommon/db/quotas/handler.go @@ -75,10 +75,6 @@ func AddQuotaHandler(manager *SQuotaBaseManager, prefix string, app *appsrv.Appl fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()), auth.Authenticate(manager.getQuotaHandler), nil, "get_quota", nil) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil) - app.AddHandler2("GET", fmt.Sprintf("%s/%s/domains", prefix, manager.KeywordPlural()), auth.Authenticate(manager.listDomainQuotaHandler), nil, "list_quotas_for_all_domains", nil) @@ -87,30 +83,14 @@ func AddQuotaHandler(manager *SQuotaBaseManager, prefix string, app *appsrv.Appl fmt.Sprintf("%s/%s/domains/", prefix, manager.KeywordPlural()), auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_domain", nil) - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/projects", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.listProjectQuotaHandler), nil, "list_quotas_for_all_projects", nil) - - app.AddHandler2("GET", - fmt.Sprintf("%s/%s/projects/", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil) - app.AddHandler2("POST", fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()), auth.Authenticate(manager.setQuotaHandler), nil, "set_quota", nil) - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil) - app.AddHandler2("POST", fmt.Sprintf("%s/%s/domains/", prefix, manager.KeywordPlural()), auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_domain", nil) - app.AddHandler2("POST", - fmt.Sprintf("%s/%s/projects/", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil) - app.AddHandler2("DELETE", fmt.Sprintf("%s/%s/pending", prefix, manager.KeywordPlural()), auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage", nil) @@ -119,9 +99,31 @@ func AddQuotaHandler(manager *SQuotaBaseManager, prefix string, app *appsrv.Appl fmt.Sprintf("%s/%s/domains//pending", prefix, manager.KeywordPlural()), auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage_for_domain", nil) - app.AddHandler2("DELETE", - fmt.Sprintf("%s/%s/projects//pending", prefix, manager.KeywordPlural()), - auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage_for_project", nil) + if manager.scope == rbacutils.ScopeProject { + app.AddHandler2("GET", + fmt.Sprintf("%s/%s/", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil) + + app.AddHandler2("GET", + fmt.Sprintf("%s/%s/projects", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.listProjectQuotaHandler), nil, "list_quotas_for_all_projects", nil) + + app.AddHandler2("GET", + fmt.Sprintf("%s/%s/projects/", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil) + + app.AddHandler2("POST", + fmt.Sprintf("%s/%s/", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil) + + app.AddHandler2("POST", + fmt.Sprintf("%s/%s/projects/", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil) + + app.AddHandler2("DELETE", + fmt.Sprintf("%s/%s/projects//pending", prefix, manager.KeywordPlural()), + auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage_for_project", nil) + } } func (manager *SQuotaBaseManager) queryQuota(ctx context.Context, quota IQuota, refresh bool) (*jsonutils.JSONDict, error) { @@ -181,24 +183,24 @@ func (manager *SQuotaBaseManager) getQuotaHandler(ctx context.Context, w http.Re } else if len(projectId) > 0 { data.Add(jsonutils.NewString(projectId), "project") } - ownerId, scope, err = db.FetchCheckQueryOwnerScope(ctx, userCred, data, manager, policy.PolicyActionGet, true) + ownerId, scope, err = db.FetchCheckQueryOwnerScope(ctx, userCred, data, manager.GetIQuotaManager(), policy.PolicyActionGet, true) if err != nil { httperrors.GeneralServerError(w, err) return } } else { scopeStr, _ := query.GetString("scope") - if scopeStr == "project" { + if scopeStr == "project" && manager.scope == rbacutils.ScopeProject { scope = rbacutils.ScopeProject } else if scopeStr == "domain" { scope = rbacutils.ScopeDomain } else { - scope = rbacutils.ScopeProject + scope = manager.scope } ownerId = userCred } - keys := OwnerIdQuotaKeys(scope, ownerId) + keys := OwnerIdProjectQuotaKeys(scope, ownerId) refresh := jsonutils.QueryBoolean(query, "refresh", false) primary := jsonutils.QueryBoolean(query, "primary", false) quotaList, err := manager.listQuotas(ctx, userCred, keys.DomainId, keys.ProjectId, scope == rbacutils.ScopeDomain, primary, refresh) @@ -208,7 +210,12 @@ func (manager *SQuotaBaseManager) getQuotaHandler(ctx context.Context, w http.Re } if len(quotaList) == 0 { quota := manager.newQuota() - baseKeys := OwnerIdQuotaKeys(scope, ownerId) + var baseKeys IQuotaKeys + if manager.scope == rbacutils.ScopeProject { + baseKeys = OwnerIdProjectQuotaKeys(scope, ownerId) + } else { + baseKeys = OwnerIdDomainQuotaKeys(ownerId) + } reflectutils.FillEmbededStructValue(reflect.Indirect(reflect.ValueOf(quota)), reflect.ValueOf(baseKeys)) quota.FetchSystemQuota() manager.SetQuota(ctx, userCred, quota) @@ -303,11 +310,16 @@ func (manager *SQuotaBaseManager) cleanPendingUsageHandler(ctx context.Context, } else if scopeStr == "domain" { scope = rbacutils.ScopeDomain } else { - scope = rbacutils.ScopeProject + scope = manager.scope } ownerId = userCred } - keys := OwnerIdQuotaKeys(scope, ownerId) + var keys IQuotaKeys + if manager.scope == rbacutils.ScopeProject { + keys = OwnerIdProjectQuotaKeys(scope, ownerId) + } else { + keys = OwnerIdDomainQuotaKeys(ownerId) + } err = manager.cleanPendingUsage(ctx, userCred, keys) if err != nil { httperrors.GeneralServerError(w, err) @@ -339,7 +351,12 @@ func (manager *SQuotaBaseManager) setQuotaHandler(ctx context.Context, w http.Re } // check is there any nonempty key other than domain_id and project_id - isBaseQuota := IsBaseQuotaKeys(quota.GetKeys()) + isBaseQuota := false + if manager.scope == rbacutils.ScopeDomain { + isBaseQuota = IsBaseDomainQuotaKeys(quota.GetKeys()) + } else { + isBaseQuota = IsBaseProjectQuotaKeys(quota.GetKeys()) + } ownerId, scope, requestScope, err := manager.fetchSetQuotaScope(ctx, userCred, data, isBaseQuota) if err != nil { httperrors.GeneralServerError(w, err) @@ -347,7 +364,12 @@ func (manager *SQuotaBaseManager) setQuotaHandler(ctx context.Context, w http.Re } // fill project_id and domain_id - baseKeys := OwnerIdQuotaKeys(scope, ownerId) + var baseKeys IQuotaKeys + if manager.scope == rbacutils.ScopeDomain { + baseKeys = OwnerIdDomainQuotaKeys(ownerId) + } else { + baseKeys = OwnerIdProjectQuotaKeys(scope, ownerId) + } reflectutils.FillEmbededStructValue(reflect.Indirect(reflect.ValueOf(quota)), reflect.ValueOf(baseKeys)) keys := quota.GetKeys() @@ -427,7 +449,7 @@ func (manager *SQuotaBaseManager) setQuotaHandler(ctx context.Context, w http.Re } } - quotaList, err := manager.listQuotas(ctx, userCred, baseKeys.DomainId, baseKeys.ProjectId, scope == rbacutils.ScopeDomain, false, true) + quotaList, err := manager.listQuotas(ctx, userCred, baseKeys.OwnerId().GetProjectDomainId(), baseKeys.OwnerId().GetProjectId(), scope == rbacutils.ScopeDomain, false, true) if err != nil { httperrors.GeneralServerError(w, err) return @@ -510,7 +532,9 @@ func (manager *SQuotaBaseManager) listQuotas(ctx context.Context, userCred mccli if len(targetDomainId) > 0 { q = q.Equals("domain_id", targetDomainId) if domainOnly { - q = q.IsEmpty("tenant_id") + if manager.scope == rbacutils.ScopeProject { + q = q.IsEmpty("tenant_id") + } } else { if len(targetProjectId) > 0 { q = q.Equals("tenant_id", targetProjectId) @@ -524,7 +548,9 @@ func (manager *SQuotaBaseManager) listQuotas(ctx context.Context, userCred mccli } else { // domain only q = q.IsNotEmpty("domain_id") - q = q.IsNullOrEmpty("tenant_id") + if manager.scope == rbacutils.ScopeProject { + q = q.IsNullOrEmpty("tenant_id") + } } if primaryOnly { // list primary quota for domain or project diff --git a/pkg/cloudcommon/db/quotas/models.go b/pkg/cloudcommon/db/quotas/models.go index 892aacd54a..f15486fa7a 100644 --- a/pkg/cloudcommon/db/quotas/models.go +++ b/pkg/cloudcommon/db/quotas/models.go @@ -38,9 +38,11 @@ type SQuotaBaseManager struct { usageStore IQuotaStore nonNegative bool + + scope rbacutils.TRbacScope } -func NewQuotaBaseManager(model interface{}, tableName string, pendingStore IQuotaStore, usageStore IQuotaStore, keyword, keywordPlural string) SQuotaBaseManager { +func NewQuotaBaseManager(model interface{}, scope rbacutils.TRbacScope, tableName string, pendingStore IQuotaStore, usageStore IQuotaStore, keyword, keywordPlural string) SQuotaBaseManager { pendingStore.SetVirtualObject(pendingStore) usageStore.SetVirtualObject(usageStore) return SQuotaBaseManager{ @@ -48,13 +50,15 @@ func NewQuotaBaseManager(model interface{}, tableName string, pendingStore IQuot pendingStore: pendingStore, usageStore: usageStore, nonNegative: false, + scope: scope, } } -func NewQuotaUsageManager(model interface{}, tableName string, keyword, keywordPlural string) SQuotaBaseManager { +func NewQuotaUsageManager(model interface{}, scope rbacutils.TRbacScope, tableName string, keyword, keywordPlural string) SQuotaBaseManager { return SQuotaBaseManager{ SResourceBaseManager: db.NewResourceBaseManager(model, tableName, keyword, keywordPlural), nonNegative: true, + scope: scope, } } @@ -271,7 +275,12 @@ func (manager *SQuotaBaseManager) InitializeData() error { } quota := manager.newQuota() - baseKeys := OwnerIdQuotaKeys(scope, ownerId) + var baseKeys IQuotaKeys + if manager.scope == rbacutils.ScopeDomain { + baseKeys = OwnerIdDomainQuotaKeys(ownerId) + } else { + baseKeys = OwnerIdProjectQuotaKeys(scope, ownerId) + } if !reflectutils.FillEmbededStructValue(reflect.Indirect(reflect.ValueOf(quota)), reflect.ValueOf(baseKeys)) { log.Fatalf("invalid quota??? fail to find SBaseQuotaKey") } diff --git a/pkg/cloudcommon/db/quotas/quotakeys.go b/pkg/cloudcommon/db/quotas/quotakeys.go index 0bbe1bede0..111e9106bc 100644 --- a/pkg/cloudcommon/db/quotas/quotakeys.go +++ b/pkg/cloudcommon/db/quotas/quotakeys.go @@ -23,22 +23,30 @@ import ( "yunion.io/x/onecloud/pkg/util/rbacutils" ) -type SBaseQuotaKeys struct { - // 配额适用的项目的域ID +type SBaseDomainQuotaKeys struct { + // 配额适用的域ID DomainId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"user" json:"domain_id"` +} + +type SBaseProjectQuotaKeys struct { + SBaseDomainQuotaKeys + // 配额适用的项目ID ProjectId string `name:"tenant_id" width:"64" charset:"ascii" nullable:"false" primary:"true" list:"user" json:"project_id"` } -type SBaseQuotaDetailKeys struct { +type SBaseDomainQuotaDetailKeys struct { // 配额适用的项目的域名称 Domain string `json:"domain"` +} + +type SBaseProjectQuotaDetailKeys struct { + SBaseDomainQuotaDetailKeys // 配额适用的项目名称 Project string `json:"project"` } -type SCloudResourceKeys struct { - SBaseQuotaKeys +type SCloudResourceBaseKeys struct { // 配额适用的平台名称,参考List接口的平台列表 Provider string `width:"32" charset:"ascii" nullable:"false" primary:"true" list:"user" json:"provider"` // 配额适用的品牌名称,参考List接口的品牌列表 @@ -51,28 +59,56 @@ type SCloudResourceKeys struct { ManagerId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"user" json:"manager_id"` } -type SCloudResourceDetailKeys struct { - SBaseQuotaDetailKeys +type SCloudResourceKeys struct { + SBaseProjectQuotaKeys + SCloudResourceBaseKeys +} +type SCloudResourceDetailKeys struct { + SBaseProjectQuotaDetailKeys + SCloudResourceDetailBaseKeys +} + +type SCloudResourceDetailBaseKeys struct { // 配额适用的云账号名称 Account string `json:"account"` // 配额适用的云订阅名称 Manager string `json:"manager"` } -type SRegionalCloudResourceKeys struct { - SCloudResourceKeys +type SRegionalBaseKeys struct { // 配额适用的区域ID RegionId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"user" json:"region_id"` } +type SRegionalCloudResourceKeys struct { + SCloudResourceKeys + SRegionalBaseKeys +} + type SRegionalCloudResourceDetailKeys struct { SCloudResourceDetailKeys + SRegionalCloudResourceDetailBaseKeys +} + +type SRegionalCloudResourceDetailBaseKeys struct { // 配额适用的区域名称 Region string `json:"region"` } +type SDomainRegionalCloudResourceKeys struct { + SBaseProjectQuotaKeys + SCloudResourceBaseKeys + SRegionalBaseKeys +} + +type SDomainRegionalCloudResourceDetailKeys struct { + SBaseDomainQuotaDetailKeys + SCloudResourceDetailBaseKeys + SRegionalCloudResourceDetailBaseKeys +} + type SZonalCloudResourceKeys struct { SRegionalCloudResourceKeys // 配额适用的可用区ID @@ -86,15 +122,20 @@ type SZonalCloudResourceDetailKeys struct { Zone string `json:"zone"` } -func (k SBaseQuotaKeys) Fields() []string { +func (k SBaseDomainQuotaKeys) Fields() []string { return []string{ "domain_id", - "tenant_id", } } +func (k SBaseProjectQuotaKeys) Fields() []string { + return append(k.SBaseDomainQuotaKeys.Fields(), + "tenant_id", + ) +} + func (k SCloudResourceKeys) Fields() []string { - return append(k.SBaseQuotaKeys.Fields(), + return append(k.SBaseProjectQuotaKeys.Fields(), "provider", "brand", "cloud_env", @@ -115,15 +156,20 @@ func (k SZonalCloudResourceKeys) Fields() []string { ) } -func (k SBaseQuotaKeys) Values() []string { +func (k SBaseDomainQuotaKeys) Values() []string { return []string{ k.DomainId, - k.ProjectId, } } +func (k SBaseProjectQuotaKeys) Values() []string { + return append(k.SBaseDomainQuotaKeys.Values(), + k.ProjectId, + ) +} + func (k SCloudResourceKeys) Values() []string { - return append(k.SBaseQuotaKeys.Values(), + return append(k.SBaseProjectQuotaKeys.Values(), k.Provider, k.Brand, k.CloudEnv, @@ -144,13 +190,22 @@ func (k SZonalCloudResourceKeys) Values() []string { ) } -func (k1 SBaseQuotaKeys) Compare(ik IQuotaKeys) int { - k2 := ik.(SBaseQuotaKeys) +func (k1 SBaseDomainQuotaKeys) Compare(ik IQuotaKeys) int { + k2 := ik.(SBaseDomainQuotaKeys) if k1.DomainId < k2.DomainId { return -1 } else if k1.DomainId > k2.DomainId { return 1 } + return 0 +} + +func (k1 SBaseProjectQuotaKeys) Compare(ik IQuotaKeys) int { + k2 := ik.(SBaseProjectQuotaKeys) + r := k1.SBaseDomainQuotaKeys.Compare(k2.SBaseDomainQuotaKeys) + if r != 0 { + return r + } if k1.ProjectId < k2.ProjectId { return -1 } else if k1.ProjectId > k2.ProjectId { @@ -161,7 +216,7 @@ func (k1 SBaseQuotaKeys) Compare(ik IQuotaKeys) int { func (k1 SCloudResourceKeys) Compare(ik IQuotaKeys) int { k2 := ik.(SCloudResourceKeys) - r := k1.SBaseQuotaKeys.Compare(k2.SBaseQuotaKeys) + r := k1.SBaseProjectQuotaKeys.Compare(k2.SBaseProjectQuotaKeys) if r != 0 { return r } @@ -221,7 +276,15 @@ func QuotaKeyWeight(k IQuotaKeys) uint64 { return w } -func (k SBaseQuotaKeys) Scope() rbacutils.TRbacScope { +func (k SBaseDomainQuotaKeys) Scope() rbacutils.TRbacScope { + if len(k.DomainId) > 0 { + return rbacutils.ScopeDomain + } else { + return rbacutils.ScopeSystem + } +} + +func (k SBaseProjectQuotaKeys) Scope() rbacutils.TRbacScope { if len(k.DomainId) > 0 && len(k.ProjectId) > 0 { return rbacutils.ScopeProject } else if len(k.DomainId) > 0 && len(k.ProjectId) == 0 { @@ -233,7 +296,11 @@ func (k SBaseQuotaKeys) Scope() rbacutils.TRbacScope { } } -func (k SBaseQuotaKeys) OwnerId() mcclient.IIdentityProvider { +func (k SBaseDomainQuotaKeys) OwnerId() mcclient.IIdentityProvider { + return &db.SOwnerId{DomainId: k.DomainId} +} + +func (k SBaseProjectQuotaKeys) OwnerId() mcclient.IIdentityProvider { return &db.SOwnerId{ DomainId: k.DomainId, ProjectId: k.ProjectId, @@ -252,7 +319,7 @@ func QuotaKeyString(k IQuotaKeys) string { return strings.Join(parts, ",") } -func IsBaseQuotaKeys(k IQuotaKeys) bool { +func IsBaseProjectQuotaKeys(k IQuotaKeys) bool { fields := k.Fields() values := k.Values() for i := range fields { @@ -263,19 +330,36 @@ func IsBaseQuotaKeys(k IQuotaKeys) bool { return true } -func OwnerIdQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider) SBaseQuotaKeys { - switch scope { - case rbacutils.ScopeDomain: - return SBaseQuotaKeys{ - DomainId: ownerId.GetProjectDomainId(), +func IsBaseDomainQuotaKeys(k IQuotaKeys) bool { + fields := k.Fields() + values := k.Values() + for i := range fields { + if fields[i] != "domain_id" && len(values[i]) > 0 { + return false } - case rbacutils.ScopeProject: - return SBaseQuotaKeys{ - DomainId: ownerId.GetProjectDomainId(), + } + return true +} + +func OwnerIdProjectQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider) SBaseProjectQuotaKeys { + if scope == rbacutils.ScopeDomain { + return SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, + } + } else { + return SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, ProjectId: ownerId.GetProjectId(), } } - return SBaseQuotaKeys{} +} + +func OwnerIdDomainQuotaKeys(ownerId mcclient.IIdentityProvider) SBaseDomainQuotaKeys { + return SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()} } type TQuotaKeysRelation string diff --git a/pkg/cloudcommon/db/quotas/quotakeys_test.go b/pkg/cloudcommon/db/quotas/quotakeys_test.go index 0c6d69f3b1..89fc78b71c 100644 --- a/pkg/cloudcommon/db/quotas/quotakeys_test.go +++ b/pkg/cloudcommon/db/quotas/quotakeys_test.go @@ -26,8 +26,10 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, }, }, }, @@ -36,8 +38,10 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain2", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain2", + }, }, }, }, @@ -46,8 +50,10 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project1", }, }, @@ -57,8 +63,10 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project2", }, }, @@ -68,8 +76,10 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain2", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain2", + }, ProjectId: "project1", }, }, @@ -79,35 +89,47 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project1", }, }, - RegionId: "region1", + SRegionalBaseKeys: SRegionalBaseKeys{ + RegionId: "region1", + }, }, }, // Project11Region2 := { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project1", }, }, - RegionId: "region2", + SRegionalBaseKeys: SRegionalBaseKeys{ + RegionId: "region2", + }, }, }, // Project11Aliyun := { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project1", }, - Provider: "Aliyun", + SCloudResourceBaseKeys: SCloudResourceBaseKeys{ + Provider: "Aliyun", + }, }, }, }, @@ -115,13 +137,19 @@ func TestRelation(t *testing.T) { { SRegionalCloudResourceKeys: SRegionalCloudResourceKeys{ SCloudResourceKeys: SCloudResourceKeys{ - SBaseQuotaKeys: SBaseQuotaKeys{ - DomainId: "domain1", + SBaseProjectQuotaKeys: SBaseProjectQuotaKeys{ + SBaseDomainQuotaKeys: SBaseDomainQuotaKeys{ + DomainId: "domain1", + }, ProjectId: "project1", }, - Provider: "Aliyun", + SCloudResourceBaseKeys: SCloudResourceBaseKeys{ + Provider: "Aliyun", + }, + }, + SRegionalBaseKeys: SRegionalBaseKeys{ + RegionId: "region1", }, - RegionId: "region1", }, }, } diff --git a/pkg/cloudcommon/db/quotas/quotas.go b/pkg/cloudcommon/db/quotas/quotas.go index 2c6880cdd4..3f15c112db 100644 --- a/pkg/cloudcommon/db/quotas/quotas.go +++ b/pkg/cloudcommon/db/quotas/quotas.go @@ -29,7 +29,7 @@ import ( ) func (manager *SQuotaBaseManager) ResourceScope() rbacutils.TRbacScope { - return rbacutils.ScopeProject + return manager.scope } func (manager *SQuotaBaseManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { diff --git a/pkg/cloudcommon/db/quotas/register.go b/pkg/cloudcommon/db/quotas/register.go index 5fb0016ec3..dabd738609 100644 --- a/pkg/cloudcommon/db/quotas/register.go +++ b/pkg/cloudcommon/db/quotas/register.go @@ -17,6 +17,7 @@ package quotas import ( "context" "reflect" + "runtime/debug" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -82,6 +83,8 @@ func cancelUsage(ctx context.Context, userCred mcclient.TokenCredential, usage I if err != nil { log.Errorf("cancelUsage %s fail: %s", jsonutils.Marshal(usage), err) } + log.Infof("cancelUsage %s", jsonutils.Marshal(usage)) + debug.PrintStack() } func GetQuotaCount(ctx context.Context, request IQuota, pendingKeys IQuotaKeys) (int, error) { diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index 87db876af5..fb40d59827 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -49,7 +49,7 @@ func (self *SAliyunGuestDriver) GetProvider() string { func (self *SAliyunGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_ALIYUN keys.Brand = api.CLOUD_PROVIDER_ALIYUN diff --git a/pkg/compute/guestdrivers/aws.go b/pkg/compute/guestdrivers/aws.go index bedfaf836d..8ca6d22e76 100644 --- a/pkg/compute/guestdrivers/aws.go +++ b/pkg/compute/guestdrivers/aws.go @@ -97,7 +97,7 @@ func (self *SAwsGuestDriver) GetProvider() string { func (self *SAwsGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_AWS keys.Brand = api.CLOUD_PROVIDER_AWS diff --git a/pkg/compute/guestdrivers/azure.go b/pkg/compute/guestdrivers/azure.go index e81cd3cb48..2e7e7b45e5 100644 --- a/pkg/compute/guestdrivers/azure.go +++ b/pkg/compute/guestdrivers/azure.go @@ -50,7 +50,7 @@ func (self *SAzureGuestDriver) GetProvider() string { func (self *SAzureGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_AZURE keys.Brand = api.CLOUD_PROVIDER_AZURE diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index f84ffedbb8..a7ed749f88 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -58,7 +58,7 @@ func (self *SBaremetalGuestDriver) GetProvider() string { func (self *SBaremetalGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE keys.Provider = api.CLOUD_PROVIDER_ONECLOUD keys.Brand = api.ONECLOUD_BRAND_ONECLOUD diff --git a/pkg/compute/guestdrivers/container.go b/pkg/compute/guestdrivers/container.go index f0810c5680..37c8e0b831 100644 --- a/pkg/compute/guestdrivers/container.go +++ b/pkg/compute/guestdrivers/container.go @@ -60,7 +60,7 @@ func (self *SContainerDriver) GetProvider() string { // for backward compatibility, deprecated driver func (self *SContainerDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE keys.Provider = api.CLOUD_PROVIDER_ONECLOUD keys.Brand = api.ONECLOUD_BRAND_ONECLOUD diff --git a/pkg/compute/guestdrivers/ctyun.go b/pkg/compute/guestdrivers/ctyun.go index 2b90478113..e7f3a7adaa 100644 --- a/pkg/compute/guestdrivers/ctyun.go +++ b/pkg/compute/guestdrivers/ctyun.go @@ -41,7 +41,7 @@ func (self *SCtyunGuestDriver) GetProvider() string { func (self *SCtyunGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_CTYUN keys.Brand = api.CLOUD_PROVIDER_CTYUN diff --git a/pkg/compute/guestdrivers/esxi.go b/pkg/compute/guestdrivers/esxi.go index dd065313f6..cfde9924e8 100644 --- a/pkg/compute/guestdrivers/esxi.go +++ b/pkg/compute/guestdrivers/esxi.go @@ -66,7 +66,7 @@ func (self *SESXiGuestDriver) GetProvider() string { func (self *SESXiGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE keys.Provider = api.CLOUD_PROVIDER_VMWARE keys.Brand = api.CLOUD_PROVIDER_VMWARE diff --git a/pkg/compute/guestdrivers/google.go b/pkg/compute/guestdrivers/google.go index 67fc5da722..5b9be59eb9 100644 --- a/pkg/compute/guestdrivers/google.go +++ b/pkg/compute/guestdrivers/google.go @@ -49,7 +49,7 @@ func init() { func (self *SGoogleGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_GOOGLE keys.Brand = api.CLOUD_PROVIDER_GOOGLE diff --git a/pkg/compute/guestdrivers/huawei.go b/pkg/compute/guestdrivers/huawei.go index 450c8c1f38..0c040228dc 100644 --- a/pkg/compute/guestdrivers/huawei.go +++ b/pkg/compute/guestdrivers/huawei.go @@ -47,7 +47,7 @@ func (self *SHuaweiGuestDriver) GetProvider() string { func (self *SHuaweiGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_HUAWEI keys.Brand = api.CLOUD_PROVIDER_HUAWEI diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index fda39765e2..203ddd497c 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -56,7 +56,7 @@ func (self *SKVMGuestDriver) GetProvider() string { func (self *SKVMGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE keys.Provider = api.CLOUD_PROVIDER_ONECLOUD keys.Brand = api.ONECLOUD_BRAND_ONECLOUD diff --git a/pkg/compute/guestdrivers/openstack.go b/pkg/compute/guestdrivers/openstack.go index f80c86a1cf..473f8ab1e9 100644 --- a/pkg/compute/guestdrivers/openstack.go +++ b/pkg/compute/guestdrivers/openstack.go @@ -65,7 +65,7 @@ func (self *SOpenStackGuestDriver) GetProvider() string { func (self *SOpenStackGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PRIVATE_CLOUD keys.Provider = api.CLOUD_PROVIDER_OPENSTACK keys.Brand = brand diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index dce2a7a6c5..32b875912d 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -49,7 +49,7 @@ func (self *SQcloudGuestDriver) GetProvider() string { func (self *SQcloudGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_QCLOUD keys.Brand = api.CLOUD_PROVIDER_QCLOUD diff --git a/pkg/compute/guestdrivers/ucloud.go b/pkg/compute/guestdrivers/ucloud.go index 5f128338cd..4286b5d556 100644 --- a/pkg/compute/guestdrivers/ucloud.go +++ b/pkg/compute/guestdrivers/ucloud.go @@ -41,7 +41,7 @@ func (self *SUCloudGuestDriver) GetProvider() string { func (self *SUCloudGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD keys.Provider = api.CLOUD_PROVIDER_UCLOUD keys.Brand = api.CLOUD_PROVIDER_UCLOUD diff --git a/pkg/compute/guestdrivers/zstack.go b/pkg/compute/guestdrivers/zstack.go index 44979c3c47..3a366dbc24 100644 --- a/pkg/compute/guestdrivers/zstack.go +++ b/pkg/compute/guestdrivers/zstack.go @@ -58,7 +58,7 @@ func (self *SZStackGuestDriver) GetProvider() string { func (self *SZStackGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { keys := models.SComputeResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) keys.CloudEnv = api.CLOUD_ENV_PRIVATE_CLOUD keys.Provider = api.CLOUD_PROVIDER_ZSTACK keys.Brand = brand diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 3e14ab866b..6e6d8fec32 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -39,6 +39,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/proxy" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -450,6 +451,17 @@ func (manager *SCloudaccountManager) ValidateCreateData( return input, err } + quota := &SDomainQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, + Cloudaccount: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) + if err != nil { + return input, errors.Wrapf(err, "CheckSetPendingQuota") + } + return input, nil } @@ -468,6 +480,16 @@ func (self *SCloudaccount) PostCreate(ctx context.Context, userCred mcclient.Tok self.SEnabledStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data) self.savePassword(self.Secret) + quota := &SDomainQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, + Cloudaccount: 1, + } + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) + } // if !self.EnableAutoSync { self.StartSyncCloudProviderInfoTask(ctx, userCred, nil, "") // } @@ -2229,3 +2251,24 @@ func (account *SCloudaccount) getAccountShareInfo() apis.SAccountShareInfo { SharedDomains: account.GetSharedDomains(), } } + +func (manager *SCloudaccountManager) totalCount(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider) int { + q := manager.Query() + switch scope { + case rbacutils.ScopeProject, rbacutils.ScopeDomain: + q = q.Equals("domain_id", ownerId.GetProjectDomainId()) + } + cnt, _ := q.CountWithError() + return cnt +} + +func (account *SCloudaccount) GetUsages() []db.IUsage { + if account.Deleted { + return nil + } + usage := SDomainQuota{Cloudaccount: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: account.DomainId}) + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/compute/models/domainquota.go b/pkg/compute/models/domainquota.go new file mode 100644 index 0000000000..838a939b55 --- /dev/null +++ b/pkg/compute/models/domainquota.go @@ -0,0 +1,193 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + commonOptions "yunion.io/x/onecloud/pkg/cloudcommon/options" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +var ( + DomainQuota SDomainQuota + DomainQuotaManager *SQuotaManager + DomainUsageManager *SQuotaManager + DomainPendingUsageManager *SQuotaManager +) + +func init() { + DomainQuota = SDomainQuota{} + + DomainUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(DomainQuota, + rbacutils.ScopeDomain, + "domain_quota_usage_tbl", + "domain_quota_usage", + "domain_quota_usages", + ), + } + DomainPendingUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(DomainQuota, + rbacutils.ScopeDomain, + "domain_quota_pending_usage_tbl", + "domain_quota_pending_usage", + "domain_quota_pending_usages", + ), + } + DomainQuotaManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaBaseManager(DomainQuota, + rbacutils.ScopeDomain, + "domain_quota_tbl", + DomainPendingUsageManager, + DomainUsageManager, + "domain_quota", + "domain_quotas", + ), + } + quotas.Register(DomainQuotaManager) +} + +type SDomainQuota struct { + quotas.SQuotaBase + + quotas.SBaseDomainQuotaKeys + + Cloudaccount int `default:"-1" allow_zero:"true" json:"cloudaccount"` + + Globalvpc int `default:"-1" allow_zero:"true" json:"globalvpc"` +} + +func (self *SDomainQuota) GetKeys() quotas.IQuotaKeys { + return self.SBaseDomainQuotaKeys +} + +func (self *SDomainQuota) SetKeys(keys quotas.IQuotaKeys) { + self.SBaseDomainQuotaKeys = keys.(quotas.SBaseDomainQuotaKeys) +} + +func (self *SDomainQuota) FetchSystemQuota() { + base := 0 + switch options.Options.DefaultQuotaValue { + case commonOptions.DefaultQuotaUnlimit: + base = -1 + case commonOptions.DefaultQuotaZero: + base = 0 + case commonOptions.DefaultQuotaDefault: + base = 1 + } + defaultValue := func(def int) int { + if base < 0 { + return -1 + } else { + return def * base + } + } + self.Globalvpc = defaultValue(options.Options.DefaultGlobalvpcQuota) + self.Cloudaccount = defaultValue(options.Options.DefaultCloudaccountQuota) +} + +func (self *SDomainQuota) FetchUsage(ctx context.Context) error { + keys := self.SBaseDomainQuotaKeys + + scope := keys.Scope() + ownerId := keys.OwnerId() + + self.Globalvpc = GlobalVpcManager.totalCount(scope, ownerId) + self.Cloudaccount = CloudaccountManager.totalCount(scope, ownerId) + + return nil +} + +func (self *SDomainQuota) ResetNegative() { + if self.Globalvpc < 0 { + self.Globalvpc = 0 + } + if self.Cloudaccount < 0 { + self.Cloudaccount = 0 + } +} + +func (self *SDomainQuota) IsEmpty() bool { + if self.Globalvpc > 0 { + return false + } + if self.Cloudaccount > 0 { + return false + } + return true +} + +func (self *SDomainQuota) Add(quota quotas.IQuota) { + squota := quota.(*SDomainQuota) + self.Globalvpc = self.Globalvpc + quotas.NonNegative(squota.Globalvpc) + self.Cloudaccount = self.Cloudaccount + quotas.NonNegative(squota.Cloudaccount) +} + +func (self *SDomainQuota) Sub(quota quotas.IQuota) { + squota := quota.(*SDomainQuota) + self.Globalvpc = nonNegative(self.Globalvpc - squota.Globalvpc) + self.Cloudaccount = nonNegative(self.Cloudaccount - squota.Cloudaccount) +} + +func (self *SDomainQuota) Allocable(request quotas.IQuota) int { + squota := request.(*SDomainQuota) + cnt := -1 + if self.Globalvpc >= 0 && squota.Globalvpc > 0 && (cnt < 0 || cnt > self.Globalvpc/squota.Globalvpc) { + cnt = self.Globalvpc / squota.Globalvpc + } + if self.Cloudaccount >= 0 && squota.Cloudaccount > 0 && (cnt < 0 || cnt > self.Cloudaccount/squota.Cloudaccount) { + cnt = self.Cloudaccount / squota.Cloudaccount + } + return cnt +} + +func (self *SDomainQuota) Update(quota quotas.IQuota) { + squota := quota.(*SDomainQuota) + if squota.Globalvpc > 0 { + self.Globalvpc = squota.Globalvpc + } + if squota.Cloudaccount > 0 { + self.Cloudaccount = squota.Cloudaccount + } +} + +func (used *SDomainQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error { + err := quotas.NewOutOfQuotaError() + sreq := request.(*SDomainQuota) + squota := quota.(*SDomainQuota) + if quotas.Exceed(used.Globalvpc, sreq.Globalvpc, squota.Globalvpc) { + err.Add("globalvpc", squota.Globalvpc, used.Globalvpc, sreq.Globalvpc) + } + if quotas.Exceed(used.Cloudaccount, sreq.Cloudaccount, squota.Cloudaccount) { + err.Add("cloudaccount", squota.Cloudaccount, used.Cloudaccount, sreq.Cloudaccount) + } + if err.IsError() { + return err + } else { + return nil + } +} + +func (self *SDomainQuota) ToJSON(prefix string) jsonutils.JSONObject { + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewInt(int64(self.Globalvpc)), keyName(prefix, "globalvpc")) + ret.Add(jsonutils.NewInt(int64(self.Cloudaccount)), keyName(prefix, "cloudaccount")) + return ret +} diff --git a/pkg/compute/models/globalvpcs.go b/pkg/compute/models/globalvpcs.go index 2f55c0a03f..5e7066adc7 100644 --- a/pkg/compute/models/globalvpcs.go +++ b/pkg/compute/models/globalvpcs.go @@ -19,13 +19,16 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" "yunion.io/x/onecloud/pkg/util/stringutils2" ) @@ -111,9 +114,34 @@ func (manager *SGlobalVpcManager) ValidateCreateData( if err != nil { return input, errors.Wrap(err, "manager.SEnabledStatusInfrasResourceBaseManager.ValidateCreateData") } + quota := &SDomainQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, + Globalvpc: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) + if err != nil { + return input, errors.Wrap(err, "CheckSetPendingQuota") + } return input, nil } +func (self *SGlobalVpc) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SEnabledStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + + quota := &SDomainQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{ + DomainId: ownerId.GetProjectDomainId(), + }, + Globalvpc: 1, + } + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage %s", err) + } +} + func (self *SGlobalVpc) ValidateUpdateData( ctx context.Context, userCred mcclient.TokenCredential, @@ -169,3 +197,24 @@ func (manager *SGlobalVpcManager) QueryDistinctExtraField(q *sqlchemy.SQuery, fi func (self *SGlobalVpc) ValidateUpdateCondition(ctx context.Context) error { return self.SEnabledStatusInfrasResourceBase.ValidateUpdateCondition(ctx) } + +func (manager *SGlobalVpcManager) totalCount(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider) int { + q := manager.Query() + switch scope { + case rbacutils.ScopeProject, rbacutils.ScopeDomain: + q = q.Equals("domain_id", ownerId.GetProjectDomainId()) + } + cnt, _ := q.CountWithError() + return cnt +} + +func (globalVpc *SGlobalVpc) GetUsages() []db.IUsage { + if globalVpc.Deleted { + return nil + } + usage := SDomainQuota{Globalvpc: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: globalVpc.DomainId}) + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 58ca92f756..11beab0252 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -69,6 +69,7 @@ type SGuestManager struct { db.SVirtualResourceBaseManager db.SExternalizedResourceBaseManager SDeletePreventableResourceBaseManager + db.SDnsNameValidatorManager SHostResourceBaseManager SBillingResourceBaseManager diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 9c13b244e1..38b37954d9 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -42,6 +42,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -61,6 +62,7 @@ import ( type SHostManager struct { db.SEnabledStatusInfrasResourceBaseManager db.SExternalizedResourceBaseManager + db.SDnsNameValidatorManager SZoneResourceBaseManager SManagedResourceBaseManager } @@ -2195,6 +2197,7 @@ func (manager *SHostManager) FetchHostById(hostId string) *SHost { func (manager *SHostManager) totalCountQ( userCred mcclient.IIdentityProvider, + scope rbacutils.TRbacScope, rangeObjs []db.IStandaloneModel, hostStatus, status string, hostTypes []string, @@ -2212,6 +2215,9 @@ func (manager *SHostManager) totalCountQ( hosts.Field("cpu_cmtbound"), hosts.Field("storage_size"), ) + if scope != rbacutils.ScopeSystem && userCred != nil { + q = q.Filter(sqlchemy.Equals(hosts.Field("domain_id"), userCred.GetProjectDomainId())) + } if len(status) > 0 { q = q.Filter(sqlchemy.Equals(hosts.Field("status"), status)) } @@ -2311,6 +2317,7 @@ func (manager *SHostManager) calculateCount(q *sqlchemy.SQuery) HostsCountStat { func (manager *SHostManager) TotalCount( userCred mcclient.IIdentityProvider, + scope rbacutils.TRbacScope, rangeObjs []db.IStandaloneModel, hostStatus, status string, hostTypes []string, @@ -2321,6 +2328,7 @@ func (manager *SHostManager) TotalCount( return manager.calculateCount( manager.totalCountQ( userCred, + scope, rangeObjs, hostStatus, status, @@ -2716,6 +2724,14 @@ func (self *SHost) PostCreate( self.StartBaremetalCreateTask(ctx, userCred, kwargs, "") } } + + keys := GetHostQuotaKeysFromCreateInput(input) + quota := SInfrasQuota{Host: 1} + quota.SetKeys(keys) + err = quotas.CancelPendingUsage(ctx, userCred, "a, "a, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) + } } func (self *SHost) StartBaremetalCreateTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { @@ -3002,6 +3018,15 @@ func (manager *SHostManager) ValidateCreateData( if err != nil { return input, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ValidateCreateData") } + + keys := GetHostQuotaKeysFromCreateInput(input) + quota := SInfrasQuota{Host: 1} + quota.SetKeys(keys) + err = quotas.CheckSetPendingQuota(ctx, userCred, "a) + if err != nil { + return input, errors.Wrapf(err, "CheckSetPendingQuota") + } + return input, nil } @@ -4929,3 +4954,40 @@ func (host *SHost) PerformChangeOwner(ctx context.Context, userCred mcclient.Tok return host.SEnabledStatusInfrasResourceBase.PerformChangeOwner(ctx, userCred, query, input) } + +func GetHostQuotaKeysFromCreateInput(input api.HostCreateInput) quotas.SDomainRegionalCloudResourceKeys { + ownerId := &db.SOwnerId{DomainId: input.ProjectDomain} + var zone *SZone + if len(input.Zone) > 0 { + zone = ZoneManager.FetchZoneById(input.Zone) + } + zoneKeys := fetchZonalQuotaKeys(rbacutils.ScopeDomain, ownerId, zone, nil) + keys := quotas.SDomainRegionalCloudResourceKeys{} + keys.SBaseDomainQuotaKeys = zoneKeys.SBaseDomainQuotaKeys + keys.SRegionalBaseKeys = zoneKeys.SRegionalBaseKeys + return keys +} + +func (model *SHost) GetQuotaKeys() quotas.SDomainRegionalCloudResourceKeys { + zone := model.GetZone() + manager := model.GetCloudprovider() + ownerId := model.GetOwnerId() + zoneKeys := fetchZonalQuotaKeys(rbacutils.ScopeDomain, ownerId, zone, manager) + keys := quotas.SDomainRegionalCloudResourceKeys{} + keys.SBaseDomainQuotaKeys = zoneKeys.SBaseDomainQuotaKeys + keys.SRegionalBaseKeys = zoneKeys.SRegionalBaseKeys + keys.SCloudResourceBaseKeys = zoneKeys.SCloudResourceBaseKeys + return keys +} + +func (host *SHost) GetUsages() []db.IUsage { + if host.Deleted { + return nil + } + usage := SInfrasQuota{Host: 1} + keys := host.GetQuotaKeys() + usage.SetKeys(keys) + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/compute/models/infrasquota.go b/pkg/compute/models/infrasquota.go new file mode 100644 index 0000000000..f7546e1854 --- /dev/null +++ b/pkg/compute/models/infrasquota.go @@ -0,0 +1,227 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + commonOptions "yunion.io/x/onecloud/pkg/cloudcommon/options" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +var ( + InfrasQuota SInfrasQuota + InfrasQuotaManager *SQuotaManager + InfrasUsageManager *SQuotaManager + InfrasPendingUsageManager *SQuotaManager +) + +func init() { + InfrasQuota = SInfrasQuota{} + + InfrasUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(InfrasQuota, + rbacutils.ScopeDomain, + "infras_quota_usage_tbl", + "infras_quota_usage", + "infras_quota_usages", + ), + } + InfrasPendingUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(InfrasQuota, + rbacutils.ScopeDomain, + "infras_quota_pending_usage_tbl", + "infras_quota_pending_usage", + "infras_quota_pending_usages", + ), + } + InfrasQuotaManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaBaseManager(InfrasQuota, + rbacutils.ScopeDomain, + "infras_quota_tbl", + InfrasPendingUsageManager, + InfrasUsageManager, + "infras_quota", + "infras_quotas", + ), + } + quotas.Register(InfrasQuotaManager) +} + +type SInfrasQuota struct { + quotas.SQuotaBase + + quotas.SRegionalCloudResourceKeys + + Host int `default:"-1" allow_zero:"true" json:"host"` + Vpc int `default:"-1" allow_zero:"true" json:"vpc"` +} + +func (self *SInfrasQuota) GetKeys() quotas.IQuotaKeys { + return self.SRegionalCloudResourceKeys +} + +func (self *SInfrasQuota) SetKeys(keys quotas.IQuotaKeys) { + self.SRegionalCloudResourceKeys = keys.(quotas.SRegionalCloudResourceKeys) +} + +func (self *SInfrasQuota) FetchSystemQuota() { + base := 0 + switch options.Options.DefaultQuotaValue { + case commonOptions.DefaultQuotaUnlimit: + base = -1 + case commonOptions.DefaultQuotaZero: + base = 0 + case commonOptions.DefaultQuotaDefault: + base = 1 + } + defaultValue := func(def int) int { + if base < 0 { + return -1 + } else { + return def * base + } + } + self.Host = defaultValue(options.Options.DefaultHostQuota) + self.Vpc = defaultValue(options.Options.DefaultVpcQuota) +} + +func (self *SInfrasQuota) FetchUsage(ctx context.Context) error { + regionKeys := self.SRegionalCloudResourceKeys + + scope := regionKeys.Scope() + ownerId := regionKeys.OwnerId() + + var rangeObjs []db.IStandaloneModel + if len(regionKeys.RegionId) > 0 { + obj, err := CloudregionManager.FetchById(regionKeys.RegionId) + if err != nil { + return errors.Wrap(err, "CloudregionManager.FetchById") + } + rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel)) + } + if len(regionKeys.ManagerId) > 0 { + obj, err := CloudproviderManager.FetchById(regionKeys.ManagerId) + if err != nil { + return errors.Wrap(err, "CloudproviderManager.FetchById") + } + rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel)) + } else if len(regionKeys.AccountId) > 0 { + obj, err := CloudaccountManager.FetchById(regionKeys.AccountId) + if err != nil { + return errors.Wrap(err, "CloudaccountManager.FetchById") + } + rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel)) + } + + var providers []string + if len(regionKeys.Provider) > 0 { + providers = []string{regionKeys.Provider} + } + var brands []string + if len(regionKeys.Brand) > 0 { + brands = []string{regionKeys.Brand} + } + + hostStat := HostManager.TotalCount(ownerId, scope, rangeObjs, "", "", nil, nil, providers, brands, regionKeys.CloudEnv, tristate.None, tristate.None) + self.Host = int(hostStat.Count) + self.Vpc = VpcManager.totalCount(ownerId, scope, rangeObjs, providers, brands, regionKeys.CloudEnv) + + return nil +} + +func (self *SInfrasQuota) ResetNegative() { + if self.Host < 0 { + self.Host = 0 + } + if self.Vpc < 0 { + self.Vpc = 0 + } +} + +func (self *SInfrasQuota) IsEmpty() bool { + if self.Host > 0 { + return false + } + if self.Vpc > 0 { + return false + } + return true +} + +func (self *SInfrasQuota) Add(quota quotas.IQuota) { + squota := quota.(*SInfrasQuota) + self.Host = self.Host + quotas.NonNegative(squota.Host) + self.Vpc = self.Vpc + quotas.NonNegative(squota.Vpc) +} + +func (self *SInfrasQuota) Sub(quota quotas.IQuota) { + squota := quota.(*SInfrasQuota) + self.Host = nonNegative(self.Host - squota.Host) + self.Vpc = nonNegative(self.Vpc - squota.Vpc) +} + +func (self *SInfrasQuota) Allocable(request quotas.IQuota) int { + squota := request.(*SInfrasQuota) + cnt := -1 + if self.Host >= 0 && squota.Host > 0 && (cnt < 0 || cnt > self.Host/squota.Host) { + cnt = self.Host / squota.Host + } + if self.Vpc >= 0 && squota.Vpc > 0 && (cnt < 0 || cnt > self.Vpc/squota.Vpc) { + cnt = self.Vpc / squota.Vpc + } + return cnt +} + +func (self *SInfrasQuota) Update(quota quotas.IQuota) { + squota := quota.(*SInfrasQuota) + if squota.Host > 0 { + self.Host = squota.Host + } + if squota.Vpc > 0 { + self.Vpc = squota.Vpc + } +} + +func (used *SInfrasQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error { + err := quotas.NewOutOfQuotaError() + sreq := request.(*SInfrasQuota) + squota := quota.(*SInfrasQuota) + if quotas.Exceed(used.Host, sreq.Host, squota.Host) { + err.Add("host", squota.Host, used.Host, sreq.Host) + } + if quotas.Exceed(used.Vpc, sreq.Vpc, squota.Vpc) { + err.Add("vpc", squota.Vpc, used.Vpc, sreq.Vpc) + } + if err.IsError() { + return err + } else { + return nil + } +} + +func (self *SInfrasQuota) ToJSON(prefix string) jsonutils.JSONObject { + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewInt(int64(self.Host)), keyName(prefix, "host")) + ret.Add(jsonutils.NewInt(int64(self.Vpc)), keyName(prefix, "vpc")) + return ret +} diff --git a/pkg/compute/models/projectquota.go b/pkg/compute/models/projectquota.go index c150c1992e..169f1c7ce2 100644 --- a/pkg/compute/models/projectquota.go +++ b/pkg/compute/models/projectquota.go @@ -39,6 +39,7 @@ func init() { ProjectUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(ProjectQuota, + rbacutils.ScopeProject, "project_quota_usage_tbl", "project_quota_usage", "project_quota_usages", @@ -46,6 +47,7 @@ func init() { } ProjectPendingUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(ProjectQuota, + rbacutils.ScopeProject, "project_quota_pending_usage_tbl", "project_quota_pending_usage", "project_quota_pending_usages", @@ -53,6 +55,7 @@ func init() { } ProjectQuotaManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaBaseManager(ProjectQuota, + rbacutils.ScopeProject, "project_quota_tbl", ProjectPendingUsageManager, ProjectUsageManager, @@ -66,21 +69,21 @@ func init() { type SProjectQuota struct { quotas.SQuotaBase - quotas.SBaseQuotaKeys + quotas.SBaseProjectQuotaKeys Secgroup int `default:"-1" allow_zero:"true" json:"secgroup"` } func (self *SProjectQuota) GetKeys() quotas.IQuotaKeys { - return self.SBaseQuotaKeys + return self.SBaseProjectQuotaKeys } func (self *SProjectQuota) SetKeys(keys quotas.IQuotaKeys) { - self.SBaseQuotaKeys = keys.(quotas.SBaseQuotaKeys) + self.SBaseProjectQuotaKeys = keys.(quotas.SBaseProjectQuotaKeys) } func (self *SProjectQuota) FetchSystemQuota() { - keys := self.SBaseQuotaKeys + keys := self.SBaseProjectQuotaKeys base := 0 switch options.Options.DefaultQuotaValue { case commonOptions.DefaultQuotaUnlimit: @@ -109,7 +112,7 @@ func (self *SProjectQuota) FetchSystemQuota() { } func (self *SProjectQuota) FetchUsage(ctx context.Context) error { - regionKeys := self.SBaseQuotaKeys + regionKeys := self.SBaseProjectQuotaKeys scope := regionKeys.Scope() ownerId := regionKeys.OwnerId() diff --git a/pkg/compute/models/quotas.go b/pkg/compute/models/quotas.go index 5f7a0fc43a..81bbf1e64e 100644 --- a/pkg/compute/models/quotas.go +++ b/pkg/compute/models/quotas.go @@ -50,6 +50,7 @@ func init() { QuotaUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(Quota, + rbacutils.ScopeProject, "quota_usage_tbl", "quota_usage", "quota_usages", @@ -57,6 +58,7 @@ func init() { } QuotaPendingUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(Quota, + rbacutils.ScopeProject, "quota_pending_usage_tbl", "quota_pending_usage", "quota_pending_usages", @@ -64,6 +66,7 @@ func init() { } QuotaManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaBaseManager(Quota, + rbacutils.ScopeProject, "quota_tbl", QuotaPendingUsageManager, QuotaUsageManager, @@ -448,7 +451,7 @@ func (k1 SComputeResourceKeys) Compare(ik quotas.IQuotaKeys) int { func fetchCloudQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, manager *SCloudprovider) quotas.SCloudResourceKeys { keys := quotas.SCloudResourceKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(scope, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) if manager != nil { account := manager.GetCloudaccount() keys.Provider = account.Provider diff --git a/pkg/compute/models/quotas_doc.go b/pkg/compute/models/quotas_doc.go index 02a08e29dc..1f4351d20a 100644 --- a/pkg/compute/models/quotas_doc.go +++ b/pkg/compute/models/quotas_doc.go @@ -85,6 +85,8 @@ func SetQuotas(input SetQuotaInput) *SQuotaDetail { // 项目配额详情 type SProjectQuotaDetail struct { SProjectQuota + + quotas.SBaseProjectQuotaDetailKeys } // +onecloud:swagger-gen-route-method=GET @@ -271,3 +273,119 @@ type SetRegionQuotaInput struct { func SetRegionQuotas(input SetRegionQuotaInput) *SRegionQuotaDetail { return nil } + +// 域配额详情 +type SDomainQuotaDetail struct { + SDomainQuota + + quotas.SBaseDomainQuotaDetailKeys +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/domain_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=domain_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=domain_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取指定域的配额 +func GetDomainQuota(query quotas.SBaseQuotaQueryInput) *SDomainQuotaDetail { + return nil +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/domain_quotas/domains +// +onecloud:swagger-gen-route-tag=domain_quota +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=domain_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取所有域的域配额 +func ListDomainQuotas(query quotas.SBaseQuotaQueryInput) *SDomainQuotaDetail { + return nil +} + +// 设置域配额输入参数 +type SetDomainQuotaInput struct { + quotas.SBaseQuotaSetInput + + SDomainQuota +} + +// +onecloud:swagger-gen-route-method=POST +// +onecloud:swagger-gen-route-path=/domain_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=domain_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-body-index=0 +// +onecloud:swagger-gen-param-body-key=domain_quotas +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=domain_quotas +// +onecloud:swagger-gen-resp-body-list + +// 设置域的域配额 +func SetDomainQuotas(input SetDomainQuotaInput) *SDomainQuotaDetail { + return nil +} + +// 基础设施配额详情 +type SInfrasQuotaDetail struct { + SInfrasQuota + + quotas.SDomainRegionalCloudResourceDetailKeys +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/infras_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=infras_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=infras_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取指定域的基础设施配额 +func GetInfrasQuota(query quotas.SBaseQuotaQueryInput) *SInfrasQuotaDetail { + return nil +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/infras_quotas/domains +// +onecloud:swagger-gen-route-tag=infras_quota +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=infras_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取所有域的基础设施配额 +func ListInfrasQuotas(query quotas.SBaseQuotaQueryInput) *SInfrasQuotaDetail { + return nil +} + +// 设置基础设施配额输入参数 +type SetInfrasQuotaInput struct { + quotas.SBaseQuotaSetInput + + SInfrasQuota +} + +// +onecloud:swagger-gen-route-method=POST +// +onecloud:swagger-gen-route-path=/infras_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=infras_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-body-index=0 +// +onecloud:swagger-gen-param-body-key=infras_quotas +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=infras_quotas +// +onecloud:swagger-gen-resp-body-list + +// 设置指定域的基础设施配额 +func SetInfrasQuotas(input SetInfrasQuotaInput) *SInfrasQuotaDetail { + return nil +} diff --git a/pkg/compute/models/regionquota.go b/pkg/compute/models/regionquota.go index 9faafdb3e5..53af1cab66 100644 --- a/pkg/compute/models/regionquota.go +++ b/pkg/compute/models/regionquota.go @@ -41,6 +41,7 @@ func init() { RegionUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(RegionQuota, + rbacutils.ScopeProject, "region_quota_usage_tbl", "region_quota_usage", "region_quota_usages", @@ -48,6 +49,7 @@ func init() { } RegionPendingUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(RegionQuota, + rbacutils.ScopeProject, "region_quota_pending_usage_tbl", "region_quota_pending_usage", "region_quota_pending_usages", @@ -55,6 +57,7 @@ func init() { } RegionQuotaManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaBaseManager(RegionQuota, + rbacutils.ScopeProject, "region_quota_tbl", RegionPendingUsageManager, RegionUsageManager, diff --git a/pkg/compute/models/secgroups.go b/pkg/compute/models/secgroups.go index 38c9897017..7183b17df7 100644 --- a/pkg/compute/models/secgroups.go +++ b/pkg/compute/models/secgroups.go @@ -934,9 +934,7 @@ func (self *SSecurityGroup) RealDelete(ctx context.Context, userCred mcclient.To } func (sg *SSecurityGroup) GetQuotaKeys() quotas.IQuotaKeys { - return quotas.OwnerIdQuotaKeys(rbacutils.ScopeProject, - sg.GetOwnerId(), - ) + return quotas.OwnerIdProjectQuotaKeys(rbacutils.ScopeProject, sg.GetOwnerId()) } func (sg *SSecurityGroup) GetUsages() []db.IUsage { diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index eb0fe7a790..f210c5c577 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -31,6 +31,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -649,10 +650,34 @@ func (manager *SVpcManager) ValidateCreateData( return input, err } - return region.GetDriver().ValidateCreateVpcData(ctx, userCred, input) + input, err = region.GetDriver().ValidateCreateVpcData(ctx, userCred, input) + if err != nil { + return input, errors.Wrapf(err, "region.GetDriver().ValidateCreateVpcData") + } + + keys := GetVpcQuotaKeysFromCreateInput(input) + quota := &SInfrasQuota{Vpc: 1} + quota.SetKeys(keys) + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) + if err != nil { + return input, errors.Wrap(err, "quotas.CheckSetPendingQuota") + } + + return input, nil } func (self *SVpc) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + input := api.VpcCreateInput{} + err := data.Unmarshal(&input) + if err != nil { + log.Errorf("input unmarshal error %s", err) + } else { + pendingUsage := &SInfrasQuota{Vpc: 1} + keys := GetVpcQuotaKeysFromCreateInput(input) + pendingUsage.SetKeys(keys) + quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, true) + } + if len(self.ManagerId) == 0 { return } @@ -998,3 +1023,66 @@ func (self *SVpc) initWire(ctx context.Context, zone *SZone) (*SWire, error) { } return wire, nil } + +func GetVpcQuotaKeysFromCreateInput(input api.VpcCreateInput) quotas.SDomainRegionalCloudResourceKeys { + ownerId := &db.SOwnerId{DomainId: input.ProjectDomain} + var region *SCloudregion + if len(input.Cloudregion) > 0 { + region = CloudregionManager.FetchRegionById(input.Cloudregion) + } + var provider *SCloudprovider + if len(input.Cloudprovider) > 0 { + provider = CloudproviderManager.FetchCloudproviderById(input.Cloudprovider) + } + regionKeys := fetchRegionalQuotaKeys(rbacutils.ScopeDomain, ownerId, region, provider) + keys := quotas.SDomainRegionalCloudResourceKeys{} + keys.SBaseDomainQuotaKeys = regionKeys.SBaseDomainQuotaKeys + keys.SRegionalBaseKeys = regionKeys.SRegionalBaseKeys + keys.SCloudResourceBaseKeys = regionKeys.SCloudResourceBaseKeys + return keys +} + +func (vpc *SVpc) GetQuotaKeys() quotas.SDomainRegionalCloudResourceKeys { + region, _ := vpc.GetRegion() + manager := vpc.GetCloudprovider() + ownerId := vpc.GetOwnerId() + regionKeys := fetchRegionalQuotaKeys(rbacutils.ScopeDomain, ownerId, region, manager) + keys := quotas.SDomainRegionalCloudResourceKeys{} + keys.SBaseDomainQuotaKeys = regionKeys.SBaseDomainQuotaKeys + keys.SRegionalBaseKeys = regionKeys.SRegionalBaseKeys + keys.SCloudResourceBaseKeys = regionKeys.SCloudResourceBaseKeys + return keys +} + +func (vpc *SVpc) GetUsages() []db.IUsage { + if vpc.Deleted { + return nil + } + usage := SInfrasQuota{Vpc: 1} + keys := vpc.GetQuotaKeys() + usage.SetKeys(keys) + return []db.IUsage{ + &usage, + } +} + +func (manager *SVpcManager) totalCount( + ownerId mcclient.IIdentityProvider, + scope rbacutils.TRbacScope, + rangeObjs []db.IStandaloneModel, + providers []string, + brands []string, + cloudEnv string, +) int { + q := VpcManager.Query() + + if scope != rbacutils.ScopeSystem && ownerId != nil { + q = q.Equals("domain_id", ownerId.GetProjectDomainId()) + } + q = CloudProviderFilter(q, q.Field("manager_id"), providers, brands, cloudEnv) + q = rangeObjectsFilter(q, rangeObjs, q.Field("cloudregion_id"), nil, q.Field("manager_id")) + + cnt, _ := q.CountWithError() + + return cnt +} diff --git a/pkg/compute/models/zonequota.go b/pkg/compute/models/zonequota.go index a1e1bcb3d4..9b8f2f6c33 100644 --- a/pkg/compute/models/zonequota.go +++ b/pkg/compute/models/zonequota.go @@ -20,6 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/util/rbacutils" ) var ( @@ -34,6 +35,7 @@ func init() { ZoneUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(ZoneQuota, + rbacutils.ScopeDomain, "zone_quota_usage_tbl", "zone_quota_usage", "zone_quota_usages", @@ -41,6 +43,7 @@ func init() { } ZonePendingUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(ZoneQuota, + rbacutils.ScopeDomain, "zone_quota_pending_usage_tbl", "zone_quota_pending_usage", "zone_quota_pending_usages", @@ -48,6 +51,7 @@ func init() { } ZoneQuotaManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaBaseManager(ZoneQuota, + rbacutils.ScopeDomain, "zone_quota_tbl", ZonePendingUsageManager, ZoneUsageManager, diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index e3fc45e526..247d389632 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -76,6 +76,12 @@ type ComputeOptions struct { DefaultRdsQuota int `default:"10" help:"Common RDS quota per tenant, default 10"` DefaultCacheQuota int `default:"10" help:"Common ElasticCache quota per tenant, default 10"` + DefaultGlobalvpcQuota int `default:"10" help:"Common global Vpc quota per domain, default 10"` + DefaultCloudaccountQuota int `default:"20" help:"Common cloud account quota per domain, default 20"` + + DefaultHostQuota int `default:"500" help:"Common host quota per domain, default 500"` + DefaultVpcQuota int `default:"500" help:"Common vpc quota per domain, default 500"` + SystemAdminQuotaCheck bool `help:"Enable quota check for system admin, default False" default:"false"` CloudaccountHealthStatusCheck bool `help:"Enable cloudaccount health status check, default True" default:"true"` diff --git a/pkg/compute/policy/defaults.go b/pkg/compute/policy/defaults.go index e3d8bc6a9f..0dc21c1f7a 100644 --- a/pkg/compute/policy/defaults.go +++ b/pkg/compute/policy/defaults.go @@ -215,6 +215,18 @@ var ( Action: PolicyActionList, Result: rbacutils.Allow, }, + { + Service: api.SERVICE_TYPE, + Resource: "infras_quotas", + Action: PolicyActionGet, + Result: rbacutils.Allow, + }, + { + Service: api.SERVICE_TYPE, + Resource: "infras_quotas", + Action: PolicyActionList, + Result: rbacutils.Allow, + }, { Service: api.SERVICE_TYPE, Resource: "storages", diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 94fd34cc6f..53671cad67 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -40,6 +40,8 @@ func InitHandlers(app *appsrv.Application) { quotas.AddQuotaHandler(&models.RegionQuotaManager.SQuotaBaseManager, "", app) quotas.AddQuotaHandler(&models.ZoneQuotaManager.SQuotaBaseManager, "", app) quotas.AddQuotaHandler(&models.ProjectQuotaManager.SQuotaBaseManager, "", app) + quotas.AddQuotaHandler(&models.DomainQuotaManager.SQuotaBaseManager, "", app) + quotas.AddQuotaHandler(&models.InfrasQuotaManager.SQuotaBaseManager, "", app) usages.AddUsageHandler("", app) capabilities.AddCapabilityHandler("", app) @@ -71,6 +73,12 @@ func InitHandlers(app *appsrv.Application) { models.ProjectQuotaManager, models.ProjectUsageManager, models.ProjectPendingUsageManager, + models.DomainQuotaManager, + models.DomainUsageManager, + models.DomainPendingUsageManager, + models.InfrasQuotaManager, + models.InfrasUsageManager, + models.InfrasPendingUsageManager, models.GroupguestManager, diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index d27ed18b07..b71751d902 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -111,6 +111,8 @@ func StartService() { cron.AddJobAtIntervalsWithStartRun("CalculateRegionQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.RegionQuotaManager.CalculateQuotaUsages, true) cron.AddJobAtIntervalsWithStartRun("CalculateZoneQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.ZoneQuotaManager.CalculateQuotaUsages, true) cron.AddJobAtIntervalsWithStartRun("CalculateProjectQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.ProjectQuotaManager.CalculateQuotaUsages, true) + cron.AddJobAtIntervalsWithStartRun("CalculateDomainQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.DomainQuotaManager.CalculateQuotaUsages, true) + cron.AddJobAtIntervalsWithStartRun("CalculateInfrasQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.InfrasQuotaManager.CalculateQuotaUsages, true) cron.AddJobAtIntervalsWithStartRun("AutoSyncCloudaccountTask", time.Duration(opts.CloudAutoSyncIntervalSeconds)*time.Second, models.CloudaccountManager.AutoSyncCloudaccountTask, true) diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index d235190fe7..257941cde0 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -208,7 +208,7 @@ func getAdminGeneralUsage(userCred mcclient.IIdentityProvider, rangeObjs []db.IS var pmemTotal float64 var pcpuTotal float64 - hostEnabledUsage := HostEnabledUsage("", userCred, rangeObjs, hostTypes, []string{api.HostResourceTypeShared}, providers, brands, cloudEnv) + hostEnabledUsage := HostEnabledUsage("", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, []string{api.HostResourceTypeShared}, providers, brands, cloudEnv) pmemTotal = float64(hostEnabledUsage.Get("enabled_hosts.memory").(int64)) pcpuTotal = float64(hostEnabledUsage.Get("enabled_hosts.cpu").(int64)) if len(rangeObjs) > 0 && rangeObjs[0].Keyword() == "host" { @@ -244,15 +244,15 @@ func getAdminGeneralUsage(userCred mcclient.IIdentityProvider, rangeObjs []db.IS storageUsage := StorageUsage("", rangeObjs, hostTypes, []string{api.HostResourceTypeShared}, providers, brands, cloudEnv) count.Include( - HostAllUsage("", userCred, rangeObjs, hostTypes, []string{api.HostResourceTypeShared}, providers, brands, cloudEnv), - HostAllUsage("prepaid_pool", userCred, rangeObjs, hostTypes, []string{api.HostResourceTypePrepaidRecycle}, providers, brands, cloudEnv), - HostAllUsage("any_pool", userCred, rangeObjs, hostTypes, nil, providers, brands, cloudEnv), + HostAllUsage("", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, []string{api.HostResourceTypeShared}, providers, brands, cloudEnv), + HostAllUsage("prepaid_pool", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, []string{api.HostResourceTypePrepaidRecycle}, providers, brands, cloudEnv), + HostAllUsage("any_pool", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, nil, providers, brands, cloudEnv), hostEnabledUsage, - HostEnabledUsage("prepaid_pool", userCred, rangeObjs, hostTypes, []string{api.HostResourceTypePrepaidRecycle}, providers, brands, cloudEnv), - HostEnabledUsage("any_pool", userCred, rangeObjs, hostTypes, nil, providers, brands, cloudEnv), + HostEnabledUsage("prepaid_pool", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, []string{api.HostResourceTypePrepaidRecycle}, providers, brands, cloudEnv), + HostEnabledUsage("any_pool", userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, nil, providers, brands, cloudEnv), - BaremetalUsage(userCred, rangeObjs, hostTypes, providers, brands, cloudEnv), + BaremetalUsage(userCred, rbacutils.ScopeSystem, rangeObjs, hostTypes, providers, brands, cloudEnv), storageUsage, StorageUsage("prepaid_pool", rangeObjs, hostTypes, []string{api.HostResourceTypePrepaidRecycle}, providers, brands, cloudEnv), @@ -512,42 +512,42 @@ func NetworkUsage(prefix string, scope rbacutils.TRbacScope, userCred mcclient.I return count } -func HostAllUsage(pref string, userCred mcclient.IIdentityProvider, rangeObjs []db.IStandaloneModel, +func HostAllUsage(pref string, userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope, rangeObjs []db.IStandaloneModel, hostTypes []string, resourceTypes []string, providers []string, brands []string, cloudEnv string) Usage { prefix := "hosts" if len(pref) > 0 { prefix = fmt.Sprintf("%s.%s", prefix, pref) } - return hostUsage(userCred, prefix, rangeObjs, hostTypes, resourceTypes, providers, brands, cloudEnv, tristate.None, tristate.None) + return hostUsage(userCred, scope, prefix, rangeObjs, hostTypes, resourceTypes, providers, brands, cloudEnv, tristate.None, tristate.None) } -func HostEnabledUsage(pref string, userCred mcclient.IIdentityProvider, rangeObjs []db.IStandaloneModel, +func HostEnabledUsage(pref string, userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope, rangeObjs []db.IStandaloneModel, hostTypes []string, resourceTypes []string, providers []string, brands []string, cloudEnv string) Usage { prefix := "enabled_hosts" if len(pref) > 0 { prefix = fmt.Sprintf("%s.%s", prefix, pref) } - return hostUsage(userCred, prefix, rangeObjs, hostTypes, resourceTypes, providers, brands, cloudEnv, tristate.True, tristate.None) + return hostUsage(userCred, scope, prefix, rangeObjs, hostTypes, resourceTypes, providers, brands, cloudEnv, tristate.True, tristate.None) } -func BaremetalUsage(userCred mcclient.IIdentityProvider, rangeObjs []db.IStandaloneModel, +func BaremetalUsage(userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope, rangeObjs []db.IStandaloneModel, hostTypes []string, providers []string, brands []string, cloudEnv string) Usage { prefix := "baremetals" - count := hostUsage(userCred, prefix, rangeObjs, hostTypes, nil, providers, brands, cloudEnv, tristate.None, tristate.True) + count := hostUsage(userCred, scope, prefix, rangeObjs, hostTypes, nil, providers, brands, cloudEnv, tristate.None, tristate.True) delete(count, fmt.Sprintf("%s.memory.virtual", prefix)) delete(count, fmt.Sprintf("%s.cpu.virtual", prefix)) return count } func hostUsage( - userCred mcclient.IIdentityProvider, prefix string, + userCred mcclient.IIdentityProvider, scope rbacutils.TRbacScope, prefix string, rangeObjs []db.IStandaloneModel, hostTypes []string, resourceTypes []string, providers []string, brands []string, cloudEnv string, enabled, isBaremetal tristate.TriState, ) Usage { count := make(map[string]interface{}) - result := models.HostManager.TotalCount(userCred, rangeObjs, "", "", hostTypes, resourceTypes, providers, brands, cloudEnv, enabled, isBaremetal) + result := models.HostManager.TotalCount(userCred, scope, rangeObjs, "", "", hostTypes, resourceTypes, providers, brands, cloudEnv, enabled, isBaremetal) count[prefix] = result.Count count[fmt.Sprintf("%s.memory", prefix)] = result.Memory count[fmt.Sprintf("%s.cpu", prefix)] = result.CPU diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index f4712968a0..081bde6b2d 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -1402,7 +1402,7 @@ func (self *SImage) CanUpdate(data jsonutils.JSONObject) bool { func (img *SImage) GetQuotaKeys() quotas.IQuotaKeys { keys := SImageQuotaKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(rbacutils.ScopeProject, img.GetOwnerId()) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(rbacutils.ScopeProject, img.GetOwnerId()) if img.GetImageType() == api.ImageTypeISO { keys.Type = string(api.ImageTypeISO) } else { @@ -1413,7 +1413,7 @@ func (img *SImage) GetQuotaKeys() quotas.IQuotaKeys { func imageCreateInput2QuotaKeys(data jsonutils.JSONObject, ownerId mcclient.IIdentityProvider) quotas.IQuotaKeys { keys := SImageQuotaKeys{} - keys.SBaseQuotaKeys = quotas.OwnerIdQuotaKeys(rbacutils.ScopeProject, ownerId) + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(rbacutils.ScopeProject, ownerId) format, _ := data.GetString("disk_format") if format == string(api.ImageTypeISO) { keys.Type = string(api.ImageTypeISO) diff --git a/pkg/image/models/quotas.go b/pkg/image/models/quotas.go index 871eab689d..a47f298869 100644 --- a/pkg/image/models/quotas.go +++ b/pkg/image/models/quotas.go @@ -47,6 +47,7 @@ func init() { QuotaPendingUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(SQuota{}, + rbacutils.ScopeProject, "quota_pending_usage_tbl", "quota_pending_usage", "quota_pending_usages", @@ -54,14 +55,21 @@ func init() { } QuotaUsageManager = &SQuotaManager{ SQuotaBaseManager: quotas.NewQuotaUsageManager(SQuota{}, + rbacutils.ScopeProject, "quota_usage_tbl", "quota_usage", "quota_usages", ), } QuotaManager = &SQuotaManager{ - SQuotaBaseManager: quotas.NewQuotaBaseManager(SQuota{}, "quota_tbl", QuotaPendingUsageManager, QuotaUsageManager, - "image_quota", "image_quotas"), + SQuotaBaseManager: quotas.NewQuotaBaseManager(SQuota{}, + rbacutils.ScopeProject, + "quota_tbl", + QuotaPendingUsageManager, + QuotaUsageManager, + "image_quota", + "image_quotas", + ), } quotas.Register(QuotaManager) @@ -187,9 +195,7 @@ func (used *SQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error { func (self *SQuota) ToJSON(prefix string) jsonutils.JSONObject { ret := jsonutils.NewDict() - // if self.Image > 0 { ret.Add(jsonutils.NewInt(int64(self.Image)), quotas.KeyName(prefix, "image")) - // } return ret } @@ -214,22 +220,22 @@ func (manager *SQuotaManager) FetchIdNames(ctx context.Context, idMap map[string } type SImageQuotaKeys struct { - quotas.SBaseQuotaKeys + quotas.SBaseProjectQuotaKeys Type string `width:"16" charset:"ascii" nullable:"false" primary:"true" list:"user"` } func (k SImageQuotaKeys) Fields() []string { - return append(k.SBaseQuotaKeys.Fields(), "type") + return append(k.SBaseProjectQuotaKeys.Fields(), "type") } func (k SImageQuotaKeys) Values() []string { - return append(k.SBaseQuotaKeys.Values(), k.Type) + return append(k.SBaseProjectQuotaKeys.Values(), k.Type) } func (k1 SImageQuotaKeys) Compare(ik quotas.IQuotaKeys) int { k2 := ik.(SImageQuotaKeys) - r := k1.SBaseQuotaKeys.Compare(k2.SBaseQuotaKeys) + r := k1.SBaseProjectQuotaKeys.Compare(k2.SBaseProjectQuotaKeys) if r != 0 { return r } @@ -248,7 +254,7 @@ func (k1 SImageQuotaKeys) Compare(ik quotas.IQuotaKeys) int { type SImageQuotaDetail struct { SQuota - quotas.SBaseQuotaDetailKeys + quotas.SBaseProjectQuotaDetailKeys } // +onecloud:swagger-gen-route-method=GET diff --git a/pkg/keystone/models/domains.go b/pkg/keystone/models/domains.go index 09016e48fd..44b1a32df3 100644 --- a/pkg/keystone/models/domains.go +++ b/pkg/keystone/models/domains.go @@ -36,6 +36,7 @@ import ( type SDomainManager struct { db.SStandaloneResourceBaseManager + db.SDnsNameValidatorManager } var ( @@ -60,12 +61,14 @@ type SDomain struct { Extra *jsonutils.JSONDict `nullable:"true"` Enabled tristate.TriState `nullable:"false" default:"true" list:"admin" update:"admin" create:"admin_optional"` - IsDomain tristate.TriState `default:"false" nullable:"false" create:"admin_optional"` + IsDomain tristate.TriState `default:"true" nullable:"false"` // IdpId string `token:"parent_id" width:"64" charset:"ascii" index:"true" list:"admin"` DomainId string `width:"64" charset:"ascii" default:"default" nullable:"false" index:"true"` ParentId string `width:"64" charset:"ascii"` + + Displayname string `with:"128" charset:"utf8" nullable:"true" list:"domain" update:"domain" create:"domain_optional"` } func (manager *SDomainManager) InitializeData() error { @@ -487,3 +490,24 @@ func (domain *SDomain) UnlinkIdp(idpId string) error { func (domain *SDomain) getExternalResources() (map[string]int, time.Time, error) { return ProjectResourceManager.getProjectResource(domain.Id) } + +func (manager *SDomainManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.DomainCreateInput, +) (api.DomainCreateInput, error) { + var err error + + input.StandaloneResourceCreateInput, err = manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SStandaloneResourceBaseManager.ValidateCreateData") + } + + return input, nil +} + +func (manager *SDomainManager) ValidateName(name string) error { + return manager.SDnsNameValidatorManager.ValidateName(name) +} diff --git a/pkg/keystone/models/groups.go b/pkg/keystone/models/groups.go index 03cd85a589..e304a57ceb 100644 --- a/pkg/keystone/models/groups.go +++ b/pkg/keystone/models/groups.go @@ -19,12 +19,14 @@ import ( "database/sql" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/rbacutils" @@ -384,3 +386,59 @@ func (manager *SGroupManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.I } return manager.SIdentityBaseResourceManager.FilterByOwner(q, owner, scope) } + +func (group *SGroup) GetUsages() []db.IUsage { + if group.Deleted { + return nil + } + usage := SIdentityQuota{Group: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: group.DomainId}) + return []db.IUsage{ + &usage, + } +} + +func (manager *SGroupManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.GroupCreateInput, +) (api.GroupCreateInput, error) { + var err error + + input.IdentityBaseResourceCreateInput, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.IdentityBaseResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SIdentityBaseResourceManager.ValidateCreateData") + } + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Group: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) + if err != nil { + return input, errors.Wrap(err, "CheckSetPendingQuota") + } + + return input, nil +} + +func (group *SGroup) PostCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) { + group.SIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data) + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Group: 1, + } + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) + } +} diff --git a/pkg/keystone/models/identitybase.go b/pkg/keystone/models/identitybase.go index 47dad3f6ce..ac7c2c6928 100644 --- a/pkg/keystone/models/identitybase.go +++ b/pkg/keystone/models/identitybase.go @@ -31,6 +31,7 @@ import ( "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" "yunion.io/x/onecloud/pkg/util/stringutils2" ) @@ -429,3 +430,12 @@ func (model *SIdentityBaseResource) PostDelete(ctx context.Context, userCred mcc model.SStandaloneResourceBase.PostDelete(ctx, userCred) logclient.AddActionLogWithContext(ctx, model, logclient.ACT_DELETE, nil, userCred, true) } + +func (manager *SIdentityBaseResourceManager) totalCount(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider) int { + q := manager.Query() + if scope != rbacutils.ScopeSystem { + q = q.Equals("domain_id", ownerId.GetProjectDomainId()) + } + cnt, _ := q.CountWithError() + return cnt +} diff --git a/pkg/keystone/models/identityquota.go b/pkg/keystone/models/identityquota.go new file mode 100644 index 0000000000..cfa5c589e0 --- /dev/null +++ b/pkg/keystone/models/identityquota.go @@ -0,0 +1,342 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + commonOptions "yunion.io/x/onecloud/pkg/cloudcommon/options" + "yunion.io/x/onecloud/pkg/keystone/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SQuotaManager struct { + quotas.SQuotaBaseManager +} + +var ( + IdentityQuota SIdentityQuota + IdentityQuotaManager *SQuotaManager + IdentityUsageManager *SQuotaManager + IdentityPendingUsageManager *SQuotaManager +) + +func init() { + IdentityQuota = SIdentityQuota{} + + IdentityUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(IdentityQuota, + rbacutils.ScopeDomain, + "identity_quota_usage_tbl", + "identity_quota_usage", + "identity_quota_usages", + ), + } + IdentityPendingUsageManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaUsageManager(IdentityQuota, + rbacutils.ScopeDomain, + "identity_quota_pending_usage_tbl", + "identity_quota_pending_usage", + "identity_quota_pending_usages", + ), + } + IdentityQuotaManager = &SQuotaManager{ + SQuotaBaseManager: quotas.NewQuotaBaseManager(IdentityQuota, + rbacutils.ScopeDomain, + "identity_quota_tbl", + IdentityPendingUsageManager, + IdentityUsageManager, + "identity_quota", + "identity_quotas", + ), + } + quotas.Register(IdentityQuotaManager) +} + +type SIdentityQuota struct { + quotas.SQuotaBase + + quotas.SBaseDomainQuotaKeys + + User int `default:"-1" allow_zero:"true" json:"user"` + Group int `default:"-1" allow_zero:"true" json:"group"` + Project int `default:"-1" allow_zero:"true" json:"project"` + Role int `default:"-1" allow_zero:"true" json:"role"` + Policy int `default:"-1" allow_zero:"true" json:"policy"` +} + +func (self *SIdentityQuota) GetKeys() quotas.IQuotaKeys { + return self.SBaseDomainQuotaKeys +} + +func (self *SIdentityQuota) SetKeys(keys quotas.IQuotaKeys) { + self.SBaseDomainQuotaKeys = keys.(quotas.SBaseDomainQuotaKeys) +} + +func (self *SIdentityQuota) FetchSystemQuota() { + base := 0 + switch options.Options.DefaultQuotaValue { + case commonOptions.DefaultQuotaUnlimit: + base = -1 + case commonOptions.DefaultQuotaZero: + base = 0 + case commonOptions.DefaultQuotaDefault: + base = 1 + } + defaultValue := func(def int) int { + if base < 0 { + return -1 + } else { + return def * base + } + } + self.User = defaultValue(options.Options.DefaultUserQuota) + self.Group = defaultValue(options.Options.DefaultGroupQuota) + self.Project = defaultValue(options.Options.DefaultProjectQuota) + self.Role = defaultValue(options.Options.DefaultRoleQuota) + self.Policy = defaultValue(options.Options.DefaultPolicyQuota) +} + +func (self *SIdentityQuota) FetchUsage(ctx context.Context) error { + keys := self.SBaseDomainQuotaKeys + + scope := keys.Scope() + ownerId := keys.OwnerId() + + self.User = UserManager.totalCount(scope, ownerId) + self.Group = GroupManager.totalCount(scope, ownerId) + self.Project = ProjectManager.totalCount(scope, ownerId) + self.Role = RoleManager.totalCount(scope, ownerId) + self.Policy = PolicyManager.totalCount(scope, ownerId) + + return nil +} + +func (self *SIdentityQuota) ResetNegative() { + if self.User < 0 { + self.User = 0 + } + if self.Group < 0 { + self.Group = 0 + } + if self.Project < 0 { + self.Project = 0 + } + if self.Role < 0 { + self.Role = 0 + } + if self.Policy < 0 { + self.Policy = 0 + } +} + +func (self *SIdentityQuota) IsEmpty() bool { + if self.User > 0 { + return false + } + if self.Group > 0 { + return false + } + if self.Project > 0 { + return false + } + if self.Role > 0 { + return false + } + if self.Policy > 0 { + return false + } + return true +} + +func (self *SIdentityQuota) Add(quota quotas.IQuota) { + squota := quota.(*SIdentityQuota) + self.User = self.User + quotas.NonNegative(squota.User) + self.Group = self.Group + quotas.NonNegative(squota.Group) + self.Project = self.Project + quotas.NonNegative(squota.Project) + self.Role = self.Role + quotas.NonNegative(squota.Role) + self.Policy = self.Policy + quotas.NonNegative(squota.Policy) +} + +func (self *SIdentityQuota) Sub(quota quotas.IQuota) { + squota := quota.(*SIdentityQuota) + self.User = quotas.NonNegative(self.User - squota.User) + self.Group = quotas.NonNegative(self.Group - squota.Group) + self.Project = quotas.NonNegative(self.Project - squota.Project) + self.Role = quotas.NonNegative(self.Role - squota.Role) + self.Policy = quotas.NonNegative(self.Policy - squota.Policy) +} + +func (self *SIdentityQuota) Allocable(request quotas.IQuota) int { + squota := request.(*SIdentityQuota) + cnt := -1 + if self.User >= 0 && squota.User > 0 && (cnt < 0 || cnt > self.User/squota.User) { + cnt = self.User / squota.User + } + if self.Group >= 0 && squota.Group > 0 && (cnt < 0 || cnt > self.Group/squota.Group) { + cnt = self.Group / squota.Group + } + if self.Project >= 0 && squota.Project > 0 && (cnt < 0 || cnt > self.Project/squota.Project) { + cnt = self.Project / squota.Project + } + if self.Role >= 0 && squota.Role > 0 && (cnt < 0 || cnt > self.Role/squota.Role) { + cnt = self.Role / squota.Role + } + if self.Policy >= 0 && squota.Policy > 0 && (cnt < 0 || cnt > self.Policy/squota.Policy) { + cnt = self.Policy / squota.Policy + } + return cnt +} + +func (self *SIdentityQuota) Update(quota quotas.IQuota) { + squota := quota.(*SIdentityQuota) + if squota.User > 0 { + self.User = squota.User + } + if squota.Group > 0 { + self.Group = squota.Group + } + if squota.Project > 0 { + self.Project = squota.Project + } + if squota.Role > 0 { + self.Role = squota.Role + } + if squota.Policy > 0 { + self.Policy = squota.Policy + } + +} + +func (used *SIdentityQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error { + err := quotas.NewOutOfQuotaError() + sreq := request.(*SIdentityQuota) + squota := quota.(*SIdentityQuota) + if quotas.Exceed(used.User, sreq.User, squota.User) { + err.Add("user", squota.User, used.User, sreq.User) + } + if quotas.Exceed(used.Group, sreq.Group, squota.Group) { + err.Add("group", squota.Group, used.Group, sreq.Group) + } + if quotas.Exceed(used.Project, sreq.Project, squota.Project) { + err.Add("project", squota.Project, used.Project, sreq.Project) + } + if quotas.Exceed(used.Role, sreq.Role, squota.Role) { + err.Add("role", squota.Role, used.Role, sreq.Role) + } + if quotas.Exceed(used.Policy, sreq.Policy, squota.Policy) { + err.Add("policy", squota.Policy, used.Policy, sreq.Policy) + } + if err.IsError() { + return err + } else { + return nil + } +} + +func (self *SIdentityQuota) ToJSON(prefix string) jsonutils.JSONObject { + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewInt(int64(self.User)), quotas.KeyName(prefix, "user")) + ret.Add(jsonutils.NewInt(int64(self.Group)), quotas.KeyName(prefix, "group")) + ret.Add(jsonutils.NewInt(int64(self.Project)), quotas.KeyName(prefix, "project")) + ret.Add(jsonutils.NewInt(int64(self.Role)), quotas.KeyName(prefix, "role")) + ret.Add(jsonutils.NewInt(int64(self.Policy)), quotas.KeyName(prefix, "policy")) + return ret +} + +func (manager *SQuotaManager) FetchIdNames(ctx context.Context, idMap map[string]map[string]string) (map[string]map[string]string, error) { + for field := range idMap { + switch field { + case "domain_id": + fieldIdMap, err := db.FetchIdNameMap(DomainManager, idMap[field]) + if err != nil { + return nil, errors.Wrap(err, "db.FetchIdNameMap") + } + idMap[field] = fieldIdMap + } + } + return idMap, nil +} + +func (manager *SQuotaManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { + return fetchDomainInfo(data) +} + +/////////////////////////////////////////////////// +// for swagger API documentation + +// 域的认证配额详情 +type SIdentityQuotaDetail struct { + SIdentityQuota + + quotas.SBaseDomainQuotaDetailKeys +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/identity_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=identity_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=identity_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取指定域的认证配额 +func GetIdentityQuota(query quotas.SBaseQuotaQueryInput) *SIdentityQuotaDetail { + return nil +} + +// +onecloud:swagger-gen-route-method=GET +// +onecloud:swagger-gen-route-path=/identity_quotas/domains +// +onecloud:swagger-gen-route-tag=identity_quota +// +onecloud:swagger-gen-param-query-index=0 +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=identity_quotas +// +onecloud:swagger-gen-resp-body-list + +// 获取所有域的域配额 +func ListIdentityQuotas(query quotas.SBaseQuotaQueryInput) *SIdentityQuotaDetail { + return nil +} + +// 设置域的认证配额输入参数 +type SetIdentityQuotaInput struct { + quotas.SBaseQuotaSetInput + + SIdentityQuota +} + +// +onecloud:swagger-gen-route-method=POST +// +onecloud:swagger-gen-route-path=/identity_quotas/domains/{domainId} +// +onecloud:swagger-gen-route-tag=identity_quota +// +onecloud:swagger-gen-param-path=domainId +// +onecloud:swagger-gen-param-path=指定域的ID +// +onecloud:swagger-gen-param-body-index=0 +// +onecloud:swagger-gen-param-body-key=identity_quotas +// +onecloud:swagger-gen-resp-index=0 +// +onecloud:swagger-gen-resp-body-key=identity_quotas +// +onecloud:swagger-gen-resp-body-list + +// 设置域的认证配额 +func SetIdentityQuotas(input SetIdentityQuotaInput) *SIdentityQuotaDetail { + return nil +} diff --git a/pkg/keystone/models/policies.go b/pkg/keystone/models/policies.go index c735f52d20..b2d9a4b948 100644 --- a/pkg/keystone/models/policies.go +++ b/pkg/keystone/models/policies.go @@ -19,12 +19,14 @@ import ( "database/sql" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" policyman "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -66,8 +68,8 @@ type SPolicy struct { SEnabledIdentityBaseResource db.SSharableBaseResource - Type string `width:"255" charset:"utf8" nullable:"false" list:"user" update:"domain"` - Blob jsonutils.JSONObject `nullable:"false" list:"user" update:"domain"` + Type string `width:"255" charset:"utf8" nullable:"false" list:"user" create:"domain_required" update:"domain"` + Blob jsonutils.JSONObject `nullable:"false" list:"user" create:"domain_required" update:"domain"` } func (manager *SPolicyManager) InitializeData() error { @@ -100,36 +102,42 @@ func (manager *SPolicyManager) FetchEnabledPolicies() ([]SPolicy, error) { return policies, nil } -func (manager *SPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - typeStr, _ := data.GetString("type") - if len(typeStr) == 0 { - return nil, httperrors.NewInputParameterError("missing input field type") - } - data.Set("name", jsonutils.NewString(typeStr)) - blobJson, err := data.Get("blob") - if err != nil { - return nil, httperrors.NewInputParameterError("invalid policy data") +func (manager *SPolicyManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.PolicyCreateInput, +) (api.PolicyCreateInput, error) { + var err error + if len(input.Type) == 0 { + return input, httperrors.NewInputParameterError("missing input field type") } + input.Name = input.Type policy := rbacutils.SRbacPolicy{} - err = policy.Decode(blobJson) + err = policy.Decode(input.Blob) if err != nil { - return nil, httperrors.NewInputParameterError("fail to decode policy data") + return input, httperrors.NewInputParameterError("fail to decode policy data") } err = db.ValidateCreateDomainId(ownerId.GetProjectDomainId()) if err != nil { - return nil, err + return input, errors.Wrap(err, "ValidateCreateDomainId") } - input := api.EnabledIdentityBaseResourceCreateInput{} - err = data.Unmarshal(&input) + input.EnabledIdentityBaseResourceCreateInput, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledIdentityBaseResourceCreateInput) if err != nil { - return nil, httperrors.NewInternalServerError("unmarshal IdentityBaseResourceCreateInput fail %s", err) + return input, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ValidateCreateData") } - input, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input) + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Policy: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) if err != nil { - return nil, err + return input, errors.Wrap(err, "CheckSetPendingQuota") } - data.Update(jsonutils.Marshal(input)) - return data, nil + + return input, nil } func (policy *SPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PolicyUpdateInput) (api.PolicyUpdateInput, error) { @@ -156,6 +164,16 @@ func (policy *SPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient func (policy *SPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { policy.SEnabledIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data) + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Policy: 1, + } + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) + } + policyman.PolicyManager.SyncOnce() } @@ -297,3 +315,14 @@ func (manager *SPolicyManager) FetchCustomizeColumns( } return rows } + +func (policy *SPolicy) GetUsages() []db.IUsage { + if policy.Deleted { + return nil + } + usage := SIdentityQuota{Policy: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: policy.DomainId}) + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index 53fcd7feca..6cb5b9e0bd 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -29,6 +29,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" @@ -38,6 +39,7 @@ import ( type SProjectManager struct { SIdentityBaseResourceManager + db.SDnsNameValidatorManager } var ProjectManager *SProjectManager @@ -75,7 +77,9 @@ type SProject struct { ParentId string `width:"64" charset:"ascii" list:"domain" create:"domain_optional"` - IsDomain tristate.TriState `default:"false" nullable:"false" create:"domain_optional"` + IsDomain tristate.TriState `default:"false" nullable:"false"` + + Displayname string `with:"128" charset:"utf8" nullable:"true" list:"domain" update:"domain" create:"domain_optional"` } func (manager *SProjectManager) GetContextManagers() [][]db.IModelManager { @@ -412,22 +416,39 @@ func (manager *SProjectManager) FetchUserProjects(userId string) ([]SProjectExte return ret, nil } -func (manager *SProjectManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SProjectManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ProjectCreateInput) (api.ProjectCreateInput, error) { err := db.ValidateCreateDomainId(ownerId.GetProjectDomainId()) if err != nil { - return nil, err + return input, errors.Wrap(err, "ValidateCreateDomainId") } - input := api.IdentityBaseResourceCreateInput{} - err = data.Unmarshal(&input) + input.IdentityBaseResourceCreateInput, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.IdentityBaseResourceCreateInput) if err != nil { - return nil, httperrors.NewInternalServerError("unmarshal IdentityBaseResourceCreateInput fail %s", err) + return input, errors.Wrap(err, "SIdentityBaseResourceManager.ValidateCreateData") } - input, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input) + quota := &SIdentityQuota{Project: 1} + quota.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}) + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) if err != nil { - return nil, err + return input, errors.Wrap(err, "CheckSetPendingQuota") + } + return input, nil +} + +func (self *SProject) PostCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) { + self.SIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data) + + quota := &SIdentityQuota{Project: 1} + quota.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}) + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) } - data.Update(jsonutils.Marshal(input)) - return data, nil } func (project *SProject) AllowPerformJoin(ctx context.Context, @@ -580,3 +601,14 @@ func (project *SProject) PerformLeave( } return nil, nil } + +func (project *SProject) GetUsages() []db.IUsage { + if project.Deleted { + return nil + } + usage := SIdentityQuota{Project: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: project.DomainId}) + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/keystone/models/roles.go b/pkg/keystone/models/roles.go index d186693dff..0839e2b1ea 100644 --- a/pkg/keystone/models/roles.go +++ b/pkg/keystone/models/roles.go @@ -27,6 +27,7 @@ import ( "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" @@ -451,20 +452,61 @@ func (role *SRole) Delete(ctx context.Context, userCred mcclient.TokenCredential return role.SIdentityBaseResource.Delete(ctx, userCred) } -func (manager *SRoleManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SRoleManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.RoleCreateInput, +) (api.RoleCreateInput, error) { err := db.ValidateCreateDomainId(ownerId.GetProjectDomainId()) if err != nil { - return nil, err + return input, errors.Wrap(err, "ValidateCreateDomainId") } - input := api.IdentityBaseResourceCreateInput{} - err = data.Unmarshal(&input) + + input.IdentityBaseResourceCreateInput, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.IdentityBaseResourceCreateInput) if err != nil { - return nil, httperrors.NewInternalServerError("unmarshal IdentityBaseResourceCreateInput fail %s", err) + return input, errors.Wrap(err, "SIdentityBaseResourceManager.ValidateCreateData") } - input, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input) + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Role: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, quota) if err != nil { - return nil, err + return input, errors.Wrap(err, "CheckSetPendingQuota") + } + + return input, nil +} + +func (role *SRole) GetUsages() []db.IUsage { + if role.Deleted { + return nil + } + usage := SIdentityQuota{Role: 1} + usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: role.DomainId}) + return []db.IUsage{ + &usage, + } +} + +func (role *SRole) PostCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) { + role.SIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data) + + quota := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + Role: 1, + } + err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true) + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) } - data.Update(jsonutils.Marshal(input)) - return data, nil } diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index 36233029e2..a8b85e3832 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -29,6 +29,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/consts" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" o "yunion.io/x/onecloud/pkg/keystone/options" @@ -445,25 +446,29 @@ func (manager *SUserManager) FilterByHiddenSystemAttributes(q *sqlchemy.SQuery, return q } -func (manager *SUserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - if data.Contains("password") && !jsonutils.QueryBoolean(data, "skip_password_complexity_check", false) { - passwd, _ := data.GetString("password") - err := validatePasswordComplexity(passwd) +func (manager *SUserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.UserCreateInput) (api.UserCreateInput, error) { + var err error + if len(input.Password) > 0 && (input.SkipPasswordComplexityCheck == nil || !*input.SkipPasswordComplexityCheck) { + err = validatePasswordComplexity(input.Password) if err != nil { - return nil, errors.Wrap(err, "validatePasswordComplexity") + return input, errors.Wrap(err, "validatePasswordComplexity") } } - input := api.EnabledIdentityBaseResourceCreateInput{} - err := data.Unmarshal(&input) + input.EnabledIdentityBaseResourceCreateInput, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledIdentityBaseResourceCreateInput) if err != nil { - return nil, httperrors.NewInternalServerError("unmarshal EnabledIdentityBaseResourceCreateInput fail %s", err) + return input, errors.Wrap(err, "SEnabledIdentityBaseResourceManager.ValidateCreateData") } - input, err = manager.SEnabledIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input) + + quota := SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + User: 1, + } + err = quotas.CheckSetPendingQuota(ctx, userCred, "a) if err != nil { - return nil, err + return input, errors.Wrapf(err, "CheckSetPendingQuota") } - data.Update(jsonutils.Marshal(input)) - return data, nil + + return input, nil } func (user *SUser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.UserUpdateInput) (api.UserUpdateInput, error) { @@ -627,6 +632,12 @@ func (user *SUser) PostCreate(ctx context.Context, userCred mcclient.TokenCreden log.Errorf("fail to register localUser %s", err) return } + + pendingUsage := &SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()}, + User: 1, + } + quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, true) } func (user *SUser) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { @@ -979,3 +990,13 @@ func (manager *SUserManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.II } return manager.SEnabledIdentityBaseResourceManager.FilterByOwner(q, owner, scope) } + +func (user *SUser) GetUsages() []db.IUsage { + usage := SIdentityQuota{ + SBaseDomainQuotaKeys: quotas.SBaseDomainQuotaKeys{DomainId: user.DomainId}, + User: 1, + } + return []db.IUsage{ + &usage, + } +} diff --git a/pkg/keystone/options/options.go b/pkg/keystone/options/options.go index 45ba8b9719..e904555d07 100644 --- a/pkg/keystone/options/options.go +++ b/pkg/keystone/options/options.go @@ -43,6 +43,12 @@ type SKeystoneOptions struct { PasswordUniqueHistoryCheck int `help:"password must be unique in last N passwords"` PasswordErrorLockCount int `help:"lock user account if given number of failed auth"` + + DefaultUserQuota int `default:"500" help:"default quota for user per domain, default is 500"` + DefaultGroupQuota int `default:"500" help:"default quota for group per domain, default is 500"` + DefaultProjectQuota int `default:"100" help:"default quota for project per domain, default is 500"` + DefaultRoleQuota int `default:"100" help:"default quota for role per domain, default is 500"` + DefaultPolicyQuota int `default:"100" help:"default quota for policy per domain, default is 500"` } var ( diff --git a/pkg/keystone/service/handlers.go b/pkg/keystone/service/handlers.go index 3a1a8529e8..96726ae273 100644 --- a/pkg/keystone/service/handlers.go +++ b/pkg/keystone/service/handlers.go @@ -18,6 +18,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/appsrv/dispatcher" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/keystone/tokens" @@ -34,7 +35,8 @@ func InitHandlers(app *appsrv.Application) { // add version handler with API_VERSION prefix app.AddDefaultHandler("GET", API_VERSION+"/version", appsrv.VersionHandler, "version") - // quotas.AddQuotaHandler(models.QuotaManager, API_VERSION, app) + quotas.AddQuotaHandler(&models.IdentityQuotaManager.SQuotaBaseManager, API_VERSION, app) + usages.AddUsageHandler(API_VERSION, app) taskman.AddTaskHandler(API_VERSION, app) @@ -64,6 +66,10 @@ func InitHandlers(app *appsrv.Application) { models.ProjectResourceManager, db.SharedResourceManager, + + models.IdentityQuotaManager, + models.IdentityUsageManager, + models.IdentityPendingUsageManager, } { db.RegisterModelManager(manager) } diff --git a/pkg/keystone/service/service.go b/pkg/keystone/service/service.go index c877b3c454..e2c315cddd 100644 --- a/pkg/keystone/service/service.go +++ b/pkg/keystone/service/service.go @@ -82,6 +82,7 @@ func StartService() { cron.AddJobAtIntervalsWithStartRun("AutoSyncIdentityProviderTask", time.Duration(opts.AutoSyncIntervalSeconds)*time.Second, models.AutoSyncIdentityProviderTask, true) cron.AddJobAtIntervalsWithStartRun("FetchProjectResourceCount", time.Duration(opts.FetchProjectResourceCountIntervalSeconds)*time.Second, cronjobs.FetchProjectResourceCount, false) + cron.AddJobAtIntervalsWithStartRun("CalculateIdentityQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.IdentityQuotaManager.CalculateQuotaUsages, true) cron.Start() defer cron.Stop() diff --git a/pkg/mcclient/modules/mod_domains.go b/pkg/mcclient/modules/mod_domains.go index 98603458b0..f99263fff1 100644 --- a/pkg/mcclient/modules/mod_domains.go +++ b/pkg/mcclient/modules/mod_domains.go @@ -26,7 +26,7 @@ var ( func init() { Domains = DomainManager{NewIdentityV3Manager("domain", "domains", - []string{"ID", "Name", "Enabled", "Description", "Driver"}, + []string{"ID", "Name", "Enabled", "Description", "Driver", "Displayname"}, []string{})} register(&Domains) diff --git a/pkg/mcclient/modules/mod_projects.go b/pkg/mcclient/modules/mod_projects.go index 5100720ce3..802bcd8e2f 100644 --- a/pkg/mcclient/modules/mod_projects.go +++ b/pkg/mcclient/modules/mod_projects.go @@ -356,7 +356,7 @@ func (this *ProjectManagerV3) DoCreateProject(s *mcclient.ClientSession, p jsonu func init() { Projects = ProjectManagerV3{NewIdentityV3Manager("project", "projects", []string{}, - []string{"ID", "Name", "Domain_Id", "Project_Domain", "Parent_Id", "Enabled", "Description", "Created_At"})} + []string{"ID", "Name", "Domain_Id", "Project_Domain", "Parent_Id", "Enabled", "Description", "Created_At", "Displayname"})} register(&Projects) } diff --git a/pkg/mcclient/modules/mod_quotas.go b/pkg/mcclient/modules/mod_quotas.go index 7c843b0f89..86d3311e3e 100644 --- a/pkg/mcclient/modules/mod_quotas.go +++ b/pkg/mcclient/modules/mod_quotas.go @@ -137,8 +137,12 @@ var ( ProjectQuotas QuotaManager RegionQuotas QuotaManager ZoneQuotas QuotaManager + DomainQuotas QuotaManager + InfrasQuotas QuotaManager ImageQuotas QuotaManager + IdentityQuotas QuotaManager + quotaColumns = []string{} /*quotaColumns = []string{ "domain", "domain_id", @@ -175,8 +179,23 @@ func init() { []string{})} registerCompute(&ZoneQuotas) + DomainQuotas = QuotaManager{NewComputeManager("domain_quota", "domain_quotas", + quotaColumns, + []string{})} + registerCompute(&DomainQuotas) + + InfrasQuotas = QuotaManager{NewComputeManager("infras_quota", "infras_quotas", + quotaColumns, + []string{})} + registerCompute(&InfrasQuotas) + ImageQuotas = QuotaManager{NewImageManager("image_quota", "image_quotas", quotaColumns, []string{})} registerV2(&ImageQuotas) + + IdentityQuotas = QuotaManager{NewIdentityV3Manager("identity_quota", "identity_quotas", + quotaColumns, + []string{})} + registerV2(&IdentityQuotas) }