diff --git a/cmd/climc/shell/disks.go b/cmd/climc/shell/disks.go index 720b0850cb..7a3b62e8e3 100644 --- a/cmd/climc/shell/disks.go +++ b/cmd/climc/shell/disks.go @@ -81,6 +81,7 @@ func init() { type DiskDeleteOptions struct { ID []string `help:"ID of disks to delete" metavar:"DISK"` OverridePendingDelete bool `help:"Delete disk directly instead of pending delete" short-token:"f"` + DeleteSnapshots bool `help:"Delete disk snapshots before delete disk"` } R(&DiskDeleteOptions{}, "disk-delete", "Delete a disk", func(s *mcclient.ClientSession, args *DiskDeleteOptions) error { @@ -88,6 +89,9 @@ func init() { if args.OverridePendingDelete { params.Add(jsonutils.JSONTrue, "override_pending_delete") } + if args.DeleteSnapshots { + params.Add(jsonutils.JSONTrue, "delete_snapshots") + } ret := modules.Disks.BatchDeleteWithParam(s, args.ID, params, nil) printBatchResults(ret, modules.Disks.GetColumns(s)) return nil diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go index 990ba5634c..d1ca091c66 100644 --- a/pkg/compute/guestdrivers/virtualization.go +++ b/pkg/compute/guestdrivers/virtualization.go @@ -190,7 +190,7 @@ func (self *SVirtualizedGuestDriver) StartGuestResetTask(guest *models.SGuest, c func (self *SVirtualizedGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk *models.SDisk, task taskman.ITask, isPurge bool) error { return disk.StartDiskDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), isPurge, - jsonutils.QueryBoolean(task.GetParams(), "override_pending_delete", false)) + jsonutils.QueryBoolean(task.GetParams(), "override_pending_delete", false), false) } func (self *SVirtualizedGuestDriver) StartGuestSyncstatusTask(guest *models.SGuest, ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index d06ef9546f..58461d71c4 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1604,7 +1604,7 @@ func (self *SDisk) PerformPurge(ctx context.Context, userCred mcclient.TokenCred } } - return nil, self.StartDiskDeleteTask(ctx, userCred, "", true, false) + return nil, self.StartDiskDeleteTask(ctx, userCred, "", true, false, false) } func (self *SDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { @@ -1620,7 +1620,8 @@ func (self *SDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenC } return self.StartDiskDeleteTask(ctx, userCred, "", false, - jsonutils.QueryBoolean(query, "override_pending_delete", false)) + jsonutils.QueryBoolean(query, "override_pending_delete", false), + jsonutils.QueryBoolean(query, "delete_snapshots", false)) } func (self *SDisk) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, @@ -1722,7 +1723,10 @@ func (self *SDisk) StartDiskResizeTask(ctx context.Context, userCred mcclient.To return nil } -func (self *SDisk) StartDiskDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge, overridePendingDelete bool) error { +func (self *SDisk) StartDiskDeleteTask( + ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, + isPurge, overridePendingDelete, deleteSnapshots bool, +) error { params := jsonutils.NewDict() if isPurge { params.Add(jsonutils.JSONTrue, "purge") @@ -1730,6 +1734,9 @@ func (self *SDisk) StartDiskDeleteTask(ctx context.Context, userCred mcclient.To if overridePendingDelete { params.Add(jsonutils.JSONTrue, "override_pending_delete") } + if deleteSnapshots { + params.Add(jsonutils.JSONTrue, "delete_snapshots") + } task, err := taskman.TaskManager.NewTask(ctx, "DiskDeleteTask", self, userCred, params, parentTaskId, "", nil) if err != nil { log.Errorf("%s", err) @@ -1887,7 +1894,7 @@ func (manager *SDiskManager) CleanPendingDeleteDisks(ctx context.Context, userCr return } for i := 0; i < len(disks); i += 1 { - disks[i].StartDiskDeleteTask(ctx, userCred, "", false, false) + disks[i].StartDiskDeleteTask(ctx, userCred, "", false, false, false) } } @@ -2205,3 +2212,17 @@ func (self *SDisk) syncSnapshots(ctx context.Context, userCred mcclient.TokenCre } return syncResult } + +func (self *SDisk) GetSnapshotsNotInInstanceSnapshot() ([]SSnapshot, error) { + snapshots := make([]SSnapshot, 0) + sq := InstanceSnapshotJointManager.Query("snapshot_id").SubQuery() + q := SnapshotManager.Query().IsFalse("fake_deleted") + q = q.LeftJoin(sq, sqlchemy.Equals(q.Field("id"), sq.Field("snapshot_id"))). + Filter(sqlchemy.IsNull(sq.Field("snapshot_id"))) + err := db.FetchModelObjects(SnapshotManager, q, &snapshots) + if err != nil { + log.Errorf("Fetch db snapshots failed %s", err) + return nil, err + } + return snapshots, nil +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 7315eae918..6eb075235c 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -905,10 +905,13 @@ func (self *SGuest) StartSyncstatus(ctx context.Context, userCred mcclient.Token func (self *SGuest) StartAutoDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { db.OpsLog.LogEvent(self, db.ACT_DELETE, "auto-delete after stop", userCred) - return self.StartDeleteGuestTask(ctx, userCred, parentTaskId, false, false) + return self.StartDeleteGuestTask(ctx, userCred, parentTaskId, false, false, false) } -func (self *SGuest) StartDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool, overridePendingDelete bool) error { +func (self *SGuest) StartDeleteGuestTask( + ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, + isPurge, overridePendingDelete, deleteSnapshots bool, +) error { params := jsonutils.NewDict() params.Add(jsonutils.NewString(self.Status), "guest_status") if isPurge { @@ -917,6 +920,9 @@ func (self *SGuest) StartDeleteGuestTask(ctx context.Context, userCred mcclient. if overridePendingDelete { params.Add(jsonutils.JSONTrue, "override_pending_delete") } + if deleteSnapshots { + params.Add(jsonutils.JSONTrue, "delete_snapshots") + } self.SetStatus(userCred, api.VM_START_DELETE, "") return self.GetDriver().StartDeleteGuestTask(ctx, userCred, self, params, parentTaskId) } @@ -1170,7 +1176,7 @@ func (self *SGuest) PerformPurge(ctx context.Context, userCred mcclient.TokenCre if host != nil && host.Enabled { return nil, httperrors.NewInvalidStatusError("Cannot purge server on enabled host") } - err = self.StartDeleteGuestTask(ctx, userCred, "", true, false) + err = self.StartDeleteGuestTask(ctx, userCred, "", true, false, false) return nil, err } @@ -1465,7 +1471,9 @@ func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.Tok return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) } -func (self *SGuest) StartGuestDetachdiskTask(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, keepDisk bool, parentTaskId string, purge bool) error { +func (self *SGuest) StartGuestDetachdiskTask( + ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, keepDisk bool, parentTaskId string, purge bool, +) error { taskData := jsonutils.NewDict() taskData.Add(jsonutils.NewString(disk.Id), "disk_id") taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk") @@ -4063,3 +4071,12 @@ func (self *SGuest) GetDetailsJnlp(ctx context.Context, userCred mcclient.TokenC } return host.GetDetailsJnlp(ctx, userCred, query) } + +func (guest *SGuest) StartDeleteGuestSnapshots(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "GuestDeleteSnapshotsTask", guest, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index b09d31aede..07cca98562 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -3208,13 +3208,17 @@ func (self *SGuest) AllowDeleteItem(ctx context.Context, userCred mcclient.Token } func (self *SGuest) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - overridePendingDelete := false - purge := false + var ( + overridePendingDelete = false + purge = false + deleteSnapshots = false + ) if query != nil { overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false) purge = jsonutils.QueryBoolean(query, "purge", false) + deleteSnapshots = jsonutils.QueryBoolean(query, "delete_snapshots", false) } - return self.StartDeleteGuestTask(ctx, userCred, "", purge, overridePendingDelete) + return self.StartDeleteGuestTask(ctx, userCred, "", purge, overridePendingDelete, deleteSnapshots) } func (self *SGuest) DeleteAllDisksInDB(ctx context.Context, userCred mcclient.TokenCredential) error { @@ -4023,7 +4027,7 @@ func (manager *SGuestManager) CleanPendingDeleteServers(ctx context.Context, use return } for i := 0; i < len(guests); i += 1 { - guests[i].StartDeleteGuestTask(ctx, userCred, "", false, true) + guests[i].StartDeleteGuestTask(ctx, userCred, "", false, true, false) } } @@ -4087,7 +4091,7 @@ func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, u } } guests[i].SetDisableDelete(userCred, false) - guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false) + guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false, false) } } @@ -4105,7 +4109,7 @@ func (manager *SGuestManager) DeleteExpiredPostpaidServers(ctx context.Context, } } guests[i].SetDisableDelete(userCred, false) - guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false) + guests[i].StartDeleteGuestTask(ctx, userCred, "", false, false, false) } } @@ -4721,3 +4725,32 @@ func (guest *SGuest) GetDetailsRemoteNics(ctx context.Context, userCred mcclient // ret.Set("vnics", jsonutils.Marshal(nics)) return jsonutils.Marshal(nics), nil } + +func (self *SGuest) GetInstanceSnapshots() ([]SInstanceSnapshot, error) { + instanceSnapshots := make([]SInstanceSnapshot, 0) + q := InstanceSnapshotManager.Query().Equals("guest_id", self.Id) + err := db.FetchModelObjects(InstanceSnapshotManager, q, &instanceSnapshots) + if err != nil { + return nil, err + } + return instanceSnapshots, nil +} + +func (self *SGuest) GetDiskSnapshotsNotInInstanceSnapshots() ([]SSnapshot, error) { + guestDisks := self.GetDisks() + diskIds := make([]string, len(guestDisks)) + for i := 0; i < len(guestDisks); i++ { + diskIds[i] = guestDisks[i].DiskId + } + snapshots := make([]SSnapshot, 0) + q := SnapshotManager.Query().IsFalse("fake_deleted").In("disk_id", diskIds) + sq := InstanceSnapshotJointManager.Query("snapshot_id").SubQuery() + q = q.LeftJoin(sq, sqlchemy.Equals(q.Field("id"), sq.Field("snapshot_id"))). + Filter(sqlchemy.IsNull(sq.Field("snapshot_id"))) + err := db.FetchModelObjects(SnapshotManager, q, &snapshots) + if err != nil { + log.Errorf("fetch db snapshots failed %s", err) + return nil, err + } + return snapshots, nil +} diff --git a/pkg/compute/models/host_recycle.go b/pkg/compute/models/host_recycle.go index 0f9b6d4cf6..08442a1573 100644 --- a/pkg/compute/models/host_recycle.go +++ b/pkg/compute/models/host_recycle.go @@ -92,7 +92,7 @@ func (self *SGuest) DoPerformPrepaidRecycle(ctx context.Context, userCred mcclie logclient.AddActionLogWithContext(ctx, self, logclient.ACT_RECYCLE_PREPAID, self.GetShortDesc(ctx), userCred, true) if autoDelete { - self.StartDeleteGuestTask(ctx, userCred, "", false, true) + self.StartDeleteGuestTask(ctx, userCred, "", false, true, false) } return nil, nil diff --git a/pkg/compute/models/instance_snapshot_joint.go b/pkg/compute/models/instance_snapshot_joint.go index a6eb020106..57364a8004 100644 --- a/pkg/compute/models/instance_snapshot_joint.go +++ b/pkg/compute/models/instance_snapshot_joint.go @@ -35,7 +35,7 @@ func (manager *SInstanceSnapshotJointManager) GetMasterFieldName() string { } func (manager *SInstanceSnapshotJointManager) GetSlaveFieldName() string { - return "disk_id" + return "snapshot_id" } var InstanceSnapshotJointManager *SInstanceSnapshotJointManager diff --git a/pkg/compute/models/instance_snapshots.go b/pkg/compute/models/instance_snapshots.go index 6710e82aa8..ae1db3098c 100644 --- a/pkg/compute/models/instance_snapshots.go +++ b/pkg/compute/models/instance_snapshots.go @@ -67,24 +67,30 @@ func (self *SInstanceSnapshot) AllowUpdateItem(ctx context.Context, userCred mcc func (self *SInstanceSnapshot) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + extra = self.getMoreDetails(extra) + return extra +} + +func (self *SInstanceSnapshot) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { if guest := GuestManager.FetchGuestById(self.GuestId); guest != nil { extra.Set("guest_status", jsonutils.NewString(guest.Status)) extra.Set("guest_name", jsonutils.NewString(guest.Name)) } + snapshots, _ := self.GetSnapshots() + snapshotsDesc := jsonutils.NewDict() + for i := 0; i < len(snapshots); i++ { + snapshotsDesc.Set(snapshots[i].Id, jsonutils.NewString(snapshots[i].Name)) + } + extra.Set("snapshots", snapshotsDesc) 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)) - } + extra = self.getMoreDetails(extra) return extra, nil } func (self *SInstanceSnapshot) StartCreateInstanceSnapshotTask( @@ -209,3 +215,7 @@ func (self *SInstanceSnapshot) StartInstanceSnapshotDeleteTask( func (self *SInstanceSnapshot) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { return db.DeleteModel(ctx, userCred, self) } + +func (self *SInstanceSnapshot) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index cf5dbfe3c6..82d53c554d 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -581,7 +581,7 @@ func (self *SSnapshot) RealDelete(ctx context.Context, userCred mcclient.TokenCr if err == nil { val := disk.GetMetadata("disk_delete_after_snapshots", userCred) if cnt == 0 && val == "true" { - disk.StartDiskDeleteTask(ctx, userCred, "", false, true) + disk.StartDiskDeleteTask(ctx, userCred, "", false, true, false) } } else { // very unlikely diff --git a/pkg/compute/tasks/baremetal_convert_hypervisor_task.go b/pkg/compute/tasks/baremetal_convert_hypervisor_task.go index 347006a083..959913f8a4 100644 --- a/pkg/compute/tasks/baremetal_convert_hypervisor_task.go +++ b/pkg/compute/tasks/baremetal_convert_hypervisor_task.go @@ -90,7 +90,7 @@ func (self *BaremetalConvertHypervisorTask) OnGuestDeployCompleteFailed(ctx cont guest := self.getGuest() guest.SetDisableDelete(self.UserCred, false) self.SetStage("OnGuestDeleteComplete", nil) - guest.StartDeleteGuestTask(ctx, self.UserCred, self.GetTaskId(), false, true) + guest.StartDeleteGuestTask(ctx, self.UserCred, self.GetTaskId(), false, true, false) logclient.AddActionLogWithStartable(self, baremetal, logclient.ACT_BM_CONVERT_HYPER, fmt.Sprintf("convert deploy failed: %s", body.String()), self.UserCred, false) } diff --git a/pkg/compute/tasks/baremetal_unconvert_hypervisor_task.go b/pkg/compute/tasks/baremetal_unconvert_hypervisor_task.go index 508d73cf0e..51536fa50a 100644 --- a/pkg/compute/tasks/baremetal_unconvert_hypervisor_task.go +++ b/pkg/compute/tasks/baremetal_unconvert_hypervisor_task.go @@ -45,7 +45,7 @@ func (self *BaremetalUnconvertHypervisorTask) OnInit(ctx context.Context, obj db if len(guests) == 1 { guest := guests[0] self.SetStage("OnGuestDeleteComplete", nil) - guest.StartDeleteGuestTask(ctx, self.UserCred, self.GetTaskId(), false, true) + guest.StartDeleteGuestTask(ctx, self.UserCred, self.GetTaskId(), false, true, false) } else { self.OnGuestDeleteComplete(ctx, baremetal, nil) } diff --git a/pkg/compute/tasks/disk_delete_task.go b/pkg/compute/tasks/disk_delete_task.go index 4a7d23dedf..9c37e1d703 100644 --- a/pkg/compute/tasks/disk_delete_task.go +++ b/pkg/compute/tasks/disk_delete_task.go @@ -53,7 +53,14 @@ func (self *DiskDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, db.OpsLog.LogEvent(disk, db.ACT_DELOCATE_FAIL, reason, self.UserCred) return } + if jsonutils.QueryBoolean(self.Params, "delete_snapshots", false) { + } else { + self.OnDeleteSnapshots(ctx, disk) + } +} + +func (self *DiskDeleteTask) OnDeleteSnapshots(ctx context.Context, disk *models.SDisk) { isPurge := jsonutils.QueryBoolean(self.Params, "purge", false) overridePendingDelete := jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) if options.Options.EnablePendingDelete && !isPurge && !overridePendingDelete { @@ -61,6 +68,9 @@ func (self *DiskDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, self.SetStageComplete(ctx, nil) return } + if jsonutils.QueryBoolean(self.Params, "delete_sanpshots", false) { + disk.SetMetadata(ctx, "__delete_snapshots_on_delete", "true", self.UserCred) + } self.startPendingDeleteDisk(ctx, disk) } else { self.startDeleteDisk(ctx, disk) @@ -143,6 +153,7 @@ func (self *DiskDeleteTask) OnGuestDiskDeleteComplete(ctx context.Context, obj d self.SetStageComplete(ctx, nil) return } + disk := obj.(*models.SDisk) self.CleanHostSchedCache(disk) db.OpsLog.LogEvent(disk, db.ACT_DELOCATE, disk.GetShortDesc(ctx), self.UserCred) diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index 9f9630ba74..a93f283c8c 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -45,7 +45,7 @@ func (self *GuestDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel host := guest.GetHost() if guest.Hypervisor == api.HYPERVISOR_BAREMETAL && host != nil && host.HostType != api.HOST_TYPE_BAREMETAL { // if a fake server for converted hypervisor, then just skip stop - self.OnGuestStopComplete(ctx, obj, data) + self.OnGuestStopComplete(ctx, guest, data) return } if len(guest.BackupHostId) > 0 { @@ -77,9 +77,29 @@ func (self *GuestDeleteTask) OnMasterHostStopGuestCompleteFailed(ctx context.Con self.OnGuestStopComplete(ctx, guest, nil) // ignore stop error } -func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - guest := obj.(*models.SGuest) +func (self *GuestDeleteTask) StartDeleteGuestSnapshots(ctx context.Context, guest *models.SGuest) { + guest.StartDeleteGuestSnapshots(ctx, self.UserCred, self.GetTaskId()) +} +func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + if jsonutils.QueryBoolean(self.Params, "delete_snapshots", false) { + self.SetStage("OnStartEipDissociate", nil) + guest.StartDeleteGuestSnapshots(ctx, self.UserCred, self.Id) + return + } + self.OnStartEipDissociate(ctx, guest, data) +} + +func (self *GuestDeleteTask) OnGuestStopCompleteFailed(ctx context.Context, guest *models.SGuest, err jsonutils.JSONObject) { + self.OnGuestStopComplete(ctx, guest, err) // ignore stop error +} + +func (self *GuestDeleteTask) OnStartEipDissociateFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + log.Errorf("Delete guest snapshots faield: %s", data) + self.OnStartEipDissociate(ctx, guest, nil) +} + +func (self *GuestDeleteTask) OnStartEipDissociate(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { eip, _ := guest.GetEip() if eip != nil && eip.Mode != api.EIP_MODE_INSTANCE_PUBLICIP { // detach floating EIP only @@ -92,14 +112,10 @@ func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.ISt eip.StartEipDissociateTask(ctx, self.UserCred, false, self.GetTaskId()) } } else { - self.OnEipDissociateComplete(ctx, obj, nil) + self.OnEipDissociateComplete(ctx, guest, nil) } } -func (self *GuestDeleteTask) OnGuestStopCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { - self.OnGuestStopComplete(ctx, obj, err) // ignore stop error -} - func (self *GuestDeleteTask) OnEipDissociateCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { guest := obj.(*models.SGuest) self.OnFailed(ctx, guest, err) @@ -117,12 +133,15 @@ func (self *GuestDeleteTask) OnDiskDetachComplete(ctx context.Context, obj db.IS guestdisks := guest.GetDisks() if len(guestdisks) == 0 { + // on guest disks detached self.doClearSecurityGroupComplete(ctx, guest) return } - lastDisk := guestdisks[len(guestdisks)-1].GetDisk() // remove last detachable disk + // detach last detachable disk + lastDisk := guestdisks[len(guestdisks)-1].GetDisk() log.Debugf("lastDisk IsDetachable?? %v", lastDisk.IsDetachable()) if !lastDisk.IsDetachable() { + // no more disk need detach self.doClearSecurityGroupComplete(ctx, guest) return } @@ -238,7 +257,7 @@ func (self *GuestDeleteTask) DoDeleteGuest(ctx context.Context, guest *models.SG } else if (host == nil || !host.Enabled) && jsonutils.QueryBoolean(self.Params, "purge", false) { self.OnGuestDeleteComplete(ctx, guest, nil) } else { - self.SetStage("on_guest_delete_complete", nil) + self.SetStage("OnGuestDeleteComplete", nil) guest.StartUndeployGuestTask(ctx, self.UserCred, self.GetTaskId(), "") } } diff --git a/pkg/compute/tasks/guest_disk_snapshot_task.go b/pkg/compute/tasks/guest_disk_snapshot_task.go index 1ff3a3aee6..c1c64d8f72 100644 --- a/pkg/compute/tasks/guest_disk_snapshot_task.go +++ b/pkg/compute/tasks/guest_disk_snapshot_task.go @@ -37,7 +37,6 @@ type GuestDiskSnapshotTask struct { func (self *GuestDiskSnapshotTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) - guest.SetStatus(self.UserCred, api.VM_START_SNAPSHOT, "StartDiskSnapshot") self.DoDiskSnapshot(ctx, guest) } diff --git a/pkg/compute/tasks/snapshot_delete_task.go b/pkg/compute/tasks/snapshot_delete_task.go index d218ca8d5a..6688c5ed18 100644 --- a/pkg/compute/tasks/snapshot_delete_task.go +++ b/pkg/compute/tasks/snapshot_delete_task.go @@ -30,6 +30,8 @@ import ( func init() { taskman.RegisterTask(SnapshotDeleteTask{}) taskman.RegisterTask(BatchSnapshotsDeleteTask{}) + taskman.RegisterTask(GuestDeleteSnapshotsTask{}) + taskman.RegisterTask(DiskDeleteSnapshotsTask{}) } /***************************** Snapshot Delete Task *****************************/ @@ -189,3 +191,105 @@ func (self *BatchSnapshotsDeleteTask) OnStorageDeleteSnapshot(ctx context.Contex } self.SetStageComplete(ctx, nil) } + +type GuestDeleteSnapshotsTask struct { + taskman.STask +} + +func (self *GuestDeleteSnapshotsTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + instanceSnapshots, _ := guest.GetInstanceSnapshots() + self.StartDeleteInstanceSnapshots(ctx, guest, instanceSnapshots) +} + +func (self *GuestDeleteSnapshotsTask) StartDeleteInstanceSnapshots( + ctx context.Context, guest *models.SGuest, instanceSnapshots []models.SInstanceSnapshot) { + if len(instanceSnapshots) > 0 { + instanceSnapshot := instanceSnapshots[0] + instanceSnapshots := instanceSnapshots[1:] + self.Params.Set("instance_snapshots", jsonutils.Marshal(instanceSnapshots)) + self.SetStage("OnInstanceSnapshotDelete", nil) + instanceSnapshot.SetModelManager(models.InstanceSnapshotManager, &instanceSnapshot) + instanceSnapshot.StartInstanceSnapshotDeleteTask(ctx, self.UserCred, self.Id) + return + } + snapshots, _ := guest.GetDiskSnapshotsNotInInstanceSnapshots() + self.StartDeleteDiskSnapshots(ctx, guest, snapshots) +} + +func (self *GuestDeleteSnapshotsTask) OnInstanceSnapshotDelete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + instanceSnapshots := make([]models.SInstanceSnapshot, 0) + self.Params.Unmarshal(&instanceSnapshots, "instance_snapshots") + self.StartDeleteInstanceSnapshots(ctx, guest, instanceSnapshots) +} + +func (self *GuestDeleteSnapshotsTask) OnInstanceSnapshotDeleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + log.Errorln(data.String()) + instanceSnapshots := make([]models.SInstanceSnapshot, 0) + self.Params.Unmarshal(&instanceSnapshots, "instance_snapshots") + self.StartDeleteInstanceSnapshots(ctx, guest, instanceSnapshots) +} + +func (self *GuestDeleteSnapshotsTask) StartDeleteDiskSnapshots( + ctx context.Context, guest *models.SGuest, snapshots []models.SSnapshot) { + if len(snapshots) > 0 { + snapshot := snapshots[0] + snapshots := snapshots[1:] + self.Params.Set("snapshots", jsonutils.Marshal(snapshots)) + self.SetStage("OnSnapshotDelete", nil) + snapshot.SetModelManager(models.SnapshotManager, &snapshot) + snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.Id) + return + } + self.SetStageComplete(ctx, nil) +} + +func (self *GuestDeleteSnapshotsTask) OnSnapshotDelete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + snapshots := make([]models.SSnapshot, 0) + self.Params.Unmarshal(&snapshots, "snapshots") + self.StartDeleteDiskSnapshots(ctx, guest, snapshots) +} + +func (self *GuestDeleteSnapshotsTask) OnSnapshotDeleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + log.Errorln(data.String()) + snapshots := make([]models.SSnapshot, 0) + self.Params.Unmarshal(&snapshots, "snapshots") + self.StartDeleteDiskSnapshots(ctx, guest, snapshots) +} + +type DiskDeleteSnapshotsTask struct { + taskman.STask +} + +func (self *DiskDeleteSnapshotsTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + snapshots, _ := disk.GetSnapshotsNotInInstanceSnapshot() + self.StartDeleteDiskSnapshots(ctx, disk, snapshots) +} + +func (self *DiskDeleteSnapshotsTask) StartDeleteDiskSnapshots( + ctx context.Context, disk *models.SDisk, snapshots []models.SSnapshot) { + if len(snapshots) > 0 { + snapshot := snapshots[0] + snapshots := snapshots[1:] + self.Params.Set("snapshots", jsonutils.Marshal(snapshots)) + self.SetStage("OnSnapshotDelete", nil) + snapshot.SetModelManager(models.SnapshotManager, &snapshot) + snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.Id) + return + } + self.SetStageComplete(ctx, nil) +} + +func (self *DiskDeleteSnapshotsTask) OnSnapshotDelete(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + snapshots := make([]models.SSnapshot, 0) + self.Params.Unmarshal(&snapshots, "snapshots") + self.StartDeleteDiskSnapshots(ctx, disk, snapshots) +} + +func (self *DiskDeleteSnapshotsTask) OnSnapshotDeleteFailed(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + log.Errorln(data.String()) + snapshots := make([]models.SSnapshot, 0) + self.Params.Unmarshal(&snapshots, "snapshots") + self.StartDeleteDiskSnapshots(ctx, disk, snapshots) +} diff --git a/pkg/mcclient/modules/mod_instance_snapshots.go b/pkg/mcclient/modules/mod_instance_snapshots.go index 62a8cf65da..0ddadab932 100644 --- a/pkg/mcclient/modules/mod_instance_snapshots.go +++ b/pkg/mcclient/modules/mod_instance_snapshots.go @@ -9,10 +9,9 @@ var ( func init() { InstanceSnapshots = NewComputeManager("instance_snapshot", "instance_snapshots", []string{"ID", "Name", - "Status", "GuestId", - "ServerConfig", + "Status", "Guest_Id", }, - []string{}, + []string{"Guest_Name", "Snapshots"}, ) registerCompute(&InstanceSnapshots) diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 46b3362776..de698297fd 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -468,6 +468,7 @@ func (opts *ServerUpdateOptions) Params() (*jsonutils.JSONDict, error) { type ServerDeleteOptions struct { ID []string `help:"ID of servers to operate" metavar:"SERVER" json:"-"` OverridePendingDelete *bool `help:"Delete server directly instead of pending delete" short-token:"f"` + DeleteSnapshots *bool `help:"Delete server snapshots"` } type ServerDeployOptions struct {