diff --git a/pkg/apis/compute/api.go b/pkg/apis/compute/api.go index 14fe3c2ba2..334f8dee4b 100644 --- a/pkg/apis/compute/api.go +++ b/pkg/apis/compute/api.go @@ -216,6 +216,20 @@ type ServerDeployInput struct { AutoStart *bool `json:"auto_start"` } +type GuestBatchMigrateRequest struct { + apis.Meta + + GuestIds []string + PreferHost string +} + +type GuestBatchMigrateParams struct { + Id string + LiveMigrate bool + RescueMode bool + OldStatus string +} + type HostLoginInfo struct { apis.Meta diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index b491e2fb09..ba336fad53 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -283,6 +283,7 @@ func (self *SGuest) StartMigrateTask( ctx context.Context, userCred mcclient.TokenCredential, isRescueMode, autoStart bool, guestStatus, preferHostId, parentTaskId string, ) error { + self.SetStatus(userCred, api.VM_START_MIGRATE, "") data := jsonutils.NewDict() if isRescueMode { data.Set("is_rescue_mode", jsonutils.JSONTrue) @@ -3518,3 +3519,106 @@ func (self *SGuest) StartBlockIoThrottleTask(ctx context.Context, userCred mccli task.ScheduleRun(nil) return nil } + +func (self *SGuestManager) AllowPerformBatchMigrate(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "batch-guest-migrate") +} + +func (self *SGuestManager) PerformBatchMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := new(api.GuestBatchMigrateRequest) + err := data.Unmarshal(params) + if err != nil { + return nil, httperrors.NewInputParameterError("Unmarshal input error %s", err) + } + if len(params.GuestIds) == 0 { + return nil, httperrors.NewInputParameterError("missing guest id") + } + + var preferHostId string + if len(params.PreferHost) > 0 { + if !db.IsAdminAllowPerform(userCred, self, "assign-host") { + return nil, httperrors.NewBadRequestError("Only system admin can assign host") + } + iHost, _ := HostManager.FetchByIdOrName(userCred, params.PreferHost) + if iHost == nil { + return nil, httperrors.NewBadRequestError("Host %s not found", params.PreferHost) + } + host := iHost.(*SHost) + preferHostId = host.Id + } + + guests := make([]SGuest, 0) + q := GuestManager.Query().In("id", params.GuestIds) + err = db.FetchModelObjects(GuestManager, q, &guests) + if err != nil { + return nil, httperrors.NewInternalServerError(err.Error()) + } + if len(guests) != len(params.GuestIds) { + return nil, httperrors.NewBadRequestError("Check input guests is exist") + } + for i := 0; i < len(guests); i++ { + if guests[i].Hypervisor != api.HYPERVISOR_KVM { + return nil, httperrors.NewBadRequestError("guest %s hypervisor %s can't migrate", + guests[i].Name, guests[i].Hypervisor) + } + if len(guests[i].BackupHostId) > 0 { + return nil, httperrors.NewBadRequestError("guest %s has backup, can't migrate", guests[i].Name) + } + if !utils.IsInStringArray(guests[i].Status, []string{api.VM_RUNNING, api.VM_READY, api.VM_UNKNOWN}) { + return nil, httperrors.NewBadRequestError("guest %s status %s can't migrate", guests[i].Name, guests[i].Status) + } + if guests[i].Status == api.VM_RUNNING { + if len(guests[i].GetIsolatedDevices()) > 0 { + return nil, httperrors.NewBadRequestError( + "guest %s status %s has isolated device, can't do migrate", + guests[i].Name, guests[i].Status, + ) + } + cdrom := guests[i].getCdrom(false) + if cdrom != nil && len(cdrom.ImageId) > 0 { + return nil, httperrors.NewBadRequestError("cannot migrate with cdrom") + } + } else if guests[i].Status == api.VM_UNKNOWN { + if guests[i].getDefaultStorageType() == api.STORAGE_LOCAL { + return nil, httperrors.NewBadRequestError( + "guest %s status %s can't migrate with local storage", + guests[i].Name, guests[i].Status, + ) + } + } + } + + var hostGuests = map[string][]*api.GuestBatchMigrateParams{} + for i := 0; i < len(guests); i++ { + bmp := &api.GuestBatchMigrateParams{ + Id: guests[i].Id, + LiveMigrate: guests[i].Status == api.VM_RUNNING, + RescueMode: guests[i].Status == api.VM_UNKNOWN, + OldStatus: guests[i].Status, + } + guests[i].SetStatus(userCred, api.VM_START_MIGRATE, "batch migrate") + if _, ok := hostGuests[guests[i].HostId]; ok { + hostGuests[guests[i].HostId] = append(hostGuests[guests[i].HostId], bmp) + } else { + hostGuests[guests[i].HostId] = []*api.GuestBatchMigrateParams{bmp} + } + } + for hostId, params := range hostGuests { + kwargs := jsonutils.NewDict() + kwargs.Set("guests", jsonutils.Marshal(params)) + if len(preferHostId) > 0 { + kwargs.Set("prefer_host_id", jsonutils.NewString(preferHostId)) + } + host := HostManager.FetchHostById(hostId) + task, err := taskman.TaskManager.NewTask(ctx, "HostGuestsMigrateTask", host, userCred, kwargs, "", "", nil) + if err != nil { + log.Errorln(err) + continue + } + task.ScheduleRun(nil) + } + return nil, nil +} diff --git a/pkg/compute/tasks/host_guests_migrate_task.go b/pkg/compute/tasks/host_guests_migrate_task.go new file mode 100644 index 0000000000..8c07acd970 --- /dev/null +++ b/pkg/compute/tasks/host_guests_migrate_task.go @@ -0,0 +1,73 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + +func init() { + taskman.RegisterTask(HostGuestsMigrateTask{}) +} + +type HostGuestsMigrateTask struct { + taskman.STask +} + +func (self *HostGuestsMigrateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guests := make([]*api.GuestBatchMigrateParams, 0) + err := self.Params.Unmarshal(&guests, "guests") + if err != nil { + self.SetStageFailed(ctx, err.Error()) + return + } + preferHostId, _ := self.Params.GetString("prefer_host_id") + + var guestMigrating bool + var migrateIndex int + for i := 0; i < len(guests); i++ { + guest := models.GuestManager.FetchGuestById(guests[i].Id) + if guests[i].LiveMigrate { + err := guest.StartGuestLiveMigrateTask( + ctx, self.UserCred, guests[i].OldStatus, preferHostId, self.Id) + if err != nil { + log.Errorln(err) + continue + } else { + guestMigrating = true + migrateIndex = i + break + } + } else { + err := guest.StartMigrateTask(ctx, self.UserCred, guests[i].RescueMode, + false, guests[i].OldStatus, preferHostId, self.Id) + if err != nil { + log.Errorln(err) + continue + } else { + guestMigrating = true + migrateIndex = i + break + } + } + } + if !guestMigrating { + self.SetStageComplete(ctx, nil) + } else { + guests := append(guests[:migrateIndex], guests[migrateIndex+1:]...) + params := jsonutils.NewDict() + params.Set("guests", jsonutils.Marshal(guests)) + self.SaveParams(params) + } +} + +func (self *HostGuestsMigrateTask) OnInitFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + log.Errorf("HostGuestsMigrateTask on failed %s", data) + self.OnInit(ctx, obj, data) +}