Files
cloudpods/pkg/compute/models/server_skus.go
2023-09-21 20:49:22 +08:00

1661 lines
53 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
"sync"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"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"
"yunion.io/x/onecloud/pkg/apis"
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/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/scheduler"
"yunion.io/x/onecloud/pkg/util/stringutils2"
"yunion.io/x/onecloud/pkg/util/yunionmeta"
)
type SServerSkuManager struct {
db.SEnabledStatusStandaloneResourceBaseManager
SCloudregionResourceBaseManager
SZoneResourceBaseManager
}
var ServerSkuManager *SServerSkuManager
func init() {
ServerSkuManager = &SServerSkuManager{
SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager(
SServerSku{},
"serverskus_tbl",
"serversku",
"serverskus",
),
}
ServerSkuManager.NameRequireAscii = false
ServerSkuManager.SetVirtualObject(ServerSkuManager)
// CREATE INDEX sku_index ON serverskus_tbl (`deleted`, `is_emulated`, `provider`, `cloudregion_id`, `postpaid_status`, `prepaid_status`)
ServerSkuManager.TableSpec().AddIndex(false, "deleted", "is_emulated", "provider", "cloudregion_id", "postpaid_status", "prepaid_status")
}
// SServerSku 实际对应的是instance type清单. 这里的Sku实际指的是instance type。
type SServerSku struct {
db.SEnabledStatusStandaloneResourceBase
db.SExternalizedResourceBase
SCloudregionResourceBase
SZoneResourceBase
// 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" update:"admin" create:"admin_optional" default:"available"` // 预付费资源状态 available|soldout
PostpaidStatus string `width:"32" charset:"utf8" nullable:"true" list:"user" update:"admin" create:"admin_optional" default:"available"` // 按需付费资源状态 available|soldout
CpuArch string `width:"16" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"` // CPU 架构 x86|xarm
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" list:"user" create:"admin_optional" update:"admin"`
SysDiskType string `width:"128" 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" list:"user" create:"admin_optional" update:"admin"`
GpuSpec string `width:"128" charset:"ascii" nullable:"true" list:"user" create:"admin_optional" update:"admin"`
GpuCount string `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"`
Md5 string `width:"32" charset:"utf8" nullable:"true"`
}
func (manager *SServerSkuManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
regionId, _ := data.GetString("cloudregion_id")
return jsonutils.Marshal(map[string]string{"cloudregion_id": regionId})
}
func (manager *SServerSkuManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
regionId, _ := values.GetString("cloudregion_id")
if len(regionId) > 0 {
q = q.Equals("cloudregion_id", regionId)
}
return q
}
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, memMb int64) (string, error) {
if cpu <= 0 {
return "", fmt.Errorf("cpu_core_count should great than zero")
}
if memMb <= 0 {
return "", fmt.Errorf("memory_size_mb should great than zero")
}
if memMb%1024 != 0 && memMb != 512 {
return "", fmt.Errorf("memory_size_mb should be 512 or integral multiple of 1024")
}
switch memMb {
case 512:
return fmt.Sprintf("ecs.%s.c%dm1.nano", family, cpu), nil
default:
return fmt.Sprintf("ecs.%s.c%dm%d", family, cpu, memMb/1024), nil
}
}
func (self SServerSku) GetGlobalId() string {
return self.ExternalId
}
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.SEnabledStatusStandaloneResourceBaseManager.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)
instanceTypes := []string{}
for i := range rows {
rows[i] = api.ServerSkuDetails{
EnabledStatusStandaloneResourceDetails: stdRows[i],
ZoneResourceInfoBase: zoneRows[i].ZoneResourceInfoBase,
CloudregionResourceInfo: regRows[i],
}
sku := objs[i].(*SServerSku)
if !utils.IsInStringArray(sku.Name, instanceTypes) {
instanceTypes = append(instanceTypes, sku.Name)
}
rows[i].CloudEnv = strings.Split(zoneRows[i].RegionExternalId, "/")[0]
}
ret := []struct {
InstanceType string
CloudregionId string
ZoneId string
}{}
guestsQ := GuestManager.Query().SubQuery()
hostsQ := HostManager.Query().SubQuery()
zonesQ := ZoneManager.Query().SubQuery()
q := guestsQ.Query(
guestsQ.Field("instance_type"),
hostsQ.Field("zone_id"),
zonesQ.Field("cloudregion_id"),
).
Join(hostsQ, sqlchemy.Equals(guestsQ.Field("host_id"), hostsQ.Field("id"))).
Join(zonesQ, sqlchemy.Equals(hostsQ.Field("zone_id"), zonesQ.Field("id"))).
Filter(sqlchemy.In(guestsQ.Field("instance_type"), instanceTypes))
err := q.All(&ret)
if err != nil {
log.Errorf("query instance cnt error: %v", err)
return rows
}
skuMap := map[string]map[string]int{}
for _, sku := range ret {
_, ok := skuMap[sku.InstanceType]
if !ok {
skuMap[sku.InstanceType] = map[string]int{}
}
_, ok = skuMap[sku.InstanceType][sku.ZoneId]
if !ok {
skuMap[sku.InstanceType][sku.ZoneId] = 0
}
skuMap[sku.InstanceType][sku.ZoneId] += 1
_, ok = skuMap[sku.InstanceType][sku.CloudregionId]
if !ok {
skuMap[sku.InstanceType][sku.CloudregionId] = 0
}
skuMap[sku.InstanceType][sku.CloudregionId] += 1
}
for i := range rows {
sku := objs[i].(*SServerSku)
if len(sku.ZoneId) > 0 {
rows[i].TotalGuestCount = skuMap[sku.Name][sku.ZoneId]
} else {
rows[i].TotalGuestCount = skuMap[sku.Name][sku.CloudregionId]
}
}
return rows
}
func (self *SServerSkuManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ServerSkuCreateInput) (api.ServerSkuCreateInput, error) {
var region *SCloudregion
if len(input.CloudregionId) > 0 {
_region, err := validators.ValidateModel(userCred, CloudregionManager, &input.CloudregionId)
if err != nil {
return input, err
}
region = _region.(*SCloudregion)
}
if len(input.ZoneId) > 0 {
_zone, err := validators.ValidateModel(userCred, ZoneManager, &input.ZoneId)
if err != nil {
return input, err
}
zone := _zone.(*SZone)
if len(input.CloudregionId) == 0 {
input.CloudregionId = zone.CloudregionId
}
if input.CloudregionId != zone.CloudregionId {
return input, httperrors.NewConflictError("zone %s not in cloudregion %s", zone.Name, input.CloudregionId)
}
region, _ = zone.GetRegion()
}
if input.CpuCoreCount < 1 || input.CpuCoreCount > options.Options.SkuMaxCpuCount {
return input, httperrors.NewOutOfRangeError("cpu_core_count should be range of 1~%d", options.Options.SkuMaxCpuCount)
}
if input.MemorySizeMB < 512 || input.MemorySizeMB > 1024*options.Options.SkuMaxMemSize {
return input, httperrors.NewOutOfRangeError("memory_size_mb, shoud be range of 512~%d", 1024*options.Options.SkuMaxMemSize)
}
if len(input.InstanceTypeCategory) == 0 {
input.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
}
if !utils.IsInStringArray(input.InstanceTypeCategory, api.SKU_FAMILIES) {
return input, 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
if region != nil {
input.Provider = region.Provider
}
if input.Provider == api.CLOUD_PROVIDER_ONECLOUD {
} else if utils.IsInStringArray(input.Provider, api.PRIVATE_CLOUD_PROVIDERS) {
input.Status = api.SkuStatusCreating
} else {
return input, httperrors.NewUnsupportOperationError("Not support create public cloud sku")
}
input.LocalCategory = input.InstanceTypeCategory
input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory]
var err error
if len(input.Name) == 0 {
// 格式 ecs.g1.c1m1
input.Name, err = genInstanceType(input.InstanceTypeFamily, input.CpuCoreCount, input.MemorySizeMB)
if err != nil {
return input, httperrors.NewInputParameterError("%v", err)
}
q := self.Query().Equals("name", input.Name)
if len(input.CloudregionId) > 0 {
q = q.Equals("cloudregion_id", input.CloudregionId)
}
count, err := q.CountWithError()
if err != nil {
return input, httperrors.NewInternalServerError("checkout server sku name duplicate error: %v", err)
}
if count > 0 {
return input, httperrors.NewDuplicateResourceError("Duplicate sku %s", input.Name)
}
}
input.EnabledStatusStandaloneResourceCreateInput, err = self.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusStandaloneResourceCreateInput)
if err != nil {
return input, err
}
return input, nil
}
func (self *SServerSku) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
self.SEnabledStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
if self.Provider != api.CLOUD_PROVIDER_ONECLOUD {
self.StartSkuCreateTask(ctx, userCred)
}
}
func (self *SServerSku) StartSkuCreateTask(ctx context.Context, userCred mcclient.TokenCredential) error {
task, err := taskman.TaskManager.NewTask(ctx, "ServerSkuCreateTask", self, userCred, nil, "", "", nil)
if err != nil {
return errors.Wrapf(err, "NewTask")
}
task.ScheduleRun(nil)
return nil
}
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 *SServerSkuManager) ClearSchedDescCache(wait bool) error {
s := auth.GetAdminSession(context.Background(), options.Options.Region)
_, err := scheduler.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 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 {
providers := usableCloudProviders()
networks := NetworkManager.Query("wire_id").Equals("status", api.NETWORK_STATUS_AVAILABLE)
wires := WireManager.Query("vpc_id").In("id", networks)
_vpcs := VpcManager.Query("cloudregion_id").
Equals("status", api.VPC_STATUS_AVAILABLE).
In("id", wires)
filters := sqlchemy.OR(sqlchemy.In(_vpcs.Field("manager_id"), providers), sqlchemy.IsNullOrEmpty(_vpcs.Field("manager_id")))
vpcs := _vpcs.Filter(filters).SubQuery()
sq := vpcs.Query(sqlchemy.DISTINCT("cloudregion_id", vpcs.Field("cloudregion_id")))
return []sqlchemy.ICondition{sqlchemy.In(f, sq.SubQuery())}
}
func usableFilter(q *sqlchemy.SQuery, public_cloud bool) (*sqlchemy.SQuery, error) {
// 过滤出公有云provider状态健康的sku
if public_cloud {
providerTable := usableCloudProviders().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"))).SubQuery()
q.Join(subq, sqlchemy.Equals(q.Field("cloudregion_id"), subq.Field("cloudregion_id")))
}
// 过滤出network usable的sku
if public_cloud {
zoneIds, err := NetworkUsableZoneIds(true, true, nil)
if err != nil {
return nil, errors.Wrap(err, "NetworkUsableZoneIds")
}
q = q.Filter(sqlchemy.OR(sqlchemy.In(q.Field("zone_id"), zoneIds), sqlchemy.IsNullOrEmpty(q.Field("zone_id")))) //Azure的zone_id可能为空
} 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, nil
}
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 {
log.Infof("FetchModelObjects %s", err)
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) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSkuUpdateInput) (api.ServerSkuUpdateInput, error) {
if len(input.Name) > 0 {
return input, httperrors.NewUnsupportOperationError("Cannot change server sku name")
}
var err error
input.EnabledStatusStandaloneResourceBaseUpdateInput, err = self.SEnabledStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, input.EnabledStatusStandaloneResourceBaseUpdateInput)
if err != nil {
return input, errors.Wrap(err, "SEnabledStatusStandaloneResourceBase.ValidateUpdateData")
}
return input, nil
}
func (self *SServerSku) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
ServerSkuManager.ClearSchedDescCache(true)
self.SEnabledStatusStandaloneResourceBase.PostUpdate(ctx, userCred, query, data)
}
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 db.RealDeleteModel(ctx, userCred, self)
}
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) GetGuestCount() (int, error) {
guestsQ := GuestManager.Query().SubQuery()
hostsQ := HostManager.Query().SubQuery()
zonesQ := ZoneManager.Query().SubQuery()
q := guestsQ.Query().
Join(hostsQ, sqlchemy.Equals(guestsQ.Field("host_id"), hostsQ.Field("id"))).
Join(zonesQ, sqlchemy.Equals(hostsQ.Field("zone_id"), zonesQ.Field("id"))).
Filter(sqlchemy.Equals(guestsQ.Field("instance_type"), self.Name))
if len(self.ZoneId) > 0 {
q = q.Filter(sqlchemy.Equals(hostsQ.Field("zone_id"), self.ZoneId))
} else {
q = q.Filter(sqlchemy.Equals(zonesQ.Field("cloudregion_id"), self.CloudregionId))
}
return q.CountWithError()
}
func (self *SServerSku) ValidateDeleteCondition(ctx context.Context, info *api.ServerSkuDetails) error {
totalGuestCnt := 0
if info != nil {
totalGuestCnt = info.TotalGuestCount
} else {
totalGuestCnt, _ = self.GetGuestCount()
}
if totalGuestCnt > 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)
}
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
pq := CloudproviderManager.GetPublicProviderProvidersQuery()
q = q.Join(pq, sqlchemy.Equals(q.Field("provider"), pq.Field("provider")))
}
if cloudEnvStr == api.CLOUD_ENV_PRIVATE_CLOUD {
pq := CloudproviderManager.GetPrivateProviderProvidersQuery()
q = q.Join(pq, sqlchemy.Equals(q.Field("provider"), pq.Field("provider")))
}
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(ctx, 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
}
}
conditions := []sqlchemy.ICondition{}
for _, arch := range query.CpuArch {
if len(arch) == 0 {
continue
}
if arch == apis.OS_ARCH_X86 {
conditions = append(conditions, sqlchemy.OR(
sqlchemy.Startswith(q.Field("cpu_arch"), arch),
sqlchemy.Equals(q.Field("cpu_arch"), apis.OS_ARCH_I386),
sqlchemy.IsNullOrEmpty(q.Field("cpu_arch")),
))
} else if arch == apis.OS_ARCH_ARM {
conditions = append(conditions, sqlchemy.OR(
sqlchemy.Startswith(q.Field("cpu_arch"), arch),
sqlchemy.Equals(q.Field("cpu_arch"), apis.OS_ARCH_AARCH32),
sqlchemy.Equals(q.Field("cpu_arch"), apis.OS_ARCH_AARCH64),
sqlchemy.IsNullOrEmpty(q.Field("cpu_arch")),
))
} else {
conditions = append(conditions, sqlchemy.Startswith(q.Field("cpu_arch"), arch))
}
}
if len(conditions) > 0 {
q = q.Filter(sqlchemy.OR(conditions...))
}
if query.Distinct {
q = q.GroupBy(q.Field("name"))
}
brands := query.Brands
if len(brands) > 0 {
q = q.Filter(sqlchemy.In(q.Field("brand"), brands))
}
q, err := manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.ListItemFilter")
}
if query.Usable != nil && *query.Usable {
q, err := usableFilter(q, publicCloud)
if err != nil {
return nil, err
}
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)
}
conditions = []sqlchemy.ICondition{}
for _, sizeMb := range query.MemorySizeMb {
// 按区间查询内存, 避免0.75G这样的套餐不好过滤
if sizeMb > 0 {
s, e := intervalMem(sizeMb)
conditions = append(
conditions,
sqlchemy.AND(
sqlchemy.GT(q.Field("memory_size_mb"), s),
sqlchemy.LE(q.Field("memory_size_mb"), e),
),
)
}
}
if len(conditions) > 0 {
q = q.Filter(sqlchemy.OR(conditions...))
}
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.SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields")
}
q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput)
if err != nil {
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields")
}
if db.NeedOrderQuery([]string{query.OrderByTotalGuestCount}) {
guestQ := GuestManager.Query()
guestQ = guestQ.AppendField(guestQ.Field("instance_type"), sqlchemy.COUNT("total_guest_count"))
guestQ = guestQ.GroupBy(guestQ.Field("instance_type"))
guestSQ := guestQ.SubQuery()
q = q.Join(guestSQ, sqlchemy.Equals(guestSQ.Field("instance_type"), q.Field("name")))
q = q.AppendField(q.QueryFields()...)
q = q.AppendField(guestSQ.Field("total_guest_count"))
q = db.OrderByFields(q, []string{query.OrderByTotalGuestCount}, []sqlchemy.IQueryField{q.Field("total_guest_count")})
}
return q, nil
}
func (manager *SServerSkuManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SEnabledStatusStandaloneResourceBaseManager.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().IsTrue("enabled")
q = q.Equals("name", name)
if utils.IsInStringArray(provider, []string{api.CLOUD_PROVIDER_ONECLOUD, api.CLOUD_PROVIDER_VMWARE, api.CLOUD_PROVIDER_NUTANIX}) {
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 (manager *SServerSkuManager) SyncPrivateCloudSkus(
ctx context.Context,
userCred mcclient.TokenCredential,
region *SCloudregion,
skus []cloudprovider.ICloudSku,
xor bool,
) compare.SyncResult {
lockman.LockRawObject(ctx, manager.Keyword(), region.Id)
defer lockman.ReleaseRawObject(ctx, manager.Keyword(), region.Id)
result := compare.SyncResult{}
dbSkus, err := region.GetServerSkus()
if err != nil {
result.Error(errors.Wrapf(err, "GetServerSkus"))
return result
}
removed := make([]SServerSku, 0)
commondb := make([]SServerSku, 0)
commonext := make([]cloudprovider.ICloudSku, 0)
added := make([]cloudprovider.ICloudSku, 0)
err = compare.CompareSets(dbSkus, skus, &removed, &commondb, &commonext, &added)
if err != nil {
result.Error(errors.Wrapf(err, "CompareSets"))
return result
}
for i := 0; i < len(removed); i += 1 {
err = removed[i].RealDelete(ctx, userCred)
if err != nil {
result.DeleteError(err)
continue
}
result.Delete()
}
if !xor {
for i := 0; i < len(commondb); i += 1 {
err = commondb[i].SyncWithPrivateCloudSku(ctx, userCred, commonext[i])
if err != nil {
result.UpdateError(err)
}
result.Update()
}
}
for i := 0; i < len(added); i += 1 {
err := manager.newPrivateCloudSku(ctx, userCred, region, added[i])
if err != nil {
result.AddError(err)
continue
}
result.Add()
}
return result
}
func (self *SServerSku) SyncWithPrivateCloudSku(ctx context.Context, userCred mcclient.TokenCredential, sku cloudprovider.ICloudSku) error {
_, err := db.Update(self, func() error {
self.Status = api.SkuStatusAvailable
self.constructSku(sku)
return nil
})
return err
}
func (self *SServerSku) constructSku(extSku cloudprovider.ICloudSku) {
self.ExternalId = extSku.GetGlobalId()
self.InstanceTypeFamily = extSku.GetInstanceTypeFamily()
self.InstanceTypeCategory = extSku.GetInstanceTypeCategory()
self.PrepaidStatus = extSku.GetPrepaidStatus()
self.PostpaidStatus = extSku.GetPostpaidStatus()
self.CpuArch = extSku.GetCpuArch()
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 (region *SCloudregion) newPublicCloudSku(ctx context.Context, userCred mcclient.TokenCredential, extSku SServerSku) error {
meta, err := yunionmeta.FetchYunionmeta(ctx)
if err != nil {
return err
}
zones, err := region.GetZones()
if err != nil {
return errors.Wrap(err, "GetZones")
}
zoneMaps := map[string]string{}
for _, zone := range zones {
zoneMaps[zone.ExternalId] = zone.Id
}
sku := &SServerSku{}
sku.SetModelManager(ServerSkuManager, sku)
skuUrl := fmt.Sprintf("%s/%s/%s.json", meta.ServerBase, region.ExternalId, extSku.ExternalId)
err = meta.Get(skuUrl, sku)
if err != nil {
return errors.Wrapf(err, "Get")
}
if len(sku.ZoneId) > 0 {
zoneId := yunionmeta.GetZoneIdBySuffix(zoneMaps, sku.ZoneId)
if len(zoneId) > 0 {
sku.ZoneId = zoneId
}
}
// 第一次同步新建的套餐是启用状态
sku.Enabled = tristate.True
sku.Status = api.SkuStatusReady
sku.CloudregionId = region.Id
sku.Provider = region.Provider
return ServerSkuManager.TableSpec().Insert(ctx, sku)
}
func (manager *SServerSkuManager) newPrivateCloudSku(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, extSku cloudprovider.ICloudSku) error {
sku := &SServerSku{Provider: region.Provider}
sku.SetModelManager(manager, sku)
// 第一次同步新建的套餐是启用状态
sku.Enabled = tristate.True
sku.Status = api.SkuStatusReady
sku.constructSku(extSku)
sku.CloudregionId = region.Id
sku.Name = extSku.GetName()
return manager.TableSpec().Insert(ctx, sku)
}
func (self *SServerSku) syncWithCloudSku(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, extSku SServerSku) error {
if self.Md5 == extSku.Md5 {
return nil
}
meta, err := yunionmeta.FetchYunionmeta(ctx)
if err != nil {
return err
}
sku := &SServerSku{}
skuUrl := fmt.Sprintf("%s/%s/%s.json", meta.ServerBase, region.ExternalId, extSku.ExternalId)
err = meta.Get(skuUrl, sku)
if err != nil {
return errors.Wrapf(err, "Get")
}
_, err = db.Update(self, func() error {
self.PrepaidStatus = sku.PrepaidStatus
self.PostpaidStatus = sku.PostpaidStatus
self.SysDiskType = sku.SysDiskType
self.DataDiskTypes = sku.DataDiskTypes
self.CpuArch = sku.CpuArch
self.InstanceTypeFamily = sku.InstanceTypeFamily
self.InstanceTypeCategory = sku.InstanceTypeCategory
self.LocalCategory = sku.LocalCategory
self.NicType = sku.NicType
self.GpuAttachable = sku.GpuAttachable
self.GpuSpec = sku.GpuSpec
self.GpuCount = sku.GpuCount
self.Md5 = sku.Md5
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, xor bool) compare.SyncResult {
lockman.LockRawObject(ctx, manager.Keyword(), region.Id)
defer lockman.ReleaseRawObject(ctx, manager.Keyword(), region.Id)
result := compare.SyncResult{}
meta, err := yunionmeta.FetchYunionmeta(ctx)
if err != nil {
result.Error(errors.Wrapf(err, "FetchYunionmeta"))
return result
}
extSkus := []SServerSku{}
err = meta.List(manager.Keyword(), region.ExternalId, &extSkus)
if err != nil {
result.Error(errors.Wrapf(err, "List"))
return result
}
dbSkus, err := manager.FetchSkusByRegion(region.GetId())
if err != nil {
result.Error(err)
return result
}
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 {
result.Error(err)
return result
}
for i := 0; i < len(removed); i += 1 {
cnt, err := removed[i].GetGuestCount()
if err != nil || cnt > 0 {
err = removed[i].MarkAsSoldout(ctx)
} else {
err = removed[i].RealDelete(ctx, userCred)
}
if err != nil {
result.DeleteError(err)
} else {
result.Delete()
}
}
if !xor {
for i := 0; i < len(commondb); i += 1 {
err = commondb[i].syncWithCloudSku(ctx, userCred, region, commonext[i])
if err != nil {
result.UpdateError(err)
} else {
result.Update()
}
}
}
ch := make(chan struct{}, options.Options.SkuBatchSync)
defer close(ch)
var wg sync.WaitGroup
for i := 0; i < len(added); i += 1 {
ch <- struct{}{}
wg.Add(1)
go func(sku SServerSku) {
defer func() {
wg.Done()
<-ch
}()
err = region.newPublicCloudSku(ctx, userCred, sku)
if err != nil {
result.AddError(err)
} else {
result.Add()
}
}(added[i])
}
wg.Wait()
// notfiy sched manager
_, err = scheduler.SchedManager.SyncSku(auth.GetAdminSession(ctx, options.Options.Region), false)
if err != nil {
log.Errorf("SchedManager SyncSku %s", err)
}
return result
}
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) fixAliyunSkus() error {
q := manager.Query().Equals("provider", api.CLOUD_PROVIDER_ALIYUN)
q = q.Filter(sqlchemy.OR(
sqlchemy.AND(
sqlchemy.Contains(q.Field("sys_disk_type"), api.STORAGE_CLOUD_ESSD),
sqlchemy.NOT(sqlchemy.Contains(q.Field("sys_disk_type"), api.STORAGE_CLOUD_ESSD_PL0)),
),
sqlchemy.AND(
sqlchemy.Contains(q.Field("data_disk_types"), api.STORAGE_CLOUD_ESSD),
sqlchemy.NOT(sqlchemy.Contains(q.Field("data_disk_types"), api.STORAGE_CLOUD_ESSD_PL0)),
),
))
skus := []SServerSku{}
err := db.FetchModelObjects(manager, q, &skus)
if err != nil {
return errors.Wrapf(err, "db.FetchModelObjects")
}
storages := []string{api.STORAGE_CLOUD_ESSD_PL0, api.STORAGE_CLOUD_ESSD_PL2, api.STORAGE_CLOUD_ESSD_PL3}
for i := range skus {
_, err := db.Update(&skus[i], func() error {
sys := strings.Split(skus[i].SysDiskType, ",")
if utils.IsInStringArray(api.STORAGE_CLOUD_ESSD, sys) {
for _, storage := range storages {
if !utils.IsInStringArray(storage, sys) {
sys = append(sys, storage)
}
}
skus[i].SysDiskType = strings.Join(sys, ",")
}
data := strings.Split(skus[i].DataDiskTypes, ",")
if utils.IsInStringArray(api.STORAGE_CLOUD_ESSD, data) {
for _, storage := range storages {
if !utils.IsInStringArray(storage, data) {
data = append(data, storage)
}
}
skus[i].DataDiskTypes = strings.Join(data, ",")
}
return nil
})
if err != nil {
return errors.Wrapf(err, "db.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 {
return errors.Wrapf(err, "fetch default region skus")
}
if count == 0 {
skus := []struct {
cpu int
memGb int
}{
{1, 1}, {1, 2}, {1, 4}, {1, 8},
{2, 2}, {2, 4}, {2, 8}, {2, 12}, {2, 16},
{4, 4}, {4, 12}, {4, 16}, {4, 24}, {4, 32},
{8, 8}, {8, 16}, {8, 24}, {8, 32}, {8, 64},
{12, 12}, {12, 16}, {12, 24}, {12, 32}, {12, 64},
{16, 16}, {16, 24}, {16, 32}, {16, 48}, {16, 64},
{24, 24}, {24, 32}, {24, 48}, {24, 64}, {24, 128},
{32, 32}, {32, 48}, {32, 64}, {32, 128},
}
for _, item := range skus {
sku := &SServerSku{}
sku.CloudregionId = api.DEFAULT_REGION_ID
sku.CpuCoreCount = item.cpu
sku.MemorySizeMB = item.memGb * 1024
sku.IsEmulated = false
sku.Enabled = tristate.True
sku.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
sku.LocalCategory = api.SkuCategoryGeneralPurpose
sku.InstanceTypeFamily = api.InstanceFamilies[api.SkuCategoryGeneralPurpose]
sku.Name, _ = genInstanceType(sku.InstanceTypeFamily, int64(item.cpu), int64(item.memGb*1024))
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)
}
}
}
privateSkus := make([]SServerSku, 0)
q := manager.Query().IsNullOrEmpty("local_category").IsNotNull("instance_type_category").IsNullOrEmpty("zone_id")
if err != nil {
return err
}
err = db.FetchModelObjects(manager, q, &privateSkus)
if err != nil {
return err
}
for i := range privateSkus {
_, err = db.Update(&privateSkus[i], func() error {
privateSkus[i].LocalCategory = privateSkus[i].InstanceTypeCategory
return nil
})
if err != nil {
return err
}
}
err = manager.fixAliyunSkus()
if err != nil {
return errors.Wrapf(err, "fixAliyunSkus")
}
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.SEnabledStatusStandaloneResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.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
}
func (manager *SServerSkuManager) PerformSyncSkus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.SkuSyncInput) (jsonutils.JSONObject, error) {
return PerformActionSyncSkus(ctx, userCred, manager.Keyword(), input)
}
func (manager *SServerSkuManager) GetPropertySyncTasks(ctx context.Context, userCred mcclient.TokenCredential, query api.SkuTaskQueryInput) (jsonutils.JSONObject, error) {
return GetPropertySkusSyncTasks(ctx, userCred, query)
}
func (self *SServerSku) GetICloudSku(ctx context.Context) (cloudprovider.ICloudSku, error) {
region, err := self.GetRegion()
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "GetRegion")
}
return nil, errors.Wrapf(err, "GetRegion")
}
provider, err := region.GetCloudprovider()
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "GetCloudprovider")
}
return nil, errors.Wrapf(err, "GetCloudprovider")
}
driver, err := provider.GetProvider(ctx)
if err != nil {
return nil, errors.Wrapf(err, "GetDriver()")
}
iRegion, err := driver.GetIRegionById(region.ExternalId)
if err != nil {
return nil, errors.Wrapf(err, "GetIRegionById(%s)", region.ExternalId)
}
skus, err := iRegion.GetISkus()
if err != nil {
return nil, errors.Wrapf(err, "GetICloudSku")
}
for i := range skus {
if skus[i].GetGlobalId() == self.ExternalId {
return skus[i], nil
}
}
return nil, errors.Wrapf(cloudprovider.ErrNotFound, self.ExternalId)
}
func fetchSkuSyncCloudregions() []SCloudregion {
cloudregions := []SCloudregion{}
q := CloudregionManager.Query()
q = q.In("provider", CloudproviderManager.GetPublicProviderProvidersQuery())
err := db.FetchModelObjects(CloudregionManager, q, &cloudregions)
if err != nil {
log.Errorf("fetchSkuSyncCloudregions.FetchCloudregions failed: %v", err)
return nil
}
return cloudregions
}
// 全量同步sku列表.
func SyncServerSkus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
if isStart {
cnt, err := ServerSkuManager.GetPublicCloudSkuCount()
if err != nil {
log.Errorf("GetPublicCloudSkuCount fail %s", err)
return
}
if cnt > 0 {
log.Debugf("GetPublicCloudSkuCount synced skus, skip...")
return
}
}
meta, err := yunionmeta.FetchYunionmeta(ctx)
if err != nil {
log.Errorf("FetchYunionmeta %v", err)
return
}
index, err := meta.Index(ServerSkuManager.Keyword())
if err != nil {
log.Errorf("getServerSkuIndex error: %v", err)
return
}
cloudregions := fetchSkuSyncCloudregions()
for i := range cloudregions {
region := &cloudregions[i]
skuMeta := &SServerSku{}
skuMeta.SetModelManager(ServerSkuManager, skuMeta)
skuMeta.Id = region.ExternalId
oldMd5 := db.Metadata.GetStringValue(ctx, skuMeta, db.SKU_METADAT_KEY, userCred)
newMd5, ok := index[region.ExternalId]
if !ok || newMd5 == yunionmeta.EMPTY_MD5 || len(oldMd5) > 0 && newMd5 == oldMd5 {
continue
}
db.Metadata.SetValue(ctx, skuMeta, db.SKU_METADAT_KEY, newMd5, userCred)
result := ServerSkuManager.SyncServerSkus(ctx, userCred, region, false)
notes := fmt.Sprintf("SyncServerSkusByRegion %s result: %s", region.Name, result.Result())
log.Debugf(notes)
}
// 清理无效的sku
log.Debugf("DeleteInvalidSkus in processing...")
ServerSkuManager.PendingDeleteInvalidSku()
}
// 同步指定region sku列表
func SyncServerSkusByRegion(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, xor bool) compare.SyncResult {
result := compare.SyncResult{}
result = ServerSkuManager.SyncServerSkus(ctx, userCred, region, xor)
notes := fmt.Sprintf("SyncServerSkusByRegion %s result: %s", region.Name, result.Result())
log.Infof(notes)
return result
}