- instance snapshot

- instance clone

- create form snapshot don't reset passwd
This commit is contained in:
wanyaoqi
2019-08-29 17:49:24 +08:00
parent 905fb307a8
commit 8fc4b2920a
26 changed files with 1203 additions and 26 deletions

View File

@@ -0,0 +1,49 @@
package shell
import (
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
type InstanceSnapshotsListOptions struct {
options.BaseListOptions
GuestId string `help:"guest id" json:"guest_id"`
}
R(&InstanceSnapshotsListOptions{}, "instance-snapshot-list", "Show instance snapshots", func(s *mcclient.ClientSession, args *InstanceSnapshotsListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
result, err := modules.InstanceSnapshots.List(s, params)
if err != nil {
return err
}
printList(result, modules.InstanceSnapshots.GetColumns(s))
return nil
})
type InstanceSnapshotDeleteOptions struct {
ID []string `help:"Delete snapshot id"`
}
R(&InstanceSnapshotDeleteOptions{}, "instance-snapshot-delete", "Delete snapshots", func(s *mcclient.ClientSession, args *InstanceSnapshotDeleteOptions) error {
ret := modules.InstanceSnapshots.BatchDelete(s, args.ID, nil)
printBatchResults(ret, modules.InstanceSnapshots.GetColumns(s))
return nil
})
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 {
result, err := modules.InstanceSnapshots.Get(s, args.ID, nil)
if err != nil {
return err
}
printObject(result)
return nil
})
}

View File

@@ -156,6 +156,25 @@ func init() {
return nil
})
R(&options.ServerCreateFromInstanceSnapshot{}, "server-create-from-instance-snapshot", "server create from instance snapshot",
func(s *mcclient.ClientSession, opts *options.ServerCreateFromInstanceSnapshot) error {
params := &compute.ServerCreateInput{}
params.InstanceSnapshotId = opts.InstaceSnapshotId
params.Name = opts.NAME
params.AutoStart = opts.AutoStart
params.Eip = opts.Eip
params.EipChargeType = opts.EipChargeType
params.EipBw = opts.EipBw
server, err := modules.Servers.Create(s, params.JSON(params))
if err != nil {
return err
}
printObject(server)
return nil
},
)
R(&options.ServerCreateOptions{}, "server-create", "Create a server", func(s *mcclient.ClientSession, opts *options.ServerCreateOptions) error {
params, err := opts.Params()
if err != nil {
@@ -1080,4 +1099,52 @@ func init() {
printObject(result)
return nil
})
type ServerCreateSnapshot struct {
ID string `help:"ID or name of VM" json:"-"`
SNAPSHOT string `help:"Instance snapshot name" json:"name"`
}
R(&ServerCreateSnapshot{}, "instance-snapshot-create", "create instance snapshot", func(s *mcclient.ClientSession, opts *ServerCreateSnapshot) error {
params := jsonutils.Marshal(opts)
result, err := modules.Servers.PerformAction(s, opts.ID, "instance-snapshot", params)
if err != nil {
return err
}
printObject(result)
return nil
})
type ServerSnapshotAndClone struct {
ID string `help:"ID or name of VM" json:"-"`
NAME string `help:"Newly instance name" json:"name"`
AutoStart bool `help:"Auto start new guest"`
AllowDelete bool `help:"Allow new guest delete" json:"-"`
Count int `help:"Guest count"`
}
R(&ServerSnapshotAndClone{}, "instance-snapshot-and-clone", "create instance snapshot and clone new instance", func(s *mcclient.ClientSession, opts *ServerSnapshotAndClone) error {
params := jsonutils.Marshal(opts)
dictParams := params.(*jsonutils.JSONDict)
if opts.AllowDelete {
dictParams.Set("disable_delete", jsonutils.JSONFalse)
}
result, err := modules.Servers.PerformAction(s, opts.ID, "snapshot-and-clone", dictParams)
if err != nil {
return err
}
printObject(result)
return nil
})
type ServerRollBackSnapshot struct {
ID string `help:"ID or name of VM" json:"-"`
InstanceSnapshot string `help:"Instance snapshot id or name" json:"instance_snapshot"`
}
R(&ServerRollBackSnapshot{}, "instance-snapshot-reset", "reset instance snapshot", func(s *mcclient.ClientSession, opts *ServerRollBackSnapshot) error {
params := jsonutils.Marshal(opts)
result, err := modules.Servers.PerformAction(s, opts.ID, "instance-snapshot-reset", params)
if err != nil {
return err
}
printObject(result)
return nil
})
}

View File

@@ -187,6 +187,7 @@ type ServerCreateInput struct {
EipBw int `json:"eip_bw,omitzero"`
EipChargeType string `json:"eip_charge_type,omitempty"`
Eip string `json:"eip,omitempty"`
InstanceSnapshotId string `json:"instance_snapshot_id,omitempty"`
OsType string `json:"os_type"`
// Fill by server

View File

@@ -84,6 +84,12 @@ const (
VM_DISK_RESET = "disk_reset"
VM_DISK_RESET_FAIL = "disk_reset_failed"
VM_START_INSTANCE_SNAPSHOT = "start_instance_snapshot"
VM_INSTANCE_SNAPSHOT_FAILED = "instance_snapshot_failed"
VM_START_SNAPSHOT_RESET = "start_snapshot_reset"
VM_SNAPSHOT_RESET_FAILED = "snapshot_reset_failed"
VM_SNAPSHOT_AND_CLONE_FAILED = "clone_from_snapshot_failed"
VM_SYNCING_STATUS = "syncing"
VM_SYNC_CONFIG = "sync_config"
VM_SYNC_FAIL = "sync_fail"

View File

@@ -43,4 +43,9 @@ const (
SNAPSHOT_POLICY_DISK_READY = "ready"
SNAPSHOT_POLICY_DISK_DELETING = "deleting"
SNAPSHOT_POLICY_DISK_DELETE_FAILED = "delete_failed"
INSTANCE_SNAPSHOT_READY = "ready"
INSTANCE_SNAPSHOT_FAILED = "instance_snapshot_create_failed"
INSTANCE_SNAPSHOT_START_DELETE = "instance_snapshot_start_delete"
INSTANCE_SNAPSHOT_DELETE_FAILED = "instance_snapshot_delete_failed"
)

View File

@@ -47,6 +47,7 @@ const (
ACT_DETACH = "detach"
ACT_ATTACH_FAIL = "attach_fail"
ACT_DETACH_FAIL = "detach_fail"
ACT_DELETE_FAIL = "delete_fail"
ACT_SYNC_UPDATE = "sync_update"
ACT_SYNC_CREATE = "sync_create"
@@ -103,6 +104,11 @@ const (
ACT_APPLY_SNAPSHOT_POLICY_FAILED = "apply_snapshot_policy_failed"
ACT_CANCEL_SNAPSHOT_POLICY = "cancel_snapshot_policy"
ACT_CANCEL_SNAPSHOT_POLICY_FAILED = "cancel_snapshot_policy_failed"
ACT_VM_SNAPSHOT_AND_CLONE = "vm_snapshot_and_clone"
ACT_VM_SNAPSHOT_AND_CLONE_FAILED = "vm_snapshot_and_clone_failed"
ACT_VM_RESET_SNAPSHOT = "instance_reset_snapshot"
ACT_VM_RESET_SNAPSHOT_FAILED = "instance_reset_snapshot_failed"
ACT_SNAPSHOT_POLICY_BIND_DISK = "snapshot_policy_bind_disk"
ACT_SNAPSHOT_POLICY_BIND_DISK_FAIL = "snapshot_policy_bind_disk_fail"

View File

@@ -234,9 +234,6 @@ func (self *SKVMGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest
return err
}
log.Debugf("RequestDeployGuestOnHost: %s", config)
if config.Contains("container") {
// ...
}
action, err := config.GetString("action")
if err != nil {
return err

View File

@@ -714,22 +714,21 @@ func (self *SDisk) PerformDiskReset(ctx context.Context, userCred mcclient.Token
autoStart := jsonutils.QueryBoolean(data, "auto_start", false)
snapshotId, _ := data.GetString("snapshot_id")
guests := self.GetGuests()
self.StartResetDisk(ctx, userCred, snapshotId, autoStart, guests)
return nil, nil
return nil, self.StartResetDisk(ctx, userCred, snapshotId, autoStart, &guests[0], "")
}
func (self *SDisk) StartResetDisk(
ctx context.Context, userCred mcclient.TokenCredential,
snapshotId string, autoStart bool, guests []SGuest,
snapshotId string, autoStart bool, guest *SGuest, parentTaskId string,
) error {
self.SetStatus(userCred, api.DISK_RESET, "")
if len(guests) == 1 {
guests[0].SetStatus(userCred, api.VM_DISK_RESET, "disk reset")
if guest != nil {
guest.SetStatus(userCred, api.VM_DISK_RESET, "disk reset")
}
params := jsonutils.NewDict()
params.Set("snapshot_id", jsonutils.NewString(snapshotId))
params.Set("auto_start", jsonutils.NewBool(autoStart))
task, err := taskman.TaskManager.NewTask(ctx, "DiskResetTask", self, userCred, params, "", "", nil)
task, err := taskman.TaskManager.NewTask(ctx, "DiskResetTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
} else {
@@ -2012,7 +2011,7 @@ func (self *SDisk) CreateSnapshotAuto(
}
db.OpsLog.LogEvent(snap, db.ACT_CREATE, "disk create snapshot auto", userCred)
err = snap.StartSnapshotCreateTask(ctx, userCred, nil)
err = snap.StartSnapshotCreateTask(ctx, userCred, nil, "")
if err != nil {
return errors.Wrap(err, "disk auto snapshot start snapshot task")
}

View File

@@ -3779,3 +3779,237 @@ func (manager *SGuestManager) StartHostGuestsMigrateTask(
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) AllowPerformInstanceSnapshot(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "instance-snapshot")
}
func (self *SGuest) validateCreateInstanceSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (*SQuota, error) {
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewBadRequestError("guest hypervisor %s can't create instance snapshot", self.Hypervisor)
}
if len(self.BackupHostId) > 0 {
return nil, httperrors.NewBadRequestError("Can't do instance snapshot with backup guest")
}
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return nil, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status)
}
ownerId := self.GetOwnerId()
dataDict := data.(*jsonutils.JSONDict)
name, err := dataDict.GetString("name")
if err != nil || len(name) == 0 {
return nil, httperrors.NewMissingParameterError("name")
}
err = db.NewNameValidator(InstanceSnapshotManager, ownerId, name, "")
if err != nil {
return nil, err
}
disks := self.GetDisks()
for i := 0; i < len(disks); i++ {
count, err := SnapshotManager.GetDiskManualSnapshotCount(disks[i].DiskId)
if err != nil {
return nil, httperrors.NewInternalServerError(err.Error())
}
if count >= options.Options.DefaultMaxManualSnapshotCount {
return nil, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i)
}
}
quotaPlatform := self.GetQuotaPlatformID()
pendingUsage := &SQuota{Snapshot: len(disks)}
err = QuotaManager.CheckSetPendingQuota(ctx, userCred, rbacutils.ScopeProject, ownerId, quotaPlatform, pendingUsage)
if err != nil {
return nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err)
}
return pendingUsage, nil
}
// 1. validate guest status, guest hypervisor
// 2. validate every disk manual snapshot count
// 3. validate snapshot quota with disk count
func (self *SGuest) PerformInstanceSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
pendingUsage, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, data)
if err != nil {
return nil, err
}
name, _ := data.GetString("name")
ownerId := self.GetOwnerId()
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, ownerId, self, name)
if err != nil {
QuotaManager.CancelPendingUsage(
ctx, userCred, rbacutils.ScopeProject, ownerId, self.GetQuotaPlatformID(), pendingUsage, pendingUsage)
return nil, httperrors.NewInternalServerError("create instance snapshot failed: %s", err)
}
err = self.InstaceCreateSnapshot(ctx, userCred, ownerId, instanceSnapshot, pendingUsage)
if err != nil {
QuotaManager.CancelPendingUsage(
ctx, userCred, rbacutils.ScopeProject, ownerId, self.GetQuotaPlatformID(), pendingUsage, pendingUsage)
return nil, httperrors.NewInternalServerError("start create snapshot task failed: %s", err)
}
return nil, nil
}
func (self *SGuest) InstaceCreateSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
instanceSnapshot *SInstanceSnapshot, pendingUsage *SQuota) error {
self.SetStatus(userCred, api.VM_START_INSTANCE_SNAPSHOT, "instance snapshot")
return instanceSnapshot.StartCreateInstanceSnapshotTask(ctx, userCred, ownerId, pendingUsage, "")
}
func (self *SGuest) AllowPerformInstanceSnapshotReset(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "instance-snapshot")
}
func (self *SGuest) PerformInstanceSnapshotReset(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("guest can't do snapshot in status ", self.Status)
}
dataDict := data.(*jsonutils.JSONDict)
instanceSnapshotV := validators.NewModelIdOrNameValidator(
"instance_snapshot", "instance_snapshot", self.GetOwnerId(),
)
err := instanceSnapshotV.Validate(dataDict)
if err != nil {
return nil, err
}
instanceSnapshot := instanceSnapshotV.Model.(*SInstanceSnapshot)
if instanceSnapshot.Status != api.INSTANCE_SNAPSHOT_READY {
return nil, httperrors.NewBadRequestError("Instance sanpshot not ready")
}
err = self.StartSnapshotResetTask(ctx, userCred, instanceSnapshot)
if err != nil {
return nil, httperrors.NewInternalServerError("start snapshot reset failed %s", err)
}
return nil, nil
}
func (self *SGuest) StartSnapshotResetTask(
ctx context.Context, userCred mcclient.TokenCredential, instanceSnapshot *SInstanceSnapshot) error {
self.SetStatus(userCred, api.VM_START_SNAPSHOT_RESET, "start snapshot reset task")
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotResetTask", instanceSnapshot, userCred, nil, "", "", nil,
); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) AllowPerformSnapshotAndClone(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "snapshot-and-clone")
}
func (self *SGuest) PerformSnapshotAndClone(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
newlyGuestName, err := data.GetString("name")
if err != nil {
return nil, httperrors.NewMissingParameterError("name")
}
err = db.NewNameValidator(GuestManager, self.GetOwnerId(), newlyGuestName, "")
if err != nil {
return nil, err
}
pendingUsage, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, data)
if err != nil {
return nil, err
}
lockman.LockClass(ctx, InstanceSnapshotManager, self.ProjectId)
defer lockman.ReleaseClass(ctx, InstanceSnapshotManager, self.ProjectId)
instanceSnapshotName, err := db.GenerateName(InstanceSnapshotManager, self.GetOwnerId(), "Snapshot-For-"+newlyGuestName)
if err != nil {
QuotaManager.CancelPendingUsage(
ctx, userCred, rbacutils.ScopeProject, self.GetOwnerId(),
self.GetQuotaPlatformID(), pendingUsage, pendingUsage)
return nil, httperrors.NewInternalServerError("Generate snapshot name failed %s", err)
}
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, self.GetOwnerId(), self, instanceSnapshotName)
if err != nil {
QuotaManager.CancelPendingUsage(
ctx, userCred, rbacutils.ScopeProject, self.GetOwnerId(),
self.GetQuotaPlatformID(), pendingUsage, pendingUsage)
return nil, httperrors.NewInternalServerError("create instance snapshot failed: %s", err)
}
err = self.StartInstanceSnapshotAndCloneTask(
ctx, userCred, newlyGuestName, pendingUsage, instanceSnapshot, data.(*jsonutils.JSONDict))
if err != nil {
QuotaManager.CancelPendingUsage(
ctx, userCred, rbacutils.ScopeProject, self.GetOwnerId(),
self.GetQuotaPlatformID(), pendingUsage, pendingUsage)
return nil, err
}
return nil, nil
}
func (self *SGuest) StartInstanceSnapshotAndCloneTask(
ctx context.Context, userCred mcclient.TokenCredential, newlyGuestName string,
pendingUsage *SQuota, instanceSnapshot *SInstanceSnapshot, data *jsonutils.JSONDict) error {
params := jsonutils.NewDict()
params.Set("guest_params", data)
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotAndCloneTask", instanceSnapshot, userCred, params, "", "", pendingUsage); err != nil {
return err
} else {
self.SetStatus(userCred, api.VM_START_INSTANCE_SNAPSHOT, "instance snapshot")
task.ScheduleRun(nil)
return nil
}
}
func (manager *SGuestManager) CreateGuestFromInstanceSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, guestParams *jsonutils.JSONDict,
isp *SInstanceSnapshot, index int,
) (*SGuest, *jsonutils.JSONDict, error) {
lockman.LockClass(ctx, manager, isp.ProjectId)
defer lockman.ReleaseClass(ctx, manager, isp.ProjectId)
guestName, err := guestParams.GetString("name")
if err != nil {
return nil, nil, fmt.Errorf("No new guest name provider")
}
if err := db.NewNameValidator(manager, isp.GetOwnerId(), guestName, ""); err != nil {
guestName, err = db.GenerateName2(manager, isp.GetOwnerId(), guestName, nil, index)
if err != nil {
return nil, nil, err
}
}
guestParams.Set("name", jsonutils.NewString(guestName))
guestParams.Set("instance_snapshot_id", jsonutils.NewString(isp.Id))
iGuest, err := db.DoCreate(manager, ctx, userCred, nil, guestParams, isp.GetOwnerId())
if err != nil {
return nil, nil, err
}
guest := iGuest.(*SGuest)
return guest, guestParams, nil
}

View File

@@ -868,15 +868,37 @@ func (manager *SGuestManager) BatchPreValidate(
return nil, nil
}
func parseInstanceSnapshot(input *api.ServerCreateInput) (*api.ServerCreateInput, error) {
ispi, err := InstanceSnapshotManager.FetchByIdOrName(nil, input.InstanceSnapshotId)
if err == sql.ErrNoRows {
return nil, httperrors.NewBadRequestError("can't find instance snapshot %s", input.InstanceSnapshotId)
}
if err != nil {
return nil, httperrors.NewInternalServerError("fetch instance snapshot error %s", err)
}
isp := ispi.(*SInstanceSnapshot)
if isp.Status != api.INSTANCE_SNAPSHOT_READY {
return nil, httperrors.NewBadRequestError("Instance snapshot not ready")
}
return isp.ToInstanceCreateInput(input)
}
func (manager *SGuestManager) validateCreateData(
ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
query jsonutils.JSONObject, data *jsonutils.JSONDict) (*api.ServerCreateInput, error) {
// TODO: 定义 api.ServerCreateInput 的 Unmarshal 函数,直接通过 data.Unmarshal(input) 解析参数
input, err := cmdline.FetchServerCreateInputByJSON(data)
if err != nil {
return nil, err
}
if len(input.InstanceSnapshotId) > 0 {
input, err = parseInstanceSnapshot(input)
if err != nil {
return nil, err
}
}
resetPassword := true
if input.ResetPassword != nil {
resetPassword = *input.ResetPassword
@@ -1144,8 +1166,6 @@ func (manager *SGuestManager) validateCreateData(
return nil, httperrors.NewResourceNotFoundError("Keypair %s not found", keypairId)
}
input.KeypairId = keypairObj.GetId()
} else {
input.KeypairId = "None" // TODO: ??? None?
}
if input.SecgroupId != "" {
@@ -3225,6 +3245,15 @@ func (self *SGuest) DeleteAllDisksInDB(ctx context.Context, userCred mcclient.To
return nil
}
func (self *SGuest) isNeedDoResetPasswd() bool {
guestdisks := self.GetDisks()
disk := guestdisks[0].GetDisk()
if len(disk.SnapshotId) > 0 {
return false
}
return true
}
func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, params *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
config := jsonutils.NewDict()
@@ -3245,12 +3274,13 @@ func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, userCred mcclient
deployAction = "deploy"
}
// resetPasswd := true
// if deployAction == "deploy" {
resetPasswd := jsonutils.QueryBoolean(params, "reset_password", true)
//}
config.Add(jsonutils.NewBool(jsonutils.QueryBoolean(params, "enable_cloud_init", false)), "enable_cloud_init")
resetPasswd := jsonutils.QueryBoolean(params, "reset_password", true)
if deployAction == "create" && resetPasswd {
resetPasswd = self.isNeedDoResetPasswd()
}
if resetPasswd {
config.Add(jsonutils.JSONTrue, "reset_password")
passwd, _ := params.GetString("password")

View File

@@ -0,0 +1,51 @@
package models
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
func init() {
db.InitManager(func() {
InstanceSnapshotJointManager = &SInstanceSnapshotJointManager{
SVirtualJointResourceBaseManager: db.NewVirtualJointResourceBaseManager(
SInstanceSnapshotJoint{},
"instancesnapshotjoints_tbl",
"instancesnapshotjoint",
"instancesnapshotjoints",
InstanceSnapshotManager,
SnapshotManager,
),
}
InstanceSnapshotJointManager.SetVirtualObject(InstanceSnapshotJointManager)
})
}
type SInstanceSnapshotJoint struct {
db.SVirtualJointResourceBase
InstanceSnapshotId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
SnapshotId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
DiskIndex int8 `nullable:"false" default:"0" list:"user" create:"required"`
}
type SInstanceSnapshotJointManager struct {
db.SVirtualJointResourceBaseManager
}
func (manager *SInstanceSnapshotJointManager) GetMasterFieldName() string {
return "instance_snapshot_id"
}
func (manager *SInstanceSnapshotJointManager) GetSlaveFieldName() string {
return "disk_id"
}
var InstanceSnapshotJointManager *SInstanceSnapshotJointManager
func (manager *SInstanceSnapshotJointManager) CreateJoint(instanceSnapshotId, snapshotId string, diskIndex int8) error {
instanceSnapshotJoint := &SInstanceSnapshotJoint{}
instanceSnapshotJoint.SetModelManager(manager, instanceSnapshotJoint)
instanceSnapshotJoint.InstanceSnapshotId = instanceSnapshotId
instanceSnapshotJoint.SnapshotId = snapshotId
instanceSnapshotJoint.DiskIndex = diskIndex
return manager.TableSpec().Insert(instanceSnapshotJoint)
}

View File

@@ -0,0 +1,211 @@
package models
import (
"context"
"database/sql"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis/compute"
schedapi "yunion.io/x/onecloud/pkg/apis/scheduler"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
)
func init() {
InstanceSnapshotManager = &SInstanceSnapshotManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SInstanceSnapshot{},
"instance_snapshots_tbl",
"instance_snapshot",
"instance_snapshots",
),
}
InstanceSnapshotManager.SetVirtualObject(InstanceSnapshotManager)
}
type SInstanceSnapshot struct {
db.SVirtualResourceBase
GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
ServerConfig jsonutils.JSONObject `nullable:"true" list:"user"`
}
type SInstanceSnapshotManager struct {
db.SVirtualResourceBaseManager
}
var InstanceSnapshotManager *SInstanceSnapshotManager
func (manager *SInstanceSnapshotManager) AllowCreateItem(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) bool {
return false
}
func (manager *SInstanceSnapshotManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
queryDict, ok := query.(*jsonutils.JSONDict)
if !ok {
return nil, fmt.Errorf("invalid querystring format")
}
if guestId, _ := queryDict.GetString("guest_id"); len(guestId) > 0 {
q = q.Equals("guest_id", guestId)
}
return q, nil
}
func (self *SInstanceSnapshot) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool {
return false
}
func (self *SInstanceSnapshot) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)
if guest := GuestManager.FetchGuestById(self.GuestId); guest != nil {
extra.Set("guest_status", jsonutils.NewString(guest.Status))
extra.Set("guest_name", jsonutils.NewString(guest.Name))
}
return extra
}
// func (self *SInstanceSnapshot) getMoreDetails()
func (self *SInstanceSnapshot) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
extra, err := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
if err != nil {
return nil, err
}
if guest := GuestManager.FetchGuestById(self.GuestId); guest != nil {
extra.Set("guest_status", jsonutils.NewString(guest.Status))
extra.Set("guest_name", jsonutils.NewString(guest.Name))
}
return extra, nil
}
func (self *SInstanceSnapshot) StartCreateInstanceSnapshotTask(
ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
pendingUsage quotas.IQuota, parentTaskId string) error {
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotCreateTask", self, userCred, nil, parentTaskId, "", pendingUsage); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (manager *SInstanceSnapshotManager) CreateInstanceSnapshot(
ctx context.Context, ownerId mcclient.IIdentityProvider, guest *SGuest, name string,
) (*SInstanceSnapshot, error) {
instanceSnapshot := &SInstanceSnapshot{}
instanceSnapshot.SetModelManager(manager, instanceSnapshot)
instanceSnapshot.Name = name
instanceSnapshot.ProjectId = ownerId.GetProjectId()
instanceSnapshot.DomainId = ownerId.GetProjectDomainId()
instanceSnapshot.GuestId = guest.Id
guestSchedInput := guest.ToSchedDesc()
for i := 0; i < len(guestSchedInput.Disks); i++ {
guestSchedInput.Disks[i].ImageId = ""
}
guestSchedInput.Name = ""
guestSchedInput.HostId = ""
guestSchedInput.Project = ""
guestSchedInput.Domain = ""
for i := 0; i < len(guestSchedInput.Networks); i++ {
guestSchedInput.Networks[i].Mac = ""
guestSchedInput.Networks[i].Address = ""
guestSchedInput.Networks[i].Address6 = ""
}
instanceSnapshot.ServerConfig = jsonutils.Marshal(guestSchedInput.ServerConfig)
err := manager.TableSpec().Insert(instanceSnapshot)
if err != nil {
return nil, err
}
return instanceSnapshot, nil
}
func (self *SInstanceSnapshot) ToInstanceCreateInput(
sourceInput *compute.ServerCreateInput) (*compute.ServerCreateInput, error) {
serverConfig := new(schedapi.ServerConfig)
if err := self.ServerConfig.Unmarshal(serverConfig); err != nil {
return nil, errors.Wrap(err, "unmarshal sched input")
}
isjs := make([]SInstanceSnapshotJoint, 0)
err := InstanceSnapshotJointManager.Query().Equals("instance_snapshot_id", self.Id).Asc("disk_index").All(&isjs)
if err != nil {
return nil, errors.Wrap(err, "fetch instance snapshots")
}
for i := 0; i < len(serverConfig.Disks); i++ {
serverConfig.Disks[i].SnapshotId = isjs[serverConfig.Disks[i].Index].SnapshotId
}
sourceInput.Disks = serverConfig.Disks
sourceInput.VmemSize = serverConfig.Memory
sourceInput.VcpuCount = serverConfig.Ncpu
sourceInput.Networks = serverConfig.Networks
return sourceInput, nil
}
func (self *SInstanceSnapshot) GetSnapshots() ([]SSnapshot, error) {
isjq := InstanceSnapshotJointManager.Query("snapshot_id").Equals("instance_snapshot_id", self.Id)
snapshots := make([]SSnapshot, 0)
err := SnapshotManager.Query().In("id", isjq).All(&snapshots)
if err != nil && err != sql.ErrNoRows {
return nil, err
} else if err != nil && err == sql.ErrNoRows {
return nil, nil
} else {
for i := 0; i < len(snapshots); i++ {
snapshots[i].SetModelManager(SnapshotManager, &snapshots[i])
}
return snapshots, nil
}
}
func (self *SInstanceSnapshot) GetInstanceSnapshotJointAt(diskIndex int) (*SInstanceSnapshotJoint, error) {
ispj := new(SInstanceSnapshotJoint)
err := InstanceSnapshotJointManager.Query().
Equals("instance_snapshot_id", self.Id).Equals("disk_index", diskIndex).First(ispj)
return ispj, err
}
func (self *SInstanceSnapshot) ValidateDeleteCondition(ctx context.Context) error {
if self.Status == compute.INSTANCE_SNAPSHOT_START_DELETE {
return httperrors.NewBadRequestError("can't delete snapshot in deleting")
}
return nil
}
func (self *SInstanceSnapshot) CustomizeDelete(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return self.StartInstanceSnapshotDeleteTask(ctx, userCred, "")
}
func (self *SInstanceSnapshot) StartInstanceSnapshotDeleteTask(
ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotDeleteTask", self, userCred, nil, parentTaskId, "", nil)
if err != nil {
log.Errorf("%s", err)
return err
}
self.SetStatus(userCred, compute.INSTANCE_SNAPSHOT_START_DELETE, "InstanceSnapshotDeleteTask")
task.ScheduleRun(nil)
return nil
}
func (self *SInstanceSnapshot) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DeleteModel(ctx, userCred, self)
}

View File

@@ -50,7 +50,7 @@ type SSnapshot struct {
SManagedResourceBase
DiskId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user"`
DiskId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user" index:"true"`
// Only onecloud has StorageId
StorageId string `width:"36" charset:"ascii" nullable:"true" list:"admin" create:"optional"`
@@ -288,11 +288,11 @@ func (self *SSnapshot) CustomizeCreate(ctx context.Context, userCred mcclient.To
func (manager *SSnapshotManager) OnCreateComplete(ctx context.Context, items []db.IModel, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
snapshot := items[0].(*SSnapshot)
snapshot.StartSnapshotCreateTask(ctx, userCred, nil)
snapshot.StartSnapshotCreateTask(ctx, userCred, nil, "")
}
func (self *SSnapshot) StartSnapshotCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict) error {
task, err := taskman.TaskManager.NewTask(ctx, "SnapshotCreateTask", self, userCred, params, "", "", nil)
func (self *SSnapshot) StartSnapshotCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "SnapshotCreateTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
}
@@ -383,6 +383,10 @@ func (self *SSnapshotManager) GetDiskSnapshots(diskId string) []SSnapshot {
return dest
}
func (self *SSnapshotManager) GetDiskManualSnapshotCount(diskId string) (int, error) {
return self.Query().Equals("disk_id", diskId).Equals("fake_deleted", false).CountWithError()
}
func (self *SSnapshotManager) IsDiskSnapshotsNeedConvert(diskId string) (bool, error) {
count, err := self.Query().Equals("disk_id", diskId).
In("status", []string{api.SNAPSHOT_READY, api.SNAPSHOT_DELETING}).
@@ -471,6 +475,13 @@ func (self *SSnapshot) ValidateDeleteCondition(ctx context.Context) error {
if self.Status == api.SNAPSHOT_DELETING {
return httperrors.NewBadRequestError("Cannot delete snapshot in status %s", self.Status)
}
count, err := InstanceSnapshotJointManager.Query().Equals("snapshot_id", self.Id).CountWithError()
if err != nil {
return httperrors.NewInternalServerError("Fetch instance snapshot error %s", err)
}
if count > 0 {
return httperrors.NewBadRequestError("snapshot referenced by instance snapshot")
}
return self.GetRegionDriver().ValidateSnapshotDelete(ctx, self)
}

View File

@@ -94,6 +94,7 @@ func InitHandlers(app *appsrv.Application) {
models.NatGatewayManager,
models.NatDEntryManager,
models.NatSEntryManager,
models.InstanceSnapshotManager,
models.SnapshotManager,
models.SnapshotPolicyManager,
models.BaremetalagentManager,
@@ -156,6 +157,7 @@ func InitHandlers(app *appsrv.Application) {
models.DBInstanceNetworkManager,
models.NetworkinterfacenetworkManager,
models.SnapshotPolicyDiskManager,
models.InstanceSnapshotJointManager,
} {
db.RegisterModelManager(manager)
handler := db.NewJointModelHandler(manager)

View File

@@ -55,7 +55,7 @@ func (self *DiskResetTask) TaskCompleted(ctx context.Context, disk *models.SDisk
guests[0].StartGueststartTask(ctx, self.UserCred, nil, self.GetTaskId())
}
} else {
if len(guests) == 1 {
if len(guests) == 1 && !self.IsSubtask() {
guests[0].SetStatus(self.UserCred, api.VM_READY, "")
}
// data不能为空指针否则会导致AddActionLog抛空指针异常

View File

@@ -52,7 +52,9 @@ func (self *GuestDiskSnapshotTask) DoDiskSnapshot(ctx context.Context, guest *mo
self.TaskFailed(ctx, guest, err.Error())
return
}
self.SetStage("OnDiskSnapshotComplete", nil)
params := jsonutils.NewDict()
params.Set("guest_old_status", jsonutils.NewString(guest.Status))
self.SetStage("OnDiskSnapshotComplete", params)
guest.SetStatus(self.UserCred, api.VM_SNAPSHOT, "")
err = guest.GetDriver().RequestDiskSnapshot(ctx, guest, self, snapshotId, diskId)
if err != nil {
@@ -87,7 +89,8 @@ func (self *GuestDiskSnapshotTask) OnDiskSnapshotCompleteFailed(ctx context.Cont
func (self *GuestDiskSnapshotTask) TaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_DISK_CREATE_SNAPSHOT, nil, self.UserCred, true)
guest.StartSyncstatus(ctx, self.UserCred, "")
status, _ := self.Params.GetString("guest_old_status")
guest.SetStatus(self.UserCred, status, "on guest disk snapshot complete")
self.SetStageComplete(ctx, nil)
}

View File

@@ -0,0 +1,130 @@
package tasks
import (
"context"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
type InstanceSnapshotAndCloneTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceSnapshotAndCloneTask{})
}
func (self *InstanceSnapshotAndCloneTask) taskFailed(
ctx context.Context, isp *models.SInstanceSnapshot, reason string) {
guest := models.GuestManager.FetchGuestById(isp.GuestId)
guest.SetStatus(self.UserCred, compute.VM_SNAPSHOT_AND_CLONE_FAILED, reason)
logclient.AddActionLogWithContext(
ctx, guest, logclient.ACT_VM_SNAPSHOT_AND_CLONE, reason, self.UserCred, false,
)
db.OpsLog.LogEvent(guest, db.ACT_VM_SNAPSHOT_AND_CLONE_FAILED, reason, self.UserCred)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotAndCloneTask) taskComplete(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.finalReleasePendingUsage(ctx)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
guest.StartSyncstatus(ctx, self.UserCred, "")
db.OpsLog.LogEvent(guest, db.ACT_VM_SNAPSHOT_AND_CLONE, "", self.UserCred)
logclient.AddActionLogWithContext(
ctx, guest, logclient.ACT_VM_SNAPSHOT_AND_CLONE, "", self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceSnapshotAndCloneTask) SetStageFailed(ctx context.Context, reason string) {
self.finalReleasePendingUsage(ctx)
self.STask.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotAndCloneTask) finalReleasePendingUsage(ctx context.Context) {
pendingUsage := models.SQuota{}
err := self.GetPendingUsage(&pendingUsage)
if err == nil && !pendingUsage.IsEmpty() {
isp := self.GetObject().(*models.SInstanceSnapshot)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
quotaPlatform := guest.GetQuotaPlatformID()
models.QuotaManager.CancelPendingUsage(
ctx, self.UserCred, rbacutils.ScopeProject,
guest.GetOwnerId(), quotaPlatform, &pendingUsage, &pendingUsage,
)
}
}
func (self *InstanceSnapshotAndCloneTask) OnInit(
ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
isp := obj.(*models.SInstanceSnapshot)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
self.SetStage("OnCreateInstanceSnapshot", nil)
err := isp.StartCreateInstanceSnapshotTask(ctx, self.UserCred, guest.GetOwnerId(), nil, self.Id)
if err != nil {
self.taskFailed(ctx, isp, err.Error())
return
}
}
func (self *InstanceSnapshotAndCloneTask) OnCreateInstanceSnapshot(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
// start create server
params, err := self.Params.Get("guest_params")
if err != nil {
self.taskFailed(ctx, isp, "Failed get new guest params")
return
}
count, _ := params.Int("count")
if count == 0 {
count = 1
}
err = self.doGuestCreate(ctx, isp, params, int(count))
if err != nil {
self.taskFailed(ctx, isp, err.Error())
return
}
self.taskComplete(ctx, isp, nil)
}
func (self *InstanceSnapshotAndCloneTask) doGuestCreate(
ctx context.Context, isp *models.SInstanceSnapshot, params jsonutils.JSONObject, count int) error {
dictParmas := params.(*jsonutils.JSONDict)
var errStr string
for i := 0; i < count; i++ {
newGuest, input, err := models.GuestManager.CreateGuestFromInstanceSnapshot(
ctx, self.UserCred, dictParmas.DeepCopy().(*jsonutils.JSONDict), isp, i+1)
if err != nil {
log.Errorln(err)
errStr += err.Error() + "\n"
continue
}
models.GuestManager.OnCreateComplete(ctx, []db.IModel{newGuest}, self.UserCred, nil, input)
}
if len(errStr) > 0 {
return fmt.Errorf(errStr)
}
return nil
}
func (self *InstanceSnapshotAndCloneTask) OnGuestCreated(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.taskComplete(ctx, isp, data)
}
func (self *InstanceSnapshotAndCloneTask) OnCreateInstanceSnapshotFailed(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.taskFailed(ctx, isp, data.String())
}

View File

@@ -0,0 +1,144 @@
package tasks
import (
"context"
"fmt"
"strconv"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
type InstanceSnapshotCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceSnapshotCreateTask{})
}
func (self *InstanceSnapshotCreateTask) SetStageFailed(ctx context.Context, reason string) {
self.finalReleasePendingUsage(ctx)
self.STask.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotCreateTask) finalReleasePendingUsage(ctx context.Context) {
pendingUsage := models.SQuota{}
err := self.GetPendingUsage(&pendingUsage)
if err == nil && !pendingUsage.IsEmpty() {
isp := self.GetObject().(*models.SInstanceSnapshot)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
quotaPlatform := guest.GetQuotaPlatformID()
models.QuotaManager.CancelPendingUsage(
ctx, self.UserCred, rbacutils.ScopeProject,
guest.GetOwnerId(), quotaPlatform, &pendingUsage, &pendingUsage,
)
}
}
func (self *InstanceSnapshotCreateTask) taskFail(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, reason string) {
if guest == nil {
guest = models.GuestManager.FetchGuestById(isp.GuestId)
}
isp.SetStatus(self.UserCred, compute.INSTANCE_SNAPSHOT_FAILED, reason)
guest.SetStatus(self.UserCred, compute.VM_INSTANCE_SNAPSHOT_FAILED, reason)
db.OpsLog.LogEvent(isp, db.ACT_ALLOCATE_FAIL, reason, self.UserCred)
logclient.AddActionLogWithStartable(self, isp, logclient.ACT_CREATE, false, self.UserCred, false)
notifyclient.NotifySystemError(isp.GetId(), isp.Name, compute.INSTANCE_SNAPSHOT_FAILED, reason)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotCreateTask) taskComplete(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, data jsonutils.JSONObject) {
self.finalReleasePendingUsage(ctx)
if guest == nil {
guest = models.GuestManager.FetchGuestById(isp.GuestId)
}
isp.SetStatus(self.UserCred, compute.INSTANCE_SNAPSHOT_READY, "")
guest.StartSyncstatus(ctx, self.UserCred, "")
db.OpsLog.LogEvent(isp, db.ACT_ALLOCATE, "instance snapshot create success", self.UserCred)
logclient.AddActionLogWithStartable(self, isp, logclient.ACT_CREATE, false, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceSnapshotCreateTask) OnInit(
ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
isp := obj.(*models.SInstanceSnapshot)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
self.GuestDiskCreateSnapshot(ctx, isp, guest, 0)
}
func (self *InstanceSnapshotCreateTask) GuestDiskCreateSnapshot(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, diskIndex int) {
disks := guest.GetDisks()
if diskIndex >= len(disks) {
self.taskComplete(ctx, isp, guest, nil)
return
}
snapshot, err := models.SnapshotManager.CreateSnapshot(
ctx, self.UserCred, compute.SNAPSHOT_MANUAL, disks[diskIndex].DiskId, guest.Id,
"", fmt.Sprintf("%s-snapshot-%s", guest.Name, timeutils.CompactTime(time.Now())), -1)
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
params := jsonutils.NewDict()
params.Set("disk_index", jsonutils.NewInt(int64(diskIndex)))
params.Set(strconv.Itoa(diskIndex), jsonutils.NewString(snapshot.Id))
self.SetStage("OnDiskSnapshot", params)
if err := snapshot.StartSnapshotCreateTask(ctx, self.UserCred, nil, self.Id); err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
}
func (self *InstanceSnapshotCreateTask) OnDiskSnapshot(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
guest := models.GuestManager.FetchGuestById(isp.GuestId)
diskIndex, err := self.Params.Int("disk_index")
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
snapshotId, err := self.Params.GetString(strconv.Itoa(int(diskIndex)))
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
err = models.InstanceSnapshotJointManager.CreateJoint(isp.Id, snapshotId, int8(diskIndex))
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
self.GuestDiskCreateSnapshot(ctx, isp, guest, int(diskIndex+1))
}
func (self *InstanceSnapshotCreateTask) OnDiskSnapshotFailed(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.taskFail(ctx, isp, nil, data.String())
}

View File

@@ -0,0 +1,93 @@
package tasks
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type InstanceSnapshotDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceSnapshotDeleteTask{})
}
func (self *InstanceSnapshotDeleteTask) taskFail(
ctx context.Context, isp *models.SInstanceSnapshot, reason string) {
isp.SetStatus(self.UserCred, compute.INSTANCE_SNAPSHOT_DELETE_FAILED, "on delete failed")
db.OpsLog.LogEvent(isp, db.ACT_DELETE_FAIL, reason, self.UserCred)
logclient.AddActionLogWithContext(ctx, isp, logclient.ACT_DELETE, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotDeleteTask) taskComplete(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
isp.RealDelete(ctx, self.UserCred)
logclient.AddActionLogWithContext(ctx, isp, logclient.ACT_DELETE, nil, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceSnapshotDeleteTask) OnInit(
ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
isp := obj.(*models.SInstanceSnapshot)
self.SetStage("OnSnapshotDelete", nil)
self.StartSnapshotDelete(ctx, isp)
}
func (self *InstanceSnapshotDeleteTask) StartSnapshotDelete(
ctx context.Context, isp *models.SInstanceSnapshot) {
snapshots, err := isp.GetSnapshots()
if err != nil {
self.taskFail(ctx, isp, err.Error())
return
}
if len(snapshots) == 0 {
self.taskComplete(ctx, isp, nil)
return
}
// detach snapshot and instance
isjp := new(models.SInstanceSnapshotJoint)
err = models.InstanceSnapshotJointManager.Query().
Equals("instance_snapshot_id", isp.Id).Equals("snapshot_id", snapshots[0].Id).First(isjp)
if err != nil {
self.taskFail(ctx, isp, err.Error())
return
}
isjp.SetModelManager(models.InstanceSnapshotJointManager, isjp)
err = isjp.Delete(ctx, self.UserCred)
if err != nil {
self.taskFail(ctx, isp, err.Error())
return
}
err = snapshots[0].StartSnapshotDeleteTask(ctx, self.UserCred, false, self.Id)
if err != nil {
self.taskFail(ctx, isp, err.Error())
return
}
}
func (self *InstanceSnapshotDeleteTask) OnSnapshotDelete(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.StartSnapshotDelete(ctx, isp)
}
func (self *InstanceSnapshotDeleteTask) OnSnapshotDeleteFailed(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.taskFail(ctx, isp, data.String())
}

View File

@@ -0,0 +1,103 @@
package tasks
import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type InstanceSnapshotResetTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceSnapshotResetTask{})
}
func (self *InstanceSnapshotResetTask) taskFail(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, reason string) {
if guest == nil {
guest = models.GuestManager.FetchGuestById(isp.GuestId)
}
guest.SetStatus(self.UserCred, compute.VM_SNAPSHOT_RESET_FAILED, reason)
db.OpsLog.LogEvent(guest, db.ACT_VM_RESET_SNAPSHOT_FAILED, reason, self.UserCred)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_RESET, false, self.UserCred, false)
notifyclient.NotifySystemError(guest.GetId(), isp.Name, compute.VM_SNAPSHOT_RESET_FAILED, reason)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceSnapshotResetTask) taskComplete(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, data jsonutils.JSONObject) {
if guest == nil {
guest = models.GuestManager.FetchGuestById(isp.GuestId)
}
guest.StartSyncstatus(ctx, self.UserCred, "")
db.OpsLog.LogEvent(isp, db.ACT_VM_RESET_SNAPSHOT, "instance snapshot reset success", self.UserCred)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_RESET, false, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceSnapshotResetTask) OnInit(
ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
isp := obj.(*models.SInstanceSnapshot)
guest := models.GuestManager.FetchGuestById(isp.GuestId)
self.GuestDiskResetTask(ctx, isp, guest, 0)
}
func (self *InstanceSnapshotResetTask) GuestDiskResetTask(
ctx context.Context, isp *models.SInstanceSnapshot, guest *models.SGuest, diskIndex int) {
disks := guest.GetDisks()
if diskIndex >= len(disks) {
self.taskComplete(ctx, isp, guest, nil)
return
}
isj, err := isp.GetInstanceSnapshotJointAt(diskIndex)
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
params := jsonutils.NewDict()
params.Set("disk_index", jsonutils.NewInt(int64(diskIndex)))
self.SetStage("OnDiskReset", params)
disk := disks[diskIndex].GetDisk()
err = disk.StartResetDisk(ctx, self.UserCred, isj.SnapshotId, false, guest, self.Id)
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
}
func (self *InstanceSnapshotResetTask) OnDiskReset(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
guest := models.GuestManager.FetchGuestById(isp.GuestId)
diskIndex, err := self.Params.Int("disk_index")
if err != nil {
self.taskFail(ctx, isp, guest, err.Error())
return
}
self.GuestDiskResetTask(ctx, isp, guest, int(diskIndex+1))
}
func (self *InstanceSnapshotResetTask) OnDiskResetFailed(
ctx context.Context, isp *models.SInstanceSnapshot, data jsonutils.JSONObject) {
self.taskFail(ctx, isp, nil, data.String())
}

View File

@@ -103,6 +103,7 @@ func (p *SKVMGuestDiskPartition) MountPartReadOnly() bool {
func (p *SKVMGuestDiskPartition) Mount() bool {
if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap", "btrfs"}) {
log.Errorf("Mount fs failed: %s", p.fs)
return false
}
err := p.fsck()

View File

@@ -1183,6 +1183,7 @@ func (s *SKVMGuestInstance) streamDisksComplete(ctx context.Context) {
diskpath, _ := disk.GetString("path")
d := storageman.GetManager().GetDiskByPath(diskpath)
if d != nil {
log.Infof("Disk %s do post create from fuse", d.GetId())
d.PostCreateFromImageFuse()
}
if jsonutils.QueryBoolean(disk, "merge_snapshot", false) {

View File

@@ -0,0 +1,19 @@
package modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
var (
InstanceSnapshots modulebase.ResourceManager
)
func init() {
InstanceSnapshots = NewComputeManager("instance_snapshot", "instance_snapshots",
[]string{"ID", "Name",
"Status", "GuestId",
"ServerConfig",
},
[]string{},
)
registerCompute(&InstanceSnapshots)
}

View File

@@ -232,6 +232,17 @@ type ServerCloneOptions struct {
Eip string `help:"associate with an existing EIP when server is created" json:"eip,omitempty"`
}
type ServerCreateFromInstanceSnapshot struct {
InstaceSnapshotId string `help:"Instace snapshot id or name"`
NAME string `help:"Name of newly server" json:"name"`
AutoStart bool `help:"Auto start server after it is created"`
AllowDelete bool `help:"Unlock server to allow deleting"`
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" 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
@@ -259,6 +270,7 @@ type ServerCreateOptions struct {
TaskNotify *bool `help:"Setup task notify" json:"-"`
DryRun *bool `help:"Dry run to test scheduler" json:"-"`
UserDataFile string `help:"user_data file path" json:"-"`
InstanceSnapshot string `help:"instance snapshot" json:"instance_snapshot"`
OsType string `help:"os type, e.g. Linux, Windows, etc."`

View File

@@ -124,6 +124,8 @@ const (
ACT_ATTACH_HOST = "关联宿主机"
ACT_DETACH_HOST = "取消关联宿主机"
ACT_VM_IO_THROTTLE = "虚拟机磁盘限速"
ACT_VM_RESET = "虚拟机回滚快照"
ACT_VM_SNAPSHOT_AND_CLONE = "虚拟机快照并克隆"
ACT_IMAGE_SAVE = "上传镜像"
ACT_IMAGE_PROBE = "镜像检测"

View File

@@ -404,9 +404,9 @@ func (img *SQemuImage) create(sizeMB int, format TImageFormat, options []string)
args = append(args, fmt.Sprintf("%dM", sizeMB))
}
cmd := exec.Command("ionice", args...)
err := cmd.Run()
output, err := cmd.Output()
if err != nil {
log.Errorf("create error %s", err)
log.Errorf("%v create error %s %s", args, output, err)
return err
}
return img.parse()