feat(scheduler): Change the action for guests with backup and instanceGroup

1. 对于主机组的打散功能,放在调度的最后单独处理。
2. 主备机的调度,通过在主机和备机之间设置一个粒度为1的主机组来实现。
3. 虚拟机的调度默认打散,在打散的基础上,选择容量大的宿主机。
3. 主备机中,主机和备机的默认打散流程分散开,互不影响。
This commit is contained in:
rainzm
2020-07-21 20:29:47 +08:00
parent 3c5e2998f6
commit fa0450b454
6 changed files with 328 additions and 335 deletions
@@ -1,156 +0,0 @@
// 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 predicates
import (
"fmt"
"math"
"yunion.io/x/onecloud/pkg/scheduler/core"
)
type InstanceGroupPredicate struct {
BasePredicate
}
func (p *InstanceGroupPredicate) Name() string {
return "instance_group"
}
func (p *InstanceGroupPredicate) Clone() core.FitPredicate {
return &InstanceGroupPredicate{}
}
func (p *InstanceGroupPredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool, error) {
schedDate := u.SchedData()
if schedDate.InstanceGroupIds == nil || len(schedDate.InstanceGroupIds) == 0 {
return false, nil
}
return true, nil
}
func (p *InstanceGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) {
return true, nil, nil
}
type SForcedGroupPredicate struct {
InstanceGroupPredicate
}
func (p *SForcedGroupPredicate) Name() string {
return "forced_instance_group"
}
func (p *SForcedGroupPredicate) Clone() core.FitPredicate {
return &SForcedGroupPredicate{}
}
// SForcedGroupPredicate make sure that there is no more guest with same group whose IsForcedSpe is ture in a host
// for all forced groups in u.SchedData.InstanceGroupIds, so that the capacity is the min value of the FreeGroupCounts
func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) {
h := NewPredicateHelper(p, u, c)
schedDate := u.SchedData()
instanceGroups := c.Getter().InstanceGroups()
minFree := math.MaxInt32
for _, id := range schedDate.InstanceGroupIds {
detail := schedDate.InstanceGroupsDetail[id]
// SForcedGroupPredicate only deal with group whose ForceDispersion is ture
if detail.ForceDispersion.IsFalse() {
continue
}
var free int
if _, ok := instanceGroups[id]; ok {
free, _ = c.Getter().GetFreeGroupCount(id)
if free < 1 {
h.AppendPredicateFailMsg(fmt.Sprintf(
"the number of guests with same instance group '%s' in this host has reached the upper limit",
instanceGroups[id].GetName()))
minFree = 0
break
}
} else {
free = detail.Granularity
}
if free < minFree {
minFree = free
}
}
// chose the min capacity of groups
h.SetCapacity(int64(minFree))
return h.GetResult()
}
type SUnForcedGroupPredicate struct {
InstanceGroupPredicate
}
func (p *SUnForcedGroupPredicate) Name() string {
return "unforced_instance_group"
}
func (p *SUnForcedGroupPredicate) Clone() core.FitPredicate {
return &SUnForcedGroupPredicate{}
}
func (p *SUnForcedGroupPredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool, error) {
ret, err := p.InstanceGroupPredicate.PreExecute(u, cs)
if err != nil || !ret {
return ret, err
}
u.RegisterSelectPriorityUpdater(p.Name(), func(u *core.Unit, origin core.SSelectPriorityValue,
hostID string) core.SSelectPriorityValue {
return origin.SubOne()
})
return ret, err
}
// SUnForcedGroupPredicate make sure that the guests are assigned to these hosts who has enough FreeGroupCount of
// unforced groups, so that it will improve the priority of these hosts meet the conditions and the priority should
// be the max value of the FreeGroupCounts
func (p *SUnForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason,
error) {
h := NewPredicateHelper(p, u, c)
schedDate := u.SchedData()
instanceGroups := c.Getter().InstanceGroups()
maxPriority := 0
for _, id := range schedDate.InstanceGroupIds {
detail := schedDate.InstanceGroupsDetail[id]
// SUnForcedGroupPredicate only deal with group whose ForceDispersion is false
if detail.ForceDispersion.IsTrue() {
continue
}
var priority int
if _, ok := instanceGroups[id]; ok {
free, _ := c.Getter().GetFreeGroupCount(id)
if free < 1 {
priority = 0
}
priority = free
} else {
priority = detail.Granularity
}
if priority > maxPriority {
maxPriority = priority
}
}
// set priority
h.SetSelectPriority(maxPriority)
return h.GetResult()
}
@@ -45,8 +45,6 @@ func defaultPredicates() sets.String {
factory.RegisterFitPredicate("m-GuestDiskschedtagFilter", &predicates.DiskSchedtagPredicate{}),
factory.RegisterFitPredicate("n-ServerSkuFilter", &predicates.InstanceTypePredicate{}),
factory.RegisterFitPredicate("o-GuestNetschedtagFilter", &predicates.NetworkSchedtagPredicate{}),
factory.RegisterFitPredicate("p-GuestForcedDispersionFilter", &predicates.SForcedGroupPredicate{}),
factory.RegisterFitPredicate("p-GuestUnForcedDispersionFilter", &predicates.SUnForcedGroupPredicate{}),
factory.RegisterFitPredicate("z-QuotaFilter", &predicates.SQuotaPredicate{}),
)
}
+3 -1
View File
@@ -31,6 +31,8 @@ import (
schedmodels "yunion.io/x/onecloud/pkg/scheduler/models"
)
var ErrInstanceGroupNotFound = errors.Error("InstanceGroupNotFound")
type BaseHostDesc struct {
*computemodels.SHost
Region *computemodels.SCloudregion `json:"region"`
@@ -128,7 +130,7 @@ func (b baseHostGetter) GetFreeGroupCount(groupId string) (int, error) {
// Must Be
scg, ok := b.h.InstanceGroups[groupId]
if !ok {
return 0, fmt.Errorf("No such Group id")
return 0, errors.Wrap(ErrInstanceGroupNotFound, groupId)
}
free := scg.Granularity - scg.ReferCount
if free < 1 {
-8
View File
@@ -450,17 +450,12 @@ func SelectHosts(unit *Unit, priorityList HostPriorityList) ([]*SelectedCandidat
completed:
for len(priorityList) > 0 {
log.V(10).Debugf("PriorityList: %#v", priorityList)
currentPriority := unit.GetMaxSelectPriority()
priorityList0 := HostPriorityList{}
for _, it := range priorityList {
if count <= 0 {
break completed
}
hostID := it.Host
if !currentPriority.IsEmpty() && unit.GetSelectPriority(hostID).Less(currentPriority) {
priorityList0 = append(priorityList0, it)
continue
}
var (
selectedItem *SelectedCandidate
ok bool
@@ -479,9 +474,6 @@ completed:
priorityList0 = append(priorityList0, it)
}
}
if !currentPriority.IsEmpty() {
unit.UpdateSelectPriority()
}
// sort by score
priorityList = priorityList0
//sort.Sort(sort.Reverse(priorityList))
@@ -0,0 +1,323 @@
// 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 manager
import (
"fmt"
"sort"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
schedapi "yunion.io/x/onecloud/pkg/apis/scheduler"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/scheduler/api"
"yunion.io/x/onecloud/pkg/scheduler/cache/candidate"
"yunion.io/x/onecloud/pkg/scheduler/core"
)
func transToInstanceGroupSchedResult(result *core.SchedResultItemList, schedInfo *api.SchedInfo) *schedapi.ScheduleOutput {
for _, item := range result.Data {
item.Count = 0
}
guestInfos, backGuestInfos, groups := generateGuestInfo(schedInfo)
hosts := buildHosts(result, groups)
if len(backGuestInfos) > 0 {
return getBackupSchedResult(hosts, guestInfos, backGuestInfos, schedInfo.SessionId)
}
return getSchedResult(hosts, guestInfos, schedInfo.SessionId)
}
type sGuestInfo struct {
schedInfo *api.SchedInfo
instanceGroupsDetail map[string]*models.SGroup
preferHost string
}
type sSchedResultItem struct {
*core.SchedResultItem
instanceGroupCapacity map[string]int64
masterCount int64
backupCount int64
}
func buildHosts(result *core.SchedResultItemList, groups map[string]*models.SGroup) []*sSchedResultItem {
hosts := make([]*sSchedResultItem, result.Data.Len())
for i := 0; i < len(result.Data); i++ {
getter := result.Data[i].Candidater.Getter()
igCapacity := make(map[string]int64)
for id, group := range groups {
c, err := getter.GetFreeGroupCount(id)
if err != nil {
if errors.Cause(err) == candidate.ErrInstanceGroupNotFound {
igCapacity[id] = int64(group.Granularity)
} else {
igCapacity[id] = 0
log.Errorf("GetFreeGroupCount: %s", err.Error())
}
} else {
igCapacity[id] = int64(c)
}
}
hosts[i] = &sSchedResultItem{
SchedResultItem: result.Data[i],
instanceGroupCapacity: igCapacity,
}
}
sortHosts(hosts, nil)
return hosts
}
// sortHost sorts the host for guest that is the backup one of the high-availability guest
// if isBackup is true and the master one if isBackup is false.
func sortHosts(hosts []*sSchedResultItem, isBackup *bool) {
sort.Slice(hosts, func(i, j int) bool {
var counti, countj int64
switch {
case isBackup == nil:
counti, countj = hosts[i].Count, hosts[j].Count
case *isBackup:
counti, countj = hosts[i].backupCount, hosts[j].backupCount
default:
counti, countj = hosts[i].masterCount, hosts[j].masterCount
}
if counti == countj {
return hosts[i].Capacity > hosts[j].Capacity
}
return counti < countj
})
}
// buildWireHosts classify hosts according to their wire
func buildWireHosts(hosts []*sSchedResultItem) map[string][]*sSchedResultItem {
wireHostMap := make(map[string][]*sSchedResultItem)
for _, host := range hosts {
networks := host.Candidater.Getter().Networks()
for j := 0; j < len(networks); j++ {
if hosts, ok := wireHostMap[networks[j].WireId]; ok {
if hostsIndex(host.ID, hosts) < 0 {
wireHostMap[networks[j].WireId] = append(hosts, host)
}
} else {
wireHostMap[networks[j].WireId] = []*sSchedResultItem{host}
}
}
}
return wireHostMap
}
// generateGuestInfo return guestInfos, backupGuestInfos and all instanceGroups
func generateGuestInfo(schedInfo *api.SchedInfo) ([]sGuestInfo, []sGuestInfo, map[string]*models.SGroup) {
infos := make([]sGuestInfo, 0, schedInfo.Count)
infobs := make([]sGuestInfo, 0, schedInfo.Count)
groups := make(map[string]*models.SGroup)
name := schedInfo.Name
if len(name) == 0 {
name = "default"
}
for id, group := range schedInfo.InstanceGroupsDetail {
groups[id] = group
}
for i := 0; i < schedInfo.Count; i++ {
info := sGuestInfo{
schedInfo: schedInfo,
instanceGroupsDetail: make(map[string]*models.SGroup),
preferHost: schedInfo.PreferHost,
}
for id, group := range schedInfo.InstanceGroupsDetail {
info.instanceGroupsDetail[id] = group
}
infos = append(infos, info)
if !schedInfo.Backup {
continue
}
infob := sGuestInfo{
schedInfo: schedInfo,
preferHost: schedInfo.PreferBackupHost,
instanceGroupsDetail: make(map[string]*models.SGroup),
}
infobs = append(infobs, infob)
// Virtual an instanceGroup
group := models.SGroup{
Granularity: 1,
ForceDispersion: tristate.True,
}
groupid := fmt.Sprintf("virtual-%s-%d", name, i)
group.Id = groupid
infos[i].instanceGroupsDetail[groupid] = &group
infobs[i].instanceGroupsDetail[groupid] = &group
groups[groupid] = &group
}
return infos, infobs, groups
}
func hostsIndex(hostId string, hosts []*sSchedResultItem) int {
for i := 0; i < len(hosts); i++ {
if hosts[i].ID == hostId {
return i
}
}
return -1
}
// getBackupSchedResult return the ScheduleOutput for guest without backup
func getSchedResult(hosts []*sSchedResultItem, guestInfos []sGuestInfo, sid string) *schedapi.ScheduleOutput {
apiResults := make([]*schedapi.CandidateResource, 0)
storageUsed := core.NewStorageUsed()
var i int = 0
for ; i < len(guestInfos); i++ {
host := selectHost(hosts, guestInfos[i], nil, true)
if host == nil {
host = selectHost(hosts, guestInfos[i], nil, false)
if host == nil {
er := &schedapi.CandidateResource{Error: fmt.Sprintf("no suitable Host for No.%d Guest", i+1)}
apiResults = append(apiResults, er)
break
}
}
markHostUsed(host, guestInfos[i], nil)
tr := host.ToCandidateResource(storageUsed)
tr.SessionId = sid
apiResults = append(apiResults, tr)
}
for ; i < len(guestInfos); i++ {
er := &schedapi.CandidateResource{Error: fmt.Sprintf("no suitable Host for No.%d Guest", i+1)}
apiResults = append(apiResults, er)
}
ret := new(schedapi.ScheduleOutput)
ret.Candidates = apiResults
return ret
}
// getBackupSchedResult return the ScheduleOutput for guest with backup
func getBackupSchedResult(hosts []*sSchedResultItem, guestInfos, backGuestInfos []sGuestInfo, sid string) *schedapi.ScheduleOutput {
wireHostMap := buildWireHosts(hosts)
apiResults := make([]*schedapi.CandidateResource, 0, len(guestInfos))
nowireIds := sets.NewString()
storageUsed := core.NewStorageUsed()
isBackup := true
isMaster := false
for i := 0; i < len(guestInfos); i++ {
for wireid, hosts := range wireHostMap {
if nowireIds.Has(wireid) {
continue
}
masterItem := selectHost(hosts, guestInfos[i], &isMaster, true)
if masterItem == nil {
masterItem = selectHost(hosts, guestInfos[i], &isMaster, false)
if masterItem == nil {
nowireIds.Insert(wireid)
continue
}
}
// mark master used for now
markHostUsed(masterItem, guestInfos[i], &isMaster)
backupItem := selectHost(hosts, backGuestInfos[i], &isBackup, false)
if backupItem == nil {
nowireIds.Insert(wireid)
unMarkHostUsed(masterItem, guestInfos[i], &isMaster)
continue
}
markHostUsed(backupItem, backGuestInfos[i], &isBackup)
canRe := masterItem.ToCandidateResource(storageUsed)
canRe.BackupCandidate = backupItem.ToCandidateResource(storageUsed)
canRe.SessionId = sid
canRe.BackupCandidate.SessionId = sid
apiResults = append(apiResults, canRe)
break
}
if len(apiResults) == i+1 {
continue
}
er := &schedapi.CandidateResource{Error: fmt.Sprintf("no suitable Host for No.%d Highly available Guest", i+1)}
apiResults = append(apiResults, er)
}
ret := new(schedapi.ScheduleOutput)
ret.Candidates = apiResults
return ret
}
func markHostUsed(host *sSchedResultItem, guestInfo sGuestInfo, isBackup *bool) {
for gid := range guestInfo.instanceGroupsDetail {
host.instanceGroupCapacity[gid] = host.instanceGroupCapacity[gid] - 1
}
host.Capacity--
host.Count++
if isBackup == nil {
return
}
if *isBackup {
host.backupCount++
} else {
host.masterCount++
}
}
// unMarkHostUsed is the reverse operation of markHostUsed
func unMarkHostUsed(host *sSchedResultItem, guestInfo sGuestInfo, isBackup *bool) {
for gid := range guestInfo.instanceGroupsDetail {
host.instanceGroupCapacity[gid] = host.instanceGroupCapacity[gid] + 1
}
host.Capacity++
host.Count--
if isBackup == nil {
return
}
if *isBackup {
host.backupCount--
} else {
host.masterCount--
}
}
// selectHost select host from hosts for guest described by guestInfo.
// If forced is true, all instanceGroups will be forced.
// Otherwise, the instanceGroups with ForceDispersion 'false' will be unforced.
func selectHost(hosts []*sSchedResultItem, guestInfo sGuestInfo, isBackup *bool, forced bool) *sSchedResultItem {
sortHosts(hosts, isBackup)
var idx = -1
if len(guestInfo.preferHost) > 0 {
if idx = hostsIndex(guestInfo.preferHost, hosts); idx < 0 {
return nil
}
}
var choosed bool
Loop:
for i, host := range hosts {
if idx >= 0 && idx != i {
continue
}
if host.Capacity <= 0 {
continue
}
// check forced instanceGroup
for id, group := range guestInfo.instanceGroupsDetail {
capacity := host.instanceGroupCapacity[id]
checkCapacity := forced || group.ForceDispersion.IsTrue()
if checkCapacity && capacity <= 0 {
continue Loop
}
}
idx = i
choosed = true
break
}
if choosed {
return hosts[idx]
}
return nil
}
+2 -168
View File
@@ -16,7 +16,6 @@ package manager
import (
"fmt"
"sort"
"yunion.io/x/log"
@@ -28,9 +27,8 @@ import (
)
func transToSchedResult(result *core.SchedResultItemList, schedInfo *api.SchedInfo) *schedapi.ScheduleOutput {
if schedInfo.Backup {
return transToBackupSchedResult(result,
schedInfo.PreferHost, schedInfo.PreferBackupHost, int64(schedInfo.Count), schedInfo.SessionId)
if schedInfo.Backup || len(schedInfo.InstanceGroupsDetail) > 0 {
return transToInstanceGroupSchedResult(result, schedInfo)
} else {
return transToRegionSchedResult(result.Data, int64(schedInfo.Count), schedInfo.SessionId)
}
@@ -81,101 +79,6 @@ func transToRegionSchedResult(result core.SchedResultItems, count int64, sid str
}
}
func transToBackupSchedResult(
result *core.SchedResultItemList, preferMasterHost, preferBackupHost string, count int64, sid string,
) *schedapi.ScheduleOutput {
// clean each result sched result item's count
for _, item := range result.Data {
item.Count = 0
}
apiResults := newBackupSchedResult(result, preferMasterHost, preferBackupHost, count, sid)
return apiResults
}
func newBackupSchedResult(
result *core.SchedResultItemList,
preferMasterHost, preferBackupHost string,
count int64,
sid string,
) *schedapi.ScheduleOutput {
ret := new(schedapi.ScheduleOutput)
apiResults := make([]*schedapi.CandidateResource, 0)
storageUsed := core.NewStorageUsed()
var wireHostMap map[string]core.SchedResultItems
for i := 0; i < int(count); i++ {
log.V(10).Debugf("Select backup host from result: %s", result)
target, err := getSchedBackupResult(result, preferMasterHost, preferBackupHost, sid, wireHostMap, storageUsed)
if err != nil {
er := &schedapi.CandidateResource{Error: err.Error()}
apiResults = append(apiResults, er)
continue
}
apiResults = append(apiResults, target)
}
ret.Candidates = apiResults
return ret
}
func getSchedBackupResult(
result *core.SchedResultItemList,
preferMasterHost, preferBackupHost string,
sid string, wireHostMap map[string]core.SchedResultItems,
storageUsed *core.StorageUsed,
) (*schedapi.CandidateResource, error) {
if wireHostMap == nil {
wireHostMap = buildWireHostMap(result)
} else {
reviseWireHostMap(wireHostMap)
}
masterHost, backupHost := selectHosts(wireHostMap, preferMasterHost, preferBackupHost)
if masterHost == nil {
return nil, fmt.Errorf("Can't find master host %q", preferMasterHost)
}
if backupHost == nil {
return nil, fmt.Errorf("Can't find backup host %q by master %q", preferBackupHost, masterHost.ID)
}
markHostUsed(masterHost)
markHostUsed(backupHost)
ret := masterHost.ToCandidateResource(storageUsed)
ret.BackupCandidate = backupHost.ToCandidateResource(storageUsed)
ret.SessionId = sid
ret.BackupCandidate.SessionId = sid
return ret, nil
}
func buildWireHostMap(result *core.SchedResultItemList) map[string]core.SchedResultItems {
sort.Sort(sort.Reverse(result.Data))
wireHostMap := make(map[string]core.SchedResultItems)
for i := 0; i < len(result.Data); i++ {
networks := result.Data[i].Candidater.Getter().Networks()
for j := 0; j < len(networks); j++ {
if hosts, ok := wireHostMap[networks[j].WireId]; ok {
if hostInResultItemsIndex(result.Data[i].ID, hosts) < 0 {
wireHostMap[networks[j].WireId] = append(hosts, result.Data[i])
}
} else {
wireHostMap[networks[j].WireId] = core.SchedResultItems{result.Data[i]}
}
}
}
return wireHostMap
}
func reviseWireHostMap(wireHostMap map[string]core.SchedResultItems) {
for _, hosts := range wireHostMap {
sort.Sort(sort.Reverse(hosts))
}
}
func markHostUsed(host *core.SchedResultItem) {
host.Count++
host.Capacity--
}
func hostInResultItemsIndex(hostId string, hosts core.SchedResultItems) int {
for i := 0; i < len(hosts); i++ {
if hosts[i].ID == hostId {
@@ -185,75 +88,6 @@ func hostInResultItemsIndex(hostId string, hosts core.SchedResultItems) int {
return -1
}
func selectHosts(
wireHostMap map[string]core.SchedResultItems, preferMasterHost, preferBackupHost string,
) (*core.SchedResultItem, *core.SchedResultItem) {
var scroe int64
var masterIdx, backupIdx int
var selectedWireId string
for wireId, hosts := range wireHostMap {
masterIdx, backupIdx = -1, -1
if len(hosts) < 2 {
continue
}
if len(preferMasterHost) > 0 {
if masterIdx = hostInResultItemsIndex(preferMasterHost, hosts); masterIdx < 0 {
continue
}
}
if len(preferBackupHost) > 0 {
if backupIdx = hostInResultItemsIndex(preferBackupHost, hosts); backupIdx < 0 {
continue
}
}
// select master host index
if masterIdx < 0 {
for i := 0; i < len(hosts); i++ {
if hosts[i].ID != preferBackupHost {
masterIdx = i
}
}
}
if hosts[masterIdx].Capacity <= 0 {
if len(preferMasterHost) > 0 {
// in case prefer master host capacity isn't enough
break
} else {
continue
}
}
// select backup host index
if backupIdx < 0 {
for i := 0; i < len(hosts); i++ {
if i != masterIdx {
backupIdx = i
}
}
}
if hosts[backupIdx].Capacity <= 0 {
if len(preferBackupHost) > 0 {
// in case perfer backup host capacity isn't enough
break
} else {
continue
}
}
// the highest total score wins
curScore := hosts[masterIdx].Capacity + hosts[backupIdx].Capacity
if curScore > scroe {
selectedWireId = wireId
scroe = curScore
}
}
if len(selectedWireId) == 0 {
return nil, nil
}
return wireHostMap[selectedWireId][masterIdx], wireHostMap[selectedWireId][backupIdx]
}
func transToSchedTestResult(result *core.SchedResultItemList, limit int64) interface{} {
return &api.SchedTestResult{
Data: result.Data,