fix(region): prefer zones (#24966)

This commit is contained in:
屈轩
2026-06-04 18:51:48 +08:00
committed by GitHub
parent 9d85cfcd5d
commit adbefb74bf
14 changed files with 270 additions and 30 deletions
+18 -1
View File
@@ -372,7 +372,10 @@ type ServerConfigs struct {
PreferRegion string `json:"prefer_region_id"`
// 调度到指定可用区,优先级低于prefer_host_id
PreferZone string `json:"prefer_zone_id"`
PreferZone string `json:"prefer_zone_id" yunion-deprecated-by:"prefer_zones"`
// 调度到指定可用区列表,优先级低于prefer_host_id
PreferZones []string `json:"prefer_zones"`
// 调度使用指定二层网络, 优先级低于prefer_host_id
PreferWire string `json:"prefer_wire_id"`
@@ -480,6 +483,20 @@ func NewServerConfigs() *ServerConfigs {
}
}
func (c *ServerConfigs) GetPreferZones() []string {
if len(c.PreferZones) > 0 {
return c.PreferZones
}
if c.PreferZone != "" {
return []string{c.PreferZone}
}
return nil
}
func (c *ServerConfigs) HasPreferZone() bool {
return len(c.GetPreferZones()) > 0
}
type DeployConfig struct {
Action string `json:"action"`
Path string `json:"path"`
+8 -2
View File
@@ -48,9 +48,13 @@ type DiskCreateInput struct {
// required: false
PreferRegion string `json:"prefer_region_id"`
// 此参数仅适用于未指定storage时进行调度到指定可用区创建磁盘
// 此参数仅适用于未指定storage时进行调度到指定可用区创建磁盘
// required: false
PreferZone string `json:"prefer_zone_id"`
PreferZone string `json:"prefer_zone_id" yunion-deprecated-by:"prefer_zones"`
// 此参数仅适用于未指定storage时进行调度到指定可用区列表创建磁盘
// required: false
PreferZones []string `json:"prefer_zones"`
// swagger:ignore
PreferWire string `json:"prefer_wire_id"`
@@ -72,6 +76,7 @@ func (req *DiskCreateInput) ToServerCreateInput() *ServerCreateInput {
PreferManager: req.PreferManager,
PreferRegion: req.PreferRegion,
PreferZone: req.PreferZone,
PreferZones: req.PreferZones,
PreferWire: req.PreferWire,
PreferHost: req.PreferHost,
Hypervisor: req.Hypervisor,
@@ -92,6 +97,7 @@ func (req *ServerCreateInput) ToDiskCreateInput() *DiskCreateInput {
PreferRegion: req.PreferRegion,
PreferHost: req.PreferHost,
PreferZone: req.PreferZone,
PreferZones: req.PreferZones,
PreferWire: req.PreferWire,
Hypervisor: req.Hypervisor,
}
+27
View File
@@ -142,6 +142,33 @@ type ServerSkuDetails struct {
// 绑定云主机数量
TotalGuestCount int `json:"total_guest_count"`
// 同名套餐在各区域/可用区的计费可用性
RegionalAvailability []ServerSkuRegionalAvailability `json:"regional_availability"`
}
type ServerSkuRegionalAvailability struct {
// 区域Id
CloudregionId string `json:"cloudregion_id"`
// 区域名称
Cloudregion string `json:"cloudregion"`
// 区域级套餐后付费(按量)状态, zone_id 为空时有效
PostpaidStatus string `json:"postpaid_status,omitempty"`
// 区域级套餐预付费(包年包月)状态, zone_id 为空时有效
PrepaidStatus string `json:"prepaid_status,omitempty"`
// 可用区级套餐可用性
Zones []ServerSkuZoneAvailability `json:"zones,omitempty"`
}
type ServerSkuZoneAvailability struct {
// 可用区Id
ZoneId string `json:"zone_id"`
// 可用区名称
Zone string `json:"zone"`
// 后付费(按量)状态 available|soldout
PostpaidStatus string `json:"postpaid_status"`
// 预付费(包年包月)状态 available|soldout
PrepaidStatus string `json:"prepaid_status"`
}
type ServerSkuUpdateInput struct {
+10 -1
View File
@@ -565,7 +565,16 @@ func diskCreateInput2ComputeQuotaKeys(input api.DiskCreateInput, ownerId mcclien
keys.ZoneId = wire.ZoneId
}
}
if len(input.PreferZone) > 0 {
if len(input.PreferZones) > 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZones[0])
if err != nil {
return keys, err
}
zone := zoneObj.(*SZone)
input.PreferRegion = zone.CloudregionId
keys.ZoneId = zone.Id
keys.RegionId = zone.CloudregionId
} else if len(input.PreferZone) > 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZone)
if err != nil {
return keys, err
+19 -2
View File
@@ -1569,7 +1569,16 @@ func serverCreateInput2ComputeQuotaKeys(input api.ServerCreateInput, ownerId mcc
keys.ZoneId = wire.ZoneId
}
}
if len(input.PreferZone) > 0 {
if len(input.PreferZones) > 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZones[0])
if err != nil {
return keys, err
}
zone := zoneObj.(*SZone)
input.PreferRegion = zone.CloudregionId
keys.ZoneId = zone.Id
keys.RegionId = zone.CloudregionId
} else if len(input.PreferZone) > 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZone)
if err != nil {
return keys, err
@@ -1768,7 +1777,15 @@ func (manager *SGuestManager) validateCreateData(
return nil, errors.Wrap(err, "checkGuestImage")
}
if len(input.PreferZone) > 0 && len(input.Provider) == 0 {
preferZones := input.GetPreferZones()
if len(preferZones) > 0 && len(input.Provider) == 0 {
zoneObj, err := ZoneManager.FetchById(preferZones[0])
if err != nil {
return nil, errors.Wrapf(err, "zone fetch by id %s", preferZones[0])
}
zone := zoneObj.(*SZone)
input.PreferRegion = zone.CloudregionId
} else if len(input.PreferZone) > 0 && len(input.Provider) == 0 {
zoneObj, err := ZoneManager.FetchById(input.PreferZone)
if err != nil {
return nil, errors.Wrapf(err, "zone fetch by id %s", input.PreferZone)
+33
View File
@@ -29,6 +29,35 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
)
func validatePreferZones(ctx context.Context, userCred mcclient.IIdentityProvider, input *api.ServerConfigs) error {
if len(input.PreferZones) == 0 {
return nil
}
zoneIds := make([]string, 0, len(input.PreferZones))
var regionId string
for _, zoneStr := range input.PreferZones {
zoneObj, err := ZoneManager.FetchByIdOrName(ctx, userCred, zoneStr)
if err != nil {
if err == sql.ErrNoRows {
return httperrors.NewResourceNotFoundError("Zone %s not found", zoneStr)
}
return httperrors.NewGeneralError(err)
}
zone := zoneObj.(*SZone)
if regionId == "" {
regionId = zone.CloudregionId
} else if regionId != zone.CloudregionId {
return httperrors.NewInputParameterError("All prefer zones must be in the same region")
}
zoneIds = append(zoneIds, zone.Id)
}
input.PreferZones = zoneIds
if input.PreferRegion == "" {
input.PreferRegion = regionId
}
return nil
}
func RunBatchCreateTask(
ctx context.Context,
items []db.IModel,
@@ -149,6 +178,10 @@ func ValidateScheduleCreateData(ctx context.Context, userCred mcclient.TokenCred
input.PreferZone = zone.Id
region, _ := zone.GetRegion()
input.PreferRegion = region.Id
} else if len(input.PreferZones) > 0 {
if err := validatePreferZones(ctx, userCred, input.ServerConfigs); err != nil {
return nil, err
}
} else if input.PreferZone != "" {
zoneStr := input.PreferZone
zoneObj, err := ZoneManager.FetchByIdOrName(ctx, userCred, zoneStr)
+126
View File
@@ -187,6 +187,121 @@ func (self SServerSku) GetGlobalId() string {
return self.ExternalId
}
func skuAvailabilityKey(name, provider string) string {
return name + "\x00" + provider
}
func (manager *SServerSkuManager) fetchServerSkuAvailabilityMap(names []string) (map[string][]api.ServerSkuRegionalAvailability, error) {
ret := map[string][]api.ServerSkuRegionalAvailability{}
if len(names) == 0 {
return ret, nil
}
skus := []struct {
Name string
Provider string
CloudregionId string
ZoneId string
PostpaidStatus string
PrepaidStatus string
}{}
q := manager.Query("name", "provider", "cloudregion_id", "zone_id", "postpaid_status", "prepaid_status").
IsTrue("enabled").In("name", names)
err := q.All(&skus)
if err != nil {
return nil, errors.Wrap(err, "query availability skus")
}
type regionAvail struct {
cloudregionId string
postpaidStatus string
prepaidStatus string
hasRegionLevel bool
zones map[string]api.ServerSkuZoneAvailability
}
tmp := map[string]map[string]*regionAvail{}
regionIdSet := map[string]bool{}
zoneIdSet := map[string]bool{}
for i := range skus {
sku := &skus[i]
key := skuAvailabilityKey(sku.Name, sku.Provider)
if _, ok := tmp[key]; !ok {
tmp[key] = map[string]*regionAvail{}
}
regionMap := tmp[key]
if _, ok := regionMap[sku.CloudregionId]; !ok {
regionMap[sku.CloudregionId] = &regionAvail{
cloudregionId: sku.CloudregionId,
zones: map[string]api.ServerSkuZoneAvailability{},
}
regionIdSet[sku.CloudregionId] = true
}
ra := regionMap[sku.CloudregionId]
if len(sku.ZoneId) == 0 {
ra.hasRegionLevel = true
ra.postpaidStatus = sku.PostpaidStatus
ra.prepaidStatus = sku.PrepaidStatus
} else {
ra.zones[sku.ZoneId] = api.ServerSkuZoneAvailability{
ZoneId: sku.ZoneId,
PostpaidStatus: sku.PostpaidStatus,
PrepaidStatus: sku.PrepaidStatus,
}
zoneIdSet[sku.ZoneId] = true
}
}
regionIds := make([]string, 0, len(regionIdSet))
for id := range regionIdSet {
regionIds = append(regionIds, id)
}
zoneIds := make([]string, 0, len(zoneIdSet))
for id := range zoneIdSet {
zoneIds = append(zoneIds, id)
}
regionNames, err := db.FetchIdNameMap2(CloudregionManager, regionIds)
if err != nil {
return nil, errors.Wrap(err, "FetchIdNameMap2 cloudregion")
}
zoneNames, err := db.FetchIdNameMap2(ZoneManager, zoneIds)
if err != nil {
return nil, errors.Wrap(err, "FetchIdNameMap2 zone")
}
for key, regionMap := range tmp {
avails := make([]api.ServerSkuRegionalAvailability, 0, len(regionMap))
for _, ra := range regionMap {
item := api.ServerSkuRegionalAvailability{
CloudregionId: ra.cloudregionId,
Cloudregion: regionNames[ra.cloudregionId],
}
if ra.hasRegionLevel {
item.PostpaidStatus = ra.postpaidStatus
item.PrepaidStatus = ra.prepaidStatus
}
if len(ra.zones) > 0 {
item.Zones = make([]api.ServerSkuZoneAvailability, 0, len(ra.zones))
for zoneId, za := range ra.zones {
za.Zone = zoneNames[zoneId]
item.Zones = append(item.Zones, za)
}
sort.Slice(item.Zones, func(i, j int) bool {
return item.Zones[i].Zone < item.Zones[j].Zone
})
}
avails = append(avails, item)
}
sort.Slice(avails, func(i, j int) bool {
return avails[i].Cloudregion < avails[j].Cloudregion
})
ret[key] = avails
}
return ret, nil
}
func (manager *SServerSkuManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
@@ -263,6 +378,17 @@ func (manager *SServerSkuManager) FetchCustomizeColumns(
}
}
availMap, err := manager.fetchServerSkuAvailabilityMap(instanceTypes)
if err != nil {
log.Errorf("fetchServerSkuAvailabilityMap error: %v", err)
return rows
}
for i := range rows {
sku := objs[i].(*SServerSku)
key := skuAvailabilityKey(sku.Name, sku.Provider)
rows[i].RegionalAvailability = availMap[key]
}
return rows
}
+8 -6
View File
@@ -23,12 +23,13 @@ import (
)
type DiskCreateOptions struct {
Manager string `help:"Preferred manager where virtual server should be created" json:"prefer_manager_id"`
Region string `help:"Preferred region where virtual server should be created" json:"prefer_region_id"`
Zone string `help:"Preferred zone where virtual server should be created" json:"prefer_zone_id"`
Wire string `help:"Preferred wire where virtual server should be created" json:"prefer_wire_id"`
Host string `help:"Preferred host where virtual server should be created" json:"prefer_host_id"`
Count int `help:"Count to create" json:"count"`
Manager string `help:"Preferred manager where virtual server should be created" json:"prefer_manager_id"`
Region string `help:"Preferred region where virtual server should be created" json:"prefer_region_id"`
Zone string `help:"Preferred zone where virtual server should be created" json:"prefer_zone_id"`
Zones []string `help:"Preferred zones where virtual server should be created" json:"prefer_zones"`
Wire string `help:"Preferred wire where virtual server should be created" json:"prefer_wire_id"`
Host string `help:"Preferred host where virtual server should be created" json:"prefer_host_id"`
Count int `help:"Count to create" json:"count"`
NAME string `help:"Name of the disk"`
DISKDESC string `help:"Image size or size of virtual disk"`
@@ -63,6 +64,7 @@ func (o DiskCreateOptions) Params() (*api.DiskCreateInput, error) {
PreferManager: o.Manager,
PreferRegion: o.Region,
PreferZone: o.Zone,
PreferZones: o.Zones,
PreferWire: o.Wire,
PreferHost: o.Host,
DiskConfig: config,
+7 -5
View File
@@ -249,11 +249,12 @@ func ParseServerDeployInfoList(list []string) ([]*computeapi.DeployConfig, error
}
type ServerCreateCommonConfig struct {
Manager string `help:"Preferred cloudprovider where virtual server should bd created" json:"prefer_manager"`
Region string `help:"Preferred region where virtual server should be created" json:"prefer_region"`
Zone string `help:"Preferred zone where virtual server should be created" json:"prefer_zone"`
Wire string `help:"Preferred wire where virtual server should be created" json:"prefer_wire"`
Host string `help:"Preferred host where virtual server should be created" json:"prefer_host"`
Manager string `help:"Preferred cloudprovider where virtual server should bd created" json:"prefer_manager"`
Region string `help:"Preferred region where virtual server should be created" json:"prefer_region"`
Zone string `help:"Preferred zone where virtual server should be created" json:"prefer_zone"`
Zones []string `help:"Preferred zones where virtual server should be created" json:"prefer_zones"`
Wire string `help:"Preferred wire where virtual server should be created" json:"prefer_wire"`
Host string `help:"Preferred host where virtual server should be created" json:"prefer_host"`
ResourceType string `help:"Resource type" choices:"shared|prepaid|dedicated"`
Schedtag []string `help:"Schedule policy, key = aggregate name, value = require|exclude|prefer|avoid" metavar:"<KEY:VALUE>"`
@@ -294,6 +295,7 @@ func (o ServerCreateCommonConfig) Data() (*computeapi.ServerConfigs, error) {
PreferManager: o.Manager,
PreferRegion: o.Region,
PreferZone: o.Zone,
PreferZones: o.Zones,
PreferWire: o.Wire,
PreferHost: o.Host,
ResourceType: o.ResourceType,
@@ -52,7 +52,7 @@ func (f *ImagePredicate) PreExecute(ctx context.Context, u *core.Unit, cs []core
return false, nil
}
imageId := disks[0].ImageId
if len(imageId) == 0 || u.SchedData().PreferZone != "" {
if len(imageId) == 0 || u.SchedData().HasPreferZone() {
return false, nil
}
if !utils.IsInStringArray(u.SchedData().Provider, compute.PUBLIC_CLOUD_PROVIDERS) && !utils.IsInStringArray(u.SchedData().Provider, compute.PRIVATE_CLOUD_PROVIDERS) {
@@ -58,9 +58,9 @@ func (p *InstanceTypePredicate) Execute(ctx context.Context, u *core.Unit, c cor
instanceType := d.InstanceType
reqRegion := d.PreferRegion
reqZone := d.PreferZone
reqZones := d.GetPreferZones()
if reqRegion != "" && reqZone == "" {
if reqRegion != "" && len(reqZones) == 0 {
skus := skuman.GetByRegion(instanceType, regionId)
if len(skus) == 0 {
h.Exclude(fmt.Sprintf("Not found server sku %s at region %s", instanceType, regionName))
@@ -43,7 +43,7 @@ func (p *ZoneSchedtagPredicate) Clone() core.FitPredicate {
type zoneSchedtagInputW struct {
schedData *api.SchedInfo
zone string
zones []string
schedtags []*computeapi.SchedtagConfig
}
@@ -57,7 +57,7 @@ func (p *ZoneSchedtagPredicate) GetInputs(u *core.Unit) []ISchedtagCustomer {
return []ISchedtagCustomer{
&zoneSchedtagInputW{
schedData: data,
zone: data.PreferZone,
zones: data.GetPreferZones(),
schedtags: schedtags,
},
}
@@ -76,7 +76,7 @@ func (w *zoneSchedtagInputW) GetDynamicConditionInput() *jsonutils.JSONDict {
}
func (w *zoneSchedtagInputW) IsSpecifyResource() bool {
return w.zone != ""
return len(w.zones) > 0
}
func (w *zoneSchedtagInputW) GetSchedtags() []*computeapi.SchedtagConfig {
@@ -33,6 +33,7 @@ type CandidateGetArgs struct {
ResType string
RegionID string
ZoneID string
ZoneIDs []string
ManagerID string
HostTypes []string
}
@@ -208,12 +209,12 @@ func (cm *CandidateManager) GetCandidates(args CandidateGetArgs) ([]core.Candida
result := []core.Candidater{}
matchZone := func(r core.Candidater, zoneId string) bool {
matchZone := func(r core.Candidater, zoneId string, zoneIds []string) bool {
if len(zoneIds) > 0 {
return utils.IsInStringArray(r.Getter().Zone().GetId(), zoneIds)
}
if zoneId != "" {
if r.Getter().Zone().GetId() == zoneId {
return true
}
return false
return r.Getter().Zone().GetId() == zoneId
}
return true
}
@@ -258,7 +259,7 @@ func (cm *CandidateManager) GetCandidates(args CandidateGetArgs) ([]core.Candida
continue
}
if !matchZone(r, args.ZoneID) {
if !matchZone(r, args.ZoneID, args.ZoneIDs) {
continue
}
+1 -1
View File
@@ -44,7 +44,7 @@ func candidatesByProvider(provider CandidatesProvider, schedData *api.SchedInfo)
} else {
args := data_manager.CandidateGetArgs{
ResType: provider.CandidateType(),
ZoneID: schedData.PreferZone,
ZoneIDs: schedData.GetPreferZones(),
RegionID: schedData.PreferRegion,
ManagerID: schedData.PreferManager,
HostTypes: schedData.GetCandidateHostTypes(),