// 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" "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" ) // +onecloud:swagger-gen-model-singular=serversku // +onecloud:swagger-gen-model-plural=serverskus 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 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 `width:"32" 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 `width:"32" 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 `width:"16" 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") zoneId, _ := data.GetString("zone_id") return jsonutils.Marshal(map[string]string{"cloudregion_id": regionId, "zone_id": zoneId}) } 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) } zoneId, _ := values.GetString("zone_id") if len(zoneId) > 0 { q = q.Equals("zone_id", zoneId) } return q } 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 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(ctx, userCred, CloudregionManager, &input.CloudregionId) if err != nil { return input, err } region = _region.(*SCloudregion) } if len(input.ZoneId) > 0 { _zone, err := validators.ValidateModel(ctx, 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.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 { input.CloudregionId = api.DEFAULT_REGION_ID } else if utils.IsInStringArray(input.Provider, api.PRIVATE_CLOUD_PROVIDERS) { input.Status = api.SkuStatusCreating } if !utils.IsInStringArray(input.Provider, api.PUBLIC_CLOUD_PROVIDERS) { 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) } input.LocalCategory = input.InstanceTypeCategory input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory] } if len(input.LocalCategory) == 0 { input.LocalCategory = input.InstanceTypeCategory } if len(input.InstanceTypeFamily) == 0 { 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 utils.IsInStringArray(self.Provider, api.PRIVATE_CLOUD_PROVIDERS) { 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") } return task.ScheduleRun(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 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.Errorf("FetchModelObjects %s: %s", q.DebugString(), 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) if utils.IsInStringArray(self.Provider, api.PUBLIC_CLOUD_PROVIDERS) && (data.Contains("prepaid_status") || data.Contains("postpaid_status")) { self.SetMetadata(ctx, api.SERVER_SKU_PROJECT_SRC_KEY, api.SERVER_SKU_PROJECT_SRC_VALUE_LOCAL, userCred) } 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(ctx, 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 !options.Options.EnableDeletePublicCloudSku && utils.IsInStringArray(self.Provider, api.PUBLIC_CLOUD_PROVIDERS) { 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 if arch == apis.OS_ARCH_RISCV { conditions = append(conditions, sqlchemy.OR( sqlchemy.Startswith(q.Field("cpu_arch"), arch), sqlchemy.Equals(q.Field("cpu_arch"), apis.OS_ARCH_RISCV32), sqlchemy.Equals(q.Field("cpu_arch"), apis.OS_ARCH_RISCV64), 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 { zoneObj, err := validators.ValidateModel(ctx, userCred, ZoneManager, &zoneStr) if err != nil { return nil, err } zone := zoneObj.(*SZone) region, err := zone.GetRegion() if err != nil { return nil, errors.Wrapf(err, "GetRegion %s", zone.Name) } q = q.Filter( sqlchemy.OR( sqlchemy.AND( sqlchemy.Equals(q.Field("cloudregion_id"), region.Id), sqlchemy.IsNullOrEmpty(q.Field("zone_id")), ), sqlchemy.Equals(q.Field("zone_id"), zone.Id), ), ) } q, err = managedResourceFilterByRegion(ctx, 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) DeleteInvalidSkus() error { _, err := sqlchemy.GetDB().Exec( fmt.Sprintf( "delete from %s where length(zone_id) > 0 and zone_id not in (select id from zones_tbl where deleted=0)", manager.TableSpec().Name(), ), ) return err } 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 (region *SCloudregion) getMetaUrl(base string, externalId string) string { if region.Provider == api.CLOUD_PROVIDER_HUAWEI && strings.Contains(region.ExternalId, "_") { idx := strings.Index(region.ExternalId, "_") return fmt.Sprintf("%s/%s/%s.json", base, region.ExternalId[:idx], externalId) } return fmt.Sprintf("%s/%s/%s.json", base, region.ExternalId, externalId) } 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 := region.getMetaUrl(meta.ServerBase, extSku.ExternalId) err = meta.Get(skuUrl, sku) if err != nil { return errors.Wrapf(err, "Get") } if len(sku.ZoneId) > 0 { zoneId := sku.ZoneId sku.ZoneId = yunionmeta.GetZoneIdBySuffix(zoneMaps, zoneId) if len(sku.ZoneId) == 0 { return errors.Wrapf(cloudprovider.ErrNotFound, "%v", 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, region *SCloudregion, isLocalChangedStatus bool, extSku SServerSku) error { if self.Md5 == extSku.Md5 { return nil } meta, err := yunionmeta.FetchYunionmeta(ctx) if err != nil { return err } sku := &SServerSku{} skuUrl := region.getMetaUrl(meta.ServerBase, extSku.ExternalId) err = meta.Get(skuUrl, sku) if err != nil { return errors.Wrapf(err, "Get") } _, err = db.Update(self, func() error { if !isLocalChangedStatus { 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.CpuCoreCount = sku.CpuCoreCount self.MemorySizeMB = sku.MemorySizeMB 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 (region *SCloudregion) FetchSkusByRegion() ([]SServerSku, error) { q := ServerSkuManager.Query().Equals("cloudregion_id", region.Id) skus := make([]SServerSku, 0) err := db.FetchModelObjects(ServerSkuManager, q, &skus) if err != nil { return nil, errors.Wrapf(err, "FetchSkusByRegion %s", region.ExternalId) } return skus, nil } func (region *SCloudregion) GetUsedSkus() (map[string]bool, error) { hosts := HostManager.Query().SubQuery() zones := ZoneManager.Query().Equals("cloudregion_id", region.Id).SubQuery() q := GuestManager.Query("instance_type").Distinct() q = q.Join(hosts, sqlchemy.Equals(q.Field("host_id"), hosts.Field("id"))) q = q.Join(zones, sqlchemy.Equals(hosts.Field("zone_id"), zones.Field("id"))) ret := []struct { InstanceType string `json:"instance_type"` }{} err := q.All(&ret) if err != nil { return nil, errors.Wrapf(err, "GetUsedSkus %s", region.ExternalId) } usedSkus := make(map[string]bool, 0) for _, item := range ret { usedSkus[item.InstanceType] = true } return usedSkus, nil } // 获取本地已变更过套餐状态的公有云套餐 func (manager *SServerSkuManager) GetLocalSkus() (map[string]bool, error) { q := db.Metadata.Query("obj_id").Equals("obj_type", manager.Keyword()).Equals("key", api.SERVER_SKU_PROJECT_SRC_KEY).Equals("value", api.SERVER_SKU_PROJECT_SRC_VALUE_LOCAL) ret := []struct { ObjId string `json:"obj_id"` }{} err := q.All(&ret) if err != nil { return nil, errors.Wrapf(err, "GetLocalSkus") } localSkus := make(map[string]bool, 0) for _, item := range ret { localSkus[item.ObjId] = true } return localSkus, nil } func (region *SCloudregion) SyncServerSkus(ctx context.Context, userCred mcclient.TokenCredential, xor bool) compare.SyncResult { lockman.LockRawObject(ctx, ServerSkuManager.Keyword(), region.Id) defer lockman.ReleaseRawObject(ctx, ServerSkuManager.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(ServerSkuManager.Keyword(), region.ExternalId, &extSkus) if err != nil { result.Error(errors.Wrapf(err, "List")) return result } dbSkus, err := region.FetchSkusByRegion() if err != nil { result.Error(errors.Wrapf(err, "FetchSkusByRegion %s", region.ExternalId)) 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(errors.Wrapf(err, "CompareSets %s", region.ExternalId)) return result } usedSkus, err := region.GetUsedSkus() if err != nil { result.Error(errors.Wrapf(err, "GetUsedSkus %s", region.ExternalId)) return result } localSkus, err := ServerSkuManager.GetLocalSkus() if err != nil { result.Error(errors.Wrapf(err, "GetLocalSkus")) return result } purgeIds := []string{} for i := 0; i < len(removed); i += 1 { if usedSkus[removed[i].Name] { continue } purgeIds = append(purgeIds, removed[i].Id) } if len(purgeIds) > 0 { err = db.Purge(ServerSkuManager, "id", purgeIds, true) if err != nil { result.Error(errors.Wrapf(err, "Purge %s", region.ExternalId)) } else { result.DelCnt += len(purgeIds) } } if !xor { for i := 0; i < len(commondb); i += 1 { _, isLocalChangedStatus := localSkus[commondb[i].Id] err = commondb[i].syncWithCloudSku(ctx, region, isLocalChangedStatus, 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) 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 sku.SetModelManager(manager, sku) err := manager.TableSpec().Insert(context.TODO(), sku) if err != nil { log.Errorf("ServerSkuManager Initialize local sku %s", err) } } } return nil } 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") } providers, err := region.GetCloudproviders() if err != nil { return nil, errors.Wrapf(err, "GetCloudprovider") } for i := range providers { provider := providers[i] 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, "%v", 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) { // 清理无效的sku log.Debugf("DeleteInvalidSkus in processing...") ServerSkuManager.DeleteInvalidSkus() cloudregions := fetchSkuSyncCloudregions() if len(cloudregions) == 0 { 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 } 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 := region.SyncServerSkus(ctx, userCred, false) notes := fmt.Sprintf("SyncServerSkusByRegion %s result: %v", region.Name, result.Result()) log.Debugf("%s", notes) } } // 同步指定region sku列表 func SyncServerSkusByRegion(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, xor bool) compare.SyncResult { result := compare.SyncResult{} result = region.SyncServerSkus(ctx, userCred, xor) notes := fmt.Sprintf("SyncServerSkusByRegion %s result: %v", region.Name, result.Result()) log.Infof("%s", notes) return result }