feature: allow delete none-default quota

fix: cannot set a quota to zero
This commit is contained in:
Qiu Jian
2019-12-14 23:34:17 +08:00
parent f220d0c89a
commit dc34a66d00
10 changed files with 160 additions and 80 deletions
+7 -1
View File
@@ -84,14 +84,20 @@ type ProjectQuotaOptions struct {
Secgroup int64 `help:"Secgroup count" json:"secgroup,omitzero"`
}
type ImageQuotaKeys struct {
Type string `help:"image type, either iso or image" choices:"iso|image" json:"type,omitempty"`
}
type ImageQuotaOptions struct {
ImageQuotaKeys
Image int64 `help:"Template count" json:"image,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"`
Action string `help:"quota set action" choices:"add|sub|reset|replace"`
Action string `help:"quota set action" choices:"add|sub|reset|replace|delete|update"`
}
func printQuotaList(result jsonutils.JSONObject) {
+109 -52
View File
@@ -42,63 +42,62 @@ const (
QUOTA_ACTION_SUB = "sub"
QUOTA_ACTION_RESET = "reset"
QUOTA_ACTION_REPLACE = "replace"
QUOTA_ACTION_UPDATE = "update"
QUOTA_ACTION_DELETE = "delete"
)
func AddQuotaHandler(manager *SQuotaBaseManager, prefix string, app *appsrv.Application) {
app.AddHandler2("GET",
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.getQuotaHanlder), nil, "get_quota", nil)
auth.Authenticate(manager.getQuotaHandler), nil, "get_quota", nil)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/<tenantid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.getQuotaHanlder), nil, "get_quota_for_project", nil)
auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/domains", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.listDomainQuotaHanlder), nil, "list_quotas_for_all_domains", nil)
auth.Authenticate(manager.listDomainQuotaHandler), nil, "list_quotas_for_all_domains", nil)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/domains/<domainid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.getQuotaHanlder), nil, "get_quota_for_domain", nil)
auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_domain", nil)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/projects", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.listProjectQuotaHanlder), nil, "list_quotas_for_all_projects", nil)
auth.Authenticate(manager.listProjectQuotaHandler), nil, "list_quotas_for_all_projects", nil)
app.AddHandler2("GET",
fmt.Sprintf("%s/%s/projects/<tenantid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.getQuotaHanlder), nil, "get_quota_for_project", nil)
auth.Authenticate(manager.getQuotaHandler), nil, "get_quota_for_project", nil)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.setQuotaHanlder), nil, "set_quota", nil)
auth.Authenticate(manager.setQuotaHandler), nil, "set_quota", nil)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/<tenantid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.setQuotaHanlder), nil, "set_quota_for_project", nil)
auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/domains/<domainid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.setQuotaHanlder), nil, "set_quota_for_domain", nil)
auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_domain", nil)
app.AddHandler2("POST",
fmt.Sprintf("%s/%s/projects/<tenantid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.setQuotaHanlder), nil, "set_quota_for_project", nil)
auth.Authenticate(manager.setQuotaHandler), nil, "set_quota_for_project", nil)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.cleanPendingUsageHanlder), nil, "clean_pending_usage", nil)
fmt.Sprintf("%s/%s/pending", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage", nil)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s/domains/<domainid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.cleanPendingUsageHanlder), nil, "clean_pending_usage_for_domain", nil)
fmt.Sprintf("%s/%s/domains/<domainid>/pending", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.cleanPendingUsageHandler), nil, "clean_pending_usage_for_domain", nil)
app.AddHandler2("DELETE",
fmt.Sprintf("%s/%s/projects/<tenantid>", prefix, manager.KeywordPlural()),
auth.Authenticate(manager.cleanPendingUsageHanlder), nil, "clean_pending_usage_for_project", nil)
/*app.AddHandler2("POST",
fmt.Sprintf("%s/%s/<tenantid>/<action>", prefix, _manager.Keyword()),
auth.Authenticate(checkQuotaHanlder), nil, "check_quota", nil)*/
fmt.Sprintf("%s/%s/projects/<tenantid>/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) {
@@ -139,7 +138,7 @@ func (manager *SQuotaBaseManager) queryQuota(ctx context.Context, quota IQuota,
return ret, nil
}
func (manager *SQuotaBaseManager) getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
func (manager *SQuotaBaseManager) getQuotaHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
params, query, _ := appsrv.FetchEnv(ctx, w, r)
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
@@ -196,14 +195,23 @@ func (manager *SQuotaBaseManager) getQuotaHanlder(ctx context.Context, w http.Re
manager.sendQuotaList(w, quotaList)
}
func (manager *SQuotaBaseManager) fetchSetQuotaScope(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, isBaseQuotaKeys bool) (mcclient.IIdentityProvider, rbacutils.TRbacScope, rbacutils.TRbacScope, error) {
func (manager *SQuotaBaseManager) fetchSetQuotaScope(
ctx context.Context,
userCred mcclient.TokenCredential,
data jsonutils.JSONObject,
isBaseQuotaKeys bool,
) (
mcclient.IIdentityProvider,
rbacutils.TRbacScope,
rbacutils.TRbacScope,
error,
) {
var scope rbacutils.TRbacScope
ownerId, err := db.FetchProjectInfo(ctx, data)
if err != nil {
return nil, scope, scope, err
}
var requestScope rbacutils.TRbacScope
ownerScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionUpdate)
if ownerId != nil {
if len(ownerId.GetProjectId()) > 0 {
// project level
@@ -235,13 +243,11 @@ func (manager *SQuotaBaseManager) fetchSetQuotaScope(ctx context.Context, userCr
requestScope = rbacutils.ScopeProject
}
}
if requestScope.HigherThan(ownerScope) {
return nil, scope, scope, httperrors.NewForbiddenError("not enough privilleges")
}
return ownerId, scope, ownerScope, nil
return ownerId, scope, requestScope, nil
}
func (manager *SQuotaBaseManager) cleanPendingUsageHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
func (manager *SQuotaBaseManager) cleanPendingUsageHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
params, query, _ := appsrv.FetchEnv(ctx, w, r)
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
@@ -284,7 +290,7 @@ func (manager *SQuotaBaseManager) cleanPendingUsageHanlder(ctx context.Context,
appsrv.SendJSON(w, rbody)
}
func (manager *SQuotaBaseManager) setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
func (manager *SQuotaBaseManager) setQuotaHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
params, _, body := appsrv.FetchEnv(ctx, w, r)
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
@@ -305,45 +311,96 @@ func (manager *SQuotaBaseManager) setQuotaHanlder(ctx context.Context, w http.Re
return
}
ownerId, scope, _, err := manager.fetchSetQuotaScope(ctx, userCred, data, IsBaseQuotaKeys(quota.GetKeys()))
// check is there any nonempty key other than domain_id and project_id
isBaseQuota := IsBaseQuotaKeys(quota.GetKeys())
ownerId, scope, requestScope, err := manager.fetchSetQuotaScope(ctx, userCred, data, isBaseQuota)
if err != nil {
httperrors.GeneralServerError(w, err)
return
}
// fill project_id and domain_id
baseKeys := OwnerIdQuotaKeys(scope, ownerId)
reflectutils.FillEmbededStructValue(reflect.Indirect(reflect.ValueOf(quota)), reflect.ValueOf(baseKeys))
keys := quota.GetKeys()
isNew := false
oquota := manager.newQuota()
err = manager.GetQuota(ctx, quota.GetKeys(), oquota)
oquota.SetKeys(keys)
err = manager.getQuotaByKeys(ctx, keys, oquota)
if err != nil {
log.Errorf("get quota fail %s", err)
httperrors.GeneralServerError(w, err)
return
if errors.Cause(err) != sql.ErrNoRows {
log.Errorf("get quota %s fail %s", QuotaKeyString(keys), err)
httperrors.GeneralServerError(w, err)
return
} else {
isNew = true
}
}
action, _ := body.GetString(manager.KeywordPlural(), "action")
switch action {
case QUOTA_ACTION_ADD:
oquota.Add(quota)
case QUOTA_ACTION_SUB:
oquota.Sub(quota)
case QUOTA_ACTION_RESET:
oquota.FetchSystemQuota()
case QUOTA_ACTION_REPLACE:
oquota = quota
default:
oquota.Update(quota)
var policyAction string
if action == QUOTA_ACTION_DELETE {
if isNew {
// no need to delete
httperrors.NotFoundError(w, "Quota %s not found", QuotaKeyString(keys))
return
} else if isBaseQuota {
// base quota is not deletable
httperrors.ForbiddenError(w, "Default quota %s not allow to delete", QuotaKeyString(keys))
return
}
policyAction = policy.PolicyActionDelete
} else {
if isNew {
policyAction = policy.PolicyActionCreate
} else {
policyAction = policy.PolicyActionUpdate
}
}
err = manager.SetQuota(ctx, userCred, oquota)
if err != nil {
log.Errorf("set quota fail %s", err)
httperrors.GeneralServerError(w, err)
log.Debugf("is_new: %v action: %s origin: %s current: %s", isNew, action, jsonutils.Marshal(oquota), jsonutils.Marshal(quota))
// check rbac policy
ownerScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policyAction)
if requestScope.HigherThan(ownerScope) {
httperrors.ForbiddenError(w, "not enough privilleges")
return
}
keys := OwnerIdQuotaKeys(scope, ownerId)
quotaList, err := manager.listQuotas(ctx, userCred, keys.DomainId, keys.ProjectId, scope == rbacutils.ScopeDomain, true)
if action == QUOTA_ACTION_DELETE {
err = manager.DeleteQuota(ctx, userCred, keys)
if err != nil {
httperrors.GeneralServerError(w, err)
return
}
} else {
switch action {
case QUOTA_ACTION_ADD:
oquota.Add(quota)
case QUOTA_ACTION_SUB:
oquota.Sub(quota)
case QUOTA_ACTION_RESET:
oquota.FetchSystemQuota()
case QUOTA_ACTION_REPLACE:
oquota = quota
case QUOTA_ACTION_UPDATE:
fallthrough
default:
oquota.Update(quota)
}
log.Debugf("To set %s", jsonutils.Marshal(oquota))
err = manager.SetQuota(ctx, userCred, oquota)
if err != nil {
log.Errorf("set quota fail %s", err)
httperrors.GeneralServerError(w, err)
return
}
}
quotaList, err := manager.listQuotas(ctx, userCred, baseKeys.DomainId, baseKeys.ProjectId, scope == rbacutils.ScopeDomain, true)
if err != nil {
httperrors.GeneralServerError(w, err)
return
@@ -351,7 +408,7 @@ func (manager *SQuotaBaseManager) setQuotaHanlder(ctx context.Context, w http.Re
manager.sendQuotaList(w, quotaList)
}
func (manager *SQuotaBaseManager) listDomainQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
func (manager *SQuotaBaseManager) listDomainQuotaHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
_, query, _ := appsrv.FetchEnv(ctx, w, r)
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
@@ -383,7 +440,7 @@ func (manager *SQuotaBaseManager) sendQuotaList(w http.ResponseWriter, quotaList
appsrv.SendJSON(w, rbody)
}
func (manager *SQuotaBaseManager) listProjectQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
func (manager *SQuotaBaseManager) listProjectQuotaHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
_, query, _ := appsrv.FetchEnv(ctx, w, r)
owner, err := db.FetchDomainInfo(ctx, query)
+3 -5
View File
@@ -84,14 +84,12 @@ func (manager *SQuotaBaseManager) getQuotaByKeys(ctx context.Context, keys IQuot
}
err := q.First(quota)
if err != nil {
if errors.Cause(err) != sql.ErrNoRows {
return errors.Wrap(err, "q.First")
}
}
if manager.nonNegative {
quota.ResetNegative()
}
if err != nil {
return errors.Wrap(err, "q.Query")
}
return nil
}
+12 -1
View File
@@ -16,6 +16,7 @@ package quotas
import (
"context"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -150,7 +151,17 @@ func (manager *SQuotaBaseManager) GetPendingUsages(ctx context.Context, keys IQu
}
func (manager *SQuotaBaseManager) GetQuota(ctx context.Context, keys IQuotaKeys, quota IQuota) error {
return manager.getQuotaByKeys(ctx, keys, quota)
LockQuotaKeys(ctx, manager, keys)
defer ReleaseQuotaKeys(ctx, manager, keys)
err := manager.getQuotaByKeys(ctx, keys, quota)
if err != nil {
if errors.Cause(err) != sql.ErrNoRows {
return errors.Wrap(err, "manager.getQuotaByKeys")
}
// else, ignore the sql.ErrNoRows error
}
return nil
}
func (manager *SQuotaBaseManager) GetChildrenQuotas(ctx context.Context, keys IQuotaKeys) ([]IQuota, error) {
+1 -1
View File
@@ -68,7 +68,7 @@ type SProjectQuota struct {
quotas.SBaseQuotaKeys
Secgroup int `default:"-1"`
Secgroup int `default:"-1" allow_zero:"true"`
}
func (self *SProjectQuota) GetKeys() quotas.IQuotaKeys {
+6 -6
View File
@@ -80,13 +80,13 @@ type SQuota struct {
SComputeResourceKeys
Count int `default:"-1"`
Cpu int `default:"-1"`
Memory int `default:"-1"`
Storage int `default:"-1"`
Count int `default:"-1" allow_zero:"true"`
Cpu int `default:"-1" allow_zero:"true"`
Memory int `default:"-1" allow_zero:"true"`
Storage int `default:"-1" allow_zero:"true"`
Group int `default:"-1"`
IsolatedDevice int `default:"-1"`
Group int `default:"-1" allow_zero:"true"`
IsolatedDevice int `default:"-1" allow_zero:"true"`
}
func (self *SQuota) GetKeys() quotas.IQuotaKeys {
+11 -11
View File
@@ -70,20 +70,20 @@ type SRegionQuota struct {
quotas.SRegionalCloudResourceKeys
Eip int `default:"-1"`
Port int `default:"-1"`
Eport int `default:"-1"`
Bw int `default:"-1"`
Ebw int `default:"-1"`
Eip int `default:"-1" allow_zero:"true"`
Port int `default:"-1" allow_zero:"true"`
Eport int `default:"-1" allow_zero:"true"`
Bw int `default:"-1" allow_zero:"true"`
Ebw int `default:"-1" allow_zero:"true"`
Snapshot int `default:"-1"`
Snapshot int `default:"-1" allow_zero:"true"`
Bucket int `default:"-1"`
ObjectGB int `default:"-1"`
ObjectCnt int `default:"-1"`
Bucket int `default:"-1" allow_zero:"true"`
ObjectGB int `default:"-1" allow_zero:"true"`
ObjectCnt int `default:"-1" allow_zero:"true"`
Rds int `default:"-1"`
Cache int `default:"-1"`
Rds int `default:"-1" allow_zero:"true"`
Cache int `default:"-1" allow_zero:"true"`
}
func (self *SRegionQuota) GetKeys() quotas.IQuotaKeys {
+1 -1
View File
@@ -70,7 +70,7 @@ type SZoneQuota struct {
quotas.SZonalCloudResourceKeys
Loadbalancer int `default:"-1"`
Loadbalancer int `default:"-1" allow_zero:"true"`
}
func (self *SZoneQuota) GetKeys() quotas.IQuotaKeys {
+1 -1
View File
@@ -72,7 +72,7 @@ type SQuota struct {
SImageQuotaKeys
Image int `default:"-1"`
Image int `default:"-1" allow_zero:"true"`
}
func (self *SQuota) GetKeys() quotas.IQuotaKeys {
+9 -1
View File
@@ -21,6 +21,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/util/httputils"
)
type QuotaManager struct {
@@ -28,6 +29,10 @@ type QuotaManager struct {
}
func (this *QuotaManager) getURL(params jsonutils.JSONObject) string {
return this.getURL2(params, "")
}
func (this *QuotaManager) getURL2(params jsonutils.JSONObject, extra string) string {
url := fmt.Sprintf("/%s", this.URLPath())
query := jsonutils.NewDict()
if params != nil {
@@ -50,6 +55,9 @@ func (this *QuotaManager) getURL(params jsonutils.JSONObject) string {
query.Add(jsonutils.JSONTrue, "refresh")
}
}
if len(extra) > 0 {
url = httputils.JoinPath(url, extra)
}
if query.Size() > 0 {
url += "?" + query.QueryString()
}
@@ -67,7 +75,7 @@ func (this *QuotaManager) GetQuota(s *mcclient.ClientSession, params jsonutils.J
}
func (this *QuotaManager) DoCleanPendingUsage(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
url := this.getURL(params)
url := this.getURL2(params, "pending")
results, err := modulebase.Delete(this.ResourceManager, s, url, nil, "")
if err != nil {
return nil, err