Files
cloudpods/pkg/compute/models/skus.go
T

1557 lines
50 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"
"crypto/md5"
"database/sql"
"fmt"
"math"
"sort"
"strconv"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/compare"
"yunion.io/x/pkg/utils"
"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/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/hashcache"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
var Cache *hashcache.Cache
type SServerSkuManager struct {
db.SStatusStandaloneResourceBaseManager
SCloudregionResourceBaseManager
SZoneResourceBaseManager
}
var ServerSkuManager *SServerSkuManager
func init() {
ServerSkuManager = &SServerSkuManager{
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
SServerSku{},
"serverskus_tbl",
"serversku",
"serverskus",
),
}
ServerSkuManager.NameRequireAscii = false
ServerSkuManager.SetVirtualObject(ServerSkuManager)
Cache = hashcache.NewCache(2048, time.Second*300)
}
// SServerSku 实际对应的是instance type清单. 这里的Sku实际指的是instance type。
type SServerSku struct {
db.SStatusStandaloneResourceBase
db.SExternalizedResourceBase
SCloudregionResourceBase
SZoneResourceBase
Enabled bool `nullable:"true" list:"user" create:"optional"`
// SkuId string `width:"64" charset:"ascii" nullable:"false" list:"user" create:"admin_required"` // x2.large
InstanceTypeFamily string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"` // x2
InstanceTypeCategory string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" update:"admin"` // 通用型
LocalCategory string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" update:"admin" default:""` // 记录本地分类
PrepaidStatus string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" default:"available"` // 预付费资源状态 available|soldout
PostpaidStatus string `width:"32" charset:"utf8" nullable:"true" list:"user" create:"admin_optional" default:"available"` // 按需付费资源状态 available|soldout
CpuCoreCount int `nullable:"false" list:"user" create:"admin_required"`
MemorySizeMB int `nullable:"false" list:"user" create:"admin_required"`
OsName string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin" default:"Any"` // Windows|Linux|Any
SysDiskResizable tristate.TriState `default:"true" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
SysDiskType string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
SysDiskMinSizeGB int `nullable:"true" list:"user" create:"admin_optional" update:"admin"` // not required。 windows比较新的版本都是50G左右。
SysDiskMaxSizeGB int `nullable:"true" list:"user" create:"admin_optional" update:"admin"` // not required
AttachedDiskType string `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
AttachedDiskSizeGB int `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
AttachedDiskCount int `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
DataDiskTypes string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
DataDiskMaxCount int `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
NicType string `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
NicMaxCount int `default:"1" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
GpuAttachable tristate.TriState `default:"true" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
GpuSpec string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
GpuCount int `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
GpuMaxCount int `nullable:"true" list:"user" create:"admin_optional" update:"admin"`
Provider string `width:"64" charset:"ascii" nullable:"true" list:"user" default:"OneCloud" create:"admin_optional"`
}
type SInstanceSpecQueryParams struct {
Provider string
PublicCloud bool
ZoneId string
PostpaidStatus string
PrepaidStatus string
IngoreCache bool
}
func (self *SInstanceSpecQueryParams) GetCacheKey() string {
hashStr := fmt.Sprintf("%s:%t:%s:%s:%s", self.Provider, self.PublicCloud, self.ZoneId, self.PostpaidStatus, self.PrepaidStatus)
_md5 := md5.Sum([]byte(hashStr))
return "InstanceSpecs_" + fmt.Sprintf("%x", _md5)
}
func NewInstanceSpecQueryParams(query jsonutils.JSONObject) *SInstanceSpecQueryParams {
zone := jsonutils.GetAnyString(query, []string{"zone", "zone_id"})
postpaid, _ := query.GetString("postpaid_status")
prepaid, _ := query.GetString("prepaid_status")
ingore_cache, _ := query.Bool("ingore_cache")
provider := normalizeProvider(jsonutils.GetAnyString(query, []string{"provider"}))
public_cloud, _ := query.Bool("public_cloud")
if utils.IsInStringArray(provider, cloudprovider.GetPublicProviders()) {
public_cloud = true
}
params := &SInstanceSpecQueryParams{
Provider: provider,
PublicCloud: public_cloud,
ZoneId: zone,
PostpaidStatus: postpaid,
PrepaidStatus: prepaid,
IngoreCache: ingore_cache,
}
return params
}
func sliceToJsonObject(items []int) jsonutils.JSONObject {
sort.Slice(items, func(i, j int) bool {
if items[i] < items[j] {
return true
}
return false
})
ret := jsonutils.NewArray()
for _, item := range items {
ret.Add(jsonutils.NewInt(int64(item)))
}
return ret
}
func inWhiteList(provider string) bool {
// 私有云套餐也允许更新删除
return provider == api.CLOUD_PROVIDER_ONECLOUD || utils.IsInStringArray(provider, api.PRIVATE_CLOUD_PROVIDERS)
}
func genInstanceType(family string, cpu, mem_mb int64) (string, error) {
if cpu <= 0 {
return "", fmt.Errorf("cpu_core_count should great than zero")
}
if mem_mb <= 0 || mem_mb%1024 != 0 {
return "", fmt.Errorf("memory_size_mb should great than zero. and should be integral multiple of 1024")
}
return fmt.Sprintf("ecs.%s.c%dm%d", family, cpu, mem_mb/1024), nil
}
func skuRelatedGuestCount(self *SServerSku) (int, error) {
var q *sqlchemy.SQuery
if len(self.ZoneId) > 0 {
hostTable := HostManager.Query().SubQuery()
guestTable := GuestManager.Query().SubQuery()
q = guestTable.Query().Join(hostTable, sqlchemy.Equals(hostTable.Field("id"), guestTable.Field("host_id")))
q = q.Filter(sqlchemy.Equals(hostTable.Field("zone_id"), self.ZoneId))
} else {
q = GuestManager.Query()
}
q = q.Equals("instance_type", self.GetName())
return q.CountWithError()
}
func getNameAndExtId(resId string, manager db.IModelManager) (string, string, error) {
nKey := resId + ".Name"
eKey := resId + ".ExtId"
name := Cache.Get(nKey)
extId := Cache.Get(eKey)
if name == nil || extId == nil {
imodel, err := manager.FetchById(resId)
if err != nil {
return "", "", err
}
_name := imodel.GetName()
segs := strings.Split(_name, " ")
if len(segs) > 1 {
name = strings.Join(segs[1:], " ")
} else {
name = _name
}
_extId := ""
if region, ok := imodel.(*SCloudregion); ok {
_extId = region.ExternalId
} else if zone, ok := imodel.(*SZone); ok {
_extId = zone.ExternalId
} else {
return "", "", fmt.Errorf("res %s not a region/zone resource", resId)
}
segs = strings.Split(_extId, "/")
if len(segs) > 0 {
extId = segs[len(segs)-1]
}
Cache.Set(nKey, name)
Cache.Set(eKey, extId)
}
return name.(string), extId.(string), nil
}
func (self *SServerSkuManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return true
}
func (self SServerSku) GetGlobalId() string {
return self.ExternalId
}
func (self *SServerSku) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return true
}
func (self *SServerSku) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isList bool) (api.ServerSkuDetails, error) {
return api.ServerSkuDetails{}, nil
}
func (self *SServerSku) getTotalGuestCount() int {
// count
var count int
countKey := self.GetId() + ".total_guest_count"
v := Cache.Get(countKey)
if v == nil {
count, _ = skuRelatedGuestCount(self)
Cache.Set(countKey, count)
} else {
count = v.(int)
}
return count
}
func (manager *SServerSkuManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.ServerSkuDetails {
rows := make([]api.ServerSkuDetails, len(objs))
stdRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
zoneRows := manager.SZoneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.ServerSkuDetails{
StatusStandaloneResourceDetails: stdRows[i],
ZoneResourceInfo: zoneRows[i],
}
if len(rows[i].Zone) == 0 {
rows[i].CloudregionResourceInfo = regRows[i]
}
rows[i].TotalGuestCount = objs[i].(*SServerSku).getTotalGuestCount()
}
return rows
}
func (manager *SServerSkuManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowCreate(userCred, manager)
}
func (self *SServerSkuManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ServerSkuCreateInput) (*jsonutils.JSONDict, error) {
if len(input.Cloudregion) == 0 {
input.Cloudregion = api.DEFAULT_REGION_ID
}
region, err := CloudregionManager.FetchByIdOrName(nil, input.Cloudregion)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError("failed to found cloudregion %s", input.Cloudregion)
}
return nil, httperrors.NewGeneralError(err)
}
input.CloudregionId = region.GetId()
if len(input.Zone) > 0 {
zone, err := ZoneManager.FetchByIdOrName(nil, input.Zone)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError("failed to found zone %s", input.Zone)
}
return nil, httperrors.NewGeneralError(err)
}
input.ZoneId = zone.GetId()
}
if input.CpuCoreCount < 1 || input.CpuCoreCount > 256 {
return nil, httperrors.NewOutOfRangeError("cpu_core_count should be range of 1~256")
}
if input.MemorySizeMB < 512 || input.MemorySizeMB > 1024*512 {
return nil, httperrors.NewOutOfRangeError("memory_size_mb, shoud be range of 512~%d", 1024*512)
}
if len(input.InstanceTypeCategory) == 0 {
input.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
}
if !utils.IsInStringArray(input.InstanceTypeCategory, api.SKU_FAMILIES) {
return nil, httperrors.NewInputParameterError("instance_type_category shoud be one of %s", api.SKU_FAMILIES)
}
if input.Enabled == nil {
enabled := true
input.Enabled = &enabled
}
input.Provider = api.CLOUD_PROVIDER_ONECLOUD
input.Status = api.SkuStatusReady
input.LocalCategory = input.InstanceTypeCategory
input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory]
if len(input.Name) == 0 {
// 格式 ecs.g1.c1m1
input.Name, err = genInstanceType(input.InstanceTypeFamily, input.CpuCoreCount, input.MemorySizeMB)
if err != nil {
return nil, httperrors.NewInputParameterError(err.Error())
}
q := self.Query().Equals("name", input.Name)
count, err := q.CountWithError()
if err != nil {
return nil, httperrors.NewGeneralError(fmt.Errorf("checkout server sku name duplicate error: %v", err))
}
if count > 0 {
return nil, httperrors.NewDuplicateResourceError("Duplicate sku %s", input.Name)
}
}
input.StatusStandaloneResourceCreateInput, err = self.SStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusStandaloneResourceCreateInput)
if err != nil {
return nil, err
}
return input.JSON(input), nil
}
func (self *SServerSku) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
self.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
if self.Provider != api.CLOUD_PROVIDER_ONECLOUD {
_, err := db.Update(self, func() error {
self.CloudregionId = ""
self.ZoneId = ""
return nil
})
if err != nil {
log.Warningf("failed to update %s sku %s cloudregion and zone info: %v", self.Provider, self.Name, err)
}
}
}
func (self *SServerSku) GetPrivateCloudproviders() ([]SCloudprovider, error) {
providers := []SCloudprovider{}
q := CloudproviderManager.Query().In("provider", CloudproviderManager.GetPrivateProviderProvidersQuery())
err := db.FetchModelObjects(CloudproviderManager, q, &providers)
if err != nil {
return nil, err
}
return providers, nil
}
func (self *SServerSku) GetRegion() (*SCloudregion, error) {
regionObj, err := CloudregionManager.FetchById(self.CloudregionId)
if err != nil {
return nil, err
}
return regionObj.(*SCloudregion), nil
}
func (self *SServerSkuManager) ClearSchedDescCache(wait bool) error {
s := auth.GetAdminSession(context.Background(), options.Options.Region, "")
_, err := modules.SchedManager.SyncSku(s, true)
if err != nil {
return errors.Wrapf(err, "chedManager.SyncSku")
}
return nil
}
func (self *SServerSkuManager) FetchByZoneExtId(zoneExtId string, name string) (db.IModel, error) {
zoneObj, err := db.FetchByExternalId(ZoneManager, zoneExtId)
if err != nil {
return nil, err
}
return self.FetchByZoneId(zoneObj.GetId(), name)
}
func (self *SServerSkuManager) FetchByZoneId(zoneId string, name string) (db.IModel, error) {
q := self.Query().Equals("zone_id", zoneId).Equals("name", name)
count, err := q.CountWithError()
if err != nil {
return nil, err
}
if count == 1 {
obj, err := db.NewModelObject(self)
if err != nil {
return nil, err
}
err = q.First(obj)
if err != nil {
return nil, err
} else {
return obj.(db.IStandaloneModel), nil
}
} else if count > 1 {
return nil, sqlchemy.ErrDuplicateEntry
} else {
return nil, sql.ErrNoRows
}
}
func (self *SServerSkuManager) AllowGetPropertyInstanceSpecs(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return true
}
// 四舍五入
func round(n int, step int) int {
q := float64(n) / float64(step)
return int(math.Floor(q+0.5)) * step
}
// 内存按GB取整
func roundMem(n int) int {
if n <= 512 {
return 512
}
return round(n, 1024)
}
// step必须是偶数
func interval(n int, step int) (int, int) {
r := round(n, step)
start := r - step/2
end := r + step/2
return start, end
}
// 计算内存所在区间范围
func intervalMem(n int) (int, int) {
if n <= 512 {
return 0, 512
}
return interval(n, 1024)
}
func normalizeProvider(provider string) string {
if len(provider) == 0 {
return provider
}
for _, p := range api.CLOUD_PROVIDERS {
if strings.ToLower(p) == strings.ToLower(provider) {
return p
}
}
return provider
}
func networkUsableRegionQueries(f sqlchemy.IQueryField) []sqlchemy.ICondition {
iconditions := make([]sqlchemy.ICondition, 0)
providers := CloudproviderManager.Query().SubQuery()
networks := NetworkManager.Query().SubQuery()
wires := WireManager.Query().SubQuery()
vpcs := VpcManager.Query().SubQuery()
sq := vpcs.Query(sqlchemy.DISTINCT("cloudregion_id", vpcs.Field("cloudregion_id")))
sq = sq.Join(wires, sqlchemy.Equals(vpcs.Field("id"), wires.Field("vpc_id")))
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id")))
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), api.NETWORK_STATUS_AVAILABLE))
sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled")))
sq = sq.Filter(sqlchemy.In(providers.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS))
sq = sq.Filter(sqlchemy.In(providers.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS))
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE))
sq2 := vpcs.Query(sqlchemy.DISTINCT("cloudregion_id", vpcs.Field("cloudregion_id")))
sq2 = sq2.Join(wires, sqlchemy.Equals(vpcs.Field("id"), wires.Field("vpc_id")))
sq2 = sq2.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
sq2 = sq2.Filter(sqlchemy.Equals(networks.Field("status"), api.NETWORK_STATUS_AVAILABLE))
sq2 = sq2.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id")))
sq2 = sq2.Filter(sqlchemy.Equals(vpcs.Field("status"), api.VPC_STATUS_AVAILABLE))
iconditions = append(iconditions, sqlchemy.In(f, sq.SubQuery()))
iconditions = append(iconditions, sqlchemy.In(f, sq2.SubQuery()))
return iconditions
}
func usableFilter(q *sqlchemy.SQuery, public_cloud bool) *sqlchemy.SQuery {
// 过滤出公有云provider状态健康的sku
if public_cloud {
providerTable := CloudproviderManager.Query().SubQuery()
providerRegionTable := CloudproviderRegionManager.Query().SubQuery()
subq := providerRegionTable.Query(sqlchemy.DISTINCT("cloudregion_id", providerRegionTable.Field("cloudregion_id")))
subq = subq.Join(providerTable, sqlchemy.Equals(providerRegionTable.Field("cloudprovider_id"), providerTable.Field("id")))
subq = subq.Filter(sqlchemy.IsTrue(providerTable.Field("enabled")))
subq = subq.Filter(sqlchemy.In(providerTable.Field("status"), api.CLOUD_PROVIDER_VALID_STATUS))
subq = subq.Filter(sqlchemy.In(providerTable.Field("health_status"), api.CLOUD_PROVIDER_VALID_HEALTH_STATUS))
q = q.Filter(sqlchemy.In(q.Field("cloudregion_id"), subq.SubQuery()))
}
// 过滤出network usable的sku
if public_cloud {
iconditions := NetworkUsableZoneQueries(q.Field("zone_id"), true, true)
iconditions = append(iconditions, sqlchemy.IsNullOrEmpty(q.Field("zone_id"))) //Azure的zone_id可能为空
q = q.Filter(sqlchemy.OR(iconditions...))
} else {
// 本地IDC sku 只定义到region层级, zone id 为空.因此只能按region查询
iconditions := networkUsableRegionQueries(q.Field("cloudregion_id"))
// 私有云 sku region及zone为空
iconditions = append(iconditions, sqlchemy.IsNullOrEmpty(q.Field("cloudregion_id")))
q = q.Filter(sqlchemy.OR(iconditions...))
}
return q
}
func (manager *SServerSkuManager) GetPropertyInstanceSpecs(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
listQuery := api.ServerSkuListInput{}
err := query.Unmarshal(&listQuery)
if err != nil {
return nil, errors.Wrap(err, "query.Unmarshal")
}
q, err := manager.ListItemFilter(ctx, manager.Query(), userCred, listQuery)
if err != nil {
return nil, errors.Wrap(err, "manager.ListItemFilter")
}
skus := make([]SServerSku, 0)
q = q.GroupBy(q.Field("cpu_core_count"), q.Field("memory_size_mb"))
q = q.Asc(q.Field("cpu_core_count"), q.Field("memory_size_mb"))
err = db.FetchModelObjects(manager, q, &skus)
if err != nil {
return nil, httperrors.NewBadRequestError("instance specs list query error")
}
cpus := jsonutils.NewArray()
mems_mb := []int{}
cpu_mems_mb := map[string][]int{}
mems := map[int]bool{}
oc := 0
for i := range skus {
nc := skus[i].CpuCoreCount
nm := roundMem(skus[i].MemorySizeMB) // 内存按GB取整
if nc > oc {
cpus.Add(jsonutils.NewInt(int64(nc)))
oc = nc
}
if _, exists := mems[nm]; !exists {
mems_mb = append(mems_mb, nm)
mems[nm] = true
}
k := strconv.Itoa(nc)
if _, exists := cpu_mems_mb[k]; !exists {
cpu_mems_mb[k] = []int{nm}
} else {
idx := len(cpu_mems_mb[k]) - 1
if cpu_mems_mb[k][idx] != nm {
cpu_mems_mb[k] = append(cpu_mems_mb[k], nm)
}
}
}
ret := jsonutils.NewDict()
ret.Add(cpus, "cpus")
ret.Add(sliceToJsonObject(mems_mb), "mems_mb")
r_obj := jsonutils.Marshal(&cpu_mems_mb)
ret.Add(r_obj, "cpu_mems_mb")
return ret, nil
}
func (self *SServerSku) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool {
return inWhiteList(self.Provider) && db.IsAdminAllowUpdate(userCred, self)
}
func (self *SServerSku) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSkuUpdateInput) (api.ServerSkuUpdateInput, error) {
// 目前不允许改sku信息
if !inWhiteList(self.Provider) {
return input, httperrors.NewForbiddenError("can not update instance_type for public cloud %s", self.Provider)
}
if len(input.Name) > 0 {
return input, httperrors.NewUnsupportOperationError("Cannot change server sku name")
}
var err error
input.StatusStandaloneResourceBaseUpdateInput, err = self.SStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.StatusStandaloneResourceBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "SStatusStandaloneResourceBase.ValidateUpdateData")
}
return input, nil
}
func (self *SServerSku) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return inWhiteList(self.Provider) && db.IsAdminAllowDelete(userCred, self)
}
func (self *SServerSku) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
log.Infof("SServerSku delete do nothing")
return nil
}
func (self *SServerSku) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
ServerSkuManager.ClearSchedDescCache(true)
return self.SStatusStandaloneResourceBase.Delete(ctx, userCred)
}
func (self *SServerSku) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return self.StartServerSkuDeleteTask(ctx, userCred, jsonutils.QueryBoolean(data, "purge", false), "")
}
func (self *SServerSku) StartServerSkuDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, purge bool, parentTaskId string) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewBool(purge), "purge")
task, err := taskman.TaskManager.NewTask(ctx, "ServerSkuDeleteTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
log.Errorf("newTask ServerSkuDeleteTask fail %s", err)
return err
}
self.SetStatus(userCred, api.SkuStatusDeleting, "start to delete")
task.ScheduleRun(nil)
return nil
}
func (self *SServerSku) ValidateDeleteCondition(ctx context.Context) error {
serverCount, err := skuRelatedGuestCount(self)
if err != nil {
return httperrors.NewInternalServerError("check instance")
}
if serverCount > 0 {
return httperrors.NewNotEmptyError("now allow to delete inuse instance_type.please remove related servers first: %s", self.Name)
}
if !inWhiteList(self.Provider) {
return httperrors.NewForbiddenError("not allow to delete public cloud instance_type: %s", self.Name)
}
/*count := GuestManager.Query().Equals("instance_type", self.Id).Count()
if count > 0 {
return httperrors.NewNotEmptyError("instance_type used by servers")
}*/
return nil
}
func (self *SServerSku) GetZoneExternalId() (string, error) {
zoneObj, err := ZoneManager.FetchById(self.ZoneId)
if err != nil {
return "", err
}
zone := zoneObj.(*SZone)
return zone.GetExternalId(), nil
}
func listItemDomainFilter(q *sqlchemy.SQuery, providers []string, domainId string) *sqlchemy.SQuery {
// CLOUD_PROVIDER_ONECLOUD 没有对应的cloudaccount
if len(domainId) > 0 {
if len(providers) >= 1 && !utils.IsInStringArray(api.CLOUD_PROVIDER_ONECLOUD, providers) {
// 明确指定只查询公有云provider的情况,只查询公有云skus
q = q.In("provider", getDomainManagerProviderSubq(domainId))
} else if len(providers) == 1 && utils.IsInStringArray(api.CLOUD_PROVIDER_ONECLOUD, providers) {
// 明确指定只查询私有云provider的情况
} else {
// 公有云skus & 私有云skus 混合查询
publicSkusQ := sqlchemy.In(q.Field("provider"), getDomainManagerProviderSubq(domainId))
privateSkusQ := sqlchemy.Equals(q.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD)
q = q.Filter(sqlchemy.OR(publicSkusQ, privateSkusQ))
}
}
return q
}
// 主机套餐规格列表
func (manager *SServerSkuManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.ServerSkuListInput,
) (*sqlchemy.SQuery, error) {
publicCloud := false
cloudEnvStr := query.CloudEnv
if cloudEnvStr == api.CLOUD_ENV_PUBLIC_CLOUD {
publicCloud = true
q = q.Filter(sqlchemy.In(q.Field("provider"), CloudproviderManager.GetPublicProviderProvidersQuery()))
}
if cloudEnvStr == api.CLOUD_ENV_PRIVATE_CLOUD {
q = q.Filter(sqlchemy.In(q.Field("provider"), CloudproviderManager.GetPrivateProviderProvidersQuery()))
}
if cloudEnvStr == api.CLOUD_ENV_ON_PREMISE {
q = q.Filter(
sqlchemy.OR(
sqlchemy.Equals(q.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD),
sqlchemy.In(q.Field("provider"), CloudproviderManager.GetOnPremiseProviderProvidersQuery()),
),
)
}
if cloudEnvStr == api.CLOUD_ENV_PRIVATE_ON_PREMISE {
q = q.Filter(sqlchemy.OR(
sqlchemy.In(q.Field("provider"), CloudproviderManager.GetPrivateProviderProvidersQuery()), //私有云
sqlchemy.Equals(q.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD), //本地IDC
),
)
}
if domainStr := query.ProjectDomainId; len(domainStr) > 0 {
domain, err := db.TenantCacheManager.FetchDomainByIdOrName(context.Background(), domainStr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2("domains", domainStr)
}
return nil, httperrors.NewGeneralError(err)
}
query.ProjectDomainId = domain.GetId()
}
q = listItemDomainFilter(q, query.Providers, query.ProjectDomainId)
providers := query.Providers
if len(providers) > 0 {
q = q.Filter(sqlchemy.In(q.Field("provider"), providers))
if len(providers) == 1 && utils.IsInStringArray(providers[0], cloudprovider.GetPublicProviders()) {
publicCloud = true
}
}
brands := query.Brands
if len(brands) > 0 {
q = q.Filter(sqlchemy.In(q.Field("brand"), brands))
}
q, err := manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemFilter")
}
if query.Usable != nil && *query.Usable {
q = usableFilter(q, publicCloud)
q = q.IsTrue("enabled")
}
zoneStr := query.ZoneId
if len(zoneStr) > 0 {
_zone, err := ZoneManager.FetchByIdOrName(userCred, zoneStr)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2("zone", zoneStr)
}
return nil, httperrors.NewGeneralError(err)
}
zone := _zone.(*SZone)
region := zone.GetRegion()
if region == nil {
return nil, httperrors.NewResourceNotFoundError("failed to find cloudregion for zone %s(%s)", zone.Name, zone.Id)
}
//OneCloud忽略zone参数
if region.Provider == api.CLOUD_PROVIDER_ONECLOUD {
q = q.Equals("cloudregion_id", region.Id)
} else {
q = q.Equals("zone_id", zone.Id)
}
}
q, err = managedResourceFilterByRegion(q, query.RegionalFilterListInput, "", nil)
if err != nil {
return nil, errors.Wrap(err, "managedResourceFilterByRegion")
}
if len(query.PostpaidStatus) > 0 {
q = q.Equals("postpaid_status", query.PostpaidStatus)
}
if len(query.PrepaidStatus) > 0 {
q = q.Equals("prepaid_status", query.PrepaidStatus)
}
// 按区间查询内存, 避免0.75G这样的套餐不好过滤
memSizeMB := query.MemorySizeMb
if memSizeMB > 0 {
s, e := intervalMem(int(memSizeMB))
q = q.GT("memory_size_mb", s)
q = q.LE("memory_size_mb", e)
}
if len(query.CpuCoreCount) > 0 {
q = q.In("cpu_core_count", query.CpuCoreCount)
}
return q, err
}
func (manager *SServerSkuManager) OrderByExtraFields(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.ServerSkuListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields")
}
q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput)
if err != nil {
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields")
}
return q, nil
}
func (manager *SServerSkuManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (manager *SServerSkuManager) GetMatchedSku(regionId string, cpu int64, memMB int64) (*SServerSku, error) {
ret := &SServerSku{}
_region, err := CloudregionManager.FetchById(regionId)
if err != nil {
return nil, errors.Wrapf(err, "CloudregionManager.FetchById(%s)", regionId)
}
region := _region.(*SCloudregion)
if utils.IsInStringArray(region.Provider, api.PRIVATE_CLOUD_PROVIDERS) {
regionId = api.DEFAULT_REGION_ID
}
q := manager.Query()
q = q.Equals("cpu_core_count", cpu).Equals("memory_size_mb", memMB).Equals("cloudregion_id", regionId).Equals("postpaid_status", api.SkuStatusAvailable)
err = q.First(ret)
if err != nil {
return nil, errors.Wrap(err, "ServerSkuManager.GetMatchedSku")
}
return ret, nil
}
func (manager *SServerSkuManager) FetchSkuByNameAndProvider(name string, provider string, checkConsistency bool) (*SServerSku, error) {
q := manager.Query()
q = q.Equals("name", name)
if utils.IsInStringArray(provider, []string{api.CLOUD_PROVIDER_ONECLOUD, api.CLOUD_PROVIDER_VMWARE}) {
q = q.Filter(
sqlchemy.Equals(q.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD),
)
} else if utils.IsInStringArray(provider, api.PRIVATE_CLOUD_PROVIDERS) {
q = q.Filter(sqlchemy.OR(
sqlchemy.Equals(q.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD),
sqlchemy.Equals(q.Field("provider"), provider),
))
} else {
q = q.Equals("provider", provider)
}
skus := make([]SServerSku, 0)
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
log.Errorf("fetch sku fail %s", err)
return nil, err
}
if len(skus) == 0 {
log.Errorf("no sku found for %s %s", name, provider)
return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), name)
}
if len(skus) == 1 {
return &skus[0], nil
}
if checkConsistency {
for i := 1; i < len(skus); i += 1 {
if skus[i].CpuCoreCount != skus[0].CpuCoreCount || skus[i].MemorySizeMB != skus[0].MemorySizeMB {
log.Errorf("inconsistent sku %s %s", jsonutils.Marshal(&skus[0]), jsonutils.Marshal(&skus[i]))
return nil, httperrors.NewDuplicateResourceError("duplicate instanceType %s", name)
}
}
}
return &skus[0], nil
}
func (manager *SServerSkuManager) GetPublicCloudSkuCount() (int, error) {
q := manager.Query()
q = q.Filter(sqlchemy.In(q.Field("provider"), cloudprovider.GetPublicProviders()))
return q.CountWithError()
}
func (manager *SServerSkuManager) GetSkuCountByRegion(regionId string) (int, error) {
q := manager.Query()
if len(regionId) == 0 {
q = q.IsNotEmpty("cloudregion_id")
} else {
q = q.Equals("cloudregion_id", regionId)
}
return q.CountWithError()
}
func (manager *SServerSkuManager) GetSkuCountByZone(zoneId string) []SServerSku {
skus := []SServerSku{}
q := manager.Query().Equals("zone_id", zoneId)
if err := db.FetchModelObjects(manager, q, &skus); err != nil {
log.Errorf("failed to get skus by zoneId %s error: %v", zoneId, err)
}
return skus
}
func (manager *SServerSkuManager) GetOneCloudSkus() ([]string, error) {
skus := []SServerSku{}
q := manager.Query().Equals("provider", api.CLOUD_PROVIDER_ONECLOUD)
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return nil, err
}
result := []string{}
for _, sku := range skus {
result = append(result, fmt.Sprintf("%d/%d", sku.CpuCoreCount, sku.MemorySizeMB))
}
return result, nil
}
func (manager *SServerSkuManager) GetSkus(provider string, cpu, memMB int) ([]SServerSku, error) {
skus := []SServerSku{}
q := manager.Query()
if provider == api.CLOUD_PROVIDER_ONECLOUD {
providerFilter := sqlchemy.OR(sqlchemy.Equals(q.Field("provider"), provider), sqlchemy.IsNullOrEmpty(q.Field("provider")))
q = q.Equals("cpu_core_count", cpu).Equals("memory_size_mb", memMB).Filter(providerFilter)
} else {
q = q.Equals("cpu_core_count", cpu).Equals("memory_size_mb", memMB).Equals("provider", provider)
}
if err := db.FetchModelObjects(manager, q, &skus); err != nil {
log.Errorf("failed to get skus with provider %s cpu %d mem %d error: %v", provider, cpu, memMB, err)
return nil, err
}
return skus, nil
}
// 删除表中zone not found的记录
func (manager *SServerSkuManager) PendingDeleteInvalidSku() error {
sq := ZoneManager.Query("id").Distinct().SubQuery()
skus := make([]SServerSku, 0)
q := manager.Query()
q = q.NotIn("zone_id", sq).IsNotEmpty("zone_id")
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
log.Errorln(err)
return httperrors.NewInternalServerError("query sku list failed.")
}
for i := range skus {
sku := skus[i]
_, err = db.Update(&sku, func() error {
return sku.MarkDelete()
})
if err != nil {
log.Errorln(err)
return httperrors.NewInternalServerError("delete sku %s failed.", sku.Id)
}
}
return nil
}
func (self *SServerSku) AllowPerformCacheSku(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, self, "cache-sku")
}
func (self *SServerSku) PerformCacheSku(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
cloudregionV := validators.NewModelIdOrNameValidator("cloudregion", "cloudregion", nil)
err := cloudregionV.Validate(data.(*jsonutils.JSONDict))
if err != nil {
return nil, err
}
cloudregion := cloudregionV.Model.(*SCloudregion)
if len(cloudregion.ManagerId) == 0 {
return nil, httperrors.NewInputParameterError("Only support cache sku for private cloud")
}
cloudprovider := cloudregion.GetCloudprovider()
if cloudprovider == nil {
return nil, httperrors.NewInputParameterError("failed to get cloudprovider for region %s(%s)", cloudregion.Name, cloudregion.Id)
}
if !cloudprovider.GetEnabled() {
return nil, httperrors.NewInputParameterError("cloudprovider %s(%s) disabled", cloudprovider.Name, cloudprovider.Id)
}
return nil, self.StartServerSkuCacheTask(ctx, userCred, cloudregion.Id, "")
}
func (self *SServerSku) StartServerSkuCacheTask(ctx context.Context, userCred mcclient.TokenCredential, cloudregionId string, parentTaskId string) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(cloudregionId), "cloudregion_id")
task, err := taskman.TaskManager.NewTask(ctx, "ServerSkuCacheTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (manager *SServerSkuManager) SyncPrivateCloudSkus(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, skus []cloudprovider.ICloudSku) compare.SyncResult {
lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred))
defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred))
syncResult := compare.SyncResult{}
localskus, err := manager.GetOneCloudSkus()
if err != nil {
syncResult.Error(err)
return syncResult
}
for _, sku := range skus {
if !utils.IsInStringArray(fmt.Sprintf("%d/%d", sku.GetCpuCoreCount(), sku.GetMemorySizeMB()), localskus) {
err := manager.newFromCloudSku(ctx, userCred, sku)
if err != nil {
syncResult.AddError(err)
} else {
syncResult.Add()
}
}
}
return syncResult
}
func (self *SServerSku) constructSku(extSku cloudprovider.ICloudSku) {
self.InstanceTypeFamily = extSku.GetInstanceTypeFamily()
self.InstanceTypeCategory = extSku.GetInstanceTypeCategory()
self.PrepaidStatus = extSku.GetPrepaidStatus()
self.PostpaidStatus = extSku.GetPostpaidStatus()
self.CpuCoreCount = extSku.GetCpuCoreCount()
self.MemorySizeMB = extSku.GetMemorySizeMB()
self.OsName = extSku.GetOsName()
self.SysDiskResizable = tristate.NewFromBool(extSku.GetSysDiskResizable())
self.SysDiskType = extSku.GetSysDiskType()
self.SysDiskMinSizeGB = extSku.GetSysDiskMinSizeGB()
self.SysDiskMaxSizeGB = extSku.GetSysDiskMaxSizeGB()
self.AttachedDiskType = extSku.GetAttachedDiskType()
self.AttachedDiskSizeGB = extSku.GetAttachedDiskSizeGB()
self.AttachedDiskCount = extSku.GetAttachedDiskCount()
self.DataDiskTypes = extSku.GetDataDiskTypes()
self.DataDiskMaxCount = extSku.GetDataDiskMaxCount()
self.NicType = extSku.GetNicType()
self.NicMaxCount = extSku.GetNicMaxCount()
self.GpuAttachable = tristate.NewFromBool(extSku.GetGpuAttachable())
self.GpuSpec = extSku.GetGpuSpec()
self.GpuCount = extSku.GetGpuCount()
self.GpuMaxCount = extSku.GetGpuMaxCount()
self.Name = extSku.GetName()
}
func (self *SServerSku) setPrepaidPostpaidStatus(userCred mcclient.TokenCredential, prepaidStatus, postpaidStatus string) error {
if prepaidStatus != self.PrepaidStatus || postpaidStatus != self.PostpaidStatus {
diff, err := db.Update(self, func() error {
self.PrepaidStatus = prepaidStatus
self.PostpaidStatus = postpaidStatus
return nil
})
if err != nil {
return err
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
}
return nil
}
func (manager *SServerSkuManager) newFromCloudSku(ctx context.Context, userCred mcclient.TokenCredential, extSku cloudprovider.ICloudSku) error {
sku := &SServerSku{Provider: api.CLOUD_PROVIDER_ONECLOUD}
sku.SetModelManager(manager, sku)
// 第一次同步新建的套餐是启用状态
sku.Enabled = true
sku.Status = api.SkuStatusReady
sku.constructSku(extSku)
sku.Name = extSku.GetName()
sku.CloudregionId = api.DEFAULT_REGION_ID
sku.SetModelManager(manager, sku)
err := manager.TableSpec().Insert(ctx, sku)
if err != nil {
return errors.Wrapf(err, "newFromCloudSku.Insert")
}
db.OpsLog.LogEvent(sku, db.ACT_CREATE, sku.GetShortDesc(ctx), userCred)
return nil
}
func (manager *SServerSkuManager) newPublicCloudSku(ctx context.Context, userCred mcclient.TokenCredential, extSku SServerSku) error {
extSku.Enabled = true
extSku.Status = api.SkuStatusReady
return manager.TableSpec().Insert(ctx, &extSku)
}
func (self *SServerSku) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, self, "enable")
}
func (self *SServerSku) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !self.Enabled {
_, err := db.Update(self, func() error {
self.Enabled = true
return nil
})
if err != nil {
return nil, errors.Wrap(err, "PerformEnable.db.Update()")
}
db.OpsLog.LogEvent(self, db.ACT_ENABLE, "", userCred)
logclient.AddSimpleActionLog(self, logclient.ACT_ENABLE, nil, userCred, true)
}
return nil, nil
}
func (self *SServerSku) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAllowPerform(rbacutils.ScopeSystem, userCred, self, "disable")
}
func (self *SServerSku) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Enabled {
_, err := db.Update(self, func() error {
self.Enabled = false
return nil
})
if err != nil {
return nil, errors.Wrap(err, "PerformEnable.db.Update()")
}
db.OpsLog.LogEvent(self, db.ACT_DISABLE, "", userCred)
logclient.AddSimpleActionLog(self, logclient.ACT_DISABLE, nil, userCred, true)
}
return nil, nil
}
func (self *SServerSku) syncWithCloudSku(ctx context.Context, userCred mcclient.TokenCredential, extSku SServerSku) error {
_, err := db.Update(self, func() error {
self.ZoneId = extSku.ZoneId
self.PrepaidStatus = extSku.PrepaidStatus
self.PostpaidStatus = extSku.PostpaidStatus
self.SysDiskType = extSku.SysDiskType
self.DataDiskTypes = extSku.DataDiskTypes
return nil
})
return err
}
func (self *SServerSku) MarkAsSoldout(ctx context.Context) error {
_, err := db.UpdateWithLock(ctx, self, func() error {
self.PrepaidStatus = api.SkuStatusSoldout
self.PostpaidStatus = api.SkuStatusSoldout
return nil
})
return errors.Wrap(err, "SServerSku.MarkAsSoldout")
}
func (manager *SServerSkuManager) FetchSkusByRegion(regionID string) ([]SServerSku, error) {
q := manager.Query()
q = q.Equals("cloudregion_id", regionID)
skus := make([]SServerSku, 0)
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return nil, errors.Wrap(err, "SServerSkuManager.FetchSkusByRegion")
}
return skus, nil
}
func (manager *SServerSkuManager) SyncServerSkus(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, extSkuMeta *SSkuResourcesMeta) compare.SyncResult {
lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred))
defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred))
syncResult := compare.SyncResult{}
extSkus, err := extSkuMeta.GetServerSkusByRegionExternalId(region.ExternalId)
if err != nil {
syncResult.Error(err)
return syncResult
}
dbSkus, err := manager.FetchSkusByRegion(region.GetId())
if err != nil {
syncResult.Error(err)
return syncResult
}
removed := make([]SServerSku, 0)
commondb := make([]SServerSku, 0)
commonext := make([]SServerSku, 0)
added := make([]SServerSku, 0)
err = compare.CompareSets(dbSkus, extSkus, &removed, &commondb, &commonext, &added)
if err != nil {
syncResult.Error(err)
return syncResult
}
for i := 0; i < len(removed); i += 1 {
err = removed[i].MarkAsSoldout(ctx)
if err != nil {
syncResult.DeleteError(err)
} else {
syncResult.Delete()
}
}
for i := 0; i < len(commondb); i += 1 {
err = commondb[i].syncWithCloudSku(ctx, userCred, commonext[i])
if err != nil {
syncResult.UpdateError(err)
} else {
syncResult.Update()
}
}
for i := 0; i < len(added); i += 1 {
err = manager.newPublicCloudSku(ctx, userCred, added[i])
if err != nil {
syncResult.AddError(err)
} else {
syncResult.Add()
}
}
return syncResult
}
// sku标记为soldout状态。
func (manager *SServerSkuManager) MarkAsSoldout(ctx context.Context, id string) error {
if len(id) == 0 {
log.Debugf("MarkAsSoldout sku id should not be emtpy")
return nil
}
isku, err := manager.FetchById(id)
if err != nil {
return err
}
sku, ok := isku.(*SServerSku)
if !ok {
return fmt.Errorf("%s is not a sku object", id)
}
_, err = manager.TableSpec().Update(ctx, sku, func() error {
sku.PrepaidStatus = api.SkuStatusSoldout
sku.PostpaidStatus = api.SkuStatusSoldout
return nil
})
if err != nil {
return err
}
return nil
}
// sku标记为soldout状态。
func (manager *SServerSkuManager) MarkAllAsSoldout(ctx context.Context, ids []string) error {
var err error
for _, id := range ids {
err = manager.MarkAsSoldout(ctx, id)
if err != nil {
return err
}
}
return nil
}
// 获取同一个zone下所有Available状态的sku id
func (manager *SServerSkuManager) FetchAllAvailableSkuIdByZoneId(zoneId string) ([]string, error) {
q := manager.Query()
if len(zoneId) == 0 {
return nil, fmt.Errorf("FetchAllAvailableSkuIdByZoneId zone id should not be emtpy")
}
skus := make([]SServerSku, 0)
q = q.Equals("zone_id", zoneId)
q = q.Filter(sqlchemy.OR(
sqlchemy.Equals(q.Field("prepaid_status"), api.SkuStatusAvailable),
sqlchemy.Equals(q.Field("postpaid_status"), api.SkuStatusAvailable)))
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return nil, err
}
ids := make([]string, len(skus))
for i := range skus {
ids[i] = skus[i].GetId()
}
return ids, nil
}
func (manager *SServerSkuManager) initializeSkuProvider() error {
skus := []SServerSku{}
q := manager.Query()
q = q.Filter(sqlchemy.OR(
sqlchemy.In(q.Field("provider"), CloudproviderManager.GetPrivateProviderProvidersQuery()),
sqlchemy.IsNullOrEmpty(q.Field("provider")),
))
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return errors.Wrapf(err, "initializeSkuProvider.FetchModelObjects")
}
for _, sku := range skus {
_, err = db.Update(&sku, func() error {
sku.Provider = api.CLOUD_PROVIDER_ONECLOUD
sku.CloudregionId = api.DEFAULT_REGION_ID
sku.ZoneId = ""
return nil
})
if err != nil {
return errors.Wrapf(err, "sku.Update")
}
}
return nil
}
func (manager *SServerSkuManager) initializeSkuStatus() error {
skus := []SServerSku{}
q := manager.Query().NotEquals("status", api.SkuStatusReady)
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return errors.Wrapf(err, "initializeSkuStatus.FetchModelObjects")
}
for _, sku := range skus {
_, err = db.Update(&sku, func() error {
sku.Status = api.SkuStatusReady
return nil
})
if err != nil {
return errors.Wrapf(err, "sku.Update")
}
}
return nil
}
func (manager *SServerSkuManager) initializeSkuEnableField() error {
skus := []SServerSku{}
q := manager.Query().IsNull("enabled")
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return errors.Wrapf(err, "FetchModelObjects")
}
for _, sku := range skus {
_, err = db.Update(&sku, func() error {
sku.Status = api.SkuStatusReady
sku.Enabled = true
return nil
})
if err != nil {
return errors.Wrapf(err, "sku.Update")
}
}
return nil
}
func (manager *SServerSkuManager) InitializeData() error {
count, err := manager.Query().Equals("cloudregion_id", api.DEFAULT_REGION_ID).IsNullOrEmpty("zone_id").CountWithError()
if err == nil {
if count == 0 {
type Item struct {
CPU int
MemMB int
}
items := []Item{
{1, 1 * 1024},
{1, 2 * 1024},
{1, 4 * 1024},
{1, 8 * 1024},
{2, 2 * 1024},
{2, 4 * 1024},
{2, 8 * 1024},
{2, 12 * 1024},
{2, 16 * 1024},
{4, 4 * 1024},
{4, 8 * 1024},
{4, 12 * 1024},
{4, 16 * 1024},
{4, 24 * 1024},
{4, 32 * 1024},
{8, 8 * 1024},
{8, 12 * 1024},
{8, 16 * 1024},
{8, 24 * 1024},
{8, 32 * 1024},
{8, 64 * 1024},
{12, 12 * 1024},
{12, 16 * 1024},
{12, 24 * 1024},
{12, 32 * 1024},
{12, 64 * 1024},
{16, 16 * 1024},
{16, 24 * 1024},
{16, 32 * 1024},
{16, 48 * 1024},
{16, 64 * 1024},
{24, 24 * 1024},
{24, 32 * 1024},
{24, 48 * 1024},
{24, 64 * 1024},
{24, 128 * 1024},
{32, 32 * 1024},
{32, 48 * 1024},
{32, 64 * 1024},
{32, 128 * 1024},
}
for i := range items {
item := items[i]
sku := &SServerSku{}
sku.CloudregionId = api.DEFAULT_REGION_ID
sku.CpuCoreCount = item.CPU
sku.MemorySizeMB = item.MemMB
sku.IsEmulated = false
sku.Enabled = true
sku.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
sku.LocalCategory = api.SkuCategoryGeneralPurpose
sku.InstanceTypeFamily = api.InstanceFamilies[api.SkuCategoryGeneralPurpose]
name, _ := genInstanceType(sku.InstanceTypeFamily, int64(item.CPU), int64(item.MemMB))
sku.Name = name
sku.PrepaidStatus = api.SkuStatusAvailable
sku.PostpaidStatus = api.SkuStatusAvailable
err := manager.TableSpec().Insert(context.TODO(), sku)
if err != nil {
log.Errorf("ServerSkuManager Initialize local sku %s", err)
}
}
}
} else {
log.Errorf("ServerSkuManager InitializeData %s", err)
}
privateSkus := make([]SServerSku, 0)
err = manager.Query().IsNullOrEmpty("local_category").IsNullOrEmpty("zone_id").All(&privateSkus)
if err != nil {
return err
}
for _, sku := range privateSkus {
_, err = manager.TableSpec().Update(context.TODO(), &sku, func() error {
sku.LocalCategory = sku.InstanceTypeCategory
return nil
})
if err != nil {
return err
}
}
err = manager.initializeSkuEnableField()
if err != nil {
return errors.Wrap(err, "initializeSkuEnableField")
}
err = manager.initializeSkuProvider()
if err != nil {
return errors.Wrap(err, "InitializeProvider")
}
return manager.initializeSkuStatus()
}
func (manager *SServerSkuManager) ListItemExportKeys(ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
keys stringutils2.SSortedStrings,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemExportKeys")
}
if keys.ContainsAny(manager.SCloudregionResourceBaseManager.GetExportKeys()...) {
q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys")
}
}
if keys.Contains("zone") {
q, err = manager.SZoneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, stringutils2.NewSortedStrings([]string{"zone"}))
if err != nil {
return nil, errors.Wrap(err, "SZoneResourceBaseManager.ListItemExportKeys")
}
}
return q, nil
}