Files
cloudpods/pkg/compute/models/quotas.go
T
2020-06-28 18:17:02 +08:00

495 lines
14 KiB
Go

// 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"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
api "yunion.io/x/onecloud/pkg/apis/compute"
identityapi "yunion.io/x/onecloud/pkg/apis/identity"
"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/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/utils"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
type SQuotaManager struct {
quotas.SQuotaBaseManager
}
var (
Quota SQuota
QuotaManager *SQuotaManager
QuotaUsageManager *SQuotaManager
QuotaPendingUsageManager *SQuotaManager
)
func init() {
Quota = SQuota{}
QuotaUsageManager = &SQuotaManager{
SQuotaBaseManager: quotas.NewQuotaUsageManager(Quota,
rbacutils.ScopeProject,
"quota_usage_tbl",
"quota_usage",
"quota_usages",
),
}
QuotaPendingUsageManager = &SQuotaManager{
SQuotaBaseManager: quotas.NewQuotaUsageManager(Quota,
rbacutils.ScopeProject,
"quota_pending_usage_tbl",
"quota_pending_usage",
"quota_pending_usages",
),
}
QuotaManager = &SQuotaManager{
SQuotaBaseManager: quotas.NewQuotaBaseManager(Quota,
rbacutils.ScopeProject,
"quota_tbl",
QuotaPendingUsageManager,
QuotaUsageManager,
"quota",
"quotas",
),
}
quotas.Register(QuotaManager)
}
type SQuota struct {
quotas.SQuotaBase
SComputeResourceKeys
// 主机数量配额
Count int `default:"-1" allow_zero:"true" json:"count"`
// 主机CPU核数量配额
Cpu int `default:"-1" allow_zero:"true" json:"cpu"`
// 主机内存容量配额
Memory int `default:"-1" allow_zero:"true" json:"memory"`
// 主机存储容量配额
Storage int `default:"-1" allow_zero:"true" json:"storage"`
// 主机组配额
Group int `default:"-1" allow_zero:"true" json:"group"`
// 直通设备(GPU)配额
IsolatedDevice int `default:"-1" allow_zero:"true" json:"isolated_device"`
}
func (self *SQuota) GetKeys() quotas.IQuotaKeys {
return self.SComputeResourceKeys
}
func (self *SQuota) SetKeys(keys quotas.IQuotaKeys) {
self.SComputeResourceKeys = keys.(SComputeResourceKeys)
}
func (self *SQuota) FetchSystemQuota() {
keys := self.SComputeResourceKeys
base := 0
switch options.Options.DefaultQuotaValue {
case commonOptions.DefaultQuotaUnlimit:
base = -1
case commonOptions.DefaultQuotaZero:
base = 0
if keys.Scope() == rbacutils.ScopeDomain { // domain level quota
base = 10
} else if keys.DomainId == identityapi.DEFAULT_DOMAIN_ID && keys.ProjectId == auth.AdminCredential().GetProjectId() {
base = 1
}
case commonOptions.DefaultQuotaDefault:
base = 1
if keys.Scope() == rbacutils.ScopeDomain {
base = 10
}
}
defaultValue := func(def int) int {
if base < 0 {
return -1
} else {
return def * base
}
}
self.Count = defaultValue(options.Options.DefaultServerQuota)
self.Cpu = defaultValue(options.Options.DefaultCpuQuota)
self.Memory = defaultValue(options.Options.DefaultMemoryQuota)
self.Storage = defaultValue(options.Options.DefaultStorageQuota)
self.Group = defaultValue(options.Options.DefaultGroupQuota)
self.IsolatedDevice = defaultValue(options.Options.DefaultIsolatedDeviceQuota)
}
func (self *SQuota) FetchUsage(ctx context.Context) error {
keys := self.SComputeResourceKeys
scope := keys.Scope()
ownerId := keys.OwnerId()
rangeObjs := make([]db.IStandaloneModel, 0)
if len(keys.ManagerId) > 0 {
obj, err := CloudproviderManager.FetchById(keys.ManagerId)
if err != nil {
return errors.Wrap(err, "CloudproviderManager.FetchById")
}
rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel))
} else if len(keys.AccountId) > 0 {
obj, err := CloudaccountManager.FetchById(keys.AccountId)
if err != nil {
return errors.Wrap(err, "CloudaccountManager.FetchById")
}
rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel))
}
if len(keys.ZoneId) > 0 {
obj, err := ZoneManager.FetchById(keys.ZoneId)
if err != nil {
return errors.Wrap(err, "ZoneManager.FetchById")
}
rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel))
} else if len(keys.RegionId) > 0 {
obj, err := CloudregionManager.FetchById(keys.RegionId)
if err != nil {
return errors.Wrap(err, "CloudregionManager.FetchById")
}
rangeObjs = append(rangeObjs, obj.(db.IStandaloneModel))
}
var hypervisors []string
if len(keys.Hypervisor) > 0 {
hypervisors = []string{keys.Hypervisor}
}
var providers []string
if len(keys.Provider) > 0 {
providers = []string{keys.Provider}
}
var brands []string
if len(keys.Brand) > 0 {
brands = []string{keys.Brand}
}
diskSize := totalDiskSize(scope, ownerId, tristate.None, tristate.None, false, false, rangeObjs, providers, brands, keys.CloudEnv, hypervisors)
guest := usageTotalGuestResouceCount(scope, ownerId, rangeObjs, nil, hypervisors, false, false, nil, nil, providers, brands, keys.CloudEnv)
self.Count = guest.TotalGuestCount
self.Cpu = guest.TotalCpuCount
self.Memory = guest.TotalMemSize
self.Storage = diskSize
self.Group = 0
self.IsolatedDevice = guest.TotalIsolatedCount
return nil
}
func (self *SQuota) ResetNegative() {
if self.Count < 0 {
self.Count = 0
}
if self.Cpu < 0 {
self.Cpu = 0
}
if self.Memory < 0 {
self.Memory = 0
}
if self.Storage < 0 {
self.Storage = 0
}
if self.Group < 0 {
self.Group = 0
}
if self.IsolatedDevice < 0 {
self.IsolatedDevice = 0
}
}
func (self *SQuota) IsEmpty() bool {
if self.Count > 0 {
return false
}
if self.Cpu > 0 {
return false
}
if self.Memory > 0 {
return false
}
if self.Storage > 0 {
return false
}
if self.Group > 0 {
return false
}
if self.IsolatedDevice > 0 {
return false
}
return true
}
func (self *SQuota) Add(quota quotas.IQuota) {
squota := quota.(*SQuota)
self.Count = self.Count + quotas.NonNegative(squota.Count)
self.Cpu = self.Cpu + quotas.NonNegative(squota.Cpu)
self.Memory = self.Memory + quotas.NonNegative(squota.Memory)
self.Storage = self.Storage + quotas.NonNegative(squota.Storage)
self.Group = self.Group + quotas.NonNegative(squota.Group)
self.IsolatedDevice = self.IsolatedDevice + quotas.NonNegative(squota.IsolatedDevice)
}
func nonNegative(val int) int {
return quotas.NonNegative(val)
}
func (self *SQuota) Sub(quota quotas.IQuota) {
squota := quota.(*SQuota)
self.Count = nonNegative(self.Count - squota.Count)
self.Cpu = nonNegative(self.Cpu - squota.Cpu)
self.Memory = nonNegative(self.Memory - squota.Memory)
self.Storage = nonNegative(self.Storage - squota.Storage)
self.Group = nonNegative(self.Group - squota.Group)
self.IsolatedDevice = nonNegative(self.IsolatedDevice - squota.IsolatedDevice)
}
func (self *SQuota) Allocable(request quotas.IQuota) int {
squota := request.(*SQuota)
cnt := -1
if self.Count >= 0 && squota.Count > 0 && (cnt < 0 || cnt > self.Count/squota.Count) {
cnt = self.Count / squota.Count
}
if self.Cpu >= 0 && squota.Cpu > 0 && (cnt < 0 || cnt > self.Cpu/squota.Cpu) {
cnt = self.Cpu / squota.Cpu
}
if self.Memory >= 0 && squota.Memory > 0 && (cnt < 0 || cnt > self.Memory/squota.Memory) {
cnt = self.Memory / squota.Memory
}
if self.Storage >= 0 && squota.Storage > 0 && (cnt < 0 || cnt > self.Storage/squota.Storage) {
cnt = self.Storage / squota.Storage
}
if self.Group >= 0 && squota.Group > 0 && (cnt < 0 || cnt > self.Group/squota.Group) {
cnt = self.Group / squota.Group
}
if self.IsolatedDevice >= 0 && squota.IsolatedDevice > 0 && (cnt < 0 || cnt > self.IsolatedDevice/squota.IsolatedDevice) {
cnt = self.IsolatedDevice / squota.IsolatedDevice
}
return cnt
}
func (self *SQuota) Update(quota quotas.IQuota) {
squota := quota.(*SQuota)
if squota.Count > 0 {
self.Count = squota.Count
}
if squota.Cpu > 0 {
self.Cpu = squota.Cpu
}
if squota.Memory > 0 {
self.Memory = squota.Memory
}
if squota.Storage > 0 {
self.Storage = squota.Storage
}
if squota.Group > 0 {
self.Group = squota.Group
}
if squota.IsolatedDevice > 0 {
self.IsolatedDevice = squota.IsolatedDevice
}
}
func (used *SQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error {
err := quotas.NewOutOfQuotaError()
sreq := request.(*SQuota)
squota := quota.(*SQuota)
if quotas.Exceed(used.Count, sreq.Count, squota.Count) {
err.Add("count", squota.Count, used.Count, sreq.Count)
}
if quotas.Exceed(used.Cpu, sreq.Cpu, squota.Cpu) {
err.Add("cpu", squota.Cpu, used.Cpu, sreq.Cpu)
}
if quotas.Exceed(used.Memory, sreq.Memory, squota.Memory) {
err.Add("memory", squota.Memory, used.Memory, sreq.Memory)
}
if quotas.Exceed(used.Storage, sreq.Storage, squota.Storage) {
err.Add("storage", squota.Storage, used.Storage, sreq.Storage)
}
if quotas.Exceed(used.Group, sreq.Group, squota.Group) {
err.Add("group", squota.Group, used.Group, sreq.Group)
}
if quotas.Exceed(used.IsolatedDevice, sreq.IsolatedDevice, squota.IsolatedDevice) {
err.Add("isolated_device", squota.IsolatedDevice, used.IsolatedDevice, sreq.IsolatedDevice)
}
if err.IsError() {
return err
} else {
return nil
}
}
func keyName(prefix, name string) string {
if len(prefix) > 0 {
return fmt.Sprintf("%s.%s", prefix, name)
} else {
return name
}
}
func (self *SQuota) ToJSON(prefix string) jsonutils.JSONObject {
ret := jsonutils.NewDict()
ret.Add(jsonutils.NewInt(int64(self.Count)), keyName(prefix, "count"))
ret.Add(jsonutils.NewInt(int64(self.Cpu)), keyName(prefix, "cpu"))
ret.Add(jsonutils.NewInt(int64(self.Memory)), keyName(prefix, "memory"))
ret.Add(jsonutils.NewInt(int64(self.Storage)), keyName(prefix, "storage"))
ret.Add(jsonutils.NewInt(int64(self.Group)), keyName(prefix, "group"))
ret.Add(jsonutils.NewInt(int64(self.IsolatedDevice)), keyName(prefix, "isolated_device"))
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 := utils.FetchDomainNames(ctx, idMap[field])
if err != nil {
return nil, errors.Wrap(err, "utils.FetchDomainNames")
}
idMap[field] = fieldIdMap
case "tenant_id":
fieldIdMap, err := utils.FetchTenantNames(ctx, idMap[field])
if err != nil {
return nil, errors.Wrap(err, "utils.FetchTenantNames")
}
idMap[field] = fieldIdMap
case "region_id":
fieldIdMap, err := fetchRegionNames(idMap[field])
if err != nil {
return nil, errors.Wrap(err, "fetchRegionNames")
}
idMap[field] = fieldIdMap
case "zone_id":
fieldIdMap, err := fetchZoneNames(idMap[field])
if err != nil {
return nil, errors.Wrap(err, "fetchZoneNames")
}
idMap[field] = fieldIdMap
case "account_id":
fieldIdMap, err := fetchAccountNames(idMap[field])
if err != nil {
return nil, errors.Wrap(err, "fetchAccountNames")
}
idMap[field] = fieldIdMap
case "manager_id":
fieldIdMap, err := fetchManagerNames(idMap[field])
if err != nil {
return nil, errors.Wrap(err, "fetchManagerNames")
}
idMap[field] = fieldIdMap
}
}
return idMap, nil
}
func fetchRegionNames(idMap map[string]string) (map[string]string, error) {
return db.FetchIdNameMap(CloudregionManager, idMap)
}
func fetchZoneNames(idMap map[string]string) (map[string]string, error) {
return db.FetchIdNameMap(ZoneManager, idMap)
}
func fetchAccountNames(idMap map[string]string) (map[string]string, error) {
return db.FetchIdNameMap(CloudaccountManager, idMap)
}
func fetchManagerNames(idMap map[string]string) (map[string]string, error) {
return db.FetchIdNameMap(CloudproviderManager, idMap)
}
type SComputeResourceKeys struct {
quotas.SZonalCloudResourceKeys
// 主机配额适用的主机类型,参考主机List的Hypervisor列表
Hypervisor string `width:"16" charset:"ascii" nullable:"false" primary:"true" list:"user"`
}
func (k SComputeResourceKeys) Fields() []string {
return append(k.SZonalCloudResourceKeys.Fields(), "hypervisor")
}
func (k SComputeResourceKeys) Values() []string {
return append(k.SZonalCloudResourceKeys.Values(), k.Hypervisor)
}
func (k1 SComputeResourceKeys) Compare(ik quotas.IQuotaKeys) int {
k2 := ik.(SComputeResourceKeys)
r := k1.SZonalCloudResourceKeys.Compare(k2.SZonalCloudResourceKeys)
if r != 0 {
return r
}
if k1.Hypervisor < k2.Hypervisor {
return -1
} else if k1.Hypervisor > k2.Hypervisor {
return 1
}
return 0
}
func fetchCloudQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, manager *SCloudprovider) quotas.SCloudResourceKeys {
keys := quotas.SCloudResourceKeys{}
keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId)
if manager != nil {
account := manager.GetCloudaccount()
keys.Provider = account.Provider
keys.Brand = account.Brand
keys.CloudEnv = account.GetCloudEnv()
keys.AccountId = account.Id
keys.ManagerId = manager.Id
} else {
keys.Provider = api.CLOUD_PROVIDER_ONECLOUD
keys.Brand = api.ONECLOUD_BRAND_ONECLOUD
keys.CloudEnv = api.CLOUD_ENV_ON_PREMISE
}
return keys
}
func fetchRegionalQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, region *SCloudregion, manager *SCloudprovider) quotas.SRegionalCloudResourceKeys {
keys := quotas.SRegionalCloudResourceKeys{}
keys.SCloudResourceKeys = fetchCloudQuotaKeys(scope, ownerId, manager)
if region != nil {
keys.RegionId = region.Id
}
return keys
}
func fetchZonalQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, zone *SZone, manager *SCloudprovider) quotas.SZonalCloudResourceKeys {
keys := quotas.SZonalCloudResourceKeys{}
keys.SCloudResourceKeys = fetchCloudQuotaKeys(scope, ownerId, manager)
if zone != nil {
keys.RegionId = zone.CloudregionId
keys.ZoneId = zone.Id
}
return keys
}
func fetchComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, zone *SZone, manager *SCloudprovider, hypervisor string) SComputeResourceKeys {
keys := SComputeResourceKeys{}
keys.SZonalCloudResourceKeys = fetchZonalQuotaKeys(scope, ownerId, zone, manager)
keys.Hypervisor = hypervisor
return keys
}