Merge pull request #11166 from zexi/feat/migrate-forecast

feat(region): add server migration predication API
This commit is contained in:
Zexi Li
2021-05-25 17:03:27 +08:00
committed by GitHub
6 changed files with 181 additions and 56 deletions
+1
View File
@@ -59,6 +59,7 @@ func init() {
cmd.BatchPerform("reset", new(options.ServerResetOptions))
cmd.BatchPerform("restart", new(options.ServerRestartOptions))
cmd.BatchPerform("purge", new(options.ServerIdsOptions))
cmd.PrintObjectYAML().Perform("migrate-forecast", new(options.ServerMigrateForecastOptions))
cmd.Perform("migrate", new(options.ServerMigrateOptions))
cmd.Perform("live-migrate", new(options.ServerLiveMigrateOptions))
cmd.Perform("modify-src-check", new(options.ServerModifySrcCheckOptions))
+33 -5
View File
@@ -24,6 +24,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/util/printutils"
)
type ResourceCmd struct {
@@ -32,12 +33,15 @@ type ResourceCmd struct {
keyword string
prefix string
printObject func(jsonutils.JSONObject)
}
func NewResourceCmd(manager modulebase.IBaseManager) *ResourceCmd {
return &ResourceCmd{
manager: manager,
keyword: manager.GetKeyword(),
manager: manager,
keyword: manager.GetKeyword(),
printObject: printObjectRecursive,
}
}
@@ -62,6 +66,30 @@ func (cmd *ResourceCmd) SetKeyword(keyword string) *ResourceCmd {
return cmd
}
func (cmd *ResourceCmd) PrintObjectYAML() *ResourceCmd {
cmd.printObject = func(obj jsonutils.JSONObject) {
fmt.Print(obj.YAMLString())
}
return cmd
}
func (cmd *ResourceCmd) PrintObjectTable() *ResourceCmd {
cmd.printObject = printutils.PrintJSONObject
return cmd
}
func (cmd *ResourceCmd) PrintObjectKV() *ResourceCmd {
cmd.printObject = printObjectFmtKv
return cmd
}
func (cmd *ResourceCmd) PrintObjectFlattenKV() *ResourceCmd {
cmd.printObject = func(obj jsonutils.JSONObject) {
printObjectRecursiveEx(obj, printObjectFmtKv)
}
return cmd
}
type IOpt interface {
Params() (jsonutils.JSONObject, error)
}
@@ -325,7 +353,7 @@ func (cmd ResourceCmd) PerformWithKeyword(keyword, action string, args IPerformO
if err != nil {
return err
}
printObjectRecursive(ret)
cmd.printObject(ret)
return nil
}
cmd.Run(keyword, args, callback)
@@ -342,7 +370,7 @@ func (cmd ResourceCmd) PerformClassWithKeyword(keyword, action string, args IOpt
if err != nil {
return err
}
printObjectRecursive(ret)
cmd.printObject(ret)
return nil
}
cmd.Run(keyword, args, callback)
@@ -363,7 +391,7 @@ func (cmd ResourceCmd) PerformClass(action string, args IOpt) {
if err != nil {
return err
}
printObjectRecursive(ret)
cmd.printObject(ret)
return nil
}
cmd.Run(action, args, callback)
+8
View File
@@ -532,3 +532,11 @@ type ServerDetachnetworkInput struct {
// 通过Mac解绑网卡, 优先级低于ip_addr
Mac string `json:"mac"`
}
type ServerMigrateForecastInput struct {
PreferHostId string `json:"prefer_host_id"`
// Deprecated
PreferHost string `json:"prefer_host" yunion-deprecated-by:"prefer_host_id"`
LiveMigrate bool `json:"live_migrate"`
SkipCpuCheck bool `josn:"skip_cpu_check"`
}
+119 -37
View File
@@ -358,31 +358,129 @@ func (self *SGuest) CheckQemuVersion(qemuVer, compareVer string) bool {
return true
}
func (self *SGuest) validateMigrate(
ctx context.Context,
userCred mcclient.TokenCredential,
migrateInput *api.GuestMigrateInput,
liveMigrateInput *api.GuestLiveMigrateInput,
) error {
isLiveMigrate := false
if liveMigrateInput != nil {
isLiveMigrate = true
}
if isLiveMigrate {
// do live migrate check
if !self.GetDriver().IsSupportLiveMigrate() {
return httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if err := self.GetDriver().CheckLiveMigrate(self, userCred, *liveMigrateInput); err != nil {
return err
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_SUSPEND}) {
if len(liveMigrateInput.PreferHost) > 0 {
iHost, _ := HostManager.FetchByIdOrName(userCred, liveMigrateInput.PreferHost)
if iHost == nil {
return httperrors.NewBadRequestError("Host %s not found", liveMigrateInput.PreferHost)
}
host := iHost.(*SHost)
liveMigrateInput.PreferHost = host.Id
}
return nil
}
return httperrors.NewBadRequestError("Cannot live migrate in status %s", self.Status)
} else {
// do migrate check
if !self.GetDriver().IsSupportMigrate() {
return httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if err := self.GetDriver().CheckMigrate(self, userCred, *migrateInput); err != nil {
return err
}
if self.Status != api.VM_READY {
return httperrors.NewServerStatusError("Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?", self.Status)
}
if len(migrateInput.PreferHost) > 0 {
iHost, _ := HostManager.FetchByIdOrName(userCred, migrateInput.PreferHost)
if iHost == nil {
return httperrors.NewBadRequestError("Host %s not found", migrateInput.PreferHost)
}
host := iHost.(*SHost)
migrateInput.PreferHost = host.Id
}
return nil
}
}
func (self *SGuest) PerformMigrateForecast(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ServerMigrateForecastInput) (jsonutils.JSONObject, error) {
var (
mInput *api.GuestMigrateInput = nil
lmInput *api.GuestLiveMigrateInput = nil
)
if input.LiveMigrate {
lmInput = &api.GuestLiveMigrateInput{
PreferHost: input.PreferHostId,
SkipCpuCheck: &input.SkipCpuCheck,
}
if err := self.validateMigrate(ctx, userCred, nil, lmInput); err != nil {
return nil, err
}
input.PreferHostId = lmInput.PreferHost
} else {
mInput = &api.GuestMigrateInput{
PreferHost: input.PreferHostId,
}
if err := self.validateMigrate(ctx, userCred, mInput, nil); err != nil {
return nil, err
}
input.PreferHostId = mInput.PreferHost
}
schedParams := self.GetSchedMigrateParams(userCred, input)
s := auth.GetAdminSession(ctx, options.Options.Region, "")
_, res, err := modules.SchedManager.DoScheduleForecast(s, schedParams, 1)
if err != nil {
return nil, errors.Wrap(err, "Do schedule migrate forecast")
}
return res, nil
}
func (self *SGuest) GetSchedMigrateParams(
userCred mcclient.TokenCredential,
input *api.ServerMigrateForecastInput,
) *schedapi.ScheduleInput {
schedDesc := self.ToSchedDesc()
if input.PreferHostId != "" {
schedDesc.ServerConfig.PreferHost = input.PreferHostId
}
if input.LiveMigrate {
schedDesc.LiveMigrate = input.LiveMigrate
if self.GetMetadata("__cpu_mode", userCred) != api.CPU_MODE_QEMU {
host := self.GetHost()
schedDesc.CpuDesc = host.CpuDesc
schedDesc.CpuMicrocode = host.CpuMicrocode
schedDesc.CpuMode = api.CPU_MODE_HOST
} else {
schedDesc.CpuMode = api.CPU_MODE_QEMU
}
schedDesc.SkipCpuCheck = &input.SkipCpuCheck
}
schedDesc.ReuseNetwork = true
return schedDesc
}
func (self *SGuest) AllowPerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GuestMigrateInput) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "migrate")
}
func (self *SGuest) PerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GuestMigrateInput) (jsonutils.JSONObject, error) {
if !self.GetDriver().IsSupportMigrate() {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if err := self.GetDriver().CheckMigrate(self, userCred, input); err != nil {
func (self *SGuest) PerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.GuestMigrateInput) (jsonutils.JSONObject, error) {
if err := self.validateMigrate(ctx, userCred, input, nil); err != nil {
return nil, err
}
if self.Status != api.VM_READY {
return nil, httperrors.NewServerStatusError("Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?", self.Status)
}
var preferHostId string
if len(input.PreferHost) > 0 {
iHost, _ := HostManager.FetchByIdOrName(userCred, input.PreferHost)
if iHost == nil {
return nil, httperrors.NewBadRequestError("Host %s not found", input.PreferHost)
}
host := iHost.(*SHost)
preferHostId = host.Id
}
return nil, self.StartMigrateTask(ctx, userCred, input.IsRescueMode, input.AutoStart, self.Status, preferHostId, "")
return nil, self.StartMigrateTask(ctx, userCred, input.IsRescueMode, input.AutoStart, self.Status, input.PreferHost, "")
}
func (self *SGuest) StartMigrateTask(
@@ -418,27 +516,11 @@ func (self *SGuest) AllowPerformLiveMigrate(ctx context.Context, userCred mcclie
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "live-migrate")
}
func (self *SGuest) PerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GuestLiveMigrateInput) (jsonutils.JSONObject, error) {
if !self.GetDriver().IsSupportLiveMigrate() {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if err := self.GetDriver().CheckLiveMigrate(self, userCred, input); err != nil {
func (self *SGuest) PerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.GuestLiveMigrateInput) (jsonutils.JSONObject, error) {
if err := self.validateMigrate(ctx, userCred, nil, input); err != nil {
return nil, err
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_SUSPEND}) {
var preferHostId string
if len(input.PreferHost) > 0 {
iHost, _ := HostManager.FetchByIdOrName(userCred, input.PreferHost)
if iHost == nil {
return nil, httperrors.NewBadRequestError("Host %s not found", input.PreferHost)
}
host := iHost.(*SHost)
preferHostId = host.Id
}
err := self.StartGuestLiveMigrateTask(ctx, userCred, self.Status, preferHostId, input.SkipCpuCheck, "")
return nil, err
}
return nil, httperrors.NewBadRequestError("Cannot live migrate in status %s", self.Status)
return nil, self.StartGuestLiveMigrateTask(ctx, userCred, self.Status, input.PreferHost, input.SkipCpuCheck, "")
}
func (self *SGuest) StartGuestLiveMigrateTask(ctx context.Context, userCred mcclient.TokenCredential, guestStatus, preferHostId string, skipCpuCheck *bool, parentTaskId string) error {
+5 -14
View File
@@ -60,27 +60,18 @@ func (self *GuestMigrateTask) OnInit(ctx context.Context, obj db.IStandaloneMode
func (self *GuestMigrateTask) GetSchedParams() (*schedapi.ScheduleInput, error) {
obj := self.GetObject()
guest := obj.(*models.SGuest)
schedDesc := guest.ToSchedDesc()
input := new(api.ServerMigrateForecastInput)
if self.Params.Contains("prefer_host_id") {
preferHostId, _ := self.Params.GetString("prefer_host_id")
schedDesc.ServerConfig.PreferHost = preferHostId
input.PreferHostId = preferHostId
}
guestStatus, _ := self.Params.GetString("guest_status")
if !jsonutils.QueryBoolean(self.Params, "is_rescue_mode", false) && (guestStatus == api.VM_RUNNING || guestStatus == api.VM_SUSPEND) {
schedDesc.LiveMigrate = true
if guest.GetMetadata("__cpu_mode", self.UserCred) != api.CPU_MODE_QEMU {
host := guest.GetHost()
schedDesc.CpuDesc = host.CpuDesc
schedDesc.CpuMicrocode = host.CpuMicrocode
schedDesc.CpuMode = api.CPU_MODE_HOST
} else {
schedDesc.CpuMode = api.CPU_MODE_QEMU
}
input.LiveMigrate = true
skipCpuCheck := jsonutils.QueryBoolean(self.Params, "skip_cpu_check", false)
schedDesc.SkipCpuCheck = &skipCpuCheck
input.SkipCpuCheck = skipCpuCheck
}
schedDesc.ReuseNetwork = true
return schedDesc, nil
return guest.GetSchedMigrateParams(self.GetUserCred(), input), nil
}
func (self *GuestMigrateTask) OnStartSchedule(obj IScheduleModel) {
+15
View File
@@ -874,6 +874,21 @@ func (o *ServerRestartOptions) Params() (jsonutils.JSONObject, error) {
return StructToParams(o)
}
type ServerMigrateForecastOptions struct {
ID string `help:"ID of server" json:"-"`
PreferHost string `help:"Server migration prefer host id or name" json:"prefer_host"`
LiveMigrate *bool `help:"Use live migrate"`
SkipCpuCheck *bool `help:"Skip check CPU mode of the target host" json:"skip_cpu_check"`
}
func (o *ServerMigrateForecastOptions) GetId() string {
return o.ID
}
func (o *ServerMigrateForecastOptions) Params() (jsonutils.JSONObject, error) {
return StructToParams(o)
}
type ServerMigrateOptions struct {
ID string `help:"ID of server" json:"-"`
PreferHost string `help:"Server migration prefer host id or name" json:"prefer_host"`