fix: 1. quota add API 2. tenant cache refresh 3. domain quota not initialize

This commit is contained in:
Qiu Jian
2019-07-02 03:08:52 +08:00
parent 550c674fd9
commit f184e2d8f9
23 changed files with 244 additions and 63 deletions
+1
View File
@@ -62,6 +62,7 @@ func init() {
type QuotaSetOptions struct {
Tenant string `help:"Tenant name or ID to set quota" json:"tenant,omitempty"`
ProjectDomain string `help:"Domain name or ID to set quota" json:"domain,omitempty"`
Action string `help:"quota set action" choices:"add|reset"`
QuotaBaseOptions
}
R(&QuotaSetOptions{}, "quota-set", "Set quota for tenant", func(s *mcclient.ClientSession, args *QuotaSetOptions) error {
+3 -3
View File
@@ -281,8 +281,8 @@ paths:
/quotas/projects/{project_id}:
$ref: "./quotas/projectquotas.yaml"
/quotas/domains/{domain_id}:
$ref: "./quotas/projectquotas.yaml"
$ref: "./quotas/domainquotas.yaml"
/quotas/projects:
$ref: "./quotas/projectquotalist.yaml"
/quotas/domains:
$ref: "./quotas/quotalist.yaml"
/quotas/projects:
$ref: "./quotas/quotalist.yaml"
+35
View File
@@ -18,3 +18,38 @@ domain_id:
type: string
description: 查询指定域的配额
project_domain:
name: project_domain
in: query
type: string
description: 指定查询的域名称或者ID
quotas:
name: quotas
in: body
required: true
type: object
description: 设置配额信息
properties:
action:
type: string
description: 设置配额的方式,可能值为set,add和reset,分别代表设置,增加和重置为初始值
default: set
cpu:
type: integer
description: 设置CPU配额,单位为个
memory:
type: integer
description: 设置内存配额,单位为MB
storage:
type: integer
description: 设置磁盘配额,单位为MB
isolated_device:
type: integer
description: 设置GPU配额,单位为块
snapshot:
type: integer
description: 设置快照配额,单位为MB
image:
type: integer
description: 设置镜像配额,单位为个
+11
View File
@@ -9,3 +9,14 @@ get:
$ref: "../schemas/quota.yaml#/QuotaGetResponse"
tags:
- quota
post:
summary: 设置指定域的配额
parameters:
- $ref: "../parameters/quota.yaml#/domain_id"
- $ref: "../parameters/quota.yaml#/quotas"
responses:
200:
description: 设置成功
tags:
- quota
+11
View File
@@ -0,0 +1,11 @@
get:
summary: 获取当前域或者指定域下所有项目的配额信息
parameters:
- $ref: "../parameters/quota.yaml#/project_domain"
responses:
200:
description: 配额信息
schema:
$ref: "../schemas/quota.yaml#/QuotaListResponse"
tags:
- quota
+12 -1
View File
@@ -1,7 +1,7 @@
get:
summary: 获得指定项目的配额
parameters:
$ref: "../parameters/quota.yaml#/project_id"
- $ref: "../parameters/quota.yaml#/project_id"
responses:
200:
description: 配额信息
@@ -9,3 +9,14 @@ get:
$ref: "../schemas/quota.yaml#/QuotaGetResponse"
tags:
- quota
post:
summary: 设置指定项目的配额
parameters:
- $ref: "../parameters/quota.yaml#/project_id"
- $ref: "../parameters/quota.yaml#/quotas"
responses:
200:
description: 设置成功
tags:
- quota
+1 -1
View File
@@ -4,6 +4,6 @@ get:
200:
description: 配额信息
schema:
$ref: "../schemas/quota.yaml#/QuotaGetResponse"
$ref: "../schemas/quota.yaml#/QuotaListResponse"
tags:
- quota
+16 -2
View File
@@ -1,14 +1,28 @@
QuotaListResponse:
type: object
properties:
quotas:
type: array
items:
$ref: '#/quota'
QuotaGetResponse:
type: object
properties:
quotas:
type: object
$ref: '#/Quota'
$ref: '#/quota'
Quota:
quota:
type: object
description: 配额信息
properties:
domain_id:
type: string
description: 在配额列表中,只是当前配额的适用域ID
project_id:
type: string
description: 在配额列表中,只是当前配额的适用项目ID
cpu:
type: integer
description: CPU配额
+2 -2
View File
@@ -5,12 +5,12 @@ post:
name: storage
required: true
schema:
$ref: '../schemas/storage.yaml#StorageCreate'
$ref: '../schemas/storage.yaml#/StorageCreate'
response:
200:
description: 新建存储的信息
schema:
$ref: '../schemas/storage.yaml#StorageResponse'
$ref: '../schemas/storage.yaml#/StorageResponse'
tags:
- storages
+3
View File
@@ -20,6 +20,7 @@ import (
"os"
"time"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
@@ -70,6 +71,8 @@ func InitAuth(options *common_options.CommonOptions, authComplete auth.AuthCompl
authComplete()
consts.SetTenantCacheExpireSeconds(options.TenantCacheExpireSeconds)
InitBaseAuth(&options.BaseOptions)
}
+14
View File
@@ -14,10 +14,16 @@
package consts
import (
"time"
)
var (
globalRegion = ""
globalServiceType = ""
tenantCacheExpireSeconds = 900
)
func SetRegion(region string) {
@@ -35,3 +41,11 @@ func SetServiceType(srvType string) {
func GetServiceType() string {
return globalServiceType
}
func SetTenantCacheExpireSeconds(sec int) {
tenantCacheExpireSeconds = sec
}
func GetTenantCacheExpireSeconds() time.Duration {
return time.Duration(tenantCacheExpireSeconds) * time.Second
}
+6 -13
View File
@@ -14,6 +14,10 @@
package db
import (
"time"
)
type SKeystoneCacheObjectManager struct {
SStandaloneResourceBaseManager
}
@@ -23,6 +27,8 @@ type SKeystoneCacheObject struct {
DomainId string `width:"128" charset:"ascii" nullable:"true"`
Domain string `width:"128" charset:"utf8" nullable:"true"`
LastCheck time.Time `nullable:"true"`
}
func NewKeystoneCacheObjectManager(dt interface{}, tableName string, keyword string, keywordPlural string) SKeystoneCacheObjectManager {
@@ -37,16 +43,3 @@ func NewKeystoneCacheObject(id string, name string, domainId string, domain stri
obj.DomainId = domainId
return obj
}
func (manager *SKeystoneCacheObjectManager) BatchFetchNames(idStrs []string) []string {
t := manager.TableSpec().Instance()
results, err := t.Query(t.Field("name")).In("id", idStrs).AllStringMap()
if err != nil {
return nil
}
ret := make([]string, len(results))
for i, obj := range results {
ret[i] = obj["name"]
}
return ret
}
+36 -9
View File
@@ -36,6 +36,11 @@ import (
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
const (
QUOTA_ACTION_ADD = "add"
QUOTA_ACTION_RESET = "reset"
)
func AddQuotaHandler(manager *SQuotaBaseManager, prefix string, app *appsrv.Application) {
app.AddHandler2("GET",
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
@@ -232,7 +237,15 @@ func (manager *SQuotaBaseManager) setQuotaHanlder(ctx context.Context, w http.Re
httperrors.GeneralServerError(w, err)
return
}
oquota.Update(quota)
action, _ := body.GetString(manager.KeywordPlural(), "action")
switch action {
case QUOTA_ACTION_ADD:
oquota.Add(quota)
case QUOTA_ACTION_RESET:
oquota.FetchSystemQuota(scope, ownerId)
default:
oquota.Update(quota)
}
if scope == rbacutils.ScopeProject {
total, err := manager.getDomainTotalQuota(ctx, ownerId.GetProjectDomainId(), []string{ownerId.GetProjectId()})
@@ -396,13 +409,9 @@ func (manager *SQuotaBaseManager) listQuotas(ctx context.Context, targetDomainId
// dsable platform
q = q.IsNullOrEmpty("platform")
rows, err := q.Rows()
if err != nil {
if err != sql.ErrNoRows {
log.Errorf("query quotas fail %s", err)
return nil, httperrors.NewInternalServerError("query quotas %s", err)
} else {
return []jsonutils.JSONObject{}, nil
}
if err != nil && err != sql.ErrNoRows {
log.Errorf("query quotas fail %s", err)
return nil, httperrors.NewInternalServerError("query quotas %s", err)
}
defer rows.Close()
@@ -453,7 +462,25 @@ func (manager *SQuotaBaseManager) listQuotas(ctx context.Context, targetDomainId
}
ret = append(ret, quota)
}
if len(ret) == 0 && len(targetDomainId) > 0 {
// return the initial quota of targetDomainId
scope := rbacutils.ScopeDomain
owner := db.SOwnerId{
DomainId: targetDomainId,
}
platform := []string{}
quota, _, err := manager.queryQuota(ctx, scope, &owner, platform)
if err != nil {
return nil, httperrors.NewInternalServerError("query domain initial quotas %s", err)
}
quota.Set("domain_id", jsonutils.NewString(targetDomainId))
domain, err := db.TenantCacheManager.FetchDomainById(ctx, targetDomainId)
if err != nil {
return nil, err
}
quota.Set("project_domain", jsonutils.NewString(domain.Name))
ret = append(ret, quota)
}
return ret, nil
}
+2 -2
View File
@@ -93,7 +93,7 @@ func (manager *SQuotaBaseManager) PostUsageJob(scope rbacutils.TRbacScope, owner
// check existence of project
s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1")
if scope == rbacutils.ScopeDomain {
domain, err := modules.Domains.Get(s, ownerId.GetProjectDomainId(), nil)
domain, err := modules.Domains.GetById(s, ownerId.GetProjectDomainId(), nil)
if err == nil {
// update cache
domainId, _ := domain.GetString("id")
@@ -106,7 +106,7 @@ func (manager *SQuotaBaseManager) PostUsageJob(scope rbacutils.TRbacScope, owner
save = false
}
} else {
proj, err := modules.Projects.Get(s, ownerId.GetProjectId(), nil)
proj, err := modules.Projects.GetById(s, ownerId.GetProjectId(), nil)
if err == nil {
// update cache
projId, _ := proj.GetString("id")
+38 -19
View File
@@ -19,6 +19,7 @@ import (
"database/sql"
"fmt"
"runtime/debug"
"time"
"github.com/pkg/errors"
@@ -102,24 +103,35 @@ func (manager *STenantCacheManager) fetchTenant(ctx context.Context, idStr strin
q = q.NotEquals("domain_id", identityapi.KeystoneDomainRoot)
}
q = filter(q)
tenant, err := NewModelObject(manager)
tobj, err := NewModelObject(manager)
if err != nil {
return nil, errors.Wrap(err, "NewModelObject")
}
err = q.First(tenant)
if err != nil {
if err == sql.ErrNoRows {
if isDomain {
return manager.fetchDomainFromKeystone(ctx, idStr)
} else {
return manager.fetchTenantFromKeystone(ctx, idStr)
}
} else {
return nil, errors.Wrap(err, "query")
err = q.First(tobj)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "query")
} else if tobj != nil {
tenant := tobj.(*STenant)
if !tenant.IsExpired() {
return tenant, nil
}
} else {
return tenant.(*STenant), nil
}
if isDomain {
return manager.fetchDomainFromKeystone(ctx, idStr)
} else {
return manager.fetchTenantFromKeystone(ctx, idStr)
}
}
func (t *STenant) IsExpired() bool {
if t.LastCheck.IsZero() {
return true
}
now := time.Now().UTC()
if t.LastCheck.Add(consts.GetTenantCacheExpireSeconds()).Before(now) {
return true
}
return false
}
func (manager *STenantCacheManager) FetchTenantByIdOrName(ctx context.Context, idStr string) (*STenant, error) {
@@ -154,7 +166,7 @@ func (manager *STenantCacheManager) fetchTenantFromKeystone(ctx context.Context,
return nil, fmt.Errorf("Empty idStr")
}
s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1")
tenant, err := modules.Projects.Get(s, idStr, nil)
tenant, err := modules.Projects.GetById(s, idStr, nil)
if err != nil {
if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
return nil, sql.ErrNoRows
@@ -162,10 +174,10 @@ func (manager *STenantCacheManager) fetchTenantFromKeystone(ctx context.Context,
log.Errorf("fetch project %s fail %s", idStr, err)
return nil, errors.Wrap(err, "modules.Projects.Get")
}
tenantId, err := tenant.GetString("id")
tenantName, err := tenant.GetString("name")
domainId, err := tenant.GetString("domain_id")
domainName, err := tenant.GetString("domain")
tenantId, _ := tenant.GetString("id")
tenantName, _ := tenant.GetString("name")
domainId, _ := tenant.GetString("domain_id")
domainName, _ := tenant.GetString("project_domain")
// manager.Save(ctx, domainId, domainName, identityapi.KeystoneDomainRoot, identityapi.KeystoneDomainRoot)
return manager.Save(ctx, tenantId, tenantName, domainId, domainName)
}
@@ -202,7 +214,7 @@ func (manager *STenantCacheManager) fetchDomainFromKeystone(ctx context.Context,
return nil, fmt.Errorf("Empty idStr")
}
s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1")
tenant, err := modules.Domains.Get(s, idStr, nil)
tenant, err := modules.Domains.GetById(s, idStr, nil)
if err != nil {
if je, ok := err.(*httputils.JSONClientError); ok && je.Code == 404 {
return nil, sql.ErrNoRows
@@ -239,9 +251,14 @@ func (manager *STenantCacheManager) Save(ctx context.Context, idStr string, name
log.Errorf("FetchTenantbyId fail %s", err)
return nil, err
}
now := time.Now().UTC()
if err == nil {
obj := objo.(*STenant)
if obj.Id == idStr && obj.Name == name && obj.Domain == domain && obj.DomainId == domainId {
Update(obj, func() error {
obj.LastCheck = now
return nil
})
return obj, nil
}
_, err = Update(obj, func() error {
@@ -249,6 +266,7 @@ func (manager *STenantCacheManager) Save(ctx context.Context, idStr string, name
obj.Name = name
obj.Domain = domain
obj.DomainId = domainId
obj.LastCheck = now
return nil
})
if err != nil {
@@ -263,6 +281,7 @@ func (manager *STenantCacheManager) Save(ctx context.Context, idStr string, name
obj.Name = name
obj.Domain = domain
obj.DomainId = domainId
obj.LastCheck = now
err = manager.TableSpec().Insert(obj)
if err != nil {
return nil, err
+17 -1
View File
@@ -218,7 +218,8 @@ func (manager *SVirtualResourceBaseManager) FetchCustomizeColumns(ctx context.Co
}
func FetchProjects(projectIds []string, isDomain bool) map[string]STenant {
q := TenantCacheManager.Query().In("id", projectIds)
deadline := time.Now().UTC().Add(-consts.GetTenantCacheExpireSeconds())
q := TenantCacheManager.Query().In("id", projectIds).GT("last_check", deadline)
if isDomain {
q = q.Equals("domain_id", identityapi.KeystoneDomainRoot)
} else {
@@ -233,6 +234,21 @@ func FetchProjects(projectIds []string, isDomain bool) map[string]STenant {
for i := range projects {
ret[projects[i].Id] = projects[i]
}
ctx := context.Background()
for _, pid := range projectIds {
if _, ok := ret[pid]; !ok {
// not found
var t *STenant
if isDomain {
t, _ = TenantCacheManager.fetchDomainFromKeystone(ctx, pid)
} else {
t, _ = TenantCacheManager.fetchTenantFromKeystone(ctx, pid)
}
if t != nil {
ret[t.Id] = *t
}
}
}
return ret
}
+2
View File
@@ -79,6 +79,8 @@ type CommonOptions struct {
AdminProjectDomain string `help:"Domain of Admin project"`
AuthTokenCacheSize uint32 `help:"Auth token Cache Size" default:"2048"`
TenantCacheExpireSeconds int `help:"expire seconds of cached tenant/domain info. defailt 15 minutes" default:"900"`
BaseOptions
}
+1 -1
View File
@@ -158,7 +158,7 @@ func verifyTokensV3(ctx context.Context, w http.ResponseWriter, r *http.Request)
httperrors.InvalidCredentialError(w, "invalid project")
return
}
} else {
} else if len(token.DomainId) > 0 {
domain, err = models.DomainManager.FetchDomainById(token.DomainId)
if err != nil {
httperrors.InvalidCredentialError(w, "invalid domain")
+1
View File
@@ -15,6 +15,7 @@
package mcclient
type IServiceCatalog interface {
Len() int
GetServiceURL(service, region, zone, endpointType string) (string, error)
GetServiceURLs(service, region, zone, endpointType string) ([]string, error)
}
+12 -9
View File
@@ -234,10 +234,11 @@ func (this *Client) unmarshalV3Token(rbody jsonutils.JSONObject, tokenId string)
err = fmt.Errorf("Invalid response when unmarshal V3 Token: %v", err)
}
cata := cred.GetServiceCatalog()
if cata == nil {
log.Fatalf("No srvice catalog avaiable")
if cata == nil || cata.Len() == 0 {
log.Warningf("No service catalog avaiable")
} else {
this.serviceCatalog = cata
}
this.serviceCatalog = cata
return
}
@@ -250,10 +251,11 @@ func (this *Client) unmarshalV2Token(rbody jsonutils.JSONObject) (cred TokenCred
err = fmt.Errorf("Invalid response when unmarshal V2 Token: %s", err)
}
cata := cred.GetServiceCatalog()
if cata == nil {
log.Fatalf("No srvice catalog avaiable")
if cata == nil || cata.Len() == 0 {
log.Warningf("No srvice catalog avaiable")
} else {
this.serviceCatalog = cata
}
this.serviceCatalog = cata
return
}
err = fmt.Errorf("Invalid response: no access object")
@@ -308,10 +310,11 @@ func (this *Client) SetProject(tenantId, tenantName, tenantDomain string, token
func (this *Client) NewSession(ctx context.Context, region, zone, endpointType string, token TokenCredential, apiVersion string) *ClientSession {
cata := token.GetServiceCatalog()
if this.serviceCatalog == nil {
if cata == nil {
log.Fatalf("Missing service catalog in token")
if cata == nil || cata.Len() == 0 {
log.Warningf("Missing service catalog in token")
} else {
this.serviceCatalog = cata
}
this.serviceCatalog = cata
}
if ctx == nil {
ctx = context.Background()
+8
View File
@@ -172,6 +172,10 @@ func (this *TokenCredentialV2) IsAllow(scope rbacutils.TRbacScope, service strin
}
}
func (this *TokenCredentialV2) Len() int {
return this.ServiceCatalog.Len()
}
func (this *TokenCredentialV2) GetServiceURL(service, region, zone, endpointType string) (string, error) {
return this.ServiceCatalog.GetServiceURL(service, region, zone, endpointType)
}
@@ -272,6 +276,10 @@ func (catalog KeystoneServiceCatalogV2) getServiceEndpoint(service, region, zone
}
}
func (catalog KeystoneServiceCatalogV2) Len() int {
return len(catalog)
}
func (catalog KeystoneServiceCatalogV2) GetServiceURL(service, region, zone, endpointType string) (string, error) {
ep, err := catalog.getServiceEndpoint(service, region, zone)
if err != nil {
+8
View File
@@ -176,6 +176,10 @@ func (this *TokenCredentialV3) GetRegions() []string {
return this.Token.Catalog.getRegions()
}
func (this *TokenCredentialV3) Len() int {
return this.Token.Catalog.Len()
}
func (this *TokenCredentialV3) GetServiceURL(service, region, zone, endpointType string) (string, error) {
return this.Token.Catalog.GetServiceURL(service, region, zone, endpointType)
}
@@ -293,6 +297,10 @@ func Id2RegionZone(id string) (string, string) {
}
}
func (catalog KeystoneServiceCatalogV3) Len() int {
return len(catalog)
}
func (catalog KeystoneServiceCatalogV3) GetServiceURL(service, region, zone, endpointType string) (string, error) {
urls, err := catalog.GetServiceURLs(service, region, zone, endpointType)
if err != nil {
+4
View File
@@ -137,6 +137,10 @@ func (self *SSimpleToken) GetRegions() []string {
return nil
}
func (self *SSimpleToken) Len() int {
return 0
}
func (self *SSimpleToken) GetServiceURL(service, region, zone, endpointType string) (string, error) {
return "", fmt.Errorf("Not available")
}