Merge pull request #13577 from zexi/qemu-mem-snapshot

feat(host): server instance snapshot with memory
This commit is contained in:
Zexi Li
2022-03-04 10:17:11 +08:00
committed by GitHub
29 changed files with 712 additions and 91 deletions

View File

@@ -51,7 +51,7 @@ func init() {
type InstanceSnapshotShowOptions struct {
ID string `help:"ID or Name of snapshot"`
}
R(&InstanceSnapshotShowOptions{}, "snapshot-show", "Show snapshot details", func(s *mcclient.ClientSession, args *InstanceSnapshotShowOptions) error {
R(&InstanceSnapshotShowOptions{}, "instance-snapshot-show", "Show snapshot details", func(s *mcclient.ClientSession, args *InstanceSnapshotShowOptions) error {
result, err := modules.InstanceSnapshots.Get(s, args.ID, nil)
if err != nil {
return err

View File

@@ -722,8 +722,9 @@ func init() {
return nil
})
type ServerCreateSnapshot struct {
ID string `help:"ID or name of VM" json:"-"`
SNAPSHOT string `help:"Instance snapshot name" json:"name"`
ID string `help:"ID or name of VM" json:"-"`
SNAPSHOT string `help:"Instance snapshot name" json:"name"`
WithMemory bool `help:"Save memory state" json:"with_memory"`
}
R(&ServerCreateSnapshot{}, "instance-snapshot-create", "create instance snapshot", func(s *mcclient.ClientSession, opts *ServerCreateSnapshot) error {
params := jsonutils.Marshal(opts)
@@ -773,6 +774,7 @@ func init() {
type ServerRollBackSnapshot struct {
ID string `help:"ID or name of VM" json:"-"`
InstanceSnapshot string `help:"Instance snapshot id or name" json:"instance_snapshot"`
WithMemory bool `help:"Memory restore" json:"with_memory"`
AutoStart bool `help:"Auto start VM"`
}
R(&ServerRollBackSnapshot{}, "instance-snapshot-reset", "reset instance snapshot", func(s *mcclient.ClientSession, opts *ServerRollBackSnapshot) error {

View File

@@ -484,6 +484,8 @@ type ServerResetInput struct {
InstanceSnapshot string `json:"instance_snapshot"`
// 自动启动
AutoStart *bool `json:"auto_start"`
// 恢复内存
WithMemory bool `json:"with_memory"`
}
type ServerStopInput struct {
@@ -791,6 +793,7 @@ type ServerSnapshotAndCloneInput struct {
type ServerInstanceSnapshot struct {
ServerCreateSnapshotParams
WithMemory bool `json:"with_memory"`
}
type ServerCreateSnapshotParams struct {

View File

@@ -0,0 +1,34 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package host
type GuestMemorySnapshotRequest struct {
InstanceSnapshotId string `json:"instance_snapshot_id"`
}
type GuestMemorySnapshotResponse struct {
MemorySnapshotPath string `json:"memory_snapshot_path"`
SizeMB int64 `json:"size_mb"`
}
type GuestMemorySnapshotDeleteRequest struct {
InstanceSnapshotId string `json:"instance_snapshot_id"`
Path string `json:"path"`
}
type GuestMemorySnapshotResetRequest struct {
InstanceSnapshotId string `json:"instance_snapshot_id"`
Path string `json:"path"`
}

View File

@@ -190,11 +190,11 @@ func (self *SBaseGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, dis
return fmt.Errorf("Not Implement")
}
func (self *SBaseGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
func (self *SBaseGuestDriver) RequestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
return fmt.Errorf("Not Implement")
}
func (self *SBaseGuestDriver) RqeuestResumeOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
func (self *SBaseGuestDriver) RequestResumeOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
return fmt.Errorf("Not Implement")
}

View File

@@ -427,7 +427,7 @@ func (self *SESXiGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gues
return err
}
func (self *SESXiGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
func (self *SESXiGuestDriver) RequestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
host, _ := guest.GetHost()
if host == nil {
@@ -451,7 +451,7 @@ func (self *SESXiGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *m
return nil
}
func (self *SESXiGuestDriver) RqeuestResumeOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
func (self *SESXiGuestDriver) RequestResumeOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
host, _ := guest.GetHost()
if host == nil {

View File

@@ -601,7 +601,7 @@ func (self *SKVMGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest
return err
}
func (self *SKVMGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
func (self *SKVMGuestDriver) RequestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
host, _ := guest.GetHost()
url := fmt.Sprintf("%s/servers/%s/suspend", host.ManagerUri, guest.Id)
header := self.getTaskRequestHeader(task)
@@ -609,6 +609,14 @@ func (self *SKVMGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *mo
return err
}
func (self *SKVMGuestDriver) RequestResumeOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
host, _ := guest.GetHost()
url := fmt.Sprintf("%s/servers/%s/resume", host.ManagerUri, guest.Id)
header := self.getTaskRequestHeader(task)
_, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, nil, false)
return err
}
func (self *SKVMGuestDriver) RequestGuestCreateAllDisks(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
input := new(api.ServerCreateInput)
task.GetParams().Unmarshal(input)

View File

@@ -4712,7 +4712,12 @@ func (self *SGuest) PerformInstanceSnapshot(
return nil, errors.Wrap(err, "validateCreateInstanceSnapshot")
}
input.ServerCreateSnapshotParams = params
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, userCred, self, input.Name, false)
if input.WithMemory {
if self.Status != api.VM_RUNNING {
return nil, httperrors.NewUnsupportOperationError("Can't save memory state when guest status is %q", self.Status)
}
}
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, userCred, self, input.Name, false, input.WithMemory)
if err != nil {
quotas.CancelPendingUsage(
ctx, userCred, pendingUsage, pendingUsage, false)
@@ -4795,10 +4800,14 @@ func (self *SGuest) PerformInstanceSnapshotReset(ctx context.Context, userCred m
}
if instanceSnapshot.Status != api.INSTANCE_SNAPSHOT_READY {
return nil, httperrors.NewBadRequestError("Instance sanpshot not ready")
return nil, httperrors.NewBadRequestError("Instance snapshot not ready")
}
err = self.StartSnapshotResetTask(ctx, userCred, instanceSnapshot, input.AutoStart)
if input.WithMemory && !instanceSnapshot.WithMemory {
return nil, httperrors.NewBadRequestError("Instance snapshot not with memory statefile")
}
err = self.StartSnapshotResetTask(ctx, userCred, instanceSnapshot, input.AutoStart, input.WithMemory)
if err != nil {
return nil, httperrors.NewInternalServerError("start snapshot reset failed %s", err)
}
@@ -4806,14 +4815,15 @@ func (self *SGuest) PerformInstanceSnapshotReset(ctx context.Context, userCred m
return nil, nil
}
func (self *SGuest) StartSnapshotResetTask(ctx context.Context, userCred mcclient.TokenCredential, instanceSnapshot *SInstanceSnapshot, autoStart *bool) error {
func (self *SGuest) StartSnapshotResetTask(ctx context.Context, userCred mcclient.TokenCredential, instanceSnapshot *SInstanceSnapshot, autoStart *bool, withMemory bool) error {
data := jsonutils.NewDict()
if autoStart != nil && *autoStart {
data.Set("auto_start", jsonutils.JSONTrue)
}
data.Add(jsonutils.NewBool(withMemory), "with_memory")
self.SetStatus(userCred, api.VM_START_SNAPSHOT_RESET, "start snapshot reset task")
instanceSnapshot.SetStatus(userCred, api.INSTANCE_SNAPSHOT_RESET, "start snapshot reset task")
log.Errorf("====data: %s", data)
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotResetTask", instanceSnapshot, userCred, data, "", "", nil,
); err != nil {
@@ -4886,7 +4896,7 @@ func (self *SGuest) PerformSnapshotAndClone(
}
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(
ctx, userCred, self, instanceSnapshotName,
input.AutoDeleteInstanceSnapshot != nil && *input.AutoDeleteInstanceSnapshot)
input.AutoDeleteInstanceSnapshot != nil && *input.AutoDeleteInstanceSnapshot, false)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false)

View File

@@ -155,10 +155,10 @@ type IGuestDriver interface {
StartGuestAttachDiskTask(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
StartSuspendTask(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
RqeuestSuspendOnHost(ctx context.Context, guest *SGuest, task taskman.ITask) error
RequestSuspendOnHost(ctx context.Context, guest *SGuest, task taskman.ITask) error
StartResumeTask(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
RqeuestResumeOnHost(ctx context.Context, guest *SGuest, task taskman.ITask) error
RequestResumeOnHost(ctx context.Context, guest *SGuest, task taskman.ITask) error
AllowReconfigGuest() bool
DoGuestCreateDisksTask(ctx context.Context, guest *SGuest, task taskman.ITask) error

View File

@@ -79,6 +79,14 @@ type SInstanceSnapshot struct {
SizeMb int `nullable:"false"`
// 镜像ID
ImageId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
// 是否保存内存
WithMemory bool `default:"false" list:"user"`
// 内存文件大小
MemorySizeMB int `nullable:"true" list:"user"`
// 内存文件所在宿主机
MemoryFileHostId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
// 内存文件路径
MemoryFilePath string `width:"512" charset:"utf8" nullable:"true" list:"user"`
}
type SInstanceSnapshotManager struct {
@@ -361,7 +369,7 @@ func (manager *SInstanceSnapshotManager) fillInstanceSnapshot(ctx context.Contex
instanceSnapshot.ServerMetadata = serverMetadata
}
func (manager *SInstanceSnapshotManager) CreateInstanceSnapshot(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, name string, autoDelete bool) (*SInstanceSnapshot, error) {
func (manager *SInstanceSnapshotManager) CreateInstanceSnapshot(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, name string, autoDelete bool, withMemory bool) (*SInstanceSnapshot, error) {
instanceSnapshot := &SInstanceSnapshot{}
instanceSnapshot.SetModelManager(manager, instanceSnapshot)
instanceSnapshot.Name = name
@@ -369,6 +377,8 @@ func (manager *SInstanceSnapshotManager) CreateInstanceSnapshot(ctx context.Cont
manager.fillInstanceSnapshot(ctx, userCred, guest, instanceSnapshot)
// compute size of instanceSnapshot
instanceSnapshot.SizeMb = guest.getDiskSize()
instanceSnapshot.WithMemory = withMemory
instanceSnapshot.MemoryFileHostId = guest.HostId
err := manager.TableSpec().Insert(ctx, instanceSnapshot)
if err != nil {
return nil, err

View File

@@ -29,6 +29,7 @@ import (
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
@@ -1008,9 +1009,25 @@ func (self *SKVMRegionDriver) RequestDeleteInstanceSnapshot(ctx context.Context,
}
if len(snapshots) == 0 {
task.SetStage("OnInstanceSnapshotDelete", nil)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
if isp.WithMemory && isp.MemoryFileHostId != "" && isp.MemoryFilePath != "" {
// request delete memory snapshot
host := models.HostManager.FetchHostById(isp.MemoryFileHostId)
if host == nil {
return errors.Errorf("Not found host by %q", isp.MemoryFileHostId)
}
header := task.GetTaskRequestHeader()
url := fmt.Sprintf("%s/servers/memory-snapshot", host.ManagerUri)
if _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "DELETE", url, header, jsonutils.Marshal(&hostapi.GuestMemorySnapshotDeleteRequest{
InstanceSnapshotId: isp.GetId(),
Path: isp.MemoryFilePath,
}), false); err != nil {
return err
}
} else {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
}
return nil
}
@@ -1055,9 +1072,26 @@ func (self *SKVMRegionDriver) RequestResetToInstanceSnapshot(ctx context.Context
diskIndex := int(diskIndexI64)
if diskIndex >= len(disks) {
task.SetStage("OnInstanceSnapshotReset", nil)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
withMem := jsonutils.QueryBoolean(params, "with_memory", false)
if isp.WithMemory && withMem {
// reset do memory snapshot
host, err := guest.GetHost()
if err != nil {
return err
}
header := task.GetTaskRequestHeader()
url := fmt.Sprintf("%s/servers/%s/memory-snapshot-reset", host.ManagerUri, guest.GetId())
if _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, jsonutils.Marshal(&hostapi.GuestMemorySnapshotResetRequest{
InstanceSnapshotId: isp.GetId(),
Path: isp.MemoryFilePath,
}), false); err != nil {
return err
}
} else {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
}
return nil
}
@@ -1103,9 +1137,24 @@ func (self *SKVMRegionDriver) RequestCreateInstanceSnapshot(ctx context.Context,
diskIndex := int(diskIndexI64)
if diskIndex >= len(disks) {
task.SetStage("OnInstanceSnapshot", nil)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
if isp.WithMemory {
// request do memory snapshot
host, err := guest.GetHost()
if err != nil {
return err
}
header := task.GetTaskRequestHeader()
url := fmt.Sprintf("%s/servers/%s/memory-snapshot", host.ManagerUri, guest.GetId())
if _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, jsonutils.Marshal(&hostapi.GuestMemorySnapshotRequest{
InstanceSnapshotId: isp.GetId(),
}), false); err != nil {
return err
}
} else {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
}
return nil
}

View File

@@ -278,6 +278,10 @@ func (self *GuestMigrateTask) OnUndeployTargetGuestSuccFailed(ctx context.Contex
}
func (self *GuestMigrateTask) OnMigrateConfAndDiskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
if data.Contains("dest_prepared_memory_snapshots") {
msData, _ := data.Get("dest_prepared_memory_snapshots")
self.Params.Set("dest_prepared_memory_snapshots", msData)
}
guestStatus, _ := self.Params.GetString("guest_status")
if !jsonutils.QueryBoolean(self.Params, "is_rescue_mode", false) && (guestStatus == api.VM_RUNNING || guestStatus == api.VM_SUSPEND) {
// Live migrate
@@ -324,6 +328,43 @@ func (self *GuestMigrateTask) OnGuestStartSuccFailed(ctx context.Context, guest
self.TaskFailed(ctx, guest, data)
}
func (self *GuestMigrateTask) getInstanceSnapShotsWithMemory(guest *models.SGuest) ([]*models.SInstanceSnapshot, error) {
isps, err := guest.GetInstanceSnapshots()
if err != nil {
return nil, errors.Wrap(err, "GetInstanceSnapshots")
}
ret := make([]*models.SInstanceSnapshot, 0)
for idx := range isps {
if isps[idx].WithMemory {
ret = append(ret, &isps[idx])
}
}
return ret, nil
}
func (self *GuestMigrateTask) getInstanceSnapShotIdsWithMemory(guest *models.SGuest) (*jsonutils.JSONArray, error) {
isps, err := self.getInstanceSnapShotsWithMemory(guest)
if err != nil {
return nil, errors.Wrap(err, "getInstanceSnapshotsWithMemory")
}
ret := []string{}
for _, isp := range isps {
ret = append(ret, isp.GetId())
}
return jsonutils.Marshal(ret).(*jsonutils.JSONArray), nil
}
func (self *GuestMigrateTask) setBodyMemorySnapshotParams(guest *models.SGuest, srcHost *models.SHost, body *jsonutils.JSONDict) error {
isps, err := self.getInstanceSnapShotIdsWithMemory(guest)
if err != nil {
return errors.Wrap(err, "getInstanceSnapShotsWithMemory")
}
memSnapshotUri := fmt.Sprintf("%s/download/memory_snapshots", srcHost.ManagerUri)
body.Set("memory_snapshots_uri", jsonutils.NewString(memSnapshotUri))
body.Set("src_memory_snapshots", isps)
return nil
}
func (self *GuestMigrateTask) sharedStorageMigrateConf(ctx context.Context, guest *models.SGuest, targetHost *models.SHost) (*jsonutils.JSONDict, error) {
body := jsonutils.NewDict()
body.Set("is_local_storage", jsonutils.JSONFalse)
@@ -331,6 +372,11 @@ func (self *GuestMigrateTask) sharedStorageMigrateConf(ctx context.Context, gues
body.Set("qemu_cmdline", jsonutils.NewString(guest.GetQemuCmdline(self.UserCred)))
targetDesc := guest.GetJsonDescAtHypervisor(ctx, targetHost)
body.Set("desc", jsonutils.Marshal(targetDesc))
sourceHost, _ := guest.GetHost()
if err := self.setBodyMemorySnapshotParams(guest, sourceHost, body); err != nil {
return nil, errors.Wrap(err, "setBodyMemorySnapshotParams")
}
return body, nil
}
@@ -362,6 +408,11 @@ func (self *GuestMigrateTask) localStorageMigrateConf(ctx context.Context,
body.Set("server_url", jsonutils.NewString(serverUrl))
body.Set("qemu_version", jsonutils.NewString(guest.GetQemuVersion(self.UserCred)))
body.Set("qemu_cmdline", jsonutils.NewString(guest.GetQemuCmdline(self.UserCred)))
if err := self.setBodyMemorySnapshotParams(guest, sourceHost, body); err != nil {
return nil, errors.Wrap(err, "setBodyMemorySnapshotParams")
}
targetDesc := guest.GetJsonDescAtHypervisor(ctx, targetHost)
if len(targetDesc.Disks) == 0 {
return nil, errors.Errorf("Get disksDesc error")
@@ -530,7 +581,39 @@ func (self *GuestLiveMigrateTask) OnGuestSyncStatus(ctx context.Context, guest *
self.TaskComplete(ctx, guest)
}
func (self *GuestMigrateTask) updateInstanceSnapshotMemory(ctx context.Context, guest *models.SGuest) error {
if !self.Params.Contains("dest_prepared_memory_snapshots") {
return nil
}
ms, err := self.Params.Get("dest_prepared_memory_snapshots")
if err != nil {
return errors.Wrap(err, "get dest_prepared_memory_snapshots from params")
}
isps, err := self.getInstanceSnapShotsWithMemory(guest)
if err != nil {
return errors.Wrap(err, "getInstanceSnapShotsWithMemory")
}
for _, isp := range isps {
msPath, err := ms.GetString(isp.GetId())
if err != nil {
return errors.Wrapf(err, "get instance snapshot %s memory path from dest prepared", isp.GetId())
}
if _, err := db.Update(isp, func() error {
isp.MemoryFilePath = msPath
isp.MemoryFileHostId = guest.HostId
return nil
}); err != nil {
return errors.Wrapf(err, "update instance snapshot %q memory_filie_path", isp.GetId())
}
}
return nil
}
func (self *GuestMigrateTask) TaskComplete(ctx context.Context, guest *models.SGuest) {
if err := self.updateInstanceSnapshotMemory(ctx, guest); err != nil {
self.TaskFailed(ctx, guest, jsonutils.NewString(err.Error()))
return
}
self.SetStageComplete(ctx, nil)
db.OpsLog.LogEvent(guest, db.ACT_MIGRATE, "Migrate success", self.UserCred)
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_MIGRATE, self.Params, self.UserCred, true)

View File

@@ -37,7 +37,7 @@ func (self *GuestResumeTask) OnInit(ctx context.Context, obj db.IStandaloneModel
guest := obj.(*models.SGuest)
db.OpsLog.LogEvent(guest, db.ACT_RESUMING, "", self.UserCred)
self.SetStage("OnResumeComplete", nil)
err := guest.GetDriver().RqeuestResumeOnHost(ctx, guest, self)
err := guest.GetDriver().RequestResumeOnHost(ctx, guest, self)
if err != nil {
self.OnResumeGuestFail(guest, err.Error())
}

View File

@@ -23,6 +23,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type GuestSuspendTask struct {
@@ -37,8 +38,8 @@ func (self *GuestSuspendTask) OnInit(ctx context.Context, obj db.IStandaloneMode
guest := obj.(*models.SGuest)
db.OpsLog.LogEvent(guest, db.ACT_STOPPING, "", self.UserCred)
guest.SetStatus(self.UserCred, api.VM_SUSPENDING, "GuestSusPendTask")
self.SetStage("on_suspend_complete", nil)
err := guest.GetDriver().RqeuestSuspendOnHost(ctx, guest, self)
self.SetStage("OnSuspendComplete", nil)
err := guest.GetDriver().RequestSuspendOnHost(ctx, guest, self)
if err != nil {
self.OnSuspendGuestFail(guest, err.Error())
}
@@ -48,6 +49,7 @@ func (self *GuestSuspendTask) OnSuspendComplete(ctx context.Context, obj db.ISta
guest := obj.(*models.SGuest)
guest.SetStatus(self.UserCred, api.VM_SUSPEND, "")
db.OpsLog.LogEvent(guest, db.ACT_STOP, "", self.UserCred)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_SUSPEND, "success", self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
@@ -55,6 +57,7 @@ func (self *GuestSuspendTask) OnSuspendCompleteFailed(ctx context.Context, obj d
guest := obj.(*models.SGuest)
guest.SetStatus(self.UserCred, api.VM_RUNNING, "")
db.OpsLog.LogEvent(guest, db.ACT_STOP_FAIL, err.String(), self.UserCred)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_SUSPEND, err.String(), self.UserCred, false)
self.SetStageFailed(ctx, err)
}

View File

@@ -20,6 +20,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
@@ -123,6 +124,21 @@ func (self *InstanceSnapshotCreateTask) OnKvmDiskSnapshotFailed(
func (self *InstanceSnapshotCreateTask) OnInstanceSnapshot(ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
guest, _ := isp.GetGuest()
if isp.WithMemory {
resp := new(hostapi.GuestMemorySnapshotResponse)
if err := data.Unmarshal(resp); err != nil {
self.taskFail(ctx, isp, guest, jsonutils.NewString(err.Error()))
return
}
if _, err := db.Update(isp, func() error {
isp.MemorySizeMB = int(resp.SizeMB)
isp.MemoryFilePath = resp.MemorySnapshotPath
return nil
}); err != nil {
self.taskFail(ctx, isp, guest, jsonutils.NewString(err.Error()))
return
}
}
self.taskComplete(ctx, isp, guest, data)
}

View File

@@ -73,6 +73,8 @@ func (self *InstanceSnapshotResetTask) OnInit(
self.SetStage("OnInstanceSnapshotReset", nil)
params := jsonutils.NewDict()
params.Set("disk_index", jsonutils.NewInt(0))
withMem := jsonutils.QueryBoolean(self.Params, "with_memory", false)
params.Set("with_memory", jsonutils.NewBool(withMem))
if err := isp.GetRegionDriver().RequestResetToInstanceSnapshot(ctx, guest, isp, self, params); err != nil {
self.taskFail(ctx, isp, guest, jsonutils.NewString(err.Error()))
return
@@ -91,6 +93,8 @@ func (self *InstanceSnapshotResetTask) OnKvmDiskReset(
}
params := jsonutils.NewDict()
params.Set("disk_index", jsonutils.NewInt(diskIndex+1))
withMem := jsonutils.QueryBoolean(self.Params, "with_memory", false)
params.Set("with_memory", jsonutils.NewBool(withMem))
if err := isp.GetRegionDriver().RequestResetToInstanceSnapshot(ctx, guest, isp, self, params); err != nil {
self.taskFail(ctx, isp, guest, jsonutils.NewString(err.Error()))
return

View File

@@ -22,6 +22,7 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/hostman/guestman"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman"
@@ -55,6 +56,12 @@ func AddDownloadHandler(prefix string, app *appsrv.Application) {
nil, "snapshot_download", nil)
customizeHandlerInfo(hi)
hi = app.AddHandler2("GET", fmt.Sprintf(
"%s/%s/memory_snapshots/<serverId>/<instanceSnapshotId>",
prefix, kerword), auth.Authenticate(memorySnapshotDownload),
nil, "memory_snapshot_download", nil)
customizeHandlerInfo(hi)
hi = app.AddHandler2("HEAD", fmt.Sprintf("%s/%s/disks/<storageId>/<diskId>",
prefix, kerword), auth.Authenticate(diskHead),
nil, "head_disk_download", nil)
@@ -70,6 +77,11 @@ func AddDownloadHandler(prefix string, app *appsrv.Application) {
fmt.Sprintf("%s/%s/images/<id>", prefix, kerword), auth.Authenticate(imageCacheHead),
nil, "head_image", nil)
customizeHandlerInfo(hi)
hi = app.AddHandler2("HEAD",
fmt.Sprintf("%s/%s/memory_snapshots/<instance_snapshot_id>", prefix, kerword), auth.Authenticate(memorySnapshotHead),
nil, "head_memory_snapshot", nil)
customizeHandlerInfo(hi)
}
}
@@ -230,3 +242,35 @@ func imageCacheHead(ctx context.Context, w http.ResponseWriter, r *http.Request)
hostutils.Response(ctx, w, err)
}
}
func getInstanceSnapShotPath(ctx context.Context, w http.ResponseWriter, r *http.Request) string {
var (
params, _, _ = appsrv.FetchEnv(ctx, w, r)
serverId = params["<serverId>"]
instanceSnapshotId = params["<instanceSnapshotId>"]
)
msPath := guestman.GetMemorySnapshotPath(serverId, instanceSnapshotId)
return msPath
}
func memorySnapshotDownload(ctx context.Context, w http.ResponseWriter, r *http.Request) {
msPath := getInstanceSnapShotPath(ctx, w, r)
var compress = isCompress(r)
var sparse = isSparse(r)
hand := NewSnapshotDownloadProvider(w,
compress, sparse, options.HostOptions.BandwidthLimit, msPath)
if err := hand.Start(); err != nil {
hostutils.Response(ctx, w, err)
}
}
func memorySnapshotHead(ctx context.Context, w http.ResponseWriter, r *http.Request) {
msPath := getInstanceSnapShotPath(ctx, w, r)
var compress = isCompress(r)
var sparse = isSparse(r)
hand := NewSnapshotDownloadProvider(w,
compress, sparse, options.HostOptions.BandwidthLimit, msPath)
if err := hand.HandlerHead(); err != nil {
hostutils.Response(ctx, w, err)
}
}

View File

@@ -25,6 +25,7 @@ import (
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/hostman/guestman"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
@@ -59,39 +60,45 @@ func AddGuestTaskHandler(prefix string, app *appsrv.Application) {
auth.Authenticate(deleteGuest))
for action, f := range map[string]actionFunc{
"create": guestCreate,
"deploy": guestDeploy,
"rebuild": guestRebuild,
"start": guestStart,
"stop": guestStop,
"monitor": guestMonitor,
"sync": guestSync,
"suspend": guestSuspend,
"io-throttle": guestIoThrottle,
"snapshot": guestSnapshot,
"delete-snapshot": guestDeleteSnapshot,
"reload-disk-snapshot": guestReloadDiskSnapshot,
"src-prepare-migrate": guestSrcPrepareMigrate,
"dest-prepare-migrate": guestDestPrepareMigrate,
"live-migrate": guestLiveMigrate,
"resume": guestResume,
"drive-mirror": guestDriveMirror,
"hotplug-cpu-mem": guestHotplugCpuMem,
"cancel-block-jobs": guestCancelBlockJobs,
"create-from-libvirt": guestCreateFromLibvirt,
"create-form-esxi": guestCreateFromEsxi,
"open-forward": guestOpenForward,
"list-forward": guestListForward,
"close-forward": guestCloseForward,
"storage-clone-disk": guestStorageCloneDisk,
"cpuset": guestCPUSet,
"cpuset-remove": guestCPUSetRemove,
"create": guestCreate,
"deploy": guestDeploy,
"rebuild": guestRebuild,
"start": guestStart,
"stop": guestStop,
"monitor": guestMonitor,
"sync": guestSync,
"suspend": guestSuspend,
"io-throttle": guestIoThrottle,
"snapshot": guestSnapshot,
"delete-snapshot": guestDeleteSnapshot,
"reload-disk-snapshot": guestReloadDiskSnapshot,
"src-prepare-migrate": guestSrcPrepareMigrate,
"dest-prepare-migrate": guestDestPrepareMigrate,
"live-migrate": guestLiveMigrate,
"resume": guestResume,
"drive-mirror": guestDriveMirror,
"hotplug-cpu-mem": guestHotplugCpuMem,
"cancel-block-jobs": guestCancelBlockJobs,
"create-from-libvirt": guestCreateFromLibvirt,
"create-form-esxi": guestCreateFromEsxi,
"open-forward": guestOpenForward,
"list-forward": guestListForward,
"close-forward": guestCloseForward,
"storage-clone-disk": guestStorageCloneDisk,
"cpuset": guestCPUSet,
"cpuset-remove": guestCPUSetRemove,
"memory-snapshot": guestMemorySnapshot,
"memory-snapshot-reset": guestMemorySnapshotReset,
} {
app.AddHandler("POST",
fmt.Sprintf("%s/%s/<sid>/%s", prefix, keyWord, action),
auth.Authenticate(guestActions(f)),
)
}
app.AddHandler("DELETE",
fmt.Sprintf("%s/%s/memory-snapshot", prefix, keyWord),
auth.Authenticate(guestMemorySnapshotDelete))
}
}
@@ -374,6 +381,15 @@ func guestDestPrepareMigrate(ctx context.Context, sid string, body jsonutils.JSO
}
params.RebaseDisks = jsonutils.QueryBoolean(body, "rebase_disks", false)
}
msUri, err := body.GetString("memory_snapshots_uri")
if err != nil {
return nil, httperrors.NewMissingParameterError("memory_snapshots_uri")
}
params.MemorySnapshotsUri = msUri
msIds, _ := jsonutils.GetStringArray(body, "src_memory_snapshots")
params.SrcMemorySnapshots = msIds
hostutils.DelayTask(ctx, guestman.GetGuestManager().DestPrepareMigrate, params)
return nil, nil
}
@@ -644,3 +660,56 @@ func guestCPUSetRemove(ctx context.Context, sid string, body jsonutils.JSONObjec
}
return nil, nil
}
func guestMemorySnapshot(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) {
input := new(hostapi.GuestMemorySnapshotRequest)
if err := body.Unmarshal(input); err != nil {
return nil, err
}
gm := guestman.GetGuestManager()
hostutils.DelayTaskWithoutReqctx(ctx, gm.DoMemorySnapshot, &guestman.SMemorySnapshot{
GuestMemorySnapshotRequest: input,
Sid: sid,
})
return nil, nil
}
func guestMemorySnapshotReset(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) {
input := new(hostapi.GuestMemorySnapshotResetRequest)
if err := body.Unmarshal(input); err != nil {
return nil, err
}
if input.InstanceSnapshotId == "" {
return nil, httperrors.NewMissingParameterError("instance_snapshot_id")
}
if input.Path == "" {
return nil, httperrors.NewMissingParameterError("path")
}
gm := guestman.GetGuestManager()
hostutils.DelayTaskWithoutReqctx(ctx, gm.DoResetMemorySnapshot, &guestman.SMemorySnapshotReset{
GuestMemorySnapshotResetRequest: input,
Sid: sid,
})
return nil, nil
}
func guestMemorySnapshotDelete(ctx context.Context, w http.ResponseWriter, r *http.Request) {
_, _, body := appsrv.FetchEnv(ctx, w, r)
input := new(hostapi.GuestMemorySnapshotDeleteRequest)
if err := body.Unmarshal(input); err != nil {
hostutils.Response(ctx, w, err)
return
}
if input.InstanceSnapshotId == "" {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("instance_snapshot_id"))
return
}
if input.Path == "" {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("path"))
return
}
gm := guestman.GetGuestManager()
hostutils.DelayTask(ctx, gm.DoDeleteMemorySnapshot, &guestman.SMemorySnapshotDelete{
GuestMemorySnapshotDeleteRequest: input,
})
}

View File

@@ -17,6 +17,7 @@ package guestman
import (
"yunion.io/x/jsonutils"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/multicloud/esxi/vcenter"
)
@@ -55,6 +56,9 @@ type SDestPrepareMigrate struct {
Desc jsonutils.JSONObject
DisksBackingFile jsonutils.JSONObject
SrcSnapshots jsonutils.JSONObject
MemorySnapshotsUri string
SrcMemorySnapshots []string
}
type SLiveMigrate struct {
@@ -88,6 +92,20 @@ type SDiskSnapshot struct {
Disk storageman.IDisk
}
type SMemorySnapshot struct {
*hostapi.GuestMemorySnapshotRequest
Sid string
}
type SMemorySnapshotReset struct {
*hostapi.GuestMemorySnapshotResetRequest
Sid string
}
type SMemorySnapshotDelete struct {
*hostapi.GuestMemorySnapshotDeleteRequest
}
type SDiskBackup struct {
Sid string
SnapshotId string

View File

@@ -20,6 +20,7 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime/debug"
"strings"
"sync"
@@ -41,11 +42,13 @@ import (
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/hostman/storageman/remotefile"
"yunion.io/x/onecloud/pkg/httperrors"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/cgrouputils"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/timeutils2"
)
@@ -843,6 +846,16 @@ func (m *SGuestManager) DestPrepareMigrate(ctx context.Context, params interface
}
body := jsonutils.NewDict()
if len(migParams.SrcMemorySnapshots) > 0 {
preparedMs, err := m.destinationPrepareMigrateMemorySnapshots(ctx, migParams.Sid, migParams.MemorySnapshotsUri, migParams.SrcMemorySnapshots)
if err != nil {
return nil, errors.Wrap(err, "destination prepare migrate memory snapshots")
}
body.Add(jsonutils.Marshal(preparedMs), "dest_prepared_memory_snapshots")
}
if migParams.LiveMigrate {
startParams := jsonutils.NewDict()
startParams.Set("qemu_version", jsonutils.NewString(migParams.QemuVersion))
@@ -859,7 +872,26 @@ func (m *SGuestManager) DestPrepareMigrate(ctx context.Context, params interface
hostutils.UpdateServerProgress(context.Background(), migParams.Sid, 100.0, 0)
}
return nil, nil
return body, nil
}
func (m *SGuestManager) destinationPrepareMigrateMemorySnapshots(ctx context.Context, serverId string, uri string, ids []string) (map[string]string, error) {
ret := make(map[string]string, 0)
for _, id := range ids {
url := fmt.Sprintf("%s/%s/%s", uri, serverId, id)
msPath := GetMemorySnapshotPath(serverId, id)
dir := filepath.Dir(msPath)
if err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", dir).Run(); err != nil {
return nil, errors.Wrapf(err, "mkdir -p %q", dir)
}
remotefile := remotefile.NewRemoteFile(ctx, url, msPath, false, "", -1, nil, "", "")
if err := remotefile.Fetch(nil); err != nil {
return nil, errors.Wrapf(err, "fetch memory snapshot file %s", url)
} else {
ret[id] = msPath
}
}
return ret, nil
}
func (m *SGuestManager) LiveMigrate(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
@@ -963,6 +995,41 @@ func (m *SGuestManager) DeleteSnapshot(ctx context.Context, params interface{})
}
}
func (m *SGuestManager) DoMemorySnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input, ok := params.(*SMemorySnapshot)
if !ok {
return nil, hostutils.ParamsError
}
guest, _ := m.GetServer(input.Sid)
return guest.ExecMemorySnapshotTask(ctx, input.GuestMemorySnapshotRequest)
}
func (m *SGuestManager) DoResetMemorySnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input, ok := params.(*SMemorySnapshotReset)
if !ok {
return nil, hostutils.ParamsError
}
guest, _ := m.GetServer(input.Sid)
return guest.ExecMemorySnapshotResetTask(ctx, input.GuestMemorySnapshotResetRequest)
}
func (m *SGuestManager) DoDeleteMemorySnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input, ok := params.(*SMemorySnapshotDelete)
if !ok {
return nil, hostutils.ParamsError
}
if err := procutils.NewRemoteCommandAsFarAsPossible("rm", input.Path).Run(); err != nil {
if !strings.Contains(strings.ToLower(err.Error()), "No such file or directory") {
return nil, err
}
}
log.Infof("Memory snapshot file %q removed", input.Path)
return nil, nil
}
func (m *SGuestManager) Resume(ctx context.Context, sid string, isLiveMigrate bool, cleanTLS bool) (jsonutils.JSONObject, error) {
guest, _ := m.GetServer(sid)
if guest.IsStopping() || guest.IsStopped() {

View File

@@ -96,6 +96,74 @@ func (s *SGuestStopTask) CheckGuestRunningLater() {
s.checkGuestRunning()
}
type SGuestSuspendTask struct {
*SKVMGuestInstance
ctx context.Context
onFinishCallback func(*SGuestSuspendTask, string)
}
func NewGuestSuspendTask(
guest *SKVMGuestInstance,
ctx context.Context,
onFinishCallback func(*SGuestSuspendTask, string),
) *SGuestSuspendTask {
t := &SGuestSuspendTask{
SKVMGuestInstance: guest,
ctx: ctx,
}
if onFinishCallback == nil {
onFinishCallback = t.onSaveMemStateComplete
}
t.onFinishCallback = onFinishCallback
return t
}
func (s *SGuestSuspendTask) Start() {
s.Monitor.SimpleCommand("stop", s.onSuspendGuest)
}
func (s *SGuestSuspendTask) GetStateFilePath() string {
return s.SKVMGuestInstance.GetStateFilePath("")
}
func (s *SGuestSuspendTask) onSuspendGuest(results string) {
if strings.Contains(strings.ToLower(results), "error") {
hostutils.TaskFailed(s.ctx, fmt.Sprintf("Suspend error: %s", results))
return
}
statFile := s.GetStateFilePath()
s.Monitor.SaveState(statFile, s.onSaveMemStateWait)
}
func (s *SGuestSuspendTask) onSaveMemStateWait(results string) {
if strings.Contains(strings.ToLower(results), "error") {
hostutils.TaskFailed(s.ctx, fmt.Sprintf("Save memory state error: %s", results))
// TODO: send cont command
return
}
s.Monitor.GetMigrateStatus(s.onSaveMemStateCheck)
}
func (s *SGuestSuspendTask) onSaveMemStateCheck(status string) {
if status == "failed" {
hostutils.TaskFailed(s.ctx, fmt.Sprintf("Save memory state failed"))
// TODO: send cont command
return
} else if status != "completed" {
time.Sleep(time.Second * 3)
log.Infof("Server %s saving memory state status %q", s.GetName(), status)
s.onSaveMemStateWait("")
} else {
log.Infof("Server %s save memory completed", s.GetName())
s.onFinishCallback(s, s.GetStateFilePath())
}
}
func (s *SGuestSuspendTask) onSaveMemStateComplete(_ *SGuestSuspendTask, _ string) {
log.Infof("Server %s memory state saved, stopping server", s.GetName())
s.ExecStopTask(s.ctx, int64(3))
}
/**
* GuestSyncConfigTaskExecutor
**/
@@ -782,6 +850,8 @@ type SGuestResumeTask struct {
isTimeout bool
cleanTLS bool
getTaskData func() (jsonutils.JSONObject, error)
}
func NewGuestResumeTask(ctx context.Context, s *SKVMGuestInstance, isTimeout bool, cleanTLS bool) *SGuestResumeTask {
@@ -790,6 +860,7 @@ func NewGuestResumeTask(ctx context.Context, s *SKVMGuestInstance, isTimeout boo
ctx: ctx,
isTimeout: isTimeout,
cleanTLS: cleanTLS,
getTaskData: nil,
}
}
@@ -810,6 +881,10 @@ func (s *SGuestResumeTask) Start() {
s.confirmRunning()
}
func (s *SGuestResumeTask) GetStateFilePath() string {
return s.SKVMGuestInstance.GetStateFilePath("")
}
func (s *SGuestResumeTask) Stop() {
// TODO
// stop stream disk
@@ -879,9 +954,25 @@ func (s *SGuestResumeTask) onResumeSucc(res string) {
s.confirmRunning()
}
func (s *SGuestResumeTask) SetGetTaskData(f func() (jsonutils.JSONObject, error)) {
s.getTaskData = f
}
func (s *SGuestResumeTask) onStartRunning() {
s.removeStatefile()
if s.ctx != nil && len(appctx.AppContextTaskId(s.ctx)) > 0 {
hostutils.TaskComplete(s.ctx, nil)
var (
data jsonutils.JSONObject
err error
)
if s.getTaskData != nil {
data, err = s.getTaskData()
if err != nil {
s.taskFailed(err.Error())
return
}
}
hostutils.TaskComplete(s.ctx, data)
}
if options.HostOptions.SetVncPassword {
s.SetVncPassword()

View File

@@ -20,6 +20,7 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
@@ -33,6 +34,7 @@ import (
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/appctx"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
@@ -102,6 +104,14 @@ func (s *SKVMGuestInstance) getStateFilePathRootPrefix() string {
return path.Join(s.HomeDir(), STATE_FILE_PREFIX)
}
func (s *SKVMGuestInstance) GetStateFilePath(version string) string {
p := s.getStateFilePathRootPrefix()
if version != "" {
p = fmt.Sprintf("%s_%s", p, version)
}
return p
}
func (s *SKVMGuestInstance) getQemuLogPath() string {
return path.Join(s.HomeDir(), "qemu.log")
}
@@ -1038,6 +1048,13 @@ func (s *SKVMGuestInstance) delTmpDisks(ctx context.Context, migrated bool) erro
return err
}
}
if migrated {
// remove memory snapshot files
dir := GetMemorySnapshotPath(s.GetId(), "")
if err := procutils.NewRemoteCommandAsFarAsPossible("rm", "-rf", dir).Run(); err != nil {
return errors.Wrapf(err, "remove dir %q", dir)
}
}
}
}
return nil
@@ -1115,7 +1132,7 @@ func (s *SKVMGuestInstance) ExecStopTask(ctx context.Context, params interface{}
}
func (s *SKVMGuestInstance) ExecSuspendTask(ctx context.Context) {
// TODO
NewGuestSuspendTask(s, ctx, nil).Start()
}
func (s *SKVMGuestInstance) GetNicDescMatch(mac, ip, port, bridge string) jsonutils.JSONObject {
@@ -1667,9 +1684,9 @@ func (s *SKVMGuestInstance) ListStateFilePaths() []string {
return ret
}
// 好像不用了
func (s *SKVMGuestInstance) CleanStatefiles() {
for _, stateFile := range s.ListStateFilePaths() {
log.Infof("Server %s remove statefile %q", s.GetName(), stateFile)
if _, err := procutils.NewCommand("mountpoint", stateFile).Output(); err == nil {
if output, err := procutils.NewCommand("umount", stateFile).Output(); err != nil {
log.Errorf("umount %s failed: %s, %s", stateFile, err, output)
@@ -1781,6 +1798,58 @@ func (s *SKVMGuestInstance) deleteStaticSnapshotFile(
return res, nil
}
func GetMemorySnapshotPath(serverId, instanceSnapshotId string) string {
dir := options.HostOptions.MemorySnapshotsPath
memSnapPath := filepath.Join(dir, serverId, instanceSnapshotId)
return memSnapPath
}
func (s *SKVMGuestInstance) ExecMemorySnapshotTask(ctx context.Context, input *hostapi.GuestMemorySnapshotRequest) (jsonutils.JSONObject, error) {
if !s.IsRunning() {
return nil, errors.Errorf("Server is not running status")
}
if s.IsSuspend() {
return nil, errors.Errorf("Server is suspend status")
}
memSnapPath := GetMemorySnapshotPath(s.GetId(), input.InstanceSnapshotId)
dir := filepath.Dir(memSnapPath)
if err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", dir).Run(); err != nil {
return nil, errors.Wrapf(err, "mkdir -p %q", dir)
}
NewGuestSuspendTask(s, ctx, func(_ *SGuestSuspendTask, memStatPath string) {
log.Infof("Memory state file %q saved, move it to %q", memStatPath, memSnapPath)
sizeBytes := fileutils2.FileSize(memStatPath)
sizeMB := sizeBytes / 1024
if err := procutils.NewRemoteCommandAsFarAsPossible("mv", memStatPath, memSnapPath).Run(); err != nil {
hostutils.TaskFailed(ctx, fmt.Sprintf("move statefile %q to memory snapshot %q: %v", memStatPath, memSnapPath, err))
return
}
resumeTask := NewGuestResumeTask(ctx, s, false, false)
resumeTask.SetGetTaskData(func() (jsonutils.JSONObject, error) {
resp := &hostapi.GuestMemorySnapshotResponse{
MemorySnapshotPath: memSnapPath,
SizeMB: sizeMB,
}
return jsonutils.Marshal(resp), nil
})
resumeTask.Start()
}).Start()
return nil, nil
}
func (s *SKVMGuestInstance) ExecMemorySnapshotResetTask(ctx context.Context, input *hostapi.GuestMemorySnapshotResetRequest) (jsonutils.JSONObject, error) {
if !s.IsStopped() {
return nil, errors.Errorf("Server is not stopped status")
}
memStatPath := s.GetStateFilePath("")
if err := procutils.NewRemoteCommandAsFarAsPossible("ln", "-s", input.Path, memStatPath).Run(); err != nil {
hostutils.TaskFailed(ctx, fmt.Sprintf("move %q to %q: %v", input.Path, memStatPath, err))
return nil, err
}
hostutils.TaskComplete(ctx, nil)
return nil, nil
}
func (s *SKVMGuestInstance) PrepareDisksMigrate(liveMigrage bool) (*jsonutils.JSONDict, error) {
disksBackFile := jsonutils.NewDict()
disks, _ := s.Desc.GetArray("disks")

View File

@@ -307,7 +307,7 @@ func (s *SKVMGuestInstance) generateStartScript(data *jsonutils.JSONDict) (strin
}
cmd += diskScripts
// cmd += fmt.Sprintf("STATE_FILE=`ls -d %s* | head -n 1`\n", s.getStateFilePathRootPrefix())
cmd += fmt.Sprintf("STATE_FILE=`ls -d %s* | head -n 1`\n", s.getStateFilePathRootPrefix())
cmd += fmt.Sprintf("PID_FILE=%s\n", input.PidFilePath)
var qemuCmd = qemutils.GetQemu(string(input.QemuVersion))
@@ -316,21 +316,25 @@ func (s *SKVMGuestInstance) generateStartScript(data *jsonutils.JSONDict) (strin
}
cmd += fmt.Sprintf("DEFAULT_QEMU_CMD='%s'\n", qemuCmd)
// cmd += "if [ -n \"$STATE_FILE\" ]; then\n"
// cmd += " QEMU_VER=`echo $STATE_FILE" +
// ` | grep -o '_[[:digit:]]\+\.[[:digit:]]\+.*'` + "`\n"
// cmd += " QEMU_CMD=\"qemu-system-x86_64\"\n"
// cmd += " QEMU_LOCAL_PATH=\"/usr/local/bin/$QEMU_CMD\"\n"
// cmd += " QEMU_LOCAL_PATH_VER=\"/usr/local/qemu-$QEMU_VER/bin/$QEMU_CMD\"\n"
// cmd += " QEMU_BIN_PATH=\"/usr/bin/$QEMU_CMD\"\n"
// cmd += " if [ -f \"$QEMU_LOCAL_PATH_VER\" ]; then\n"
// cmd += " QEMU_CMD=$QEMU_LOCAL_PATH_VER\n"
// cmd += " elif [ -f \"$QEMU_LOCAL_PATH\" ]; then\n"
// cmd += " QEMU_CMD=$QEMU_LOCAL_PATH\n"
// cmd += " elif [ -f \"$QEMU_BIN_PATH\" ]; then\n"
// cmd += " QEMU_CMD=$QEMU_BIN_PATH\n"
// cmd += " fi\n"
// cmd += "else\n"
/*
* cmd += "if [ -n \"$STATE_FILE\" ]; then\n"
* cmd += " QEMU_VER=`echo $STATE_FILE" +
* ` | grep -o '_[[:digit:]]\+\.[[:digit:]]\+.*'` + "`\n"
* cmd += " QEMU_CMD=\"qemu-system-x86_64\"\n"
* cmd += " QEMU_LOCAL_PATH=\"/usr/local/bin/$QEMU_CMD\"\n"
* cmd += " QEMU_LOCAL_PATH_VER=\"/usr/local/qemu-$QEMU_VER/bin/$QEMU_CMD\"\n"
* cmd += " QEMU_BIN_PATH=\"/usr/bin/$QEMU_CMD\"\n"
* cmd += " if [ -f \"$QEMU_LOCAL_PATH_VER\" ]; then\n"
* cmd += " QEMU_CMD=$QEMU_LOCAL_PATH_VER\n"
* cmd += " elif [ -f \"$QEMU_LOCAL_PATH\" ]; then\n"
* cmd += " QEMU_CMD=$QEMU_LOCAL_PATH\n"
* cmd += " elif [ -f \"$QEMU_BIN_PATH\" ]; then\n"
* cmd += " QEMU_CMD=$QEMU_BIN_PATH\n"
* cmd += " fi\n"
* cmd += "else\n"
* cmd += " QEMU_CMD=$DEFAULT_QEMU_CMD\n"
* cmd += "fi\n"
*/
cmd += "QEMU_CMD=$DEFAULT_QEMU_CMD\n"
if s.IsKvmSupport() && !options.HostOptions.DisableKVM {
cmd += "QEMU_CMD_KVM_ARG=-enable-kvm\n"
@@ -522,15 +526,15 @@ function nic_mtu() {
return "", errors.Wrap(err, "GenerateStartCommand")
}
cmd = fmt.Sprintf("%s %s", cmd, qemuOpts)
cmd += "\"\n"
// cmd += "if [ ! -z \"$STATE_FILE\" ] && [ -d \"$STATE_FILE\" ] && [ -f \"$STATE_FILE/content\" ]; then\n"
// cmd += " $CMD --incoming \"exec: cat $STATE_FILE/content\"\n"
// cmd += "elif [ ! -z \"$STATE_FILE\" ] && [ -f $STATE_FILE ]; then\n"
// cmd += " $CMD --incoming \"exec: cat $STATE_FILE\"\n"
// cmd += "else\n"
cmd += "eval $CMD\n"
// cmd += "fi\n"
cmd += `
if [ ! -z "$STATE_FILE" ] && [ -d "$STATE_FILE" ] && [ -f "$STATE_FILE/content" ]; then
CMD="$CMD --incoming \"exec: cat $STATE_FILE/content\""
elif [ ! -z "$STATE_FILE" ] && [ -f "$STATE_FILE" ]; then
CMD="$CMD --incoming \"exec: cat $STATE_FILE\""
fi
eval $CMD`
return cmd, nil
}

View File

@@ -333,12 +333,17 @@ func (h *SHostInfo) prepareEnv() error {
return fmt.Errorf("Option report_interval must no longer than 5 min")
}
output, err := procutils.NewCommand("mkdir", "-p", options.HostOptions.ServersPath).Output()
if err != nil {
return errors.Wrapf(err, "failed to create path %s: %s", options.HostOptions.ServersPath, output)
for _, dirPath := range []string{
options.HostOptions.ServersPath,
options.HostOptions.MemorySnapshotsPath,
} {
output, err := procutils.NewCommand("mkdir", "-p", dirPath).Output()
if err != nil {
return errors.Wrapf(err, "failed to create path %s: %s", dirPath, output)
}
}
_, err = procutils.NewCommand("ethtool", "-h").Output()
_, err := procutils.NewCommand("ethtool", "-h").Output()
if err != nil {
return errors.Wrap(err, "Execute 'ethtool -h'")
}
@@ -386,7 +391,7 @@ func (h *SHostInfo) prepareEnv() error {
if err != nil {
return errors.Wrap(err, "Failed to activate tun/tap device")
}
output, err = procutils.NewRemoteCommandAsFarAsPossible("modprobe", "vhost_net").Output()
output, err := procutils.NewRemoteCommandAsFarAsPossible("modprobe", "vhost_net").Output()
if err != nil {
log.Warningf("modprobe vhost_net error: %s", output)
}

View File

@@ -501,3 +501,8 @@ func (m *HmpMonitor) NetdevDel(id string, callback StringCallback) {
cmd := fmt.Sprintf("netdev_del %s", id)
m.Query(cmd, callback)
}
func (m *HmpMonitor) SaveState(stateFilePath string, callback StringCallback) {
cmd := fmt.Sprintf(`migrate -d "%s"`, getSaveStatefileUri(stateFilePath))
m.Query(cmd, callback)
}

View File

@@ -17,6 +17,7 @@ package monitor
import (
"fmt"
"net"
"strings"
"sync"
"time"
@@ -191,6 +192,8 @@ type Monitor interface {
NetdevAdd(id, netType string, params map[string]string, callback StringCallback)
NetdevDel(id string, callback StringCallback)
SaveState(statFilePath string, callback StringCallback)
}
type MonitorErrorFunc func(error)
@@ -283,3 +286,10 @@ func (m *SBaseMonitor) checkWriting() bool {
}
return true
}
func getSaveStatefileUri(stateFilePath string) string {
if strings.HasSuffix(stateFilePath, ".gz") {
return fmt.Sprintf("exec:gzip -c > %s", stateFilePath)
}
return fmt.Sprintf("exec:cat > %s", stateFilePath)
}

View File

@@ -972,3 +972,18 @@ func (m *QmpMonitor) NetdevDel(id string, callback StringCallback) {
cmd := fmt.Sprintf("netdev_del %s", id)
m.HumanMonitorCommand(cmd, callback)
}
func (m *QmpMonitor) SaveState(stateFilePath string, callback StringCallback) {
var (
cb = func(res *Response) {
callback(m.actionResult(res))
}
cmd = &Command{
Execute: "migrate",
Args: map[string]interface{}{
"uri": getSaveStatefileUri(stateFilePath),
},
}
)
m.Query(cmd, cb)
}

View File

@@ -33,8 +33,9 @@ type SHostOptions struct {
Slots string `help:"Slots of host (optional)"`
Hostname string `help:"Customized host name"`
ServersPath string `help:"Path for virtual server configuration files" default:"/opt/cloud/workspace/servers"`
ImageCachePath string `help:"Path for storing image caches" default:"/opt/cloud/workspace/disks/image_cache"`
ServersPath string `help:"Path for virtual server configuration files" default:"/opt/cloud/workspace/servers"`
ImageCachePath string `help:"Path for storing image caches" default:"/opt/cloud/workspace/disks/image_cache"`
MemorySnapshotsPath string `help:"Path for memory snapshot stat files" default:"/opt/cloud/workspace/memory_snapshots"`
// ImageCacheLimit int `help:"Maximal storage space for image caching, in GB" default:"20"`
AgentTempPath string `help:"Path for ESXi agent"`
AgentTempLimit int `help:"Maximal storage space for ESXi agent, in GB" default:"10"`

View File

@@ -59,6 +59,7 @@ const (
ACT_VM_SRC_CHECK = "vm_src_check"
ACT_VM_START = "vm_start"
ACT_VM_STOP = "vm_stop"
ACT_VM_SUSPEND = "vm_suspend"
ACT_VM_RESTART = "vm_restart"
ACT_VM_RESUME = "vm_resume"
ACT_VM_SYNC_CONF = "vm_sync_conf"