diff --git a/cmd/climc/shell/snapshots.go b/cmd/climc/shell/snapshots.go index 83506a9bd9..f7791a617d 100644 --- a/cmd/climc/shell/snapshots.go +++ b/cmd/climc/shell/snapshots.go @@ -10,14 +10,18 @@ import ( func init() { type SnapshotsListOptions struct { options.BaseListOptions - Disk string `help:"Disk snapshots"` + Disk string `help:"Disk snapshots"` + FakeDeleted bool `help:"Show fake deleted snapshot or not"` } R(&SnapshotsListOptions{}, "snapshot-list", "Show snapshots", func(s *mcclient.ClientSession, args *SnapshotsListOptions) error { params, err := args.BaseListOptions.Params() if err != nil { return err } - params.Add(jsonutils.NewString(args.Disk), "disk_id") + if len(args.Disk) > 0 { + params.Add(jsonutils.NewString(args.Disk), "disk_id") + } + params.Add(jsonutils.NewBool(args.FakeDeleted), "fake_deleted") result, err := modules.Snapshots.List(s, params) if err != nil { return err @@ -37,4 +41,17 @@ func init() { printObject(result) return nil }) + type DiskDeleteSnapshotsOptions struct { + DISK string `help:"ID of disk"` + } + R(&DiskDeleteSnapshotsOptions{}, "disk-delete-snapshots", "Delete a disk snapshots", func(s *mcclient.ClientSession, args *DiskDeleteSnapshotsOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.DISK), "disk_id") + result, err := modules.Snapshots.PerformClassAction(s, "delete-disk-snapshots", params) + if err != nil { + return err + } + printObject(result) + return nil + }) } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index af7a84bc99..33c857efbe 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -355,14 +355,17 @@ func (self *SDisk) GetDetailsConvertSnapshot(ctx context.Context, userCred mccli if err != nil { return nil, httperrors.NewBadRequestError("Get convert snapshot failed: %s", err.Error()) } - var pendingDelete bool - if deleteSnapshot.CreatedBy == MANUAL && !deleteSnapshot.PendingDeleted { - pendingDelete = true + if convertSnapshot == nil { + return nil, httperrors.NewBadRequestError("Snapshot %s dose not have convert snapshot", deleteSnapshot.Id) + } + var FakeDelete bool + if deleteSnapshot.CreatedBy == MANUAL && !deleteSnapshot.FakeDeleted { + FakeDelete = true } ret := jsonutils.NewDict() ret.Set("delete_snapshot", jsonutils.NewString(deleteSnapshot.Id)) ret.Set("convert_snapshot", jsonutils.NewString(convertSnapshot.Id)) - ret.Set("pending_delete", jsonutils.NewBool(pendingDelete)) + ret.Set("pending_delete", jsonutils.NewBool(FakeDelete)) return ret, nil } diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index 1b03bf6fc7..a2d2df78ad 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -32,12 +32,13 @@ type SSnapshotManager struct { type SSnapshot struct { db.SVirtualResourceBase - DiskId string `width:"36" charset:"ascii" nullable:"false" create:"required" key_index:"true" list:"user"` - StorageId string `width:"36" charset:"ascii" nullable:"true" list:"admin"` - CreatedBy string `width:"36" charset:"ascii" nullable:"false" default:"manual" list:"admin"` - Location string `charset:"ascii" nullable:"false" list:"admin"` - Size int `nullable:"false" list:"user"` // MB - OutOfChain bool `nullable:"false" default:"false" index:"true" get:"admin"` + DiskId string `width:"36" charset:"ascii" nullable:"false" create:"required" key_index:"true" list:"user"` + StorageId string `width:"36" charset:"ascii" nullable:"true" list:"admin"` + CreatedBy string `width:"36" charset:"ascii" nullable:"false" default:"manual" list:"admin"` + Location string `charset:"ascii" nullable:"false" list:"admin"` + Size int `nullable:"false" list:"user"` // MB + OutOfChain bool `nullable:"false" default:"false" index:"true" get:"admin"` + FakeDeleted bool `nullable:"false" default:"false" index:"true"` } var SnapshotManager *SSnapshotManager @@ -50,6 +51,19 @@ func (self *SSnapshot) AllowListItems(ctx context.Context, userCred mcclient.Tok return self.IsOwner(userCred) } +func (manager *SSnapshotManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + if jsonutils.QueryBoolean(query, "fake_deleted", false) { + q = q.Equals("fake_deleted", true) + } else { + q = q.Equals("fake_deleted", false) + } + return q, nil +} + func (self *SSnapshot) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return false } @@ -97,7 +111,7 @@ func (self *SSnapshotManager) GetDiskSnapshotsByCreate(diskId, createdBy string) q := self.Query().SubQuery() err := q.Query().Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), diskId), sqlchemy.Equals(q.Field("created_by"), createdBy), - sqlchemy.Equals(q.Field("pending_deleted"), false))).All(&dest) + sqlchemy.Equals(q.Field("fake_deleted"), false))).All(&dest) if err != nil { log.Errorf("GetDiskSnapshots error: %s", err) return nil @@ -139,7 +153,7 @@ func (self *SSnapshotManager) GetDiskFirstSnapshot(diskId string) *SSnapshot { func (self *SSnapshotManager) GetDiskSnapshotCount(diskId string) int { q := self.Query().SubQuery() return q.Query().Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), diskId), - sqlchemy.Equals(q.Field("pending_deleted"), false))).Count() + sqlchemy.Equals(q.Field("fake_deleted"), false))).Count() } func (self *SSnapshotManager) CreateSnapshot(ctx context.Context, userCred mcclient.TokenCredential, createdBy, diskId, guestId, location, name string) (*SSnapshot, error) { @@ -187,8 +201,8 @@ func (self *SSnapshot) ValidateDeleteCondition(ctx context.Context) error { func (self *SSnapshot) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { if self.CreatedBy == MANUAL { - if !self.PendingDeleted { - return self.SVirtualResourceBase.PendingDelete() + if !self.FakeDeleted { + return self.FakeDelete() } else { return self.StartSnapshotDeleteTask(ctx, userCred, false, "") } @@ -233,6 +247,8 @@ func (self *SSnapshotManager) GetConvertSnapshot(deleteSnapshot *SSnapshot) (*SS if len(dest) == 2 && dest[0].Id == deleteSnapshot.Id { dest[1].SetModelManager(self) return &dest[1], nil + } else if len(dest) == 1 && dest[0].Id == deleteSnapshot.Id { + return nil, nil } return nil, fmt.Errorf("Snapshot %s cannot convert", deleteSnapshot.Id) } @@ -254,6 +270,11 @@ func (self *SSnapshotManager) PerformDeleteDiskSnapshots(ctx context.Context, us if snapshots == nil || len(snapshots) == 0 { return nil, httperrors.NewNotFoundError("Disk %s dose not have snapshot", diskId) } + for i := 0; i < len(snapshots); i++ { + if snapshots[i].CreatedBy == MANUAL && snapshots[i].FakeDeleted == false { + return nil, httperrors.NewBadRequestError("Can not delete disk snapshots, have manual snapshot") + } + } err = snapshots[0].StartSnapshotsDeleteTask(ctx, userCred, "") return nil, err } @@ -273,6 +294,14 @@ func (self *SSnapshot) RealDelete(ctx context.Context, userCred mcclient.TokenCr return db.DeleteModel(ctx, userCred, self) } +func (self *SSnapshot) FakeDelete() error { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.FakeDeleted = true + return nil + }) + return err +} + func (self *SSnapshot) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } diff --git a/pkg/compute/tasks/guest_disk_snapshot_task.go b/pkg/compute/tasks/guest_disk_snapshot_task.go index c2b16e571e..ebf2e08e61 100644 --- a/pkg/compute/tasks/guest_disk_snapshot_task.go +++ b/pkg/compute/tasks/guest_disk_snapshot_task.go @@ -2,6 +2,7 @@ package tasks import ( "context" + "database/sql" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -109,12 +110,13 @@ func (self *SnapshotDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneMo snapshot := obj.(*models.SSnapshot) guest, err := snapshot.GetGuest() if err != nil { - self.SetStageFailed(ctx, err.Error()) - return - } - if guest == nil { - self.SetStageFailed(ctx, "Cannot delete without guest") - return + if err != sql.ErrNoRows { + self.SetStageFailed(ctx, err.Error()) + return + } else { + self.DeleteStaticSnapshot(ctx, snapshot) + return + } } if jsonutils.QueryBoolean(self.Params, "reload_disk", false) && snapshot.OutOfChain { self.StartReloadDisk(ctx, snapshot, guest) @@ -140,16 +142,20 @@ func (self *SnapshotDeleteTask) StartDeleteSnapshot(ctx context.Context, snapsho self.TaskFailed(ctx, snapshot, err.Error()) return } + if convertSnapshot == nil { + self.TaskFailed(ctx, snapshot, "snapshot dose not have convert snapshot") + return + } params := jsonutils.NewDict() params.Set("delete_snapshot", jsonutils.NewString(snapshot.Id)) params.Set("disk_id", jsonutils.NewString(snapshot.DiskId)) if !snapshot.OutOfChain { params.Set("convert_snapshot", jsonutils.NewString(convertSnapshot.Id)) - var pendingDelete = jsonutils.JSONFalse - if snapshot.CreatedBy == models.MANUAL && snapshot.PendingDeleted == false { - pendingDelete = jsonutils.JSONTrue + var FakeDelete = jsonutils.JSONFalse + if snapshot.CreatedBy == models.MANUAL && snapshot.FakeDeleted == false { + FakeDelete = jsonutils.JSONTrue } - params.Set("pending_delete", pendingDelete) + params.Set("pending_delete", FakeDelete) } else { params.Set("auto_deleted", jsonutils.JSONTrue) } @@ -162,6 +168,23 @@ func (self *SnapshotDeleteTask) StartDeleteSnapshot(ctx context.Context, snapsho } } +func (self *SnapshotDeleteTask) DeleteStaticSnapshot(ctx context.Context, snapshot *models.SSnapshot) { + // convertSnapshot, err := models.SnapshotManager.GetConvertSnapshot(snapshot) + // if err != nil { + // self.TaskFailed(ctx, snapshot, err.Error()) + // return + // } + // if convertSnapshot == nil { + // self.TaskFailed(ctx, snapshot, "Snapshot dose not have convert snapshot") + // } + err := snapshot.FakeDelete() + if err != nil { + self.TaskFailed(ctx, snapshot, err.Error()) + return + } + self.SetStageComplete(ctx, nil) +} + func (self *SnapshotDeleteTask) OnDeleteSnapshot(ctx context.Context, snapshot *models.SSnapshot, data jsonutils.JSONObject) { if !jsonutils.QueryBoolean(data, "deleted", false) { log.Infof("OnDeleteSnapshot with no deleted") @@ -176,13 +199,12 @@ func (self *SnapshotDeleteTask) OnDeleteSnapshot(ctx context.Context, snapshot * self.SetStageFailed(ctx, err.Error()) return } - var pendingDelete = false - if snapshot.CreatedBy == models.MANUAL && snapshot.PendingDeleted == false && - !jsonutils.QueryBoolean(self.Params, "force_delete", false) { - pendingDelete = true + var FakeDelete = false + if snapshot.CreatedBy == models.MANUAL && snapshot.FakeDeleted == false { + FakeDelete = true } - if pendingDelete { - snapshot.PendingDelete() + if FakeDelete { + snapshot.FakeDelete() } else { snapshot.RealDelete(ctx, self.UserCred) } @@ -207,7 +229,7 @@ func (self *SnapshotDeleteTask) OnReloadDiskSnapshot(ctx context.Context, snapsh return } } else { - err := snapshot.SVirtualResourceBase.PendingDelete() + err := snapshot.FakeDelete() if err != nil { self.TaskFailed(ctx, snapshot, err.Error()) return diff --git a/pkg/mcclient/modules/resource.go b/pkg/mcclient/modules/resource.go index 473e38b67a..cb343484b2 100644 --- a/pkg/mcclient/modules/resource.go +++ b/pkg/mcclient/modules/resource.go @@ -351,6 +351,15 @@ func (this *ResourceManager) PerformActionInContexts(session *mcclient.ClientSes return this._post(session, path, this.params2Body(params), this.Keyword) } +func (this *ResourceManager) PerformClassAction(session *mcclient.ClientSession, action string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return this.PerformClassActionInContexts(session, action, params, nil) +} + +func (this *ResourceManager) PerformClassActionInContexts(session *mcclient.ClientSession, action string, params jsonutils.JSONObject, ctxs []ManagerContext) (jsonutils.JSONObject, error) { + path := fmt.Sprintf("/%s/%s", this.ContextPath(ctxs), url.PathEscape(action)) + return this._post(session, path, params, this.Keyword) +} + func (this *ResourceManager) BatchPerformAction(session *mcclient.ClientSession, idlist []string, action string, params jsonutils.JSONObject) []SubmitResult { return this.BatchPerformActionInContexts(session, idlist, action, params, nil) }