mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-30 17:13:08 +08:00
vm clone
This commit is contained in:
@@ -152,6 +152,16 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.ServerCloneOptions{}, "server-clone", "Clone a server", func(s *mcclient.ClientSession, opts *options.ServerCloneOptions) error {
|
||||
params := jsonutils.Marshal(opts).(*jsonutils.JSONDict)
|
||||
res, err := modules.Servers.PerformAction(s, opts.SOURCE, "clone", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(res)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.ServerLoginInfoOptions{}, "server-logininfo", "Get login info of a server", func(s *mcclient.ClientSession, opts *options.ServerLoginInfoOptions) error {
|
||||
srvid, e := modules.Servers.GetId(s, opts.ID, nil)
|
||||
if e != nil {
|
||||
|
||||
@@ -164,6 +164,19 @@ type ServerCreateInput struct {
|
||||
Baremetal bool `json:"baremetal"`
|
||||
}
|
||||
|
||||
type ServerCloneInput struct {
|
||||
apis.Meta
|
||||
|
||||
Name string `json:"name"`
|
||||
AutoStart bool `json:"auto_start"`
|
||||
|
||||
EipBw int `json:"eip_bw,omitzero"`
|
||||
EipChargeType string `json:"eip_charge_type,omitempty"`
|
||||
Eip string `json:"eip,omitempty"`
|
||||
|
||||
PreferHost string `json:"prefer_host_id"`
|
||||
}
|
||||
|
||||
type ServerDeployInput struct {
|
||||
apis.Meta
|
||||
|
||||
|
||||
@@ -465,3 +465,7 @@ func (self *SBaremetalGuestDriver) OnDeleteGuestFinalCleanup(ctx context.Context
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaremetalGuestDriver) IsSupportGuestClone() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -272,3 +272,7 @@ func (self *SBaseGuestDriver) OnGuestChangeCpuMemFailed(ctx context.Context, gue
|
||||
func (self *SBaseGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error {
|
||||
return fmt.Errorf("SBaseGuestDriver: Not Implement")
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) IsSupportGuestClone() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -174,3 +174,7 @@ func (self *SContainerDriver) GetRandomNetworkTypes() []string {
|
||||
func (self *SContainerDriver) StartGuestRestartTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error {
|
||||
return fmt.Errorf("Not Implement")
|
||||
}
|
||||
|
||||
func (self *SContainerDriver) IsSupportGuestClone() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -303,6 +303,72 @@ func (self *SGuest) StartGuestLiveMigrateTask(ctx context.Context, userCred mccl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformClone(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "clone")
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformClone(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if len(self.BackupHostId) > 0 {
|
||||
return nil, httperrors.NewBadRequestError("Can't clone guest with backup guest")
|
||||
}
|
||||
|
||||
if !self.GetDriver().IsSupportGuestClone() {
|
||||
return nil, httperrors.NewBadRequestError("Guest hypervisor %s does not support clone", self.Hypervisor)
|
||||
}
|
||||
|
||||
cloneInput := new(api.ServerCloneInput)
|
||||
err := data.Unmarshal(cloneInput)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInputParameterError("Unmarshal input error %s", err)
|
||||
}
|
||||
if len(cloneInput.Name) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("name")
|
||||
}
|
||||
err = db.NewNameValidator(GuestManager, userCred.GetProjectId(), cloneInput.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createInput := self.ToCreateInput()
|
||||
createInput.Name = cloneInput.Name
|
||||
createInput.AutoStart = cloneInput.AutoStart
|
||||
|
||||
createInput.EipBw = cloneInput.EipBw
|
||||
createInput.Eip = cloneInput.Eip
|
||||
createInput.EipChargeType = cloneInput.EipChargeType
|
||||
if err := GuestManager.validateEip(userCred, createInput, createInput.PreferRegion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataDict := jsonutils.Marshal(createInput)
|
||||
model, err := db.NewModelObject(GuestManager)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
err = dataDict.Unmarshal(model)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
err = model.CustomizeCreate(ctx, userCred, self.ProjectId, query, dataDict)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
err = GuestManager.TableSpec().Insert(model)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
pendingUsage := getGuestResourceRequirements(ctx, userCred, createInput, 1, false)
|
||||
if task, err := taskman.TaskManager.NewTask(ctx, "GuestCloneTask", model, userCred,
|
||||
createInput.JSON(createInput), "", "", &pendingUsage); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return nil, err
|
||||
} else {
|
||||
task.ScheduleRun(nil)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *SGuest) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "deploy")
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ type IGuestDriver interface {
|
||||
NeedStopForChangeSpec(guest *SGuest) bool
|
||||
|
||||
OnGuestChangeCpuMemFailed(ctx context.Context, guest *SGuest, data *jsonutils.JSONDict, task taskman.ITask) error
|
||||
IsSupportGuestClone() bool
|
||||
}
|
||||
|
||||
var guestDrivers map[string]IGuestDriver
|
||||
|
||||
+147
-34
@@ -1029,40 +1029,9 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m
|
||||
input.SecgroupId = "default"
|
||||
}
|
||||
|
||||
eipStr := input.Eip
|
||||
eipBw := input.EipBw
|
||||
if len(eipStr) > 0 || eipBw > 0 {
|
||||
if !GetDriver(hypervisor).IsSupportEip() {
|
||||
return nil, httperrors.NewNotImplementedError("eip not supported for %s", hypervisor)
|
||||
}
|
||||
if len(eipStr) > 0 {
|
||||
eipObj, err := ElasticipManager.FetchByIdOrName(userCred, eipStr)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, httperrors.NewResourceNotFoundError2(ElasticipManager.Keyword(), eipStr)
|
||||
} else {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
}
|
||||
|
||||
eip := eipObj.(*SElasticip)
|
||||
if eip.Status != EIP_STATUS_READY {
|
||||
return nil, httperrors.NewInvalidStatusError("eip %s status invalid %s", eipStr, eip.Status)
|
||||
}
|
||||
if eip.IsAssociated() {
|
||||
return nil, httperrors.NewResourceBusyError("eip %s has been associated", eipStr)
|
||||
}
|
||||
input.Eip = eipObj.GetId()
|
||||
|
||||
eipRegion := eip.GetRegion()
|
||||
preferRegionId, _ := data.GetString("prefer_region_id")
|
||||
if len(preferRegionId) > 0 && preferRegionId != eipRegion.Id {
|
||||
return nil, httperrors.NewConflictError("cannot assoicate with eip %s: different region", eipStr)
|
||||
}
|
||||
input.PreferRegion = eipRegion.Id
|
||||
} else {
|
||||
// create new eip
|
||||
}
|
||||
preferRegionId, _ := data.GetString("prefer_region_id")
|
||||
if err := manager.validateEip(userCred, input, preferRegionId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1097,6 +1066,45 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m
|
||||
return input.JSON(input), nil
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) validateEip(userCred mcclient.TokenCredential, input *api.ServerCreateInput, preferRegionId string) error {
|
||||
eipStr := input.Eip
|
||||
eipBw := input.EipBw
|
||||
if len(eipStr) > 0 || eipBw > 0 {
|
||||
if !GetDriver(input.Hypervisor).IsSupportEip() {
|
||||
return httperrors.NewNotImplementedError("eip not supported for %s", input.Hypervisor)
|
||||
}
|
||||
if len(eipStr) > 0 {
|
||||
eipObj, err := ElasticipManager.FetchByIdOrName(userCred, eipStr)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return httperrors.NewResourceNotFoundError2(ElasticipManager.Keyword(), eipStr)
|
||||
} else {
|
||||
return httperrors.NewGeneralError(err)
|
||||
}
|
||||
}
|
||||
|
||||
eip := eipObj.(*SElasticip)
|
||||
if eip.Status != EIP_STATUS_READY {
|
||||
return httperrors.NewInvalidStatusError("eip %s status invalid %s", eipStr, eip.Status)
|
||||
}
|
||||
if eip.IsAssociated() {
|
||||
return httperrors.NewResourceBusyError("eip %s has been associated", eipStr)
|
||||
}
|
||||
input.Eip = eipObj.GetId()
|
||||
|
||||
eipRegion := eip.GetRegion()
|
||||
// preferRegionId, _ := data.GetString("prefer_region_id")
|
||||
if len(preferRegionId) > 0 && preferRegionId != eipRegion.Id {
|
||||
return httperrors.NewConflictError("cannot assoicate with eip %s: different region", eipStr)
|
||||
}
|
||||
input.PreferRegion = eipRegion.Id
|
||||
} else {
|
||||
// create new eip
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) checkCreateQuota(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, input *api.ServerCreateInput, hasBackup bool) error {
|
||||
req := getGuestResourceRequirements(ctx, userCred, input, 1, hasBackup)
|
||||
err := QuotaManager.CheckSetPendingQuota(ctx, userCred, ownerProjId, &req)
|
||||
@@ -4019,3 +4027,108 @@ func (guest *SGuest) GetDetailsTasks(ctx context.Context, userCred mcclient.Toke
|
||||
func (guest *SGuest) GetDynamicConditionInput() *jsonutils.JSONDict {
|
||||
return guest.ToSchedDesc().ToConditionInput()
|
||||
}
|
||||
|
||||
|
||||
func (self *SGuest) ToCreateInput() *api.ServerCreateInput {
|
||||
r := new(api.ServerCreateInput)
|
||||
r.VmemSize = self.VmemSize
|
||||
r.VcpuCount = int(self.VcpuCount)
|
||||
r.KeypairId = self.KeypairId
|
||||
if guestCdrom := self.getCdrom(); guestCdrom != nil {
|
||||
r.Cdrom = guestCdrom.ImageId
|
||||
}
|
||||
r.Vga = self.Vga
|
||||
r.Vdi = self.Vdi
|
||||
r.Bios = self.Bios
|
||||
r.Description = self.Description
|
||||
r.BootOrder = self.BootOrder
|
||||
r.DisableDelete = new(bool)
|
||||
*r.DisableDelete = self.DisableDelete.Bool()
|
||||
r.ShutdownBehavior = self.ShutdownBehavior
|
||||
// r.DeployConfigs
|
||||
r.IsSystem = self.IsSystem
|
||||
// r.Duration
|
||||
// r.AutoPrepaidRecycle
|
||||
r.SecgroupId = self.SecgrpId
|
||||
|
||||
r.ServerConfigs = new(api.ServerConfigs)
|
||||
host := self.GetHost()
|
||||
r.Hypervisor = self.Hypervisor
|
||||
r.ResourceType = host.ResourceType
|
||||
r.InstanceType = self.InstanceType
|
||||
r.Project = self.ProjectId
|
||||
r.Count = 1
|
||||
r.Disks = self.ToDisksConfig()
|
||||
r.Networks = self.ToNetworksConfig()
|
||||
r.IsolatedDevices = self.ToIsolatedDevicesConfig()
|
||||
|
||||
zone := self.getZone()
|
||||
r.PreferRegion = zone.GetRegion().GetId()
|
||||
r.PreferZone = zone.GetId()
|
||||
return r
|
||||
}
|
||||
|
||||
func (self *SGuest) ToDisksConfig() []*api.DiskConfig {
|
||||
guestDisks := self.GetDisks()
|
||||
if len(guestDisks) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]*api.DiskConfig, len(guestDisks))
|
||||
for idx, guestDisk := range guestDisks {
|
||||
diskConf := new(api.DiskConfig)
|
||||
disk := guestDisk.GetDisk()
|
||||
diskConf.Index = int(guestDisk.Index)
|
||||
diskConf.ImageId = disk.GetTemplateId()
|
||||
diskConf.SnapshotId = disk.SnapshotId
|
||||
diskConf.DiskType = disk.DiskType
|
||||
diskConf.SizeMb = disk.DiskSize
|
||||
diskConf.Fs = disk.FsFormat
|
||||
diskConf.Format = disk.DiskFormat
|
||||
diskConf.Driver = guestDisk.Driver
|
||||
diskConf.Cache = guestDisk.CacheMode
|
||||
diskConf.Mountpoint = guestDisk.Mountpoint
|
||||
storage := disk.GetStorage()
|
||||
diskConf.Backend = storage.StorageType
|
||||
diskConf.Medium = storage.MediumType
|
||||
ret[idx] = diskConf
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (self *SGuest) ToNetworksConfig() []*api.NetworkConfig {
|
||||
guestNetworks, _ := self.GetNetworks("")
|
||||
if len(guestNetworks) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]*api.NetworkConfig, len(guestNetworks))
|
||||
for idx, guestNetwork := range guestNetworks {
|
||||
netConf := new(api.NetworkConfig)
|
||||
network := guestNetwork.GetNetwork()
|
||||
netConf.Index = int(guestNetwork.Index)
|
||||
|
||||
// XXX: same wire
|
||||
netConf.Wire = network.WireId
|
||||
netConf.Exit = guestNetwork.IsExit()
|
||||
// netConf.Private
|
||||
// netConf.Reserved
|
||||
netConf.Driver = guestNetwork.Driver
|
||||
netConf.BwLimit = guestNetwork.BwLimit
|
||||
// netConf.NetType
|
||||
ret[idx] = netConf
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (self *SGuest) ToIsolatedDevicesConfig() []*api.IsolatedDeviceConfig {
|
||||
guestIsolatedDevices := self.GetIsolatedDevices()
|
||||
if len(guestIsolatedDevices) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]*api.IsolatedDeviceConfig, len(guestIsolatedDevices))
|
||||
for idx, guestIsolatedDevice := range guestIsolatedDevices {
|
||||
devConf := new(api.IsolatedDeviceConfig)
|
||||
devConf.Model = guestIsolatedDevice.Model
|
||||
ret[idx] = devConf
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
)
|
||||
|
||||
type GuestCloneTask struct {
|
||||
GuestBatchCreateTask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(GuestCloneTask{})
|
||||
}
|
||||
|
||||
func (self *GuestCloneTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
StartScheduleObjects(ctx, self, []db.IStandaloneModel{obj})
|
||||
}
|
||||
|
||||
func (self *GuestCloneTask) OnScheduleComplete(ctx context.Context, guest *models.SGuest, data *jsonutils.JSONDict) {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ type IScheduleTask interface {
|
||||
|
||||
OnStartSchedule(obj IScheduleModel)
|
||||
OnScheduleFailCallback(ctx context.Context, obj IScheduleModel, reason string)
|
||||
OnScheduleComplete(ctx context.Context, items []db.IStandaloneModel, data *jsonutils.JSONDict)
|
||||
// OnScheduleComplete(ctx context.Context, items []db.IStandaloneModel, data *jsonutils.JSONDict)
|
||||
SaveScheduleResult(ctx context.Context, obj IScheduleModel, candidate *schedapi.CandidateResource)
|
||||
SaveScheduleResultWithBackup(ctx context.Context, obj IScheduleModel, master, slave *schedapi.CandidateResource)
|
||||
OnScheduleFailed(ctx context.Context, reason string)
|
||||
|
||||
@@ -194,6 +194,16 @@ func (o ServerConfigs) Data() (*computeapi.ServerConfigs, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type ServerCloneOptions struct {
|
||||
SOURCE string `help:"Source server id or name" json:"-"`
|
||||
TARGET_NAME string `help:"Name of newly server" json:"name"`
|
||||
AutoStart bool `help:"Auto start server after it is created"`
|
||||
|
||||
EipBw int `help:"allocate EIP with bandwidth in MB when server is created" json:"eip_bw,omitzero"`
|
||||
EipChargeType string `help:"newly allocated EIP charge type, either traffic or bandwidth" choices:"traffic|bandwidth" json:"eip_charge_type,omitempty"`
|
||||
Eip string `help:"associate with an existing EIP when server is created" json:"eip,omitempty"`
|
||||
}
|
||||
|
||||
type ServerCreateOptions struct {
|
||||
ServerConfigs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user