diff --git a/cmd/climc/shell/disks.go b/cmd/climc/shell/disks.go index 092f243b96..617d44849b 100644 --- a/cmd/climc/shell/disks.go +++ b/cmd/climc/shell/disks.go @@ -194,4 +194,18 @@ func init() { printObject(disk) return nil }) + type DiskResetOptions struct { + DISK string `help:"ID or name of disk"` + SNAPSHOT string `help:"snapshots ID of disk` + } + R(&DiskResetOptions{}, "disk-reset", "Resize a disk", func(s *mcclient.ClientSession, args *DiskResetOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.SNAPSHOT), "snapshot_id") + disk, err := modules.Disks.PerformAction(s, args.DISK, "disk-reset", params) + if err != nil { + return err + } + printObject(disk) + return nil + }) } diff --git a/cmd/climc/shell/quotas.go b/cmd/climc/shell/quotas.go index b601988fe0..50e2c9b202 100644 --- a/cmd/climc/shell/quotas.go +++ b/cmd/climc/shell/quotas.go @@ -16,6 +16,7 @@ type QuotaBaseOptions struct { Ebw int64 `help:"External bandwidth in Mbps"` Image int64 `help:"Template count"` IsolatedDevice int64 `help:"Isolated device count"` + Snapshot int64 `help:"Snapshot count"` } func quotaArgs2Params(args *QuotaBaseOptions) *jsonutils.JSONDict { @@ -47,6 +48,9 @@ func quotaArgs2Params(args *QuotaBaseOptions) *jsonutils.JSONDict { if args.IsolatedDevice > 0 { params.Add(jsonutils.NewInt(args.IsolatedDevice), "isolated_device") } + if args.Snapshot > 0 { + params.Add(jsonutils.NewInt(args.Snapshot), "snapshot") + } return params } diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index 97c5f3b3ce..924a84f56f 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -60,6 +60,9 @@ const ( ACT_SNAPSHOT_DELETE_FAIL = "snapshot_del_fail" ACT_SNAPSHOT_UNLINK = "snapshot_unlink" + ACT_DISK_CLEAN_UP_SNAPSHOTS = "disk_clean_up_snapshots" + ACT_DISK_CLEAN_UP_SNAPSHOTS_FAIL = "disk_clean_up_snapshots_fail" + ACT_ALLOCATING = "allocating" ACT_ALLOCATE = "allocate" ACT_ALLOCATE_FAIL = "alloc_fail" diff --git a/pkg/compute/hostdrivers/base.go b/pkg/compute/hostdrivers/base.go index 0ba8e4d794..08f3dd0125 100644 --- a/pkg/compute/hostdrivers/base.go +++ b/pkg/compute/hostdrivers/base.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -14,3 +15,11 @@ type SBaseHostDriver struct { func (self *SBaseHostDriver) RequestDeleteSnapshotsWithStorage(ctx context.Context, host *models.SHost, snapshot *models.SSnapshot, task taskman.ITask) error { return fmt.Errorf("Not Implement") } + +func (self *SBaseHostDriver) RequestResetDisk(ctx context.Context, host *models.SHost, disk *models.SDisk, params *jsonutils.JSONDict, task taskman.ITask) error { + return fmt.Errorf("Not Implement") +} + +func (self *SBaseHostDriver) RequestCleanUpDiskSnapshots(ctx context.Context, host *models.SHost, disk *models.SDisk, params *jsonutils.JSONDict, task taskman.ITask) error { + return fmt.Errorf("Not Implement") +} diff --git a/pkg/compute/hostdrivers/kvm.go b/pkg/compute/hostdrivers/kvm.go index 24b893b7e6..5a02c49e9a 100644 --- a/pkg/compute/hostdrivers/kvm.go +++ b/pkg/compute/hostdrivers/kvm.go @@ -159,3 +159,21 @@ func (self *SKVMHostDriver) RequestDeleteSnapshotsWithStorage(ctx context.Contex _, err := host.Request(task.GetUserCred(), "POST", url, header, body) return err } + +func (self *SKVMHostDriver) RequestResetDisk(ctx context.Context, host *models.SHost, disk *models.SDisk, params *jsonutils.JSONDict, task taskman.ITask) error { + url := fmt.Sprintf("/disks/%s/reset/%s", disk.StorageId, disk.Id) + header := http.Header{} + header.Add("X-Task-Id", task.GetTaskId()) + header.Add("X-Region-Version", "v2") + _, err := host.Request(task.GetUserCred(), "POST", url, header, params) + return err +} + +func (self *SKVMHostDriver) RequestCleanUpDiskSnapshots(ctx context.Context, host *models.SHost, disk *models.SDisk, params *jsonutils.JSONDict, task taskman.ITask) error { + url := fmt.Sprintf("/disks/%s/cleanup-snapshots/%s", disk.StorageId, disk.Id) + header := http.Header{} + header.Add("X-Task-Id", task.GetTaskId()) + header.Add("X-Region-Version", "v2") + _, err := host.Request(task.GetUserCred(), "POST", url, header, params) + return err +} diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 741f8d331b..b9140bbc63 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -311,7 +311,7 @@ func (self *SDisk) StartDiskCreateTask(ctx context.Context, userCred mcclient.To func (self *SDisk) GetSnapshotCount() int { q := SnapshotManager.Query() count := q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), self.Id), - sqlchemy.Equals(q.Field("out_of_chain"), false))).Count() + sqlchemy.Equals(q.Field("fake_deleted"), false))).Count() return count } @@ -374,6 +374,75 @@ func (self *SDisk) GetDetailsConvertSnapshot(ctx context.Context, userCred mccli return ret, nil } +// On disk reset, auto delete snapshots after the reset snapshot(reserve manualed snapshot) +func (self *SDisk) CleanUpDiskSnapshots(ctx context.Context, userCred mcclient.TokenCredential, snapshot *SSnapshot) error { + dest := make([]SSnapshot, 0) + query := SnapshotManager.TableSpec().Query() + query.Filter(sqlchemy.Equals(query.Field("disk_id"), self.Id)). + GT("created_at", snapshot.CreatedAt).Asc("created_at").All(&dest) + if len(dest) == 0 { + return nil + } + convertSnapshots := jsonutils.NewArray() + deleteSnapshots := jsonutils.NewArray() + for i := 0; i < len(dest); i++ { + if dest[i].CreatedBy == MANUAL && !dest[i].FakeDeleted { + if !dest[i].OutOfChain { + convertSnapshots.Add(jsonutils.NewString(dest[i].Id)) + } + } else { + deleteSnapshots.Add(jsonutils.NewString(dest[i].Id)) + } + } + params := jsonutils.NewDict() + params.Set("convert_snapshots", convertSnapshots) + params.Set("delete_snapshots", deleteSnapshots) + task, err := taskman.TaskManager.NewTask(ctx, "DiskCleanUpSnapshotsTask", self, userCred, params, "", "", nil) + if err != nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SDisk) AllowPerformDiskReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SDisk) PerformDiskReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + snapshotId, err := data.GetString("snapshot_id") + if err != nil { + return nil, err + } + guests := self.GetGuests() + if len(guests) > 1 { + return nil, httperrors.NewBadRequestError("Disk attach muti guests") + } else if len(guests) == 1 { + if guests[0].Status != VM_READY { + return nil, httperrors.NewServerStatusError("Disk attached guest status must be ready") + } + } + _, err = SnapshotManager.FetchById(snapshotId) + if err != nil { + return nil, httperrors.NewNotFoundError("Snapshot %s not found", snapshotId) + } + self.StartResetDisk(ctx, userCred, snapshotId) + return nil, nil +} + +func (self *SDisk) StartResetDisk(ctx context.Context, userCred mcclient.TokenCredential, snapshotId string) error { + params := jsonutils.NewDict() + params.Set("snapshot_id", jsonutils.NewString(snapshotId)) + task, err := taskman.TaskManager.NewTask(ctx, "DiskResetTask", self, userCred, params, "", "", nil) + if err != nil { + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + func (self *SDisk) AllowPerformResize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) } @@ -1145,7 +1214,7 @@ func (manager *SDiskManager) AutoDiskSnapshot(ctx context.Context, userCred mccl } for _, disk := range disks { snapCount := disk.GetSnapshotCount() - if snapCount >= DISK_MAX_SNAPSHOT { + if snapCount >= options.Options.DefaultMaxSnapshotCount { continue } guests := disk.GetGuests() diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 233b006292..88578a91b8 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -3445,7 +3445,7 @@ func (self *SGuest) PerformDiskSnapshot(ctx context.Context, userCred mcclient.T } snapshots := SnapshotManager.GetDiskSnapshotsByCreate(diskId, MANUAL) if snapshots != nil { - if len(snapshots) >= DISK_MAX_MANUAL_SNAPSHOT { + if len(snapshots) >= options.Options.DefaultMaxManualSnapshotCount { return nil, httperrors.NewBadRequestError("Disk %s snapshot full, cannot take any more", diskId) } for _, snapshot := range snapshots { @@ -3454,10 +3454,17 @@ func (self *SGuest) PerformDiskSnapshot(ctx context.Context, userCred mcclient.T } } } + pendingUsage := &SQuota{Snapshot: 1} + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) + if err != nil { + return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err) + } snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name) + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) if err != nil { return nil, err } + err = self.StartDiskSnapshot(ctx, userCred, diskId, snapshot.Id) return nil, err } diff --git a/pkg/compute/models/hostdrivers.go b/pkg/compute/models/hostdrivers.go index a911c691cd..294c5f03ea 100644 --- a/pkg/compute/models/hostdrivers.go +++ b/pkg/compute/models/hostdrivers.go @@ -19,6 +19,8 @@ type IHostDriver interface { RequestResizeDiskOnHostOnline(host *SHost, storage *SStorage, disk *SDisk, size int64, task taskman.ITask) error RequestResizeDiskOnHost(host *SHost, storage *SStorage, disk *SDisk, size int64, task taskman.ITask) error RequestDeleteSnapshotsWithStorage(ctx context.Context, host *SHost, snapshot *SSnapshot, task taskman.ITask) error + RequestResetDisk(ctx context.Context, host *SHost, disk *SDisk, params *jsonutils.JSONDict, task taskman.ITask) error + RequestCleanUpDiskSnapshots(ctx context.Context, host *SHost, disk *SDisk, params *jsonutils.JSONDict, task taskman.ITask) error } var hostDrivers map[string]IHostDriver diff --git a/pkg/compute/models/quotas.go b/pkg/compute/models/quotas.go index f18d91ce89..9054a2afab 100644 --- a/pkg/compute/models/quotas.go +++ b/pkg/compute/models/quotas.go @@ -33,6 +33,7 @@ var ( ErrOutOfGroup = errors.New("out of group quota") ErrOutOfSecgroup = errors.New("out of secgroup quota") ErrOutOfIsolatedDevice = errors.New("out of isolated device quota") + ErrOutOfSnapshot = errors.New("out of snapshot quota") ) type SQuota struct { @@ -49,6 +50,7 @@ type SQuota struct { Group int Secgroup int IsolatedDevice int + Snapshot int } func (self *SQuota) FetchSystemQuota() { @@ -65,6 +67,7 @@ func (self *SQuota) FetchSystemQuota() { self.Group = options.Options.DefaultGroupQuota self.Secgroup = options.Options.DefaultSecgroupQuota self.IsolatedDevice = options.Options.DefaultIsolatedDeviceQuota + self.Snapshot = options.Options.DefaultSnapshotQuota } func (self *SQuota) FetchUsage(projectId string) error { @@ -72,6 +75,7 @@ func (self *SQuota) FetchUsage(projectId string) error { net := totalGuestNicCount(projectId, nil, false) guest := totalGuestResourceCount(projectId, nil, nil, "", false, false, "") eipUsage := ElasticipManager.TotalCount(projectId, nil, nil) + snapshotCount := totalSnapshotCount(projectId) // XXX // keypair belongs to user // keypair := totalKeypairCount(projectId) @@ -89,7 +93,7 @@ func (self *SQuota) FetchUsage(projectId string) error { self.Group = 0 self.Secgroup = totalSecurityGroupCount(projectId) self.IsolatedDevice = guest.TotalIsolatedCount - + self.Snapshot = snapshotCount return nil } @@ -133,6 +137,9 @@ func (self *SQuota) IsEmpty() bool { if self.IsolatedDevice > 0 { return false } + if self.Snapshot > 0 { + return false + } return true } @@ -151,6 +158,7 @@ func (self *SQuota) Add(quota quotas.IQuota) { self.Group = self.Group + squota.Group self.Secgroup = self.Secgroup + squota.Secgroup self.IsolatedDevice = self.IsolatedDevice + squota.IsolatedDevice + self.Snapshot = self.Snapshot + squota.Snapshot } func nonNegative(val int) int { @@ -176,6 +184,7 @@ func (self *SQuota) Sub(quota quotas.IQuota) { self.Group = nonNegative(self.Group - squota.Group) self.Secgroup = nonNegative(self.Secgroup - squota.Secgroup) self.IsolatedDevice = nonNegative(self.IsolatedDevice - squota.IsolatedDevice) + self.Snapshot = nonNegative(self.Snapshot - squota.Snapshot) } func (self *SQuota) Update(quota quotas.IQuota) { @@ -219,6 +228,9 @@ func (self *SQuota) Update(quota quotas.IQuota) { if squota.IsolatedDevice > 0 { self.IsolatedDevice = squota.IsolatedDevice } + if squota.Snapshot > 0 { + self.Snapshot = squota.Snapshot + } } func (self *SQuota) Exceed(quota quotas.IQuota) error { @@ -262,6 +274,9 @@ func (self *SQuota) Exceed(quota quotas.IQuota) error { if self.IsolatedDevice > squota.IsolatedDevice { return ErrOutOfIsolatedDevice } + if self.Snapshot > squota.Snapshot { + return ErrOutOfSnapshot + } return nil } @@ -314,5 +329,8 @@ func (self *SQuota) ToJSON(prefix string) jsonutils.JSONObject { if self.IsolatedDevice > 0 { ret.Add(jsonutils.NewInt(int64(self.IsolatedDevice)), keyName(prefix, "isolated_device")) } + if self.Snapshot > 0 { + ret.Add(jsonutils.NewInt(int64(self.Snapshot)), keyName(prefix, "snapshot")) + } return ret } diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index 8eea624e72..8b6f770899 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -11,20 +11,19 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) const ( - DISK_MAX_SNAPSHOT = 9 - DISK_MAX_MANUAL_SNAPSHOT = 2 - // create by MANUAL = "manual" AUTO = "auto" - SNAPSHOT_FAILED = "create_failed" - SNAPSHOT_READY = "ready" + SNAPSHOT_FAILED = "create_failed" + SNAPSHOT_READY = "ready" + SNAPSHOT_DELETING = "deleting" ) type SSnapshotManager struct { @@ -38,7 +37,7 @@ type SSnapshot struct { 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"` + OutOfChain bool `nullable:"false" default:"false" index:"true" list:"admin"` FakeDeleted bool `nullable:"false" default:"false" index:"true"` } @@ -150,7 +149,7 @@ func (self *SSnapshotManager) GetDiskFirstSnapshot(diskId string) *SSnapshot { dest := &SSnapshot{} q := self.Query().SubQuery() err := q.Query().Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), diskId), - sqlchemy.Equals(q.Field("status"), SNAPSHOT_READY), + sqlchemy.In(q.Field("status"), []string{SNAPSHOT_READY, SNAPSHOT_DELETING}), sqlchemy.Equals(q.Field("out_of_chain"), false))).Asc("created_at").First(dest) if err != nil { log.Errorf("Get Disk First snapshot error: %s", err.Error()) @@ -210,18 +209,23 @@ 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.Status == SNAPSHOT_DELETING { + return fmt.Errorf("Cannot delete snapshot in status %s", self.Status) + } else if self.Status == VM_SNAPSHOT_FAILED { + return self.RealDelete(ctx, userCred) + } if self.CreatedBy == MANUAL { if !self.FakeDeleted { return self.FakeDelete() } else { _, err := SnapshotManager.GetConvertSnapshot(self) if err != nil { - httperrors.NewBadRequestError("Snapshot dosen't have convert snapshot, use disk-delete-snapshots") + return fmt.Errorf("Cannot delete snapshot: %s, need delete in sequence create", err.Error()) } return self.StartSnapshotDeleteTask(ctx, userCred, false, "") } } else { - return httperrors.NewBadRequestError("Cannot delete snapshot created by %s", self.CreatedBy) + return fmt.Errorf("Cannot delete snapshot created by %s", self.CreatedBy) } } @@ -244,7 +248,7 @@ func (self *SSnapshotManager) AllowGetPropertyMaxCount(ctx context.Context, user func (self *SSnapshotManager) GetPropertyMaxCount(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { ret := jsonutils.NewDict() - ret.Set("max_count", jsonutils.NewInt(int64(DISK_MAX_SNAPSHOT))) + ret.Set("max_count", jsonutils.NewInt(int64(options.Options.DefaultMaxSnapshotCount))) return ret, nil } @@ -252,7 +256,7 @@ func (self *SSnapshotManager) GetConvertSnapshot(deleteSnapshot *SSnapshot) (*SS dest := make([]SSnapshot, 0) q := self.Query().SubQuery() err := q.Query().Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), deleteSnapshot.DiskId), - sqlchemy.Equals(q.Field("status"), SNAPSHOT_READY), + sqlchemy.In(q.Field("status"), []string{SNAPSHOT_READY, SNAPSHOT_DELETING}), sqlchemy.Equals(q.Field("out_of_chain"), false))). Asc("created_at").Limit(2).All(&dest) if err != nil { @@ -319,3 +323,9 @@ func (self *SSnapshot) FakeDelete() error { func (self *SSnapshot) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } + +func totalSnapshotCount(projectId string) int { + q := SnapshotManager.Query() + count := q.Equals("tenant_id", projectId).Equals("fake_deleted", false).Count() + return count +} diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index a1e34b14cd..f27053a85d 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -44,13 +44,17 @@ type ComputeOptions struct { DefaultGroupQuota int `default:"50" help:"Common group quota per tenant, default 50"` DefaultSecgroupQuota int `default:"50" help:"Common security group quota per tenant, default 50"` DefaultIsolatedDeviceQuota int `default:"50" help:"Common isolated device quota per tenant, default 50"` + DefaultSnapshotQuota int `default:"10" help:"Common snapshot quota per tenant, default 10"` SystemAdminQuotaCheck bool `help:"Enable quota check for system admin, default False" default:"false"` BaremetalPreparePackageUrl string `help:"Baremetal online register package"` - AutoSnapshotDay int `default:"1" help:"Days auto snapshot disks, default 1 day"` - AutoSnapshotHour int `default:"2" help:"What hour take sanpshot, default 02:00"` + // snapshot options + AutoSnapshotDay int `default:"1" help:"Days auto snapshot disks, default 1 day"` + AutoSnapshotHour int `default:"2" help:"What hour take sanpshot, default 02:00"` + DefaultMaxSnapshotCount int `default:"9" help:"Per Disk max snapshot count, default 9"` + DefaultMaxManualSnapshotCount int `default:"2" help:"Per Disk max manual snapshot count, default 2"` cloudcommon.DBOptions } diff --git a/pkg/compute/tasks/disk_recover_task.go b/pkg/compute/tasks/disk_recover_task.go new file mode 100644 index 0000000000..705ba95dfc --- /dev/null +++ b/pkg/compute/tasks/disk_recover_task.go @@ -0,0 +1,131 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type DiskResetTask struct { + SDiskBaseTask +} + +func init() { + taskman.RegisterTask(DiskResetTask{}) + taskman.RegisterTask(DiskCleanUpSnapshotsTask{}) +} + +func (self *DiskResetTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + storage := disk.GetStorage() + if storage == nil { + self.SetStageFailed(ctx, "Disk storage not found") + return + } + host := storage.GetMasterHost() + if host == nil { + self.SetStageFailed(ctx, "Storage master host not found") + return + } + self.RequestResetDisk(ctx, disk, host) +} + +func (self *DiskResetTask) RequestResetDisk(ctx context.Context, disk *models.SDisk, host *models.SHost) { + snapshotId, err := self.Params.GetString("snapshot_id") + if err != nil { + self.SetStageFailed(ctx, fmt.Sprintf("Get snapshotId error %s", err.Error())) + return + } + iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) + snapshot := iSnapshot.(*models.SSnapshot) + params := jsonutils.NewDict() + params.Set("snapshot_id", jsonutils.NewString(snapshot.Id)) + if snapshot.OutOfChain { + params.Set("out_of_chain", jsonutils.JSONTrue) + } else { + params.Set("out_of_chain", jsonutils.JSONFalse) + } + self.SetStage("OnRequestResetDisk", nil) + err = host.GetHostDriver().RequestResetDisk(ctx, host, disk, params, self) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + } +} + +func (self *DiskResetTask) OnRequestResetDisk(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + snapshotId, _ := self.Params.GetString("snapshot_id") + iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) + snapshot := iSnapshot.(*models.SSnapshot) + // self.SetStage("OnCleanUpSnapshots", nil) + err := disk.CleanUpDiskSnapshots(ctx, self.UserCred, snapshot) + if err != nil { + log.Errorln(err) + self.SetStageFailed(ctx, fmt.Sprintf("OnRequestResetDisk %s", err.Error())) + return + } + self.SetStageComplete(ctx, nil) +} + +// func (self *DiskResetTask) OnRequestResetDisk(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { +// self.SetStageComplete(ctx, data) +// } + +type DiskCleanUpSnapshotsTask struct { + SDiskBaseTask +} + +func (self *DiskCleanUpSnapshotsTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + self.StartCleanUpSnapshots(ctx, disk) +} + +func (self *DiskCleanUpSnapshotsTask) StartCleanUpSnapshots(ctx context.Context, disk *models.SDisk) { + db.OpsLog.LogEvent(disk, db.ACT_DISK_CLEAN_UP_SNAPSHOTS, + fmt.Sprintf("start clean up disk snapshots: %s", self.Params.String()), self.UserCred) + host := disk.GetStorage().GetMasterHost() + self.SetStage("OnCleanUpSnapshots", nil) + err := host.GetHostDriver().RequestCleanUpDiskSnapshots(ctx, host, disk, self.Params, self) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + } +} + +func (self *DiskCleanUpSnapshotsTask) OnCleanUpSnapshots(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + convertSnapshots, _ := self.Params.GetArray("convert_snapshots") + for i := 0; i < len(convertSnapshots); i++ { + snapshot_id, _ := convertSnapshots[i].GetString() + iSnapshot, err := models.SnapshotManager.FetchById(snapshot_id) + if err != nil { + log.Errorf("OnCleanUpSnapshots Fetch snapshot by id(%s) error:%s", snapshot_id, err.Error()) + continue + } + snapshot := iSnapshot.(*models.SSnapshot) + models.SnapshotManager.TableSpec().Update(snapshot, func() error { + snapshot.OutOfChain = true + return nil + }) + } + deleteSnapshots, _ := self.Params.GetArray("delete_snapshots") + for i := 0; i < len(deleteSnapshots); i++ { + snapshot_id, _ := convertSnapshots[i].GetString() + iSnapshot, err := models.SnapshotManager.FetchById(snapshot_id) + if err != nil { + log.Errorf("OnCleanUpSnapshots Fetch snapshot by id(%s) error:%s", snapshot_id, err.Error()) + continue + } + snapshot := iSnapshot.(*models.SSnapshot) + snapshot.RealDelete(ctx, self.UserCred) + } + self.SetStageComplete(ctx, nil) +} + +func (self *DiskCleanUpSnapshotsTask) OnCleanUpSnapshotsFailed(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(disk, db.ACT_DISK_CLEAN_UP_SNAPSHOTS_FAIL, data.String(), self.UserCred) + self.SetStageFailed(ctx, data.String()) +} diff --git a/pkg/compute/tasks/guest_disk_snapshot_task.go b/pkg/compute/tasks/guest_disk_snapshot_task.go index f2e40ab790..f5b31831ba 100644 --- a/pkg/compute/tasks/guest_disk_snapshot_task.go +++ b/pkg/compute/tasks/guest_disk_snapshot_task.go @@ -138,6 +138,7 @@ func (self *SnapshotDeleteTask) StartReloadDisk(ctx context.Context, snapshot *m } func (self *SnapshotDeleteTask) StartDeleteSnapshot(ctx context.Context, snapshot *models.SSnapshot, guest *models.SGuest) { + snapshot.SetStatus(self.UserCred, models.SNAPSHOT_DELETING, "On SnapshotDeleteTask StartDeleteSnapshot") convertSnapshot, err := models.SnapshotManager.GetConvertSnapshot(snapshot) if err != nil { self.TaskFailed(ctx, snapshot, err.Error()) @@ -183,21 +184,21 @@ func (self *SnapshotDeleteTask) OnDeleteSnapshot(ctx context.Context, snapshot * log.Infof("OnDeleteSnapshot with no deleted") return } + snapshot.SetStatus(self.UserCred, models.SNAPSHOT_READY, "OnDeleteSnapshot") if snapshot.OutOfChain { snapshot.RealDelete(ctx, self.UserCred) self.TaskComplete(ctx, snapshot, nil) } else { - guest, err := snapshot.GetGuest() - if err != nil { - self.SetStageFailed(ctx, err.Error()) - return - } + guest, _ := snapshot.GetGuest() var FakeDelete = false if snapshot.CreatedBy == models.MANUAL && snapshot.FakeDeleted == false { FakeDelete = true } if FakeDelete { - snapshot.FakeDelete() + models.SnapshotManager.TableSpec().Update(snapshot, func() error { + snapshot.OutOfChain = true + return nil + }) } else { snapshot.RealDelete(ctx, self.UserCred) } @@ -207,7 +208,7 @@ func (self *SnapshotDeleteTask) OnDeleteSnapshot(ctx context.Context, snapshot * } func (self *SnapshotDeleteTask) OnDeleteSnapshotFailed(ctx context.Context, snapshot *models.SSnapshot, data jsonutils.JSONObject) { - self.SetStageFailed(ctx, data.String()) + self.TaskFailed(ctx, snapshot, data.String()) } func (self *SnapshotDeleteTask) OnReloadDiskSnapshot(ctx context.Context, snapshot *models.SSnapshot, data jsonutils.JSONObject) { @@ -242,6 +243,9 @@ func (self *SnapshotDeleteTask) TaskComplete(ctx context.Context, snapshot *mode } func (self *SnapshotDeleteTask) TaskFailed(ctx context.Context, snapshot *models.SSnapshot, reason string) { + if snapshot.Status == models.SNAPSHOT_DELETING { + snapshot.SetStatus(self.UserCred, models.SNAPSHOT_READY, "On SnapshotDeleteTask TaskFailed") + } self.SetStageFailed(ctx, reason) guest, err := snapshot.GetGuest() if err != nil { @@ -251,6 +255,8 @@ func (self *SnapshotDeleteTask) TaskFailed(ctx context.Context, snapshot *models guest.StartSyncstatus(ctx, self.UserCred, "") } +/***************************** Batch Snapshots Delete Task *****************************/ + type BatchSnapshotsDeleteTask struct { taskman.STask } diff --git a/pkg/mcclient/modules/mod_snapshots.go b/pkg/mcclient/modules/mod_snapshots.go index f90546554e..8def8e7e3f 100644 --- a/pkg/mcclient/modules/mod_snapshots.go +++ b/pkg/mcclient/modules/mod_snapshots.go @@ -8,7 +8,7 @@ func init() { Snapshots = NewComputeManager("snapshot", "snapshots", []string{"ID", "Name", "Size", "Status", "Disk_id", "Guest_id", "Created_at"}, - []string{"Storage_id", "Create_by", "Location"}) + []string{"Storage_id", "Create_by", "Location", "Out_of_chain"}) registerCompute(&Snapshots) }