fix(region): virtual isolated device balance on guest start (#23049)

This commit is contained in:
wanyaoqi
2025-09-15 11:58:23 +08:00
committed by GitHub
parent 4428791397
commit f446f86e9f
13 changed files with 208 additions and 7 deletions
+1
View File
@@ -72,6 +72,7 @@ func init() {
cmd.Get("ipmi", &options.BaseIdOptions{})
cmd.Get("vnc", &options.BaseIdOptions{})
cmd.Get("app-options", &options.BaseIdOptions{})
cmd.Get("isolated-device-numa-stats", &compute.HostIsolatedDeviceNumaStatsOptions{})
cmd.GetWithCustomShow("worker-stats", func(data jsonutils.JSONObject) {
stats, _ := data.GetArray("workers")
printList(&printutils.ListResult{Data: stats}, nil)
+1
View File
@@ -124,6 +124,7 @@ func init() {
cmd.Perform("set-nic-traffic-limit", &options.ServerNicTrafficLimitOptions{})
cmd.Perform("add-sub-ips", &options.ServerAddSubIpsOptions{})
cmd.Perform("update-sub-ips", &options.ServerUpdateSubIpsOptions{})
cmd.BatchPerform("restore-virtual-isolated-devices", &options.ServerIdsOptions{})
cmd.BatchPerform("set-os-info", &options.ServerSetOSInfoOptions{})
cmd.BatchPerform("start-rescue", &options.ServerStartOptions{})
cmd.BatchPerform("stop-rescue", &options.ServerStartOptions{})
+2
View File
@@ -337,6 +337,8 @@ const (
VM_METADATA_RELEASED_DEVICES = "released_devices"
VM_METADATA_CPU_NUMA_PIN = "__cpu_numa_pin"
VM_METADATA_VIRTUAL_ISOLATED_DEVICE_CONFIG = "__virtual_isolated_device_config"
)
// windows allow a maximal length of 15
+4
View File
@@ -760,6 +760,10 @@ type HostUploadGuestsStatusInput struct {
Guests map[string]*HostUploadGuestStatusInput `json:"guests"`
}
type HostIsolatedDeviceNumaStatsInput struct {
DevType string
}
type GuestUploadContainerStatusResponse struct {
Error string `json:"error"`
OK bool `json:"ok"`
@@ -51,6 +51,7 @@ var VITRUAL_DEVICE_TYPES = []string{
CONTAINER_DEV_NVIDIA_MPS,
CONTAINER_DEV_ASCEND_NPU,
CONTAINER_DEV_NVIDIA_GPU_SHARE,
CONTAINER_DEV_VASTAITECH_GPU,
}
var ID_VENDOR_MAP = map[string]string{
+2 -1
View File
@@ -125,7 +125,8 @@ type BaseOptions struct {
GlobalHTTPProxy string `help:"Global http proxy"`
GlobalHTTPSProxy string `help:"Global https proxy"`
IgnoreNonrunningGuests bool `default:"true" help:"Count memory for running guests only when do scheduling. Ignore memory allocation for non-running guests"`
IgnoreNonrunningGuests bool `default:"true" help:"Count memory for running guests only when do scheduling. Ignore memory allocation for non-running guests"`
VirtualDeviceNumaBalance bool `default:"true" help:"fix virtual device numa node balance on guest start"`
PlatformName string `help:"identity name of this platform" default:"Cloudpods"`
PlatformNames map[string]string `help:"identity name of this platform by language"`
+1 -4
View File
@@ -602,10 +602,7 @@ func (p *SPodDriver) BeforeDetachIsolatedDevice(ctx context.Context, userCred mc
newDevs := make([]*api.ContainerDevice, 0)
releasedDevs := make(map[string]models.ContainerReleasedDevice)
for _, curDev := range devs {
if curDev.IsolatedDevice == nil {
continue
}
if curDev.IsolatedDevice.Id != dev.GetId() {
if curDev.IsolatedDevice == nil || curDev.IsolatedDevice.Id != dev.GetId() {
tmpDev := curDev
newDevs = append(newDevs, tmpDev)
} else {
+124
View File
@@ -1128,6 +1128,61 @@ func (self *SGuest) StartResumeTask(ctx context.Context, userCred mcclient.Token
return driver.StartResumeTask(ctx, userCred, self, nil, parentTaskId)
}
func (self *SGuest) PerformRestoreVirtualIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
config := self.GetMetadataJson(ctx, api.VM_METADATA_VIRTUAL_ISOLATED_DEVICE_CONFIG, userCred)
if config == nil {
return nil, nil
}
devConfigs := make([]api.IsolatedDeviceConfig, 0)
err := config.Unmarshal(&devConfigs)
if err != nil {
return nil, errors.Wrap(err, "unmarshal virtual dev configs")
}
devs, err := self.GetIsolatedDevices()
if err != nil {
return nil, errors.Wrap(err, "GetIsolatedDevices")
}
devCount := map[string]int{}
for i := range devConfigs {
key := devConfigs[i].DevType + "-" + devConfigs[i].Model
if cnt, ok := devCount[key]; ok {
devCount[key] = cnt + 1
} else {
devCount[key] = 1
}
}
for i := range devs {
key := devConfigs[i].DevType + "-" + devConfigs[i].Model
if cnt, ok := devCount[key]; ok {
devCount[key] = cnt - 1
}
}
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "get host")
}
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
usedDeviceMap := map[string]*SIsolatedDevice{}
for key, cnt := range devCount {
if cnt <= 0 {
continue
}
segs := strings.SplitN(key, "-", 2)
devConfig := &api.IsolatedDeviceConfig{
Model: segs[1],
DevType: segs[0],
}
err := IsolatedDeviceManager.attachHostDeviceToGuestByModel(ctx, self, host, devConfig, userCred, usedDeviceMap, nil)
if err != nil {
return nil, errors.Wrap(err, "attachHostDeviceToGuestByModel")
}
}
return nil, nil
}
// 开机
func (self *SGuest) PerformStart(
ctx context.Context,
@@ -1560,6 +1615,68 @@ func (self *SGuest) StartInsertVfdTask(ctx context.Context, floppyOrdinal int64,
return nil
}
func (self *SGuest) RebalanceVirtualIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential) error {
devs, err := self.GetIsolatedDevices()
if err != nil {
return errors.Wrap(err, "guest get isolated devices")
}
if len(devs) == 0 {
return nil
}
host, err := self.GetHost()
if err != nil {
return errors.Wrap(err, "guest get host")
}
var numaNodeBalance = true
detachDevs := make([]SIsolatedDevice, 0)
originDevConfigs := make([]api.IsolatedDeviceConfig, 0)
for i := range devs {
if utils.IsInStringArray(devs[i].DevType, api.VITRUAL_DEVICE_TYPES) {
originDevConfigs = append(originDevConfigs, api.IsolatedDeviceConfig{
Model: devs[i].Model,
DevType: devs[i].DevType,
})
detachDevs = append(detachDevs, devs[i])
isBalance, err := host.VirtualDeviceNumaBalance(devs[i].DevType, devs[i].NumaNode)
if err != nil {
return errors.Wrap(err, "VirtualDeviceNumaBalance")
}
if numaNodeBalance && !isBalance {
numaNodeBalance = false
}
}
}
log.Infof("Guest %s on host %s start virtual devices numa node balance %v", self.Id, host.Id, numaNodeBalance)
if !numaNodeBalance {
err := self.SetMetadata(ctx, api.VM_METADATA_VIRTUAL_ISOLATED_DEVICE_CONFIG, originDevConfigs, userCred)
if err != nil {
return errors.Wrap(err, "set metadata virtual isolated device config")
}
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
for i := 0; i < len(detachDevs); i++ {
err := self.detachIsolateDevice(ctx, userCred, &detachDevs[i])
if err != nil {
return errors.Wrapf(err, "detach device %s", detachDevs[i].GetId())
}
}
usedDeviceMap := map[string]*SIsolatedDevice{}
for i := range detachDevs {
devConfig := &api.IsolatedDeviceConfig{
Model: detachDevs[i].Model,
DevType: detachDevs[i].DevType,
}
err := IsolatedDeviceManager.attachHostDeviceToGuestByModel(ctx, self, host, devConfig, userCred, usedDeviceMap, nil)
if err != nil {
return errors.Wrap(err, "attachHostDeviceToGuestByModel")
}
}
}
return nil
}
func (self *SGuest) StartGueststartTask(
ctx context.Context, userCred mcclient.TokenCredential,
data *jsonutils.JSONDict, parentTaskId string,
@@ -1584,6 +1701,13 @@ func (self *SGuest) StartGueststartTask(
}
}
if options.Options.VirtualDeviceNumaBalance {
err := self.RebalanceVirtualIsolatedDevices(ctx, userCred)
if err != nil {
return errors.Wrap(err, "rebalance virtual isolated devices")
}
}
if schedStart {
return self.GuestSchedStartTask(ctx, userCred, data, parentTaskId)
} else {
+11
View File
@@ -8021,6 +8021,17 @@ func (h *SHost) GetDetailsWorkerStats(ctx context.Context, userCred mcclient.Tok
return h.Request(ctx, userCred, httputils.GET, "/worker_stats", nil, nil)
}
func (hh *SHost) GetDetailsIsolatedDeviceNumaStats(ctx context.Context, userCred mcclient.TokenCredential, input *api.HostIsolatedDeviceNumaStatsInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(input.DevType, api.VALID_PASSTHROUGH_TYPES) {
return nil, httperrors.NewInputParameterError("dev_type %s is invalid", input.DevType)
}
stats, err := IsolatedDeviceManager.GetHostAllocatedIsolatedDeviceNumaStats(input.DevType, hh.Id)
if err != nil {
return nil, err
}
return jsonutils.Marshal(stats), nil
}
func (hh *SHost) IsAttach2Wire(wireId string) bool {
netifs := hh.getNetifsOnWire(wireId)
return len(netifs) > 0
+46
View File
@@ -1742,3 +1742,49 @@ func (model *SIsolatedDevice) PerformPrivate(ctx context.Context, userCred mccli
}
return nil, nil
}
type HostIsolatedDevicesNumaStat struct {
NumaNode int8
NumaNodeDevCount int
}
func (manager *SIsolatedDeviceManager) GetHostAllocatedIsolatedDeviceNumaStats(devType, hostId string) ([]HostIsolatedDevicesNumaStat, error) {
q := manager.Query().Equals("host_id", hostId).Equals("dev_type", devType)
guestQ := GuestManager.Query().SubQuery()
q = q.Join(guestQ, sqlchemy.Equals(q.Field("guest_id"), guestQ.Field("id")))
q = q.Filter(sqlchemy.NotEquals(guestQ.Field("status"), api.VM_READY)).GroupBy(q.Field("numa_node"))
q = q.AppendField(sqlchemy.COUNT("numa_node_dev_count", q.Field("numa_node")))
subQ := q.SubQuery()
numaQ := subQ.Query(subQ.Field("numa_node"), subQ.Field("numa_node_dev_count"))
stats := make([]HostIsolatedDevicesNumaStat, 0)
err := numaQ.All(&stats)
if err != nil {
return nil, err
}
return stats, nil
}
func (host *SHost) VirtualDeviceNumaBalance(devType string, numaNode int8) (bool, error) {
if numaNode < 0 {
return true, nil
}
stats, err := IsolatedDeviceManager.GetHostAllocatedIsolatedDeviceNumaStats(devType, host.Id)
if err != nil {
return true, err
}
log.Debugf("VirtualDeviceNumaBalance numa stats %s", jsonutils.Marshal(stats))
if len(stats) <= 1 {
return true, nil
}
sort.Slice(stats, func(i, j int) bool {
return stats[i].NumaNodeDevCount > stats[j].NumaNodeDevCount
})
if stats[0].NumaNodeDevCount-stats[len(stats)-1].NumaNodeDevCount > 1 {
return false, nil
}
return true, nil
}
+4 -1
View File
@@ -37,7 +37,10 @@ func init() {
func (t *PodStartTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
t.SetStage("OnPodStarted", nil)
pod := obj.(*models.SGuest)
pod.StartGueststartTask(ctx, t.GetUserCred(), jsonutils.NewDict(), t.GetTaskId())
err := pod.StartGueststartTask(ctx, t.GetUserCred(), jsonutils.NewDict(), t.GetTaskId())
if err != nil {
t.OnPodStartedFailed(ctx, pod, jsonutils.NewString(err.Error()))
}
}
func (t *PodStartTask) OnPodStarted(ctx context.Context, pod *models.SGuest, _ jsonutils.JSONObject) {
+1 -1
View File
@@ -149,7 +149,7 @@ type SHostOptions struct {
UseBootVga bool `default:"false" help:"Use boot VGA GPU for guest"`
EnableStrictCpuBind bool `default:"false" help:"Enable strict cpu bind, one vcpu bind one pcpu"`
EnableHostAgentNumaAllocate bool `default:"false" help:"Enable host agent numa allocate"`
EnableHostAgentNumaAllocate bool `default:"true" help:"Enable host agent numa allocate"`
EnableCpuBinding bool `default:"true" help:"Enable cpu binding and rebalance"`
EnableOpenflowController bool `default:"false"`
BootVgaPciAddr string `help:"Specific boot vga pci addr incase detect wrong device"`
+10
View File
@@ -178,6 +178,16 @@ func (h HostValidateIPMI) Params() (jsonutils.JSONObject, error) {
}), nil
}
type HostIsolatedDeviceNumaStatsOptions struct {
options.BaseIdOptions
DevType string `json:"dev_type"`
}
func (o *HostIsolatedDeviceNumaStatsOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(o), nil
}
type HostUpdateOptions struct {
options.BaseIdOptions
Name string `help:"New name of the host"`