diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index ac0e1f8b90..22750292c7 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -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)) diff --git a/cmd/climc/shell/helper.go b/cmd/climc/shell/helper.go index 4cb267a632..fd33a3f9cf 100644 --- a/cmd/climc/shell/helper.go +++ b/cmd/climc/shell/helper.go @@ -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) diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index f287b89026..a3de55a9c3 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -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"` +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index d0abe8d10d..33f7f61377 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -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 { diff --git a/pkg/compute/tasks/guest_live_migrate_task.go b/pkg/compute/tasks/guest_live_migrate_task.go index 2d78896d92..8a44467b39 100644 --- a/pkg/compute/tasks/guest_live_migrate_task.go +++ b/pkg/compute/tasks/guest_live_migrate_task.go @@ -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) { diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index c4dfb68424..d7babe322d 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -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"`