mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-21 14:19:49 +08:00
Merge pull request #12585 from zexi/server-disk-change-storage
feat(region,host): implement server change disk storage
This commit is contained in:
@@ -91,6 +91,7 @@ func init() {
|
||||
cmd.Perform("migrate-network", &options.ServerMigrateNetworkOptions{})
|
||||
cmd.Perform("set-sshport", &options.ServerSetSshportOptions{})
|
||||
cmd.Perform("have-agent", &options.ServerHaveAgentOptions{})
|
||||
cmd.Perform("change-disk-storage", &options.ServerChangeDiskStorageOptions{})
|
||||
|
||||
cmd.Get("vnc", new(options.ServerIdOptions))
|
||||
cmd.Get("desc", new(options.ServerIdOptions))
|
||||
|
||||
@@ -43,6 +43,9 @@ const (
|
||||
DISK_POST_MIGRATE = "post_migrate"
|
||||
DISK_MIGRATING = "migrating"
|
||||
|
||||
DISK_CLONE = "clone"
|
||||
DISK_CLONE_FAIL = "clone_failed"
|
||||
|
||||
DISK_START_SNAPSHOT = "start_snapshot"
|
||||
DISK_SNAPSHOTING = "snapshoting"
|
||||
DISK_APPLY_SNAPSHOT_FAIL = "apply_snapshot_failed"
|
||||
|
||||
@@ -77,15 +77,17 @@ const (
|
||||
VM_REBUILD_ROOT = "rebuild_root"
|
||||
VM_REBUILD_ROOT_FAIL = "rebuild_root_fail"
|
||||
|
||||
VM_START_SNAPSHOT = "snapshot_start"
|
||||
VM_SNAPSHOT = "snapshot"
|
||||
VM_SNAPSHOT_DELETE = "snapshot_delete"
|
||||
VM_BLOCK_STREAM = "block_stream"
|
||||
VM_BLOCK_STREAM_FAIL = "block_stream_fail"
|
||||
VM_SNAPSHOT_SUCC = "snapshot_succ"
|
||||
VM_SNAPSHOT_FAILED = "snapshot_failed"
|
||||
VM_DISK_RESET = "disk_reset"
|
||||
VM_DISK_RESET_FAIL = "disk_reset_failed"
|
||||
VM_START_SNAPSHOT = "snapshot_start"
|
||||
VM_SNAPSHOT = "snapshot"
|
||||
VM_SNAPSHOT_DELETE = "snapshot_delete"
|
||||
VM_BLOCK_STREAM = "block_stream"
|
||||
VM_BLOCK_STREAM_FAIL = "block_stream_fail"
|
||||
VM_SNAPSHOT_SUCC = "snapshot_succ"
|
||||
VM_SNAPSHOT_FAILED = "snapshot_failed"
|
||||
VM_DISK_RESET = "disk_reset"
|
||||
VM_DISK_RESET_FAIL = "disk_reset_failed"
|
||||
VM_DISK_CHANGE_STORAGE = "disk_change_storage"
|
||||
VM_DISK_CHANGE_STORAGE_FAIL = "disk_change_storage_fail"
|
||||
|
||||
VM_START_INSTANCE_SNAPSHOT = "start_instance_snapshot"
|
||||
VM_INSTANCE_SNAPSHOT_FAILED = "instance_snapshot_failed"
|
||||
|
||||
@@ -688,3 +688,15 @@ type GuestJsonDesc struct {
|
||||
InstanceId string `json:"instance_id"`
|
||||
} `json:"instance_snapshot_info"`
|
||||
}
|
||||
|
||||
type ServerChangeDiskStorageInput struct {
|
||||
DiskId string `json:"disk_id"`
|
||||
TargetStorageId string `json:"target_storage_id"`
|
||||
KeepOriginDisk bool `json:"keep_origin_disk"`
|
||||
}
|
||||
|
||||
type ServerChangeDiskStorageInternalInput struct {
|
||||
ServerChangeDiskStorageInput
|
||||
StorageId string `json:"storage_id"`
|
||||
TargetDiskId string `json:"target_disk_id"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 ServerCloneDiskFromStorageResponse struct {
|
||||
TargetAccessPath string `json:"target_access_path"`
|
||||
TargetFormat string `json:"target_format"`
|
||||
}
|
||||
@@ -423,3 +423,20 @@ func (self *SBaseGuestDriver) ValidateRebuildRoot(ctx context.Context, userCred
|
||||
func (self *SBaseGuestDriver) ValidateDetachNetwork(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) ValidateChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, input *api.ServerChangeDiskStorageInput) error {
|
||||
return cloudprovider.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) StartChangeDiskStorageTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, params *api.ServerChangeDiskStorageInternalInput, parentTaskId string) error {
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeDiskStorageTask", guest, userCred, jsonutils.Marshal(params).(*jsonutils.JSONDict), parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) RequestChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, input *api.ServerChangeDiskStorageInternalInput, task taskman.ITask) error {
|
||||
return cloudprovider.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -775,3 +775,44 @@ func (self *SKVMGuestDriver) ValidateDetachNetwork(ctx context.Context, userCred
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) ValidateChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, input *api.ServerChangeDiskStorageInput) error {
|
||||
// kvm guest must in ready status
|
||||
if !utils.IsInStringArray(guest.Status, []string{api.VM_READY}) {
|
||||
return httperrors.NewBadRequestError("Cannot change disk storage in status %s", guest.Status)
|
||||
}
|
||||
|
||||
// backup guest not supported
|
||||
if guest.BackupHostId != "" {
|
||||
return httperrors.NewBadRequestError("Cannot change disk storage in backup guest %s", guest.GetName())
|
||||
}
|
||||
|
||||
// storage must attached on guest's host
|
||||
host, err := guest.GetHost()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Get guest %s host", guest.GetName())
|
||||
}
|
||||
attachedStorages := host.GetAttachedEnabledHostStorages(nil)
|
||||
foundStorage := false
|
||||
for _, storage := range attachedStorages {
|
||||
if storage.GetId() == input.TargetStorageId {
|
||||
foundStorage = true
|
||||
}
|
||||
}
|
||||
if !foundStorage {
|
||||
return httperrors.NewBadRequestError("Storage %s not attached or enabled on host %s", input.TargetStorageId, host.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) RequestChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, input *api.ServerChangeDiskStorageInternalInput, task taskman.ITask) error {
|
||||
host, err := guest.GetHost()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := jsonutils.Marshal(input)
|
||||
header := self.getTaskRequestHeader(task)
|
||||
url := fmt.Sprintf("%s/servers/%s/storage-clone-disk", host.ManagerUri, guest.GetId())
|
||||
_, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5321,3 +5321,79 @@ func (self *SGuest) PerformListForward(ctx context.Context, userCred mcclient.To
|
||||
}
|
||||
return resp.JSON(), nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "change-disk-storage")
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerChangeDiskStorageInput) (*api.ServerChangeDiskStorageInput, error) {
|
||||
// validate input
|
||||
if input.DiskId == "" {
|
||||
return nil, httperrors.NewNotEmptyError("Disk id is empty")
|
||||
}
|
||||
if input.TargetStorageId == "" {
|
||||
return nil, httperrors.NewNotEmptyError("Storage id is empty")
|
||||
}
|
||||
|
||||
// validate disk
|
||||
disks, err := self.GetDisks()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get server %s disks", self.GetName())
|
||||
}
|
||||
var srcDisk *SDisk
|
||||
for _, disk := range disks {
|
||||
if input.DiskId == disk.GetId() || input.DiskId == disk.GetName() {
|
||||
srcDisk = &disk
|
||||
input.DiskId = disk.GetId()
|
||||
break
|
||||
}
|
||||
}
|
||||
if srcDisk == nil {
|
||||
return nil, httperrors.NewNotFoundError("Disk %s not found on server %s", input.DiskId, self.GetName())
|
||||
}
|
||||
|
||||
// validate storage
|
||||
storageObj, err := StorageManager.FetchByIdOrName(userCred, input.TargetStorageId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Found storage by %s", input.TargetStorageId)
|
||||
}
|
||||
storage := storageObj.(*SStorage)
|
||||
input.TargetStorageId = storage.GetId()
|
||||
|
||||
// driver validate
|
||||
drv := self.GetDriver()
|
||||
if err := drv.ValidateChangeDiskStorage(ctx, userCred, self, input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a disk on target storage from source disk
|
||||
diskConf := &api.DiskConfig{
|
||||
Index: -1,
|
||||
ImageId: srcDisk.TemplateId,
|
||||
Format: srcDisk.DiskFormat,
|
||||
SizeMb: srcDisk.DiskSize,
|
||||
Fs: srcDisk.FsFormat,
|
||||
DiskType: srcDisk.DiskType,
|
||||
}
|
||||
|
||||
targetDisk, err := self.createDiskOnStorage(ctx, userCred, storage, diskConf, nil, true, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Create target disk on storage %s", storage.GetName())
|
||||
}
|
||||
|
||||
internalInput := &api.ServerChangeDiskStorageInternalInput{
|
||||
ServerChangeDiskStorageInput: *input,
|
||||
StorageId: srcDisk.StorageId,
|
||||
TargetDiskId: targetDisk.GetId(),
|
||||
}
|
||||
|
||||
return nil, self.StartChangeDiskStorageTask(ctx, userCred, internalInput, "")
|
||||
}
|
||||
|
||||
func (self *SGuest) StartChangeDiskStorageTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerChangeDiskStorageInternalInput, parentTaskId string) error {
|
||||
reason := fmt.Sprintf("Change disk %s to storage %s", input.DiskId, input.TargetStorageId)
|
||||
self.SetStatus(userCred, api.VM_DISK_CHANGE_STORAGE, reason)
|
||||
return self.GetDriver().StartChangeDiskStorageTask(self, ctx, userCred, input, "")
|
||||
}
|
||||
|
||||
@@ -210,6 +210,10 @@ type IGuestDriver interface {
|
||||
RequestOpenForward(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, req *guestdriver_types.OpenForwardRequest) (*guestdriver_types.OpenForwardResponse, error)
|
||||
RequestListForward(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, req *guestdriver_types.ListForwardRequest) (*guestdriver_types.ListForwardResponse, error)
|
||||
RequestCloseForward(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, req *guestdriver_types.CloseForwardRequest) (*guestdriver_types.CloseForwardResponse, error)
|
||||
|
||||
ValidateChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, input *api.ServerChangeDiskStorageInput) error
|
||||
StartChangeDiskStorageTask(guest *SGuest, ctx context.Context, userCred mcclient.TokenCredential, params *api.ServerChangeDiskStorageInternalInput, parentTaskId string) error
|
||||
RequestChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, input *api.ServerChangeDiskStorageInternalInput, task taskman.ITask) error
|
||||
}
|
||||
|
||||
var guestDrivers map[string]IGuestDriver
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
// 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 tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
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/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(GuestChangeDiskStorageTask{})
|
||||
}
|
||||
|
||||
type GuestChangeDiskStorageTask struct {
|
||||
SGuestBaseTask
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
guest := obj.(*models.SGuest)
|
||||
t.ChangeDiskStorage(ctx, guest)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) GetInputParams() (*api.ServerChangeDiskStorageInternalInput, error) {
|
||||
input := new(api.ServerChangeDiskStorageInternalInput)
|
||||
err := t.GetParams().Unmarshal(input)
|
||||
return input, err
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) getDiskById(id string) (*models.SDisk, error) {
|
||||
obj, err := models.DiskManager.FetchById(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*models.SDisk), nil
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) GetSourceDisk() (*models.SDisk, error) {
|
||||
input, err := t.GetInputParams()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetInputParams")
|
||||
}
|
||||
return t.getDiskById(input.DiskId)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) GetTargetDisk() (*models.SDisk, error) {
|
||||
input, err := t.GetInputParams()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetInputParams")
|
||||
}
|
||||
return t.getDiskById(input.TargetDiskId)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) ChangeDiskStorage(ctx context.Context, guest *models.SGuest) {
|
||||
input, err := t.GetInputParams()
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("GetInputParams error: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
targetDisk, err := t.GetTargetDisk()
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("GetTargetDisk error: %v", err)))
|
||||
return
|
||||
}
|
||||
// set target disk's status to clone
|
||||
targetDisk.SetStatus(t.GetUserCred(), api.DISK_CLONE, "")
|
||||
|
||||
t.SetStage("OnDiskChangeStorageComplete", nil)
|
||||
if err := guest.GetDriver().RequestChangeDiskStorage(ctx, t.GetUserCred(), guest, input, t); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("RequestChangeDiskStorage: %s", err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnDiskChangeStorageComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
srcDisk, err := t.GetSourceDisk()
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("GetSourceDisk: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
// update target disk attributes by response
|
||||
resp := new(hostapi.ServerCloneDiskFromStorageResponse)
|
||||
if err := data.Unmarshal(resp); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("Unmarshal response: %v", err)))
|
||||
return
|
||||
}
|
||||
targetDisk, err := t.GetTargetDisk()
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("GetTargetDisk error: %v", err)))
|
||||
return
|
||||
}
|
||||
if _, err := db.UpdateWithLock(ctx, targetDisk, func() error {
|
||||
targetDisk.AccessPath = resp.TargetAccessPath
|
||||
targetDisk.DiskFormat = resp.TargetFormat
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("Update target disk attributes error: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
guestSrcDisk := guest.GetGuestDisk(srcDisk.GetId())
|
||||
if guestSrcDisk == nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("Source disk %s not attached", srcDisk.GetId())))
|
||||
return
|
||||
}
|
||||
|
||||
conf := guestSrcDisk.ToDiskConfig()
|
||||
t.SetStage("OnSourceDiskDetachComplete", jsonutils.Marshal(conf).(*jsonutils.JSONDict))
|
||||
if err := t.detachSourceDisk(ctx, guest, srcDisk); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("detachSourceDisk: %s", err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnDiskChangeStorageCompleteFailed(ctx context.Context, guest *models.SGuest, err jsonutils.JSONObject) {
|
||||
// set target disk's status to clone
|
||||
targetDisk, _ := t.GetTargetDisk()
|
||||
targetDisk.SetStatus(t.GetUserCred(), api.DISK_CLONE_FAIL, err.String())
|
||||
t.TaskFailed(ctx, guest, err)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) detachSourceDisk(ctx context.Context, guest *models.SGuest, srcDisk *models.SDisk) error {
|
||||
input, err := t.GetInputParams()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetInputParams")
|
||||
}
|
||||
return guest.StartGuestDetachdiskTask(ctx, t.GetUserCred(), srcDisk, input.KeepOriginDisk, t.GetTaskId(), false)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnSourceDiskDetachComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
t.SetStage("OnTargetDiskAttachComplete", data.(*jsonutils.JSONDict))
|
||||
conf := new(api.DiskConfig)
|
||||
if err := data.Unmarshal(conf); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("unmarshal %s to api.DiskConfig: %s", data, err)))
|
||||
return
|
||||
}
|
||||
if err := t.attachTargetDisk(ctx, guest, conf); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("attachTargetDisk: %s", err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnSourceDiskDetachCompleteFailed(ctx context.Context, guest *models.SGuest, err jsonutils.JSONObject) {
|
||||
t.TaskFailed(ctx, guest, err)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) attachTargetDisk(ctx context.Context, guest *models.SGuest, conf *api.DiskConfig) error {
|
||||
targetDisk, err := t.GetTargetDisk()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetTargetDisk")
|
||||
}
|
||||
confData := map[string]interface{}{
|
||||
"index": conf.Index,
|
||||
"mountpoint": conf.Mountpoint,
|
||||
"driver": conf.Driver,
|
||||
"cache": conf.Cache,
|
||||
}
|
||||
attachData := jsonutils.Marshal(confData).(*jsonutils.JSONDict)
|
||||
attachData.Add(jsonutils.NewString(targetDisk.GetId()), "disk_id")
|
||||
|
||||
return guest.GetDriver().StartGuestAttachDiskTask(ctx, t.GetUserCred(), guest, attachData, t.GetTaskId())
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnTargetDiskAttachComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
t.TaskComplete(ctx, guest, nil)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) OnTargetDiskAttachCompleteFailed(ctx context.Context, guest *models.SGuest, err jsonutils.JSONObject) {
|
||||
t.TaskFailed(ctx, guest, err)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) TaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
logclient.AddActionLogWithStartable(t, guest, logclient.ACT_DISK_CHANGE_STORAGE, nil, t.GetUserCred(), true)
|
||||
t.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (t *GuestChangeDiskStorageTask) TaskFailed(ctx context.Context, guest *models.SGuest, reason jsonutils.JSONObject) {
|
||||
guest.SetStatus(t.GetUserCred(), api.VM_DISK_CHANGE_STORAGE_FAIL, reason.String())
|
||||
logclient.AddActionLogWithStartable(t, guest, logclient.ACT_DISK_CHANGE_STORAGE, reason, t.GetUserCred(), false)
|
||||
t.SetStageFailed(ctx, reason)
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/hostman/guestman"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
@@ -81,6 +82,7 @@ func AddGuestTaskHandler(prefix string, app *appsrv.Application) {
|
||||
"open-forward": guestOpenForward,
|
||||
"list-forward": guestListForward,
|
||||
"close-forward": guestCloseForward,
|
||||
"storage-clone-disk": guestStorageCloneDisk,
|
||||
} {
|
||||
app.AddHandler("POST",
|
||||
fmt.Sprintf("%s/%s/<sid>/%s", prefix, keyWord, action),
|
||||
@@ -567,3 +569,35 @@ func guestDeleteSnapshot(ctx context.Context, sid string, body jsonutils.JSONObj
|
||||
hostutils.DelayTask(ctx, guestman.GetGuestManager().DeleteSnapshot, params)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func guestStorageCloneDisk(ctx context.Context, sid string, body jsonutils.JSONObject) (interface{}, error) {
|
||||
input := new(computeapi.ServerChangeDiskStorageInternalInput)
|
||||
if err := body.Unmarshal(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcStorage := storageman.GetManager().GetStorage(input.StorageId)
|
||||
if srcStorage == nil {
|
||||
return nil, httperrors.NewNotFoundError("Source storage %q not found", input.StorageId)
|
||||
}
|
||||
srcDisk, err := srcStorage.GetDiskById(input.DiskId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get source disk %q on storage %q", input.DiskId, srcStorage.GetId())
|
||||
}
|
||||
targetStorage := storageman.GetManager().GetStorage(input.TargetStorageId)
|
||||
if targetStorage == nil {
|
||||
return nil, httperrors.NewNotFoundError("Target storage %s not found", input.TargetStorageId)
|
||||
}
|
||||
if input.TargetDiskId == "" {
|
||||
return nil, httperrors.NewMissingParameterError("Target disk id is empty")
|
||||
}
|
||||
|
||||
params := &guestman.SStorageCloneDisk{
|
||||
ServerId: sid,
|
||||
SourceStorage: srcStorage,
|
||||
SourceDisk: srcDisk,
|
||||
TargetStorage: targetStorage,
|
||||
TargetDiskId: input.TargetDiskId,
|
||||
}
|
||||
hostutils.DelayTaskWithoutReqctx(ctx, guestman.GetGuestManager().StorageCloneDisk, params)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,27 @@ func (m *SGuestManager) ExitGuestCleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
type SStorageCloneDisk struct {
|
||||
ServerId string
|
||||
SourceStorage storageman.IStorage
|
||||
SourceDisk storageman.IDisk
|
||||
TargetStorage storageman.IStorage
|
||||
TargetDiskId string
|
||||
}
|
||||
|
||||
func (m *SGuestManager) StorageCloneDisk(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
input := params.(*SStorageCloneDisk)
|
||||
guest, _ := m.GetServer(input.ServerId)
|
||||
if guest == nil {
|
||||
return nil, httperrors.NewNotFoundError("Not found guest by id %s", input.ServerId)
|
||||
}
|
||||
if guest.IsRunning() || guest.IsSuspend() {
|
||||
return nil, httperrors.NewBadRequestError("Cannot change disk storage on running/suspend guest")
|
||||
}
|
||||
NewGuestStorageCloneDiskTask(guest, input).Start(ctx)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *SGuestManager) GetHost() hostutils.IHost {
|
||||
return m.host
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appctx"
|
||||
@@ -1455,3 +1456,26 @@ func (task *SCancelBlockJobs) taskComplete() {
|
||||
hostutils.TaskComplete(task.ctx, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type SGuestStorageCloneDiskTask struct {
|
||||
guest *SKVMGuestInstance
|
||||
params *SStorageCloneDisk
|
||||
}
|
||||
|
||||
func NewGuestStorageCloneDiskTask(guest *SKVMGuestInstance, params *SStorageCloneDisk) *SGuestStorageCloneDiskTask {
|
||||
return &SGuestStorageCloneDiskTask{
|
||||
guest: guest,
|
||||
params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *SGuestStorageCloneDiskTask) Start(ctx context.Context) {
|
||||
resp, err := t.params.TargetStorage.CloneDiskFromStorage(ctx, t.params.SourceStorage, t.params.SourceDisk, t.params.TargetDiskId)
|
||||
if err != nil {
|
||||
hostutils.TaskFailed(
|
||||
ctx,
|
||||
errors.Wrapf(err, "Clone disk %s to storage %s", t.params.SourceDisk.GetPath(), t.params.TargetStorage.GetId()).Error())
|
||||
return
|
||||
}
|
||||
hostutils.TaskComplete(ctx, jsonutils.Marshal(resp))
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package storageman
|
||||
@@ -58,9 +59,7 @@ func (d *SRBDDisk) Probe() error {
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) getPath() string {
|
||||
storageConf := d.Storage.GetStorageConf()
|
||||
pool, _ := storageConf.GetString("pool")
|
||||
return fmt.Sprintf("rbd:%s/%s", pool, d.Id)
|
||||
return d.Storage.(*SRbdStorage).getDiskPath(d.Id)
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) GetPath() string {
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/host"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
@@ -116,6 +117,11 @@ type IStorage interface {
|
||||
SaveToGlance(context.Context, interface{}) (jsonutils.JSONObject, error)
|
||||
CreateDiskFromSnapshot(context.Context, IDisk, *SDiskCreateByDiskinfo) error
|
||||
|
||||
// GetCloneTargetDiskPath generate target disk path by target disk id
|
||||
GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string
|
||||
// CloneDiskFromStorage clone disk from other storage
|
||||
CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string) (*host.ServerCloneDiskFromStorageResponse, error)
|
||||
|
||||
CreateSnapshotFormUrl(ctx context.Context, snapshotUrl, diskId, snapshotPath string) error
|
||||
|
||||
DeleteDiskfile(diskPath string) error
|
||||
@@ -364,6 +370,14 @@ func (s *SBaseStorage) DestinationPrepareMigrate(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SBaseStorage) GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SBaseStorage) CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string) (*host.ServerCloneDiskFromStorageResponse, error) {
|
||||
return nil, httperrors.ErrNotImplemented
|
||||
}
|
||||
|
||||
/*************************Background delete snapshot job****************************/
|
||||
|
||||
func StartSnapshotRecycle(storage IStorage) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"yunion.io/x/pkg/util/timeutils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
hostapi "yunion.io/x/onecloud/pkg/apis/host"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
|
||||
@@ -502,3 +503,24 @@ func (s *SLocalStorage) CreateDiskFromSnapshot(
|
||||
}
|
||||
return httperrors.NewUnsupportOperationError("Unsupport protocol %s for Local storage", info.Protocol)
|
||||
}
|
||||
|
||||
func (s *SLocalStorage) GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string {
|
||||
return path.Join(s.GetPath(), targetDiskId)
|
||||
}
|
||||
|
||||
func (s *SLocalStorage) CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string) (*hostapi.ServerCloneDiskFromStorageResponse, error) {
|
||||
srcDiskPath := srcDisk.GetPath()
|
||||
srcImg, err := qemuimg.NewQemuImage(srcDiskPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get source image %q info", srcDiskPath)
|
||||
}
|
||||
accessPath := s.GetCloneTargetDiskPath(ctx, targetDiskId)
|
||||
_, err = srcImg.Clone(s.GetCloneTargetDiskPath(ctx, targetDiskId), qemuimg.QCOW2, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Clone source disk to target local storage")
|
||||
}
|
||||
return &hostapi.ServerCloneDiskFromStorageResponse{
|
||||
TargetAccessPath: accessPath,
|
||||
TargetFormat: qemuimg.QCOW2.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
hostapi "yunion.io/x/onecloud/pkg/apis/host"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
@@ -96,3 +98,7 @@ func (s *SNasStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
|
||||
func (s *SNasStorage) CreateDiskFromSnapshot(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
|
||||
return disk.CreateFromSnapshotLocation(ctx, input.DiskInfo.SnapshotUrl, int64(input.DiskInfo.DiskSizeMb))
|
||||
}
|
||||
|
||||
func (s *SNasStorage) CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string) (*hostapi.ServerCloneDiskFromStorageResponse, error) {
|
||||
return nil, httperrors.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package storageman
|
||||
@@ -29,6 +30,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/cloudprovider"
|
||||
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
|
||||
@@ -37,6 +39,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/util/cephutils"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
"yunion.io/x/onecloud/pkg/util/qemutils"
|
||||
)
|
||||
|
||||
@@ -521,6 +524,37 @@ func (s *SRbdStorage) CreateDiskFromSnapshot(ctx context.Context, disk IDisk, in
|
||||
return disk.CreateFromRbdSnapshot(ctx, info.SnapshotUrl, info.SrcDiskId, info.SrcPool)
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) getDiskPath(diskId string) string {
|
||||
storageConf := s.GetStorageConf()
|
||||
pool, _ := storageConf.GetString("pool")
|
||||
return fmt.Sprintf("rbd:%s/%s", pool, diskId)
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) GetDiskPath(diskId string) string {
|
||||
return fmt.Sprintf("%s%s", s.getDiskPath(diskId), s.getStorageConfString())
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string {
|
||||
return s.GetDiskPath(targetDiskId)
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) CloneDiskFromStorage(ctx context.Context, srcStorage IStorage, srcDisk IDisk, targetDiskId string) (*hostapi.ServerCloneDiskFromStorageResponse, error) {
|
||||
srcDiskPath := srcDisk.GetPath()
|
||||
srcImg, err := qemuimg.NewQemuImage(srcDiskPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get source image %q info", srcDiskPath)
|
||||
}
|
||||
accessPath := s.GetCloneTargetDiskPath(ctx, targetDiskId)
|
||||
_, err = srcImg.Clone(accessPath, qemuimg.RAW, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Clone source disk to target rbd storage")
|
||||
}
|
||||
return &hostapi.ServerCloneDiskFromStorageResponse{
|
||||
TargetAccessPath: accessPath,
|
||||
TargetFormat: qemuimg.RAW.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject) error {
|
||||
s.StorageId = storageId
|
||||
s.StorageName = storageName
|
||||
|
||||
@@ -1162,3 +1162,14 @@ type ServerDomainStatisticsOptions struct {
|
||||
ServerListOptions
|
||||
DomainStatisticsOptions
|
||||
}
|
||||
|
||||
type ServerChangeDiskStorageOptions struct {
|
||||
BaseIdOptions
|
||||
DISKID string `json:"disk_id" help:"Disk id or name"`
|
||||
TARGETSTORAGE string `json:"target_storage_id" help:"Target storage id or name"`
|
||||
KeepOriginDisk bool `json:"keep_origin_disk" help:"Keep origin disk when changed"`
|
||||
}
|
||||
|
||||
func (o *ServerChangeDiskStorageOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return jsonutils.Marshal(o), nil
|
||||
}
|
||||
|
||||
@@ -91,11 +91,36 @@ type SCapacity struct {
|
||||
|
||||
func (self *CephClient) output(name string, opts []string) (jsonutils.JSONObject, error) {
|
||||
opts = append([]string{"--format", "json"}, opts...)
|
||||
resp, err := procutils.NewRemoteCommandAsFarAsPossible(name, opts...).Output()
|
||||
proc := procutils.NewRemoteCommandAsFarAsPossible(name, opts...)
|
||||
outb, err := proc.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%s %s", name, string(resp))
|
||||
return nil, errors.Wrap(err, "stdout pipe")
|
||||
}
|
||||
return jsonutils.Parse(resp)
|
||||
defer outb.Close()
|
||||
|
||||
errb, err := proc.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "stderr pipe")
|
||||
}
|
||||
defer errb.Close()
|
||||
|
||||
if err := proc.Start(); err != nil {
|
||||
return nil, errors.Wrap(err, "start ceph process")
|
||||
}
|
||||
|
||||
stdoutPut, err := ioutil.ReadAll(outb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stderrPut, err := ioutil.ReadAll(errb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := proc.Wait(); err != nil {
|
||||
return nil, errors.Wrapf(err, "stderr %q", stderrPut)
|
||||
}
|
||||
return jsonutils.Parse(stdoutPut)
|
||||
}
|
||||
|
||||
func (self *CephClient) run(name string, opts []string) error {
|
||||
|
||||
@@ -79,6 +79,7 @@ const (
|
||||
ACT_EIP_CONVERT = "eip_convert"
|
||||
ACT_CHANGE_BANDWIDTH = "change_bandwidth"
|
||||
ACT_DISK_CREATE_SNAPSHOT = "disk_create_snapshot"
|
||||
ACT_DISK_CHANGE_STORAGE = "disk_change_storage"
|
||||
ACT_LB_ADD_BACKEND = "lb_add_backend"
|
||||
ACT_LB_REMOVE_BACKEND = "lb_remove_backend"
|
||||
ACL_LB_SYNC_BACKEND_CONF = "lb_sync_backend_conf"
|
||||
|
||||
Reference in New Issue
Block a user