diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 827e275b6b..79156c19c1 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -890,8 +890,9 @@ type ServerChangeConfigInput struct { // 内存大小, 1024M, 1G VmemSize string `json:"vmem_size"` - // 是否强制关机 - // 若虚拟机不支持开机调整配置, 则需要指定此参数为true, 强制关机后, 再调整配置, 再启动虚拟机 + // 是否允许强制关机 + // 仅当虚拟机不支持开机调整配置, 或开机状态下降配(降低CPU/内存), 或ARM架构时时生效: 需指定为true以允许强制关机后再调整配置并启动; + // 若已支持开机变配, 即使传入该参数也不会强制关机 ForceStop bool `json:"force_stop"` // 调整完配置后是否自动启动 @@ -1512,6 +1513,8 @@ type ServerChangeConfigSettings struct { AutoStart bool `json:"auto_start"` GuestOnline bool `json:"guest_online"` + // 需要强制关机后再调整配置(仅不支持在线变配/降配时为true), 并在完成后自动启动 + ForceStop bool `json:"force_stop"` // 设置虚拟网卡的流量上限 SetTrafficLimits []ServerNicTrafficLimit `json:"set_traffic_limits"` @@ -1525,6 +1528,10 @@ func (conf ServerChangeConfigSettings) CpuChanged() bool { return conf.VcpuCount != conf.Old.VcpuCount } +func (conf ServerChangeConfigSettings) CpuReduced() bool { + return conf.VcpuCount < conf.Old.VcpuCount +} + func (conf ServerChangeConfigSettings) AddedCpu() int { addCpu := conf.VcpuCount - conf.Old.VcpuCount if addCpu < 0 { @@ -1537,6 +1544,10 @@ func (conf ServerChangeConfigSettings) ExtraCpuChanged() bool { return conf.ExtraCpuCount != conf.Old.ExtraCpuCount } +func (conf ServerChangeConfigSettings) ExtraCpuReduced() bool { + return conf.ExtraCpuCount < conf.Old.ExtraCpuCount +} + func (conf ServerChangeConfigSettings) AddedExtraCpu() int { addCpu := conf.ExtraCpuCount - conf.Old.ExtraCpuCount if addCpu < 0 { @@ -1549,6 +1560,10 @@ func (conf ServerChangeConfigSettings) MemChanged() bool { return conf.VmemSize != conf.Old.VmemSize } +func (conf ServerChangeConfigSettings) MemReduced() bool { + return conf.VmemSize < conf.Old.VmemSize +} + func (conf ServerChangeConfigSettings) InstanceTypeChanged() bool { return len(conf.InstanceType) > 0 && conf.InstanceType != conf.Old.InstanceType } @@ -1561,6 +1576,11 @@ func (conf ServerChangeConfigSettings) AddedMem() int { return addMem } +// ConfigReduced 是否为降配(降低CPU/内存) +func (conf ServerChangeConfigSettings) ConfigReduced() bool { + return conf.CpuReduced() || conf.MemReduced() || conf.ExtraCpuReduced() +} + func (conf ServerChangeConfigSettings) AddedDisk() int { var size int for _, resize := range conf.Resize { diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index 1520fb52a1..661658d408 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -87,6 +87,10 @@ func (self *SBaremetalGuestDriver) GetMaxSecurityGroupCount() int { return 0 } +func (self *SBaremetalGuestDriver) AllowReconfigGuest() bool { + return false +} + func (self *SBaremetalGuestDriver) GetMaxVCpuCount() int { return 1024 } diff --git a/pkg/compute/guestdrivers/bingocloud.go b/pkg/compute/guestdrivers/bingocloud.go index d164bc126e..75f2b769d3 100644 --- a/pkg/compute/guestdrivers/bingocloud.go +++ b/pkg/compute/guestdrivers/bingocloud.go @@ -91,6 +91,10 @@ func (self *SBingoCloudGuestDriver) GetDeployStatus() ([]string, error) { return []string{api.VM_READY, api.VM_RUNNING}, nil } +func (self *SBingoCloudGuestDriver) AllowReconfigGuest() bool { + return false +} + func (self *SBingoCloudGuestDriver) GetDefaultSysDiskBackend() string { return "" } diff --git a/pkg/compute/guestdrivers/cnware.go b/pkg/compute/guestdrivers/cnware.go index ba8e478c3d..47dfe78c16 100644 --- a/pkg/compute/guestdrivers/cnware.go +++ b/pkg/compute/guestdrivers/cnware.go @@ -272,7 +272,7 @@ func (self *SCNwareGuestDriver) GetInstanceCapability() cloudprovider.SInstanceC } func (self *SCNwareGuestDriver) AllowReconfigGuest() bool { - return true + return false } func (self *SCNwareGuestDriver) IsSupportEip() bool { diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 15cd56282b..da1756be89 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -1187,35 +1187,6 @@ func (drv *SManagedVirtualizedGuestDriver) RequestChangeVmConfig(ctx context.Con instanceType = sku.Name } - drv, err := guest.GetDriver() - if err != nil { - return nil, errors.Wrapf(err, "GetDriver") - } - - runningOk, err := drv.IsChangeInstanceTypeWhileRunningSupported(guest) - if err != nil { - return nil, errors.Wrapf(err, "IsChangeInstanceTypeWhileRunningSupported") - } - - needStart := false - - if !runningOk { - status := iVM.GetStatus() - if status == api.VM_RUNNING { - err = iVM.StopVM(ctx, &cloudprovider.ServerStopOptions{ - IsForce: true, - }) - if err != nil { - return nil, errors.Wrapf(err, "StopVM") - } - err = cloudprovider.WaitStatus(iVM, api.VM_READY, time.Second*5, time.Minute*10) - if err != nil { - return nil, errors.Wrapf(err, "WaitStatus") - } - needStart = true - } - } - config := &cloudprovider.SManagedVMChangeConfig{ Cpu: int(vcpuCount), CpuSocket: int(cpuSockets), @@ -1261,17 +1232,6 @@ func (drv *SManagedVirtualizedGuestDriver) RequestChangeVmConfig(ctx context.Con } } - if needStart { - err = iVM.StartVM(ctx) - if err != nil { - return nil, errors.Wrapf(err, "StartVM") - } - err = cloudprovider.WaitStatus(iVM, api.VM_RUNNING, time.Second*5, time.Minute*10) - if err != nil { - return nil, errors.Wrapf(err, "WaitStatus") - } - } - return nil, nil }) diff --git a/pkg/compute/guestdrivers/remotefile.go b/pkg/compute/guestdrivers/remotefile.go index d07a069086..b7a56f0fe2 100644 --- a/pkg/compute/guestdrivers/remotefile.go +++ b/pkg/compute/guestdrivers/remotefile.go @@ -69,6 +69,10 @@ func (self *SRemoteFileGuestDriver) GetComputeQuotaKeys(scope rbacscope.TRbacSco return keys } +func (self *SRemoteFileGuestDriver) AllowReconfigGuest() bool { + return false +} + func (self *SRemoteFileGuestDriver) GetDefaultSysDiskBackend() string { return api.STORAGE_LOCAL } diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index e0756624b1..cb59daf040 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -3543,6 +3543,16 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) { return nil, httperrors.NewInvalidStatusError("Cannot change config in status %s", self.Status) } + + if self.RescueMode { + return nil, httperrors.NewInvalidStatusError("Cannot change config in rescue mode") + } + + // 停止计费模式下, 不允许调整配置 + if len(self.ExternalId) > 0 && self.ShutdownMode == api.VM_SHUTDOWN_MODE_STOP_CHARGING && self.Status == api.VM_READY { + return nil, httperrors.NewInvalidStatusError("Cannot change config in ready status when shutdown mode is stop charging") + } + driver, err := self.GetDriver() if err != nil { return nil, err @@ -3570,12 +3580,20 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T if err != nil { return nil, err } - if !runningOk && !input.ForceStop { + // 仅在不支持开机变配, 或开机降配时需要强制关机,或ARM架构时需要强制关机; force_stop 表示允许关机, 而非一定关机 + needForceStop := !runningOk || confs.ConfigReduced() || apis.IsARM(self.OsArch) + if needForceStop && !input.ForceStop { return nil, httperrors.NewInvalidStatusError("Cannot change config in %s for %s, requires force_stop to change config", self.Status, self.GetHypervisor()) } + if needForceStop { + // 走离线变配路径, 变配完成后自动启动 + confs.ForceStop = true + confs.GuestOnline = false + confs.AutoStart = true + } } - if self.PowerStates == api.VM_POWER_STATES_ON && (confs.CpuChanged() || confs.MemChanged()) { + if self.PowerStates == api.VM_POWER_STATES_ON && (confs.CpuChanged() || confs.MemChanged()) && !confs.ForceStop { confs, err = driver.ValidateGuestHotChangeConfigInput(ctx, self, confs) if err != nil { return nil, httperrors.NewInvalidStatusError("cannot change CPU/Memory spec in power status %s: %s", self.PowerStates, err) diff --git a/pkg/compute/tasks/guest/guest_change_config_task.go b/pkg/compute/tasks/guest/guest_change_config_task.go index aaaf1e407e..8b3d238735 100644 --- a/pkg/compute/tasks/guest/guest_change_config_task.go +++ b/pkg/compute/tasks/guest/guest_change_config_task.go @@ -235,6 +235,15 @@ func (task *GuestChangeConfigTask) OnCreateDisksComplete(ctx context.Context, ob } if confs.CpuChanged() || confs.MemChanged() || (drv.DoScheduleSKUFilter() && confs.InstanceTypeChanged()) { + // Status is already VM_CHANGE_FLAVOR; ForceStop means guest was running and needs offline change. + if confs.ForceStop { + task.SetStage("OnGuestStopForChangeConfigComplete", nil) + err = guest.StartGuestStopTask(ctx, task.UserCred, 60, true, false, task.GetTaskId()) + if err != nil { + task.markStageFailed(ctx, guest, jsonutils.NewString(err.Error())) + } + return + } task.SetStage("OnGuestChangeCpuMemSpecComplete", nil) task.startGuestChangeCpuMemSpec(ctx, guest, confs.InstanceType, confs.VcpuCount, confs.CpuSockets, confs.VmemSize) } else { @@ -242,6 +251,23 @@ func (task *GuestChangeConfigTask) OnCreateDisksComplete(ctx context.Context, ob } } +func (task *GuestChangeConfigTask) OnGuestStopForChangeConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + + confs, err := task.getChangeConfigSetting() + if err != nil { + task.markStageFailed(ctx, guest, jsonutils.NewString(err.Error())) + return + } + + task.SetStage("OnGuestChangeCpuMemSpecComplete", nil) + task.startGuestChangeCpuMemSpec(ctx, guest, confs.InstanceType, confs.VcpuCount, confs.CpuSockets, confs.VmemSize) +} + +func (task *GuestChangeConfigTask) OnGuestStopForChangeConfigCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + task.markStageFailed(ctx, obj.(*models.SGuest), data) +} + func (task *GuestChangeConfigTask) startGuestChangeCpuMemSpec(ctx context.Context, guest *models.SGuest, instanceType string, vcpuCount, cpuSockets int, vmemSize int) { drv, err := guest.GetDriver() if err != nil {