Merge pull request #12585 from zexi/server-disk-change-storage

feat(region,host): implement server change disk storage
This commit is contained in:
Zexi Li
2021-11-03 17:14:07 +08:00
committed by GitHub
21 changed files with 586 additions and 15 deletions
+1
View File
@@ -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))
+3
View File
@@ -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"
+11 -9
View File
@@ -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"
+12
View File
@@ -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"`
}
+20
View File
@@ -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"`
}
+17
View File
@@ -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
}
+41
View File
@@ -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
}
+76
View File
@@ -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, "")
}
+4
View File
@@ -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
}
+21
View File
@@ -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
}
+24
View File
@@ -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))
}
+2 -3
View File
@@ -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 {
+14
View File
@@ -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) {
+22
View File
@@ -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
}
+6
View File
@@ -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
}
+34
View File
@@ -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
+11
View File
@@ -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
}
+28 -3
View File
@@ -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 {
+1
View File
@@ -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"