diff --git a/cmd/climc/shell/compute/backup.go b/cmd/climc/shell/compute/backup.go index d2b611a312..9d740b43c0 100644 --- a/cmd/climc/shell/compute/backup.go +++ b/cmd/climc/shell/compute/backup.go @@ -34,7 +34,7 @@ func init() { dbCmd := shell.NewResourceCmd(&modules.DiskBackups) dbCmd.List(&compute.DiskBackupListOptions{}) dbCmd.Show(&compute.DiskBackupIdOptions{}) - dbCmd.Delete(&compute.DiskBackupIdOptions{}) + dbCmd.DeleteWithParam(&compute.DiskBackupDeleteOptions{}) dbCmd.Create(&compute.DiskBackupCreateOptions{}) dbCmd.Perform("recovery", &compute.DiskBackupRecoveryOptions{}) dbCmd.Perform("syncstatus", &compute.DiskBackupSyncstatusOptions{}) @@ -42,7 +42,7 @@ func init() { ibCmd := shell.NewResourceCmd(&modules.InstanceBackups) ibCmd.List(&compute.InstanceBackupListOptions{}) ibCmd.Show(&compute.InstanceBackupIdOptions{}) - ibCmd.Delete(&compute.InstanceBackupIdOptions{}) + ibCmd.DeleteWithParam(&compute.InstanceBackupDeleteOptions{}) ibCmd.Perform("recovery", &compute.InstanceBackupRecoveryOptions{}) ibCmd.Perform("pack", &compute.InstanceBackupPackOptions{}) ibCmd.PerformClass("create-from-package", &compute.InstanceBackupManagerCreateFromPackageOptions{}) diff --git a/cmd/climc/shell/helper.go b/cmd/climc/shell/helper.go index e71f450fce..a655b4b5aa 100644 --- a/cmd/climc/shell/helper.go +++ b/cmd/climc/shell/helper.go @@ -362,6 +362,32 @@ func (cmd ResourceCmd) Delete(args IDeleteOpt) { cmd.RunWithDesc("delete", fmt.Sprintf("Delete %s", man.GetKeyword()), args, callback) } +type IDeleteWithParamOpt interface { + IDeleteOpt + QueryParams() (jsonutils.JSONObject, error) +} + +func (cmd ResourceCmd) DeleteWithParam(args IDeleteWithParamOpt) { + man := cmd.manager + callback := func(s *mcclient.ClientSession, args IDeleteWithParamOpt) error { + queryParams, err := args.QueryParams() + if err != nil { + return err + } + params, err := args.Params() + if err != nil { + return err + } + ret, err := man.(modulebase.Manager).DeleteWithParam(s, args.GetId(), queryParams, params) + if err != nil { + return err + } + PrintObject(ret) + return nil + } + cmd.RunWithDesc("delete", fmt.Sprintf("Delete %s", man.GetKeyword()), args, callback) +} + type IWithDescOpt interface { Description() string } diff --git a/pkg/apis/compute/backup.go b/pkg/apis/compute/backup.go index 7f4cc6904c..2a66600087 100644 --- a/pkg/apis/compute/backup.go +++ b/pkg/apis/compute/backup.go @@ -44,6 +44,10 @@ const ( BACKUP_NOT_EXIST = "not_exist" ) +const ( + BackupStorageOffline = "backup storage offline" +) + type BackupStorageCreateInput struct { apis.EnabledStatusInfrasResourceBaseCreateInput diff --git a/pkg/compute/models/backup.go b/pkg/compute/models/backup.go index 25c31172b4..08460083cf 100644 --- a/pkg/compute/models/backup.go +++ b/pkg/compute/models/backup.go @@ -16,6 +16,7 @@ package models import ( "context" + "database/sql" "reflect" "time" @@ -221,10 +222,23 @@ func (dm *SDiskBackupManager) ValidateCreateData(ctx context.Context, userCred m if disk.Status != api.DISK_READY { return input, httperrors.NewInvalidStatusError("disk %s status is not %s", disk.Name, api.DISK_READY) } - _, err = validators.ValidateModel(userCred, BackupStorageManager, &input.BackupStorageId) + ibs, err := BackupStorageManager.FetchByIdOrName(userCred, input.BackupStorageId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2(BackupStorageManager.Keyword(), input.BackupStorageId) + } + if errors.Cause(err) == sqlchemy.ErrDuplicateEntry { + return input, httperrors.NewDuplicateResourceError(BackupStorageManager.Keyword(), input.BackupStorageId) + } + return input, httperrors.NewGeneralError(err) + } if err != nil { return input, err } + bs := ibs.(*SBackupStorage) + if bs.Status != api.BACKUPSTORAGE_STATUS_ONLINE { + return input, httperrors.NewForbiddenError("can't backup guest to backup storage with status %s", bs.Status) + } storage, err := disk.GetStorage() if err != nil { return input, errors.Wrapf(err, "unable to get storage of disk %s", disk.GetId()) @@ -356,13 +370,18 @@ func (self *SDiskBackup) RealDelete(ctx context.Context, userCred mcclient.Token } func (self *SDiskBackup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - return self.StartBackupDeleteTask(ctx, userCred, "") + forceDelete := jsonutils.QueryBoolean(query, "force", false) + return self.StartBackupDeleteTask(ctx, userCred, "", forceDelete) } -func (self *SDiskBackup) StartBackupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { +func (self *SDiskBackup) StartBackupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, forceDelete bool) error { self.SetStatus(userCred, api.BACKUP_STATUS_DELETING, "") log.Infof("start to delete diskbackup %s and set deleting", self.GetId()) - task, err := taskman.TaskManager.NewTask(ctx, "DiskBackupDeleteTask", self, userCred, nil, parentTaskId, "", nil) + params := jsonutils.NewDict() + if forceDelete { + params.Set("force_delete", jsonutils.JSONTrue) + } + task, err := taskman.TaskManager.NewTask(ctx, "DiskBackupDeleteTask", self, userCred, params, parentTaskId, "", nil) if err != nil { return err } else { diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index a2fe2f7660..d79dd27a9a 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -4748,9 +4748,19 @@ func (self *SGuest) PerformInstanceBackup(ctx context.Context, userCred mcclient if backupStorageId == "" { return nil, httperrors.NewMissingParameterError("backup_storage_id") } - _, err = BackupStorageManager.FetchById(backupStorageId) - if err == sql.ErrNoRows { - return nil, httperrors.NewInputParameterError("unkown backup_storage_id %s", backupStorageId) + ibs, err := BackupStorageManager.FetchByIdOrName(userCred, backupStorageId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(BackupStorageManager.Keyword(), backupStorageId) + } + if errors.Cause(err) == sqlchemy.ErrDuplicateEntry { + return nil, httperrors.NewDuplicateResourceError(BackupStorageManager.Keyword(), backupStorageId) + } + return nil, httperrors.NewGeneralError(err) + } + bs := ibs.(*SBackupStorage) + if bs.Status != api.BACKUPSTORAGE_STATUS_ONLINE { + return nil, httperrors.NewForbiddenError("can't backup guest to backup storage with status %s", bs.Status) } instanceBackup, err := InstanceBackupManager.CreateInstanceBackup(ctx, userCred, self, name, backupStorageId) if err != nil { diff --git a/pkg/compute/models/instance_backup.go b/pkg/compute/models/instance_backup.go index b55b29d3c9..57f963f7ac 100644 --- a/pkg/compute/models/instance_backup.go +++ b/pkg/compute/models/instance_backup.go @@ -376,14 +376,17 @@ func (self *SInstanceBackup) ValidateDeleteCondition(ctx context.Context, info j func (self *SInstanceBackup) CustomizeDelete( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - - return self.StartInstanceBackupDeleteTask(ctx, userCred, "") + forceDelete := jsonutils.QueryBoolean(query, "force", false) + return self.StartInstanceBackupDeleteTask(ctx, userCred, "", forceDelete) } func (self *SInstanceBackup) StartInstanceBackupDeleteTask( - ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - - task, err := taskman.TaskManager.NewTask(ctx, "InstanceBackupDeleteTask", self, userCred, nil, parentTaskId, "", nil) + ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, forceDelete bool) error { + params := jsonutils.NewDict() + if forceDelete { + params.Set("force_delete", jsonutils.JSONTrue) + } + task, err := taskman.TaskManager.NewTask(ctx, "InstanceBackupDeleteTask", self, userCred, params, parentTaskId, "", nil) if err != nil { log.Errorf("%s", err) return err diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index 84c74c125d..1794dd864e 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -1056,7 +1056,8 @@ func (self *SKVMRegionDriver) RequestDeleteInstanceBackup(ctx context.Context, i params := jsonutils.NewDict() params.Set("del_backup_id", jsonutils.NewString(backups[0].Id)) task.SetStage("OnKvmDiskBackupDelete", params) - err = backups[0].StartBackupDeleteTask(ctx, task.GetUserCred(), task.GetTaskId()) + forceDelete := jsonutils.QueryBoolean(task.GetParams(), "force_delete", false) + err = backups[0].StartBackupDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), forceDelete) if err != nil { return err } diff --git a/pkg/compute/tasks/disk_backup_delete_task.go b/pkg/compute/tasks/disk_backup_delete_task.go index d5ab5d75e4..d111ac8857 100644 --- a/pkg/compute/tasks/disk_backup_delete_task.go +++ b/pkg/compute/tasks/disk_backup_delete_task.go @@ -16,8 +16,10 @@ package tasks import ( "context" + "strings" "yunion.io/x/jsonutils" + "yunion.io/x/log" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -64,5 +66,15 @@ func (self *DiskBackupDeleteTask) OnDelete(ctx context.Context, backup *models.S } func (self *DiskBackupDeleteTask) OnDeleteFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) { - self.taskFailed(ctx, backup, data) + log.Infof("params: %s", self.Params) + log.Infof("OnDeleteFailed data: %s", data) + if forceDelete := jsonutils.QueryBoolean(self.Params, "force_delete", false); !forceDelete { + self.taskFailed(ctx, backup, data) + } + reason, _ := data.GetString("__reason__") + if !strings.Contains(reason, api.BackupStorageOffline) { + self.taskFailed(ctx, backup, data) + } + log.Infof("delete backup %s failed, force delete", backup.GetId()) + self.taskSuccess(ctx, backup, nil) } diff --git a/pkg/hostman/storageman/backupstorage/backup_storage_nfs.go b/pkg/hostman/storageman/backupstorage/backup_storage_nfs.go index 53b54b4d2e..5df3f31c97 100644 --- a/pkg/hostman/storageman/backupstorage/backup_storage_nfs.go +++ b/pkg/hostman/storageman/backupstorage/backup_storage_nfs.go @@ -37,7 +37,7 @@ import ( const BackupStoragePath = "/opt/cloud/workspace/backupstorage" -var ErrorBackupStorageOffline error = errors.Error("backup storage offline") +var ErrorBackupStorageOffline error = errors.Error(api.BackupStorageOffline) type SNFSBackupStorage struct { BackupStorageId string diff --git a/pkg/mcclient/options/compute/backup.go b/pkg/mcclient/options/compute/backup.go index cb802080d2..bf789a167b 100644 --- a/pkg/mcclient/options/compute/backup.go +++ b/pkg/mcclient/options/compute/backup.go @@ -43,6 +43,15 @@ func (opts *DiskBackupIdOptions) Params() (jsonutils.JSONObject, error) { return nil, nil } +type DiskBackupDeleteOptions struct { + DiskBackupIdOptions + Force bool `help:"force delete"` +} + +func (opts *DiskBackupDeleteOptions) QueryParams() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type DiskBackupCreateOptions struct { options.BaseCreateOptions DISKID string `help:"disk id" json:"disk_id"` @@ -121,7 +130,7 @@ func (opts *InstanceBackupListOptions) Params() (jsonutils.JSONObject, error) { } type InstanceBackupIdOptions struct { - ID string `help:"instance backup id"` + ID string `help:"instance backup id" json:"-"` } func (opts *InstanceBackupIdOptions) GetId() string { @@ -132,6 +141,15 @@ func (opts *InstanceBackupIdOptions) Params() (jsonutils.JSONObject, error) { return nil, nil } +type InstanceBackupDeleteOptions struct { + InstanceBackupIdOptions + Force bool `help:"force delete"` +} + +func (opts *InstanceBackupDeleteOptions) QueryParams() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type InstanceBackupRecoveryOptions struct { DiskBackupIdOptions Name string `help:"server name" json:"name"`