Merge pull request #13239 from rainzm/vmbackup/init

Instance/Disk Backup Support
This commit is contained in:
Zexi Li
2022-01-24 18:13:38 +08:00
committed by GitHub
52 changed files with 3446 additions and 22 deletions
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compute
import (
"yunion.io/x/onecloud/cmd/climc/shell"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/mcclient/options/compute"
)
func init() {
bsCmd := shell.NewResourceCmd(&modules.BackupStorages)
bsCmd.List(&compute.BackupStorageListOptions{})
bsCmd.Show(&compute.BackupStorageIdOptions{})
bsCmd.Create(&compute.BackupStorageCreateOptions{})
bsCmd.Delete(&compute.BackupStorageIdOptions{})
dbCmd := shell.NewResourceCmd(&modules.DiskBackups)
dbCmd.List(&compute.DiskBackupListOptions{})
dbCmd.Show(&compute.DiskBackupIdOptions{})
dbCmd.Delete(&compute.DiskBackupIdOptions{})
dbCmd.Create(&compute.DiskBackupCreateOptions{})
dbCmd.Perform("recovery", &compute.DiskBackupRecoveryOptions{})
dbCmd.Perform("syncstatus", &compute.DiskBackupSyncstatusOptions{})
ibCmd := shell.NewResourceCmd(&modules.InstanceBackups)
ibCmd.List(&compute.InstanceBackupListOptions{})
ibCmd.Show(&compute.InstanceBackupIdOptions{})
ibCmd.Delete(&compute.InstanceBackupIdOptions{})
ibCmd.Perform("recovery", &compute.InstanceBackupRecoveryOptions{})
}
+14
View File
@@ -742,6 +742,20 @@ func init() {
printObject(result)
return nil
})
type ServerCreateBackup struct {
ID string `help:"ID or name of VM" json:"-"`
BACKUP string `help:"Instance backup name" json:"name"`
BACKUPSTORAGEID string `help:"backup storage id" json:"backup_storage_id"`
}
R(&ServerCreateBackup{}, "instance-backup-create", "create instance backup", func(s *mcclient.ClientSession, opts *ServerCreateBackup) error {
params := jsonutils.Marshal(opts)
result, err := modules.Servers.PerformAction(s, opts.ID, "instance-backup", params)
if err != nil {
return err
}
printObject(result)
return nil
})
type ServerSnapshotAndClone struct {
ID string `help:"ID or name of VM" json:"-"`
+7
View File
@@ -107,6 +107,8 @@ type DiskConfig struct {
// requried: false
SnapshotId string `json:"snapshot_id"`
BackupId string `json:"backup_id"`
// 磁盘类型
// enum: sys, data, swap
DiskType string `json:"disk_type"`
@@ -495,6 +497,11 @@ type ServerCreateInput struct {
// required: false
InstanceSnapshotId string `json:"instance_snapshot_id,omitempty"`
// 使用主机备份创建虚拟机, 主机快照不会重置密码及秘钥信息
// 使用主机备份创建的虚拟机将沿用之前的密码秘钥及安全组信息
// required: false
InstanceBackupId string `json:"instance_backup_id,omitempty"`
// 安全组Id, 此参数会和secgroups参数合并
SecgroupId string `json:"secgrp_id"`
// 安全组Id列表
+121
View File
@@ -0,0 +1,121 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compute
import "yunion.io/x/onecloud/pkg/apis"
const (
BACKUPSTORAGE_TYPE_NFS = "nfs"
BACKUPSTORAGE_STATUS_ONLINE = "online"
BACKUP_STATUS_CREATING = "creating"
BACKUP_STATUS_CREATE_FAILED = "create_failed"
BACKUP_STATUS_SNAPSHOT = "snapshot"
BACKUP_STATUS_SNAPSHOT_FAILED = "snapshot_failed"
BACKUP_STATUS_SAVING = "saving"
BACKUP_STATUS_SAVE_FAILED = "save_failed"
BACKUP_STATUS_CLEANUP_SNAPSHOT = "clean_snapshot"
BACKUP_STATUS_CLEANUP_SNAPSHOT_FAILED = "clean_snapshot_failed"
BACKUP_STATUS_DELETING = "deleting"
BACKUP_STATUS_DELETE_FAILED = "delete_failed"
BACKUP_STATUS_READY = "ready"
BACKUP_STATUS_RECOVERY = "recovery"
BACKUP_STATUS_RECOVERY_FAILED = "recovery_failed"
BACKUP_STATUS_UNKNOWN = "unknown"
BACKUP_EXIST = "exist"
BACKUP_NOT_EXIST = "not_exist"
)
type BackupStorageCreateInput struct {
apis.EnabledStatusInfrasResourceBaseCreateInput
// description: storage type
// enum: nfs
StorageType string `json:"storage_type"`
// description: host of nfs, storage_type 为 nfs 时, 此参数必传
// example: 192.168.222.2
NfsHost string `json:"nfs_host"`
// description: shared dir of nfs, storage_type 为 nfs 时, 此参数必传
// example: /nfs_root/
NfsSharedDir string `json:"nfs_shared_dir"`
// description: Capacity size in MB
CapacityMb int `json:"capacity_mb"`
}
type BackupStorageAccessInfo struct {
AccessUrl string
}
type BackupStorageDetails struct {
apis.EnabledStatusInfrasResourceBaseDetails
NfsHost string
NfsSharedDir string
}
type BackupStorageListInput struct {
apis.EnabledStatusInfrasResourceBaseListInput
}
type DiskBackupListInput struct {
apis.VirtualResourceListInput
ManagedResourceListInput
RegionalFilterListInput
apis.MultiArchResourceBaseListInput
// description: disk id
DiskId string `json:"disk_id"`
// description: backup storage id
BackupStorageId string `json:"backup_storage_id"`
// description: 是否为主机备份的一部分
IsInstanceBackup *bool `json:"is_instance_backup"`
}
type DiskBackupDetails struct {
apis.VirtualResourceDetails
ManagedResourceInfo
CloudregionResourceInfo
// description: disk name
DiskName string `json:"disk_name"`
// description: backup storage name
BackupStorageName string `json:"backup_storage_name"`
// description: 是否是子备份
IsSubBackup bool `json:"is_sub_backup"`
}
type DiskBackupCreateInput struct {
apis.VirtualResourceCreateInput
// description: disk id
DiskId string `json:"disk_id"`
// description: backup storage id
BackupStorageId string `json:"back_storage_id"`
// swagger: ignore
CloudregionId string `json:"cloudregion_id"`
// swagger:ignore
ManagerId string `json:"manager_id"`
}
type DiskBackupRecoveryInput struct {
// description: name of disk
Name string
}
type DiskBackupSyncstatusInput struct {
}
+1
View File
@@ -19,4 +19,5 @@ import "yunion.io/x/onecloud/pkg/apis"
const (
SERVICE_TYPE = apis.SERVICE_TYPE_REGION
SERVICE_VERSION = "v2"
SERVER_TYPE_V2 = apis.SERVICE_TYPE_REGION + "_" + SERVICE_VERSION
)
+10
View File
@@ -17,6 +17,7 @@ package compute
import (
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/util/fileutils"
"yunion.io/x/onecloud/pkg/apis"
@@ -277,6 +278,9 @@ type DiskAllocateInput struct {
BackingDiskId string
SnapshotId string
BackupId string
Backup *DiskAllocateFromBackupInput
SnapshotUrl string
SnapshotOutOfChain bool
Protocol string
@@ -287,3 +291,9 @@ type DiskAllocateInput struct {
HostIp string
Datastore vcenter.SVCenterAccessInfo
}
type DiskAllocateFromBackupInput struct {
BackupId string
BackupStorageId string
BackupStorageAccessInfo *jsonutils.JSONDict
}
+3
View File
@@ -96,6 +96,9 @@ const (
VM_SNAPSHOT_RESET_FAILED = "snapshot_reset_failed"
VM_SNAPSHOT_AND_CLONE_FAILED = "clone_from_snapshot_failed"
VM_START_INSTANCE_BACKUP = "start_instance_backup"
VM_INSTANCE_BACKUP_FAILED = "instance_backup_failed"
VM_SYNC_CONFIG = "sync_config"
VM_SYNC_FAIL = "sync_fail"
+46
View File
@@ -0,0 +1,46 @@
package compute
import "yunion.io/x/onecloud/pkg/apis"
const (
INSTANCE_BACKUP_STATUS_CREATING = "creating"
INSTANCE_BACKUP_STATUS_CREATE_FAILED = "create_failed"
INSTANCE_BACKUP_STATUS_DELETING = "deleting"
INSTANCE_BACKUP_STATUS_DELETE_FAILED = "delete_failed"
INSTANCE_BACKUP_STATUS_RECOVERY = "recovery"
INSTANCE_BACKUP_STATUS_RECOVERY_FAILED = "recovery_failed"
INSTANCE_BACKUP_STATUS_READY = "ready"
)
type InstanceBackupListInput struct {
apis.VirtualResourceListInput
apis.MultiArchResourceBaseListInput
ManagedResourceListInput
ServerFilterListInput
// 操作系统类型
OsType []string `json:"os_type"`
}
type InstanceBackupDetails struct {
apis.VirtualResourceDetails
ManagedResourceInfo
// 云主机状态
GuestStatus string `json:"guest_status"`
// 云主机名称
Guest string `json:"guest"`
// 存储类型
BackupStorageName string `json:"backup_storage_name"`
// 主机快照大小
Size int `json:"size"`
}
type InstanceBackupRecoveryInput struct {
// description: name of guest
Name string
}
+1 -2
View File
@@ -19,7 +19,6 @@ import (
"database/sql"
"fmt"
"net/http"
"path/filepath"
"reflect"
"runtime/debug"
"strconv"
@@ -831,7 +830,7 @@ func (task *STask) GetTaskRequestHeader() http.Header {
header := mcclient.GetTokenHeaders(userCred)
header.Set(mcclient.TASK_ID, task.GetTaskId())
if len(serviceUrl) > 0 {
notifyUrl := filepath.Join(serviceUrl, "tasks", task.GetTaskId())
notifyUrl := fmt.Sprintf("%s/tasks/%s", serviceUrl, task.GetTaskId())
header.Set(mcclient.TASK_NOTIFY_URL, notifyUrl)
}
return header
+431
View File
@@ -0,0 +1,431 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"reflect"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/sqlchemy"
api "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/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SDiskBackupManager struct {
db.SVirtualResourceBaseManager
SDiskResourceBaseManager
SManagedResourceBaseManager
SCloudregionResourceBaseManager
db.SMultiArchResourceBaseManager
}
type SDiskBackup struct {
db.SVirtualResourceBase
SManagedResourceBase
SCloudregionResourceBase `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"`
db.SMultiArchResourceBase
DiskId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user" index:"true"`
BackupStorageId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user" index:"true"`
StorageId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
// 备份大小
SizeMb int `nullable:"false" list:"user" create:"optional"`
DiskSizeMb int `nullable:"false" list:"user" create:"optional"`
DiskType string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional"`
// 操作系统类型
OsType string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional"`
DiskConfig *SBackupDiskConfig `ignore:"true"`
}
var DiskBackupManager *SDiskBackupManager
func init() {
gotypes.RegisterSerializable(reflect.TypeOf(&SBackupDiskConfig{}), func() gotypes.ISerializable {
return &SBackupDiskConfig{}
})
DiskBackupManager = &SDiskBackupManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SDiskBackup{},
"diskbackups_tbl",
"diskbackup",
"diskbackups",
),
}
DiskBackupManager.SetVirtualObject(DiskBackupManager)
}
type SBackupDiskConfig struct {
api.DiskConfig
Name string
}
func (dc *SBackupDiskConfig) String() string {
return jsonutils.Marshal(dc).String()
}
func (dc *SBackupDiskConfig) IsZero() bool {
return dc == nil
}
func (dm *SDiskBackupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.DiskBackupListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = dm.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter")
}
q, err = dm.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, input.ManagedResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemFilter")
}
q, err = dm.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, input.RegionalFilterListInput)
if err != nil {
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemFilter")
}
q, err = dm.SMultiArchResourceBaseManager.ListItemFilter(ctx, q, userCred, input.MultiArchResourceBaseListInput)
if err != nil {
return nil, errors.Wrap(err, "SMultiArchResourceBaseManager.ListItemFilter")
}
if input.DiskId != "" {
q = q.Equals("disk_id", input.DiskId)
}
if input.BackupStorageId != "" {
q = q.Equals("backup_storage_id", input.BackupStorageId)
}
if input.IsInstanceBackup != nil {
insjsq := InstanceBackupJointManager.Query().SubQuery()
if !*input.IsInstanceBackup {
q = q.LeftJoin(insjsq, sqlchemy.Equals(q.Field("id"), insjsq.Field("disk_backup_id"))).
Filter(sqlchemy.IsNull(insjsq.Field("disk_backup_id")))
} else {
q = q.Join(insjsq, sqlchemy.Equals(q.Field("id"), insjsq.Field("disk_backup_id")))
}
}
return q, nil
}
func (self *SDiskBackup) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
if self.Status == api.BACKUP_STATUS_DELETING {
return httperrors.NewBadRequestError("Cannot delete disk backup in status %s", self.Status)
}
is, err := InstanceBackupJointManager.IsSubBackup(self.Id)
if err != nil {
return err
}
if is {
return httperrors.NewBadRequestError("disk backup referenced by instance backup")
}
return nil
}
func (dm *SDiskBackupManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.DiskBackupDetails {
rows := make([]api.DiskBackupDetails, len(objs))
virtRows := dm.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
manRows := dm.SManagedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
regionRows := dm.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i].VirtualResourceDetails = virtRows[i]
rows[i].ManagedResourceInfo = manRows[i]
rows[i].CloudregionResourceInfo = regionRows[i]
rows[i] = objs[i].(*SDiskBackup).getMoreDetails(rows[i])
}
return rows
}
func (db *SDiskBackup) getMoreDetails(out api.DiskBackupDetails) api.DiskBackupDetails {
disk, _ := db.GetDisk()
if disk != nil {
out.DiskName = disk.Name
}
backupStorage, _ := db.GetBackupStorage()
if backupStorage != nil {
out.BackupStorageName = backupStorage.GetName()
}
if t, _ := InstanceBackupJointManager.IsSubBackup(db.Id); t {
out.IsSubBackup = true
}
return out
}
func (db *SDiskBackup) GetDisk() (*SDisk, error) {
iDisk, err := DiskManager.FetchById(db.DiskId)
if err != nil {
return nil, err
}
disk := iDisk.(*SDisk)
return disk, nil
}
func (db *SDiskBackup) GetStorage() (*SStorage, error) {
iStorage, err := StorageManager.FetchById(db.StorageId)
if err != nil {
return nil, err
}
return iStorage.(*SStorage), nil
}
func (db *SDiskBackup) GetBackupStorage() (*SBackupStorage, error) {
ibs, err := BackupStorageManager.FetchById(db.BackupStorageId)
if err != nil {
return nil, err
}
return ibs.(*SBackupStorage), nil
}
func (db *SDiskBackup) GetRegionDriver() (IRegionDriver, error) {
cloudRegion, err := db.GetRegion()
if err != nil {
return nil, errors.Wrap(err, "db.GetRegion")
}
return cloudRegion.GetDriver(), nil
}
func (dm *SDiskBackupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.DiskBackupCreateInput) (api.DiskBackupCreateInput, error) {
if len(input.DiskId) == 0 {
return input, httperrors.NewMissingParameterError("disk_id")
}
if len(input.BackupStorageId) == 0 {
return input, httperrors.NewMissingParameterError("backup_storage_id")
}
// check disk
_disk, err := validators.ValidateModel(userCred, DiskManager, &input.DiskId)
if err != nil {
return input, err
}
disk := _disk.(*SDisk)
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)
if err != nil {
return input, err
}
storage, err := disk.GetStorage()
if err != nil {
return input, errors.Wrapf(err, "unable to get storage of disk %s", disk.GetId())
}
input.ManagerId = storage.ManagerId
region, err := storage.GetRegion()
if err != nil {
return input, err
}
input.CloudregionId = region.Id
return input, nil
}
func (db *SDiskBackup) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
err := db.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
if err != nil {
return err
}
diskObj, err := DiskManager.FetchById(db.DiskId)
if err != nil {
return errors.Wrap(err, "DiskManager.FetchById")
}
disk := diskObj.(*SDisk)
db.DiskConfig = &SBackupDiskConfig{
DiskConfig: *disk.ToDiskConfig(),
Name: disk.GetName(),
}
db.DiskType = disk.DiskType
db.DiskSizeMb = disk.DiskSize
db.OsArch = disk.OsArch
db.StorageId = disk.StorageId
return nil
}
func (db *SDiskBackup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
db.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
db.StartBackupCreateTask(ctx, userCred, nil, "")
}
func (db *SDiskBackup) StartBackupCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "DiskBackupCreateTask", db, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (manager *SDiskBackupManager) ListItemExportKeys(ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
keys stringutils2.SSortedStrings,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, err
}
if keys.Contains("disk") {
q, err = manager.SDiskResourceBaseManager.ListItemExportKeys(ctx, q, userCred, stringutils2.NewSortedStrings([]string{"disk"}))
if err != nil {
return nil, errors.Wrap(err, "SDiskResourceBaseManager.ListItemExportKeys")
}
}
if keys.ContainsAny(manager.SStorageResourceBaseManager.GetExportKeys()...) {
q, err = manager.SStorageResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, errors.Wrap(err, "SStorageResourceBaseManager.ListItemExportKeys")
}
}
return q, nil
}
func (manager *SDiskBackupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (manager *SDiskBackupManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.DiskBackupListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.OrderByExtraFields")
}
q, err = manager.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SManagedResourceBaseManager.OrderByExtraFields")
}
q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput)
if err != nil {
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields")
}
return q, nil
}
func (self *SDiskBackup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
return nil
}
func (self *SDiskBackup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DeleteModel(ctx, userCred, self)
}
func (self *SDiskBackup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return self.StartBackupDeleteTask(ctx, userCred, "")
}
func (self *SDiskBackup) StartBackupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) 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)
if err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SDiskBackup) PerformRecovery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskBackupRecoveryInput) (jsonutils.JSONObject, error) {
return nil, self.StartRecoveryTask(ctx, userCred, "", input.Name)
}
func (self *SDiskBackup) StartRecoveryTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, diskName string) error {
self.SetStatus(userCred, api.BACKUP_STATUS_RECOVERY, "")
var params *jsonutils.JSONDict
if diskName != "" {
params = jsonutils.NewDict()
params.Set("disk_name", jsonutils.NewString(diskName))
}
task, err := taskman.TaskManager.NewTask(ctx, "DiskBackupRecoveryTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (manager *SDiskBackupManager) CreateBackup(ctx context.Context, owner mcclient.IIdentityProvider, diskId, backupStorageId, name string) (*SDiskBackup, error) {
iDisk, err := DiskManager.FetchById(diskId)
if err != nil {
return nil, err
}
disk := iDisk.(*SDisk)
storage, _ := disk.GetStorage()
backup := &SDiskBackup{}
backup.SetModelManager(manager, backup)
backup.ProjectId = owner.GetProjectId()
backup.DomainId = owner.GetProjectDomainId()
backup.DiskId = disk.Id
backup.DiskConfig = &SBackupDiskConfig{
DiskConfig: *disk.ToDiskConfig(),
Name: disk.GetName(),
}
backup.DiskType = disk.DiskType
backup.DiskSizeMb = disk.DiskSize
backup.OsArch = disk.OsArch
backup.StorageId = disk.StorageId
backup.ManagerId = storage.ManagerId
if cloudregion, _ := storage.GetRegion(); cloudregion != nil {
backup.CloudregionId = cloudregion.GetId()
}
backup.BackupStorageId = backupStorageId
backup.Name = name
backup.Status = api.BACKUP_STATUS_CREATING
err = DiskBackupManager.TableSpec().Insert(ctx, backup)
if err != nil {
return nil, err
}
return backup, nil
}
func (self *SDiskBackup) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskBackupSyncstatusInput) (jsonutils.JSONObject, error) {
var openTask = true
count, err := taskman.TaskManager.QueryTasksOfObject(self, time.Now().Add(-3*time.Minute), &openTask).CountWithError()
if err != nil {
return nil, err
}
if count > 0 {
return nil, httperrors.NewBadRequestError("Snapshot has %d task active, can't sync status", count)
}
return nil, StartResourceSyncStatusTask(ctx, userCred, self, "DiskBackupSyncstatusTask", "")
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"reflect"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
type SBackupStorageManager struct {
db.SEnabledStatusInfrasResourceBaseManager
}
type SBackupStorage struct {
db.SEnabledStatusInfrasResourceBase
AccessInfo *SBackupStorageAccessInfo
StorageType string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"domain_required"`
CapacityMb int `nullable:"false" list:"user" update:"domain" create:"domain_required"`
}
var BackupStorageManager *SBackupStorageManager
func init() {
gotypes.RegisterSerializable(reflect.TypeOf(&SBackupStorageAccessInfo{}), func() gotypes.ISerializable {
return &SBackupStorageAccessInfo{}
})
BackupStorageManager = &SBackupStorageManager{
SEnabledStatusInfrasResourceBaseManager: db.NewEnabledStatusInfrasResourceBaseManager(
SBackupStorage{},
"backupstorages_tbl",
"backupstorage",
"backupstorages",
),
}
BackupStorageManager.SetVirtualObject(BackupStorageManager)
}
type SBackupStorageAccessInfo struct {
NfsHost string `json:"nfs_host"`
NfsSharedDir string `json:"nfs_shared_dir"`
}
func (ba *SBackupStorageAccessInfo) String() string {
return jsonutils.Marshal(ba).String()
}
func (ba *SBackupStorageAccessInfo) IsZero() bool {
return ba == nil
}
func (bs *SBackupStorageManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.BackupStorageCreateInput) (api.BackupStorageCreateInput, error) {
var err error
input.EnabledStatusInfrasResourceBaseCreateInput, err = bs.SEnabledStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusInfrasResourceBaseCreateInput)
if err != nil {
return input, err
}
if !utils.IsInStringArray(input.StorageType, []string{api.BACKUPSTORAGE_TYPE_NFS}) {
return input, httperrors.NewInputParameterError("Invalid storage type %s", input.StorageType)
}
switch input.StorageType {
case api.BACKUPSTORAGE_TYPE_NFS:
if input.NfsHost == "" {
return input, httperrors.NewInputParameterError("nfs_host is required when storage type is nfs")
}
if input.NfsSharedDir == "" {
return input, httperrors.NewInputParameterError("nfs_shared_dir is required when storage type is nfs")
}
}
return input, nil
}
func (bs *SBackupStorage) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
bs.SetEnabled(true)
nfsHost, _ := data.GetString("nfs_host")
nfsSharedDir, _ := data.GetString("nfs_shared_dir")
bs.Status = api.BACKUPSTORAGE_STATUS_ONLINE
bs.AccessInfo = &SBackupStorageAccessInfo{
NfsHost: nfsHost,
NfsSharedDir: nfsSharedDir,
}
return bs.SEnabledStatusInfrasResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
}
func (bs *SBackupStorage) BackupCount() (int, error) {
return DiskBackupManager.Query().Equals("backup_storage_id", bs.GetId()).CountWithError()
}
func (bs *SBackupStorage) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
cnt, err := bs.BackupCount()
if err != nil {
return httperrors.NewInternalServerError("BackupCount fail %s", err)
}
if cnt > 0 {
return httperrors.NewNotEmptyError("storage has backup")
}
return bs.SEnabledStatusInfrasResourceBase.ValidateDeleteCondition(ctx, nil)
}
func (bs *SBackupStorage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
bs.SEnabledStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
}
func (bs *SBackupStorage) getMoreDetails(ctx context.Context, out api.BackupStorageDetails) api.BackupStorageDetails {
out.NfsHost = bs.AccessInfo.NfsHost
out.NfsSharedDir = bs.AccessInfo.NfsSharedDir
return out
}
func (bm *SBackupStorageManager) FetchCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, objs []interface{}, fields stringutils2.SSortedStrings, isList bool) []api.BackupStorageDetails {
rows := make([]api.BackupStorageDetails, len(objs))
esiRows := bm.SEnabledStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i].EnabledStatusInfrasResourceBaseDetails = esiRows[i]
bs := objs[i].(*SBackupStorage)
rows[i] = bs.getMoreDetails(ctx, rows[i])
}
return rows
}
func (bm *SBackupStorageManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.BackupStorageListInput) (*sqlchemy.SQuery, error) {
var err error
q, err = bm.SEnabledStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, input.EnabledStatusInfrasResourceBaseListInput)
if err != nil {
return nil, err
}
return q, nil
}
+50
View File
@@ -104,6 +104,8 @@ type SDisk struct {
TemplateId string `width:"256" charset:"ascii" nullable:"true" list:"user" json:"template_id"`
// 快照Id
SnapshotId string `width:"256" charset:"ascii" nullable:"true" list:"user" json:"snapshot_id"`
// 备份Id
BackupId string `width:"256" charset:"ascii" nullable:"true" list:"user" json:"backup_id"`
// 文件系统
FsFormat string `width:"32" charset:"ascii" nullable:"true" list:"user" json:"fs_format"`
@@ -687,6 +689,23 @@ func (self *SDisk) GetManualSnapshotCount() (int, error) {
Equals("created_by", api.SNAPSHOT_MANUAL).CountWithError()
}
func (self *SDisk) getDiskAllocateFromBackupInput(ctx context.Context, backupId string) (*api.DiskAllocateFromBackupInput, error) {
ibackup, err := DiskBackupManager.FetchById(backupId)
if err != nil {
return nil, errors.Wrapf(err, "unable to get backup %s", backupId)
}
backup := ibackup.(*SDiskBackup)
bs, err := backup.GetBackupStorage()
if err != nil {
return nil, errors.Wrapf(err, "unable to get backupstorage of backup %s", backupId)
}
return &api.DiskAllocateFromBackupInput{
BackupId: backupId,
BackupStorageId: bs.GetId(),
BackupStorageAccessInfo: jsonutils.Marshal(bs.AccessInfo).(*jsonutils.JSONDict),
}, nil
}
func (self *SDisk) StartAllocate(ctx context.Context, host *SHost, storage *SStorage, taskId string, userCred mcclient.TokenCredential, rebuild bool, snapshot string, task taskman.ITask) error {
log.Infof("Allocating disk on host %s ...", host.GetName())
@@ -698,6 +717,13 @@ func (self *SDisk) StartAllocate(ctx context.Context, host *SHost, storage *SSto
DiskSizeMb: self.DiskSize,
SnapshotId: snapshot,
}
if self.BackupId != "" {
allocateInput, err := self.getDiskAllocateFromBackupInput(ctx, self.BackupId)
if err != nil {
return errors.Wrap(err, "unable to getDiskAllocateFromBackupInput")
}
input.Backup = allocateInput
}
if len(snapshot) > 0 {
if utils.IsInStringArray(storage.StorageType, api.FIEL_STORAGE) {
SnapshotManager.AddRefCount(self.SnapshotId, 1)
@@ -1718,6 +1744,11 @@ func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info
return nil, errors.Wrap(err, "fillDiskConfigByImage")
}
}
if info.BackupId != "" {
if err := fillDiskConfigByBackup(ctx, userCred, info, info.BackupId); err != nil {
return nil, errors.Wrap(err, "fillDiskConfigByBackup")
}
}
// XXX: do not set default disk size here, set it by each hypervisor driver
// if len(diskConfig.ImageId) > 0 && diskConfig.SizeMb == 0 {
// diskConfig.SizeMb = options.Options.DefaultDiskSize // MB
@@ -1758,6 +1789,22 @@ func fillDiskConfigBySnapshot(userCred mcclient.TokenCredential, diskConfig *api
return nil
}
func fillDiskConfigByBackup(ctx context.Context, userCred mcclient.TokenCredential, diskConfig *api.DiskConfig, backupId string) error {
iBakcup, err := DiskBackupManager.FetchByIdOrName(userCred, backupId)
if err != nil {
if err == sql.ErrNoRows {
return httperrors.NewNotFoundError("Backup %s not found", backupId)
}
return err
}
backup := iBakcup.(*SDiskBackup)
if diskConfig.DiskType == "" {
diskConfig.DiskType = backup.DiskType
}
diskConfig.BackupId = backup.GetId()
return nil
}
func fillDiskConfigByImage(ctx context.Context, userCred mcclient.TokenCredential,
diskConfig *api.DiskConfig, imageId string) error {
if userCred == nil {
@@ -1880,6 +1927,9 @@ func (self *SDisk) fetchDiskInfo(diskConfig *api.DiskConfig) {
} else if len(diskConfig.SnapshotId) > 0 {
self.SnapshotId = diskConfig.SnapshotId
self.DiskType = diskConfig.DiskType
} else if len(diskConfig.BackupId) > 0 {
self.BackupId = diskConfig.BackupId
self.DiskType = diskConfig.DiskType
}
if len(diskConfig.Fs) > 0 {
self.FsFormat = diskConfig.Fs
+71
View File
@@ -4573,6 +4573,45 @@ func (self *SGuest) validateCreateInstanceSnapshot(
return pendingUsage, nil
}
func (self *SGuest) validateCreateInstanceBackup(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) error {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM}) {
return httperrors.NewBadRequestError("guest hypervisor %s can't create instance snapshot", self.Hypervisor)
}
if len(self.BackupHostId) > 0 {
return httperrors.NewBadRequestError("Can't do instance snapshot with backup guest")
}
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status)
}
var name string
ownerId := self.GetOwnerId()
dataDict := data.(*jsonutils.JSONDict)
nameHint, err := dataDict.GetString("generate_name")
if err == nil {
name, err = db.GenerateName(ctx, InstanceBackupManager, ownerId, nameHint)
if err != nil {
return err
}
dataDict.Set("name", jsonutils.NewString(name))
} else if name, err = dataDict.GetString("name"); err != nil {
return httperrors.NewMissingParameterError("name")
}
err = db.NewNameValidator(InstanceBackupManager, ownerId, name, nil)
if err != nil {
return err
}
return nil
}
// 1. validate guest status, guest hypervisor
// 2. validate every disk manual snapshot count
// 3. validate snapshot quota with disk count
@@ -4601,6 +4640,33 @@ func (self *SGuest) PerformInstanceSnapshot(
return nil, nil
}
func (self *SGuest) PerformInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
lockman.LockClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
defer lockman.ReleaseClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
err := self.validateCreateInstanceBackup(ctx, userCred, query, data)
if err != nil {
return nil, err
}
name, _ := data.GetString("name")
backupStorageId, _ := data.GetString("backup_storage_id")
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)
}
instanceBackup, err := InstanceBackupManager.CreateInstanceBackup(ctx, userCred, self, name, backupStorageId)
if err != nil {
return nil, httperrors.NewInternalServerError("create instance backup failed: %s", err)
}
err = self.InstanceCreateBackup(ctx, userCred, instanceBackup)
if err != nil {
return nil, httperrors.NewInternalServerError("start create backup task failed: %s", err)
}
return nil, nil
}
func (self *SGuest) InstaceCreateSnapshot(
ctx context.Context,
userCred mcclient.TokenCredential,
@@ -4611,6 +4677,11 @@ func (self *SGuest) InstaceCreateSnapshot(
return instanceSnapshot.StartCreateInstanceSnapshotTask(ctx, userCred, pendingUsage, "")
}
func (self *SGuest) InstanceCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, instanceBackup *SInstanceBackup) error {
self.SetStatus(userCred, api.VM_START_INSTANCE_BACKUP, "instance backup")
return instanceBackup.StartCreateInstanceBackupTask(ctx, userCred, "")
}
func (self *SGuest) PerformInstanceSnapshotReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerResetInput) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY {
+37
View File
@@ -1084,6 +1084,25 @@ func parseInstanceSnapshot(input *api.ServerCreateInput) (*api.ServerCreateInput
return input, nil
}
func parseInstanceBackup(input *api.ServerCreateInput) (*api.ServerCreateInput, error) {
ispi, err := InstanceBackupManager.FetchByIdOrName(nil, input.InstanceBackupId)
if err == sql.ErrNoRows {
return nil, httperrors.NewBadRequestError("can't find instance backup %s", input.InstanceBackupId)
}
if err != nil {
return nil, httperrors.NewInternalServerError("fetch instance backup error %s", err)
}
isp := ispi.(*SInstanceBackup)
if isp.Status != api.INSTANCE_BACKUP_STATUS_READY && isp.Status != api.INSTANCE_BACKUP_STATUS_RECOVERY {
return nil, httperrors.NewBadRequestError("Instance backup not ready")
}
input, err = isp.ToInstanceCreateInput(input)
if len(input.Disks) == 0 {
return nil, httperrors.NewInputParameterError("there are no disks in this instance backup, try another one")
}
return input, nil
}
func (manager *SGuestManager) validateCreateData(
ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
query jsonutils.JSONObject, data *jsonutils.JSONDict) (*api.ServerCreateInput, error) {
@@ -1115,6 +1134,24 @@ func (manager *SGuestManager) validateCreateData(
input.InstanceType = inputInstaceType
}
if len(input.InstanceBackupId) > 0 {
inputMem := input.VmemSize
inputCpu := input.VcpuCount
inputInstaceType := input.InstanceType
input, err = parseInstanceBackup(input)
if err != nil {
return nil, err
}
// keep input cpu mem flavor
if inputMem > 0 {
input.VmemSize = inputMem
}
if inputMem > 0 {
input.VcpuCount = inputCpu
}
input.InstanceType = inputInstaceType
}
resetPassword := true
if input.ResetPassword != nil {
resetPassword = *input.ResetPassword
+11
View File
@@ -1365,6 +1365,17 @@ func (self *SHost) GetMasterHostwire() *SHostwire {
return &hw
}
func (self *SHostManager) GetEnabledKvmHost() (*SHost, error) {
hostq := HostManager.Query().IsTrue("enabled").Equals("host_status", api.HOST_ONLINE).In("host_type", []string{api.HOST_TYPE_HYPERVISOR, api.HOST_TYPE_KVM})
host := SHost{}
err := hostq.First(&host)
if err != nil {
return nil, err
}
host.SetModelManager(HostManager, &host)
return &host, nil
}
func (self *SHost) GetMasterWire() *SWire {
wires := WireManager.Query().SubQuery()
hostwires := HostwireManager.Query().SubQuery()
+438
View File
@@ -0,0 +1,438 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
api "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/taskman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
func init() {
InstanceBackupManager = &SInstanceBackupManager{
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
SInstanceBackup{},
"instancebackups_tbl",
"instancebackup",
"instancebackups",
),
}
InstanceBackupManager.SetVirtualObject(InstanceBackupManager)
}
type SInstanceBackup struct {
db.SVirtualResourceBase
SManagedResourceBase
SCloudregionResourceBase
db.SMultiArchResourceBase
BackupStorageId string `width:"36" charset:"ascii" nullable:"true" create:"required" list:"user" index:"true"`
GuestId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
// 云主机配置
ServerConfig jsonutils.JSONObject `nullable:"true" list:"user"`
// 云主机标签
ServerMetadata jsonutils.JSONObject `nullable:"true" list:"user"`
// 安全组
SecGroups jsonutils.JSONObject `nullable:"true" list:"user"`
// 秘钥Id
KeypairId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
// 操作系统类型
OsType string `width:"36" charset:"ascii" nullable:"true" list:"user"`
// 套餐名称
InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"`
// 主机备份容量和
SizeMb int `nullable:"false" list:"user"`
}
type SInstanceBackupManager struct {
db.SVirtualResourceBaseManager
SManagedResourceBaseManager
SCloudregionResourceBaseManager
db.SMultiArchResourceBaseManager
}
var InstanceBackupManager *SInstanceBackupManager
func (manager *SInstanceBackupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.InstanceBackupListInput) (*sqlchemy.SQuery, error) {
q, err := manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter")
}
q, err = manager.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemFilter")
}
q, err = manager.SMultiArchResourceBaseManager.ListItemFilter(ctx, q, userCred, query.MultiArchResourceBaseListInput)
if err != nil {
return nil, errors.Wrap(err, "SMultiArchResourceBaseManager.ListItemFilter")
}
guestStr := query.ServerId
if len(guestStr) > 0 {
guestObj, err := GuestManager.FetchByIdOrName(userCred, guestStr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2("guests", guestStr)
} else {
return nil, httperrors.NewGeneralError(err)
}
}
q = q.Equals("guest_id", guestObj.GetId())
}
if len(query.OsType) > 0 {
q = q.In("os_type", query.OsType)
}
return q, nil
}
func (manager *SInstanceBackupManager) OrderByExtraFields(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.InstanceBackupListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBaseManager.OrderByExtraFields")
}
q, err = manager.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput)
if err != nil {
return nil, errors.Wrap(err, "SManagedResourceBaseManager.OrderByExtraFields")
}
return q, nil
}
func (manager *SInstanceBackupManager) ListItemExportKeys(ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
keys stringutils2.SSortedStrings,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
if err != nil {
return nil, err
}
return q, nil
}
func (manager *SInstanceBackupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
q, err = manager.SManagedResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (self *SInstanceBackup) GetGuest() (*SGuest, error) {
if len(self.GuestId) == 0 {
return nil, errors.ErrNotFound
}
guest := GuestManager.FetchGuestById(self.GuestId)
if guest == nil {
return nil, errors.ErrNotFound
}
return guest, nil
}
func (self *SInstanceBackup) GetBackupStorage() (*SBackupStorage, error) {
ibs, err := BackupStorageManager.FetchById(self.BackupStorageId)
if err != nil {
return nil, err
}
return ibs.(*SBackupStorage), nil
}
func (self *SInstanceBackup) getMoreDetails(userCred mcclient.TokenCredential, out api.InstanceBackupDetails) api.InstanceBackupDetails {
guest := GuestManager.FetchGuestById(self.GuestId)
if guest != nil {
out.Guest = guest.Name
out.GuestStatus = guest.Status
}
backupStorage, _ := self.GetBackupStorage()
if backupStorage != nil {
out.BackupStorageName = backupStorage.GetName()
}
return out
}
func (manager *SInstanceBackupManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.InstanceBackupDetails {
rows := make([]api.InstanceBackupDetails, len(objs))
virtRows := manager.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
manRows := manager.SManagedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.InstanceBackupDetails{
VirtualResourceDetails: virtRows[i],
ManagedResourceInfo: manRows[i],
}
rows[i] = objs[i].(*SInstanceBackup).getMoreDetails(userCred, rows[i])
}
return rows
}
func (self *SInstanceBackup) StartCreateInstanceBackupTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
self.SetStatus(userCred, api.INSTANCE_BACKUP_STATUS_CREATING, "")
if task, err := taskman.TaskManager.NewTask(ctx, "InstanceBackupCreateTask", self, userCred, nil, parentTaskId, "", nil); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (manager *SInstanceBackupManager) fillInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, instanceBackup *SInstanceBackup) {
instanceBackup.SetModelManager(manager, instanceBackup)
instanceBackup.ProjectId = guest.ProjectId
instanceBackup.DomainId = guest.DomainId
instanceBackup.GuestId = guest.Id
guestSchedInput := guest.ToSchedDesc()
host, _ := guest.GetHost()
instanceBackup.ManagerId = host.ManagerId
zone, _ := host.GetZone()
instanceBackup.CloudregionId = zone.CloudregionId
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 = ""
}
instanceBackup.ServerConfig = jsonutils.Marshal(guestSchedInput.ServerConfig)
if len(guest.KeypairId) > 0 {
instanceBackup.KeypairId = guest.KeypairId
}
serverMetadata := jsonutils.NewDict()
if loginAccount := guest.GetMetadata(ctx, "login_account", nil); len(loginAccount) > 0 {
loginKey := guest.GetMetadata(ctx, "login_key", nil)
if len(guest.KeypairId) == 0 && len(loginKey) > 0 {
passwd, e := utils.DescryptAESBase64(guest.Id, loginKey)
if e == nil {
serverMetadata.Set("login_account", jsonutils.NewString(loginAccount))
serverMetadata.Set("passwd", jsonutils.NewString(passwd))
}
} else {
serverMetadata.Set("login_key", jsonutils.NewString(loginKey))
serverMetadata.Set("login_account", jsonutils.NewString(loginAccount))
}
}
if osArch := guest.GetMetadata(ctx, "os_arch", nil); len(osArch) > 0 {
serverMetadata.Set("os_arch", jsonutils.NewString(osArch))
}
if osDist := guest.GetMetadata(ctx, "os_distribution", nil); len(osDist) > 0 {
serverMetadata.Set("os_distribution", jsonutils.NewString(osDist))
}
if osName := guest.GetMetadata(ctx, "os_name", nil); len(osName) > 0 {
serverMetadata.Set("os_name", jsonutils.NewString(osName))
}
if osVersion := guest.GetMetadata(ctx, "os_version", nil); len(osVersion) > 0 {
serverMetadata.Set("os_version", jsonutils.NewString(osVersion))
}
secs, _ := guest.GetSecgroups()
if len(secs) > 0 {
secIds := make([]string, len(secs))
for i := 0; i < len(secs); i++ {
secIds[i] = secs[i].Id
}
instanceBackup.SecGroups = jsonutils.Marshal(secIds)
}
instanceBackup.OsType = guest.OsType
instanceBackup.OsArch = guest.OsArch
instanceBackup.ServerMetadata = serverMetadata
instanceBackup.InstanceType = guest.InstanceType
}
func (manager *SInstanceBackupManager) CreateInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, name, backupStorageId string) (*SInstanceBackup, error) {
instanceBackup := &SInstanceBackup{}
instanceBackup.SetModelManager(manager, instanceBackup)
instanceBackup.Name = name
instanceBackup.BackupStorageId = backupStorageId
manager.fillInstanceBackup(ctx, userCred, guest, instanceBackup)
// compute size of instanceBackup
//instanceBackup.SizeMb = guest.getDiskSize()
err := manager.TableSpec().Insert(ctx, instanceBackup)
if err != nil {
return nil, err
}
return instanceBackup, nil
}
func (self *SInstanceBackup) ToInstanceCreateInput(sourceInput *api.ServerCreateInput) (*api.ServerCreateInput, error) {
serverConfig := new(schedapi.ServerConfig)
if err := self.ServerConfig.Unmarshal(serverConfig); err != nil {
return nil, errors.Wrap(err, "unmarshal sched input")
}
isjs := make([]SInstanceBackupJoint, 0)
err := InstanceBackupJointManager.Query().Equals("instance_backup_id", self.Id).Asc("disk_index").All(&isjs)
if err != nil {
return nil, errors.Wrap(err, "fetch instance backups")
}
for i := 0; i < len(serverConfig.Disks); i++ {
index := serverConfig.Disks[i].Index
if index < len(isjs) {
serverConfig.Disks[i].BackupId = isjs[index].DiskBackupId
serverConfig.Disks[i].ImageId = ""
serverConfig.Disks[i].SnapshotId = ""
}
}
sourceInput.Disks = serverConfig.Disks
if sourceInput.VmemSize == 0 {
sourceInput.VmemSize = serverConfig.Memory
}
if sourceInput.VcpuCount == 0 {
sourceInput.VcpuCount = serverConfig.Ncpu
}
if len(self.KeypairId) > 0 {
sourceInput.KeypairId = self.KeypairId
}
if self.SecGroups != nil {
secGroups := make([]string, 0)
inputSecgs := make([]string, 0)
self.SecGroups.Unmarshal(&secGroups)
for i := 0; i < len(secGroups); i++ {
_, err := SecurityGroupManager.FetchSecgroupById(secGroups[i])
if err == nil {
inputSecgs = append(inputSecgs, secGroups[i])
}
}
sourceInput.Secgroups = inputSecgs
}
sourceInput.OsType = self.OsType
sourceInput.InstanceType = self.InstanceType
if len(sourceInput.Networks) == 0 {
sourceInput.Networks = serverConfig.Networks
}
return sourceInput, nil
}
func (self *SInstanceBackup) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error {
if self.Status == api.INSTANCE_SNAPSHOT_START_DELETE || self.Status == api.INSTANCE_SNAPSHOT_RESET {
return httperrors.NewForbiddenError("can't delete instance snapshot with wrong status")
}
return nil
}
func (self *SInstanceBackup) CustomizeDelete(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) error {
return self.StartInstanceBackupDeleteTask(ctx, userCred, "")
}
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)
if err != nil {
log.Errorf("%s", err)
return err
}
self.SetStatus(userCred, api.INSTANCE_BACKUP_STATUS_DELETING, "InstanceBackupDeleteTask")
task.ScheduleRun(nil)
return nil
}
func (self *SInstanceBackup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DeleteModel(ctx, userCred, self)
}
func (self *SInstanceBackup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
return nil
}
func (self *SInstanceBackup) GetRegionDriver() IRegionDriver {
provider := self.GetProviderName()
return GetRegionDriver(provider)
}
func (self *SInstanceBackup) GetBackups() ([]SDiskBackup, error) {
isjq := InstanceBackupJointManager.Query().SubQuery()
backups := make([]SDiskBackup, 0)
dq := DiskBackupManager.Query()
q := dq.Join(isjq, sqlchemy.Equals(dq.Field("id"), isjq.Field("disk_backup_id"))).Filter(
sqlchemy.Equals(isjq.Field("instance_backup_id"), self.GetId())).Asc(isjq.Field("disk_index"))
err := db.FetchModelObjects(DiskBackupManager, q, &backups)
if err != nil {
return nil, errors.Wrap(err, "db.FetchModelObjects")
}
return backups, nil
}
func (self *SInstanceBackup) PerformRecovery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.InstanceBackupRecoveryInput) (jsonutils.JSONObject, error) {
return nil, self.StartRecoveryTask(ctx, userCred, "", input.Name)
}
func (self *SInstanceBackup) StartRecoveryTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, serverName string) error {
self.SetStatus(userCred, api.INSTANCE_BACKUP_STATUS_RECOVERY, "")
var params *jsonutils.JSONDict
if serverName != "" {
params = jsonutils.NewDict()
params.Set("server_name", jsonutils.NewString(serverName))
}
task, err := taskman.TaskManager.NewTask(ctx, "InstanceBackupRecoveryTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
@@ -0,0 +1,84 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
func init() {
db.InitManager(func() {
InstanceBackupJointManager = &SInstanceBackupJointManager{
SVirtualJointResourceBaseManager: db.NewVirtualJointResourceBaseManager(
SInstanceBackupJoint{},
"instancebackupjoints_tbl",
"instancebackupjoint",
"instancebackupjoints",
InstanceBackupManager,
DiskBackupManager,
),
}
InstanceBackupJointManager.SetVirtualObject(InstanceBackupJointManager)
})
}
// +onecloud:swagger-gen-ignore
type SInstanceBackupJoint struct {
db.SVirtualJointResourceBase
InstanceBackupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
DiskBackupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"`
DiskIndex int8 `nullable:"false" default:"0" list:"user" create:"required"`
}
// +onecloud:swagger-gen-ignore
type SInstanceBackupJointManager struct {
db.SVirtualJointResourceBaseManager
}
func (manager *SInstanceBackupJointManager) GetMasterFieldName() string {
return "instance_backup_id"
}
func (manager *SInstanceBackupJointManager) GetSlaveFieldName() string {
return "disk_backup_id"
}
var InstanceBackupJointManager *SInstanceBackupJointManager
func (manager *SInstanceBackupJointManager) CreateJoint(ctx context.Context, instanceBackupId, backupId string, diskIndex int8) error {
instanceBackupJoint := &SInstanceBackupJoint{}
instanceBackupJoint.SetModelManager(manager, instanceBackupJoint)
instanceBackupJoint.InstanceBackupId = instanceBackupId
instanceBackupJoint.DiskBackupId = backupId
instanceBackupJoint.DiskIndex = diskIndex
return manager.TableSpec().Insert(ctx, instanceBackupJoint)
}
func (manager *SInstanceBackupJointManager) IsSubBackup(backupId string) (bool, error) {
count, err := manager.Query().Equals("disk_backup_id", backupId).CountWithError()
if err != nil {
return false, err
}
return count > 0, nil
}
func (self *SInstanceBackupJoint) Detach(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DetachJoint(ctx, userCred, self)
}
@@ -18,6 +18,7 @@ import (
"context"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
func init() {
@@ -77,3 +78,7 @@ func (manager *SInstanceSnapshotJointManager) IsSubSnapshot(snapshotId string) (
}
return count > 0, nil
}
func (self *SInstanceSnapshotJoint) Detach(ctx context.Context, userCred mcclient.TokenCredential) error {
return db.DetachJoint(ctx, userCred, self)
}
+6
View File
@@ -117,6 +117,12 @@ type IRegionDriver interface {
OnDiskReset(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, snapshot *SSnapshot, data jsonutils.JSONObject) error
OnSnapshotDelete(ctx context.Context, snapshot *SSnapshot, task taskman.ITask, data jsonutils.JSONObject) error
RequestSyncDiskBackupStatus(ctx context.Context, userCred mcclient.TokenCredential, backup *SDiskBackup, task taskman.ITask) error
RequestCreateBackup(ctx context.Context, backup *SDiskBackup, snapshotId string, task taskman.ITask) error
RequestDeleteBackup(ctx context.Context, backup *SDiskBackup, task taskman.ITask) error
RequestCreateInstanceBackup(ctx context.Context, guest *SGuest, ib *SInstanceBackup, task taskman.ITask, params *jsonutils.JSONDict) error
RequestDeleteInstanceBackup(ctx context.Context, ib *SInstanceBackup, task taskman.ITask) error
RequestCreateInstanceSnapshot(ctx context.Context, guest *SGuest, isp *SInstanceSnapshot, task taskman.ITask, params *jsonutils.JSONDict) error
RequestDeleteInstanceSnapshot(ctx context.Context, isp *SInstanceSnapshot, task taskman.ITask) error
RequestResetToInstanceSnapshot(ctx context.Context, guest *SGuest, isp *SInstanceSnapshot, task taskman.ITask, params *jsonutils.JSONDict) error
+16
View File
@@ -473,6 +473,22 @@ func (self *SBaseRegionDriver) RequestCreateNetwork(ctx context.Context, userCre
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestCreateNetwork")
}
func (self *SBaseRegionDriver) RequestCreateBackup(ctx context.Context, backup *models.SDiskBackup, snapshotId string, task taskman.ITask) error {
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestCreateBackup")
}
func (self *SBaseRegionDriver) RequestDeleteBackup(ctx context.Context, backup *models.SDiskBackup, task taskman.ITask) error {
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestDeleteBackup")
}
func (self *SBaseRegionDriver) RequestCreateInstanceBackup(ctx context.Context, guest *models.SGuest, ib *models.SInstanceBackup, task taskman.ITask, params *jsonutils.JSONDict) error {
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestCreateInstanceBackup")
}
func (self *SBaseRegionDriver) RequestDeleteInstanceBackup(ctx context.Context, ib *models.SInstanceBackup, task taskman.ITask) error {
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestDeleteInstanceBackup")
}
func (self *SBaseRegionDriver) ValidateCreateCdnData(ctx context.Context, userCred mcclient.TokenCredential, input api.CDNDomainCreateInput) (api.CDNDomainCreateInput, error) {
return input, errors.Wrapf(cloudprovider.ErrNotImplemented, "ValidateCreateCdnData")
}
+160
View File
@@ -1024,6 +1024,28 @@ func (self *SKVMRegionDriver) RequestDeleteInstanceSnapshot(ctx context.Context,
return nil
}
func (self *SKVMRegionDriver) RequestDeleteInstanceBackup(ctx context.Context, ib *models.SInstanceBackup, task taskman.ITask) error {
backups, err := ib.GetBackups()
if err != nil {
return err
}
if len(backups) == 0 {
task.SetStage("OnInstanceBackupDelete", nil)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
return nil, nil
})
return nil
}
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())
if err != nil {
return err
}
return nil
}
func (self *SKVMRegionDriver) RequestResetToInstanceSnapshot(ctx context.Context, guest *models.SGuest, isp *models.SInstanceSnapshot, task taskman.ITask, params *jsonutils.JSONDict) error {
disks, _ := guest.GetGuestDisks()
diskIndexI64, err := params.Int("disk_index")
@@ -1232,6 +1254,83 @@ func (self *SKVMRegionDriver) RequestSyncDiskStatus(ctx context.Context, userCre
return nil
}
func (self *SKVMRegionDriver) RequestCreateInstanceBackup(ctx context.Context, guest *models.SGuest, ib *models.SInstanceBackup, task taskman.ITask, params *jsonutils.JSONDict) error {
disks, _ := guest.GetGuestDisks()
task.SetStage("OnKvmDisksSnapshot", params)
for i := range disks {
disk := disks[i]
backup, err := func() (*models.SDiskBackup, error) {
lockman.LockClass(ctx, models.DiskBackupManager, "name")
defer lockman.ReleaseClass(ctx, models.DiskBackupManager, "name")
diskBackupName, err := db.GenerateName(ctx, models.DiskBackupManager, task.GetUserCred(),
fmt.Sprintf("%s-%s", ib.Name, rand.String(8)))
if err != nil {
return nil, errors.Wrap(err, "Generate diskbackup name")
}
return models.DiskBackupManager.CreateBackup(ctx, task.GetUserCred(), disk.DiskId, ib.BackupStorageId, diskBackupName)
}()
if err != nil {
return err
}
err = models.InstanceBackupJointManager.CreateJoint(ctx, ib.Id, backup.Id, int8(i))
if err != nil {
return err
}
taskParams := jsonutils.NewDict()
taskParams.Set("only_snapshot", jsonutils.JSONTrue)
if err := backup.StartBackupCreateTask(ctx, task.GetUserCred(), taskParams, task.GetTaskId()); err != nil {
return err
}
}
return nil
}
func (self *SKVMRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, userCred mcclient.TokenCredential, backup *models.SDiskBackup, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
originStatus, _ := task.GetParams().GetString("origin_status")
if utils.IsInStringArray(originStatus, []string{api.BACKUP_STATUS_CREATING, api.BACKUP_STATUS_SNAPSHOT, api.BACKUP_STATUS_SAVING, api.BACKUP_STATUS_CLEANUP_SNAPSHOT, api.BACKUP_STATUS_DELETING}) {
return nil, backup.SetStatus(userCred, originStatus, "sync status")
}
backupStroage, err := backup.GetBackupStorage()
if err != nil {
return nil, errors.Wrap(err, "unable to get backupStorage")
}
storage, _ := backup.GetStorage()
var host *models.SHost
if storage != nil {
host = storage.GetMasterHost()
}
if host == nil {
host, err = models.HostManager.GetEnabledKvmHost()
if err != nil {
return nil, errors.Wrap(err, "unable to GetEnabledKvmHost")
}
}
log.Infof("host: %s, ManagerUri: %s", host.GetId(), host.ManagerUri)
url := fmt.Sprintf("%s/storages/sync-backup", host.ManagerUri)
body := jsonutils.NewDict()
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
header := task.GetTaskRequestHeader()
_, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
if err != nil {
return nil, err
}
var backupStatus string
status, _ := res.GetString("status")
if status == api.BACKUP_EXIST {
backupStatus = api.BACKUP_STATUS_READY
} else {
backupStatus = api.SNAPSHOT_UNKNOWN
}
return nil, backup.SetStatus(userCred, backupStatus, "sync status")
})
return nil
}
func (self *SKVMRegionDriver) RequestSyncSnapshotStatus(ctx context.Context, userCred mcclient.TokenCredential, snapshot *models.SSnapshot, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
storage := snapshot.GetStorage()
@@ -1417,6 +1516,67 @@ func (self *SKVMRegionDriver) GetMaxElasticcacheSecurityGroupCount() int {
return 0
}
func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *models.SDiskBackup, task taskman.ITask) error {
backupStroage, err := backup.GetBackupStorage()
if err != nil {
return errors.Wrap(err, "unable to get backupStorage")
}
storage, _ := backup.GetStorage()
var host *models.SHost
if storage != nil {
host = storage.GetMasterHost()
}
if host == nil {
host, err = models.HostManager.GetEnabledKvmHost()
if err != nil {
return errors.Wrap(err, "unable to GetEnabledKvmHost")
}
}
url := fmt.Sprintf("%s/storages/delete-backup", host.ManagerUri)
body := jsonutils.NewDict()
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
header := task.GetTaskRequestHeader()
_, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
if err != nil {
return errors.Wrap(err, "unable to backup")
}
return nil
}
func (self *SKVMRegionDriver) RequestCreateBackup(ctx context.Context, backup *models.SDiskBackup, snapshotId string, task taskman.ITask) error {
backupStroage, err := backup.GetBackupStorage()
if err != nil {
return errors.Wrap(err, "unable to get backupStorage")
}
disk, err := backup.GetDisk()
if err != nil {
return errors.Wrap(err, "unable to get disk")
}
guest := disk.GetGuest()
if guest == nil {
return errors.Wrap(err, "unable to get guest")
}
storage, err := disk.GetStorage()
if err != nil {
return errors.Wrap(err, "unable to get storage")
}
host, _ := guest.GetHost()
url := fmt.Sprintf("%s/disks/%s/backup/%s", host.ManagerUri, storage.Id, disk.Id)
body := jsonutils.NewDict()
body.Set("snapshot_id", jsonutils.NewString(snapshotId))
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
header := task.GetTaskRequestHeader()
_, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
if err != nil {
return errors.Wrap(err, "unable to backup")
}
return nil
}
func (self *SKVMRegionDriver) RequestAssociatEip(ctx context.Context, userCred mcclient.TokenCredential, eip *models.SElasticip, input api.ElasticipAssociateInput, obj db.IStatusStandaloneModel, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
if input.InstanceType == api.EIP_ASSOCIATE_TYPE_SERVER {
@@ -2861,6 +2861,10 @@ func (self *SManagedVirtualizationRegionDriver) RequestSyncDiskStatus(ctx contex
return nil
}
func (self *SManagedVirtualizationRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, userCred mcclient.TokenCredential, backup *models.SDiskBackup, task taskman.ITask) error {
return fmt.Errorf("Not Implement RequestSyncDiskBackupStatus")
}
func (self *SManagedVirtualizationRegionDriver) RequestSyncSnapshotStatus(ctx context.Context, userCred mcclient.TokenCredential, snapshot *models.SSnapshot, task taskman.ITask) error {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
iRegion, err := snapshot.GetISnapshotRegion()
+5
View File
@@ -233,6 +233,10 @@ func InitHandlers(app *appsrv.Application) {
models.KubeClusterManager,
models.KubeNodeManager,
models.KubeNodePoolManager,
models.BackupStorageManager,
models.DiskBackupManager,
models.InstanceBackupManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
@@ -265,6 +269,7 @@ func InitHandlers(app *appsrv.Application) {
models.DBInstanceSecgroupManager,
models.ElasticcachesecgroupManager,
models.InterVpcNetworkVpcManager,
models.InstanceBackupJointManager,
} {
db.RegisterModelManager(manager)
handler := db.NewJointModelHandler(manager)
+11 -1
View File
@@ -26,10 +26,12 @@ import (
_ "yunion.io/x/sqlchemy/backends"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/cloudcommon"
app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/elect"
"yunion.io/x/onecloud/pkg/cloudcommon/etcd"
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
@@ -43,6 +45,7 @@ import (
"yunion.io/x/onecloud/pkg/compute/tasks"
"yunion.io/x/onecloud/pkg/controller/autoscaling"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/multicloud/esxi"
_ "yunion.io/x/onecloud/pkg/multicloud/loader"
)
@@ -64,7 +67,14 @@ func StartService() {
})
common_options.StartOptionManager(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, api.SERVICE_VERSION, options.OnOptionsChange)
err := esxi.InitEsxiConfig(opts.EsxiOptions)
serviceUrl, err := auth.GetServiceURL(api.SERVER_TYPE_V2, opts.Region, "", identity.EndpointInterfaceInternal)
if err != nil {
log.Fatalf("unable to get service url: %v", err)
}
log.Infof("serviceUrl: %s", serviceUrl)
taskman.SetServiceUrl(serviceUrl)
err = esxi.InitEsxiConfig(opts.EsxiOptions)
if err != nil {
log.Fatalf("unable to init esxi configs: %v", err)
}
@@ -0,0 +1,202 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tasks
import (
"context"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"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/rand"
)
type DiskBackupCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DiskBackupCreateTask{})
}
func (self *DiskBackupCreateTask) taskFailed(ctx context.Context, backup *models.SDiskBackup, reason jsonutils.JSONObject, status string) {
snapshotId, _ := self.Params.GetString("snapshot_id")
if len(snapshotId) > 0 {
snapshotModel, err := models.SnapshotManager.FetchById(snapshotId)
if err != nil {
log.Errorf("unable to get snapshot %s: %s", snapshotId, err.Error())
} else {
err := snapshotModel.(*models.SSnapshot).RealDelete(ctx, self.UserCred)
if err != nil {
log.Errorf("unable to delete snapshot %s: %s", snapshotId, err.Error())
}
}
}
reasonStr, _ := reason.GetString()
backup.SetStatus(self.UserCred, status, reasonStr)
logclient.AddActionLogWithStartable(self, backup, logclient.ACT_CREATE, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *DiskBackupCreateTask) taksSuccess(ctx context.Context, backup *models.SDiskBackup, data *jsonutils.JSONDict) {
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_READY, "")
logclient.AddActionLogWithStartable(self, backup, logclient.ACT_CREATE, backup.GetShortDesc(ctx), self.UserCred, true)
notifyclient.EventNotify(ctx, self.UserCred, notifyclient.SEventNotifyParam{
Obj: backup,
Action: notifyclient.ActionCreate,
})
self.SetStageComplete(ctx, data)
}
func (self *DiskBackupCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
backup := obj.(*models.SDiskBackup)
if self.Params.Contains("snapshot_id") {
self.OnSnapshot(ctx, backup, nil)
return
}
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_SNAPSHOT, "")
snapshot, err := self.CreateSnapshot(ctx, backup)
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_SNAPSHOT_FAILED)
}
params := jsonutils.NewDict()
params.Set("snapshot_id", jsonutils.NewString(snapshot.GetId()))
self.SetStage("OnSnapshot", params)
err = snapshot.StartSnapshotCreateTask(ctx, self.UserCred, nil, self.GetId())
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_SNAPSHOT_FAILED)
}
}
func (self *DiskBackupCreateTask) OnSnapshot(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
snapshotId, _ := self.Params.GetString("snapshot_id")
if self.Params.Contains("only_snapshot") {
p := jsonutils.NewDict()
p.Set("snapshot_id", jsonutils.NewString(snapshotId))
p.Set("disk_backup_id", jsonutils.NewString(backup.GetId()))
self.taksSuccess(ctx, backup, p)
return
}
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_SAVING, "")
self.SetStage("OnSave", nil)
rd, err := backup.GetRegionDriver()
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_SAVE_FAILED)
return
}
if err := rd.RequestCreateBackup(ctx, backup, snapshotId, self); err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_SAVE_FAILED)
}
}
func (self *DiskBackupCreateTask) OnSnapshotFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
// remove snapshot
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_SNAPSHOT_FAILED)
}
func (self *DiskBackupCreateTask) OnSave(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
// cleanup snapshot
snapshotId, _ := self.Params.GetString("snapshot_id")
self.SetStage("OnCleanupSnapshot", nil)
snapshotModel, err := models.SnapshotManager.FetchById(snapshotId)
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_CLEANUP_SNAPSHOT_FAILED)
return
}
log.Infof("data from RequestCreateBackup: %s", data)
sizeMb, _ := data.Int("size_mb")
db.Update(backup, func() error {
backup.SizeMb = int(sizeMb)
return nil
})
snapshot := snapshotModel.(*models.SSnapshot)
err = snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.GetId())
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()), api.BACKUP_STATUS_CLEANUP_SNAPSHOT_FAILED)
return
}
}
func (self *DiskBackupCreateTask) OnSaveFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
snapshotId, _ := self.Params.GetString("snapshot_id")
snapshotModel, err := models.SnapshotManager.FetchById(snapshotId)
if err != nil {
log.Errorf("unable to cleanup snapshot: %s", err.Error())
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_SAVE_FAILED)
return
}
snapshot := snapshotModel.(*models.SSnapshot)
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_SAVE_FAILED)
err = snapshot.StartSnapshotDeleteTask(ctx, self.UserCred, false, self.GetId())
if err != nil {
log.Errorf("unable to cleanup snapshot: %s", err.Error())
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_SAVE_FAILED)
return
}
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_SAVE_FAILED)
}
func (self *DiskBackupCreateTask) OnCleanupSnapshot(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
snapshotId, _ := self.Params.GetString("snapshot_id")
snapshotModel, err := models.SnapshotManager.FetchById(snapshotId)
if err != nil {
log.Errorf("unable to get snapshot %s: %s", snapshotId, err.Error())
} else {
err := snapshotModel.(*models.SSnapshot).RealDelete(ctx, self.UserCred)
if err != nil {
log.Errorf("unable to delete snapshot %s: %s", snapshotId, err.Error())
}
}
self.taksSuccess(ctx, backup, nil)
}
func (self *DiskBackupCreateTask) OnCleanupSnapshotFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, backup, data, api.BACKUP_STATUS_CLEANUP_SNAPSHOT_FAILED)
}
func (self *DiskBackupCreateTask) CreateSnapshot(ctx context.Context, diskBackup *models.SDiskBackup) (*models.SSnapshot, error) {
disk, err := diskBackup.GetDisk()
if err != nil {
return nil, errors.Wrapf(err, "unable to getdisk of disk backup %s", disk.GetId())
}
guest := disk.GetGuest()
snapshot, err := func() (*models.SSnapshot, error) {
lockman.LockClass(ctx, models.SnapshotManager, "name")
defer lockman.ReleaseClass(ctx, models.SnapshotManager, "name")
snapshotName, err := db.GenerateName(ctx, models.SnapshotManager, self.GetUserCred(),
fmt.Sprintf("%s-%s", diskBackup.Name, rand.String(8)))
if err != nil {
return nil, errors.Wrap(err, "Generate snapshot name")
}
return models.SnapshotManager.CreateSnapshot(
ctx, self.GetUserCred(), api.SNAPSHOT_MANUAL, disk.GetId(),
guest.Id, "", snapshotName, -1)
}()
if err != nil {
return nil, errors.Wrapf(err, "unable to create snapshot of disk %s", disk.GetId())
}
return snapshot, nil
}
@@ -0,0 +1,68 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tasks
import (
"context"
"yunion.io/x/jsonutils"
api "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 DiskBackupDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DiskBackupDeleteTask{})
}
func (self *DiskBackupDeleteTask) taskFailed(ctx context.Context, backup *models.SDiskBackup, reason jsonutils.JSONObject) {
reasonStr, _ := reason.GetString()
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_DELETE_FAILED, reasonStr)
logclient.AddActionLogWithStartable(self, backup, logclient.ACT_DELETE, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *DiskBackupDeleteTask) taskSuccess(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
backup.RealDelete(ctx, self.UserCred)
self.SetStageComplete(ctx, nil)
}
func (self *DiskBackupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
backup := obj.(*models.SDiskBackup)
self.SetStage("OnDelete", nil)
rd, err := backup.GetRegionDriver()
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()))
return
}
if err := rd.RequestDeleteBackup(ctx, backup, self); err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()))
}
}
func (self *DiskBackupDeleteTask) OnDelete(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
self.taskSuccess(ctx, backup, nil)
}
func (self *DiskBackupDeleteTask) OnDeleteFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, backup, data)
}
@@ -0,0 +1,92 @@
package tasks
import (
"context"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
api "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/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
compute_modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type DiskBackupRecoveryTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DiskBackupRecoveryTask{})
}
func (self *DiskBackupRecoveryTask) taskFaild(ctx context.Context, backup *models.SDiskBackup, reason jsonutils.JSONObject) {
reasonStr, _ := reason.GetString()
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_RECOVERY_FAILED, reasonStr)
logclient.AddActionLogWithStartable(self, backup, logclient.ACT_RECOVERY, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *DiskBackupRecoveryTask) taskSuccess(ctx context.Context, backup *models.SDiskBackup, data *jsonutils.JSONDict) {
backup.SetStatus(self.UserCred, api.BACKUP_STATUS_READY, "")
logclient.AddActionLogWithStartable(self, backup, logclient.ACT_RECOVERY, nil, self.UserCred, true)
self.SetStageComplete(ctx, data)
}
func (self *DiskBackupRecoveryTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
backup := obj.(*models.SDiskBackup)
diskName, _ := self.Params.GetString("disk_name")
if diskName == "" {
diskName = backup.DiskConfig.Name
}
diskConfig := &backup.DiskConfig.DiskConfig
diskConfig.ImageId = ""
diskConfig.SnapshotId = ""
diskConfig.BackupId = backup.GetId()
input := api.DiskCreateInput{}
input.GenerateName = diskName
input.Description = fmt.Sprintf("recovery from backup %s", backup.GetName())
input.Hypervisor = api.HYPERVISOR_KVM
input.DiskConfig = diskConfig
taskHeader := self.GetTaskRequestHeader()
session := auth.GetSession(ctx, self.UserCred, "", "")
log.Infof("task_notify_url: %s\ntask_id: %s\n", taskHeader.Get(mcclient.TASK_NOTIFY_URL), taskHeader.Get(mcclient.TASK_ID))
session.Header.Set(mcclient.TASK_NOTIFY_URL, taskHeader.Get(mcclient.TASK_NOTIFY_URL))
session.Header.Set(mcclient.TASK_ID, taskHeader.Get(mcclient.TASK_ID))
diskData, err := compute_modules.Disks.Create(session, jsonutils.Marshal(input))
if err != nil {
self.taskFaild(ctx, backup, jsonutils.NewString(err.Error()))
return
}
diskId, _ := diskData.GetString("id")
params := jsonutils.NewDict()
params.Set("disk_id", jsonutils.NewString(diskId))
self.SetStage("OnCreateDisk", params)
}
func (self *DiskBackupRecoveryTask) OnCreateDisk(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
diskId, _ := self.Params.GetString("disk_id")
disk := models.DiskManager.FetchDiskById(diskId)
if disk == nil {
self.taskFaild(ctx, backup, jsonutils.NewString(fmt.Sprintf("disk %s disappeared", diskId)))
return
}
imageId := backup.DiskConfig.ImageId
snapshotId := backup.DiskConfig.SnapshotId
db.Update(disk, func() error {
disk.TemplateId = imageId
disk.SnapshotId = snapshotId
return nil
})
self.taskSuccess(ctx, backup, nil)
}
func (self *DiskBackupRecoveryTask) OnCreateDiskFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
self.taskFaild(ctx, backup, data)
}
@@ -0,0 +1,63 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tasks
import (
"context"
"yunion.io/x/jsonutils"
"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 DiskBackupSyncstatusTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(DiskBackupSyncstatusTask{})
}
func (self *DiskBackupSyncstatusTask) taskFailed(ctx context.Context, backup *models.SDiskBackup, err jsonutils.JSONObject) {
logclient.AddActionLogWithContext(ctx, backup, logclient.ACT_SYNC_STATUS, err, self.UserCred, false)
self.SetStageFailed(ctx, err)
}
func (self *DiskBackupSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
backup := obj.(*models.SDiskBackup)
self.SetStage("OnDiskBackupSyncStatus", nil)
rd, err := backup.GetRegionDriver()
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()))
return
}
err = rd.RequestSyncDiskBackupStatus(ctx, self.GetUserCred(), backup, self)
if err != nil {
self.taskFailed(ctx, backup, jsonutils.NewString(err.Error()))
return
}
}
func (self *DiskBackupSyncstatusTask) OnDiskBackupSyncStatus(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
self.SetStageComplete(ctx, nil)
}
func (self *DiskBackupSyncstatusTask) OnDiskBackupSyncStatusFailed(ctx context.Context, backup *models.SDiskBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, backup, data)
}
@@ -0,0 +1,134 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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"
)
type InstanceBackupCreateTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceBackupCreateTask{})
}
func (self *InstanceBackupCreateTask) taskFailed(ctx context.Context, ib *models.SInstanceBackup, guest *models.SGuest, reason jsonutils.JSONObject) {
if guest != nil {
guest.SetStatus(self.UserCred, compute.VM_INSTANCE_BACKUP_FAILED, reason.String())
}
reasonStr, _ := reason.GetString()
ib.SetStatus(self.UserCred, compute.INSTANCE_BACKUP_STATUS_CREATE_FAILED, reasonStr)
logclient.AddActionLogWithStartable(self, ib, logclient.ACT_CREATE, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceBackupCreateTask) taskSuccess(ctx context.Context, ib *models.SInstanceBackup) {
ib.SetStatus(self.UserCred, compute.INSTANCE_BACKUP_STATUS_READY, "")
logclient.AddActionLogWithStartable(self, ib, logclient.ACT_CREATE, nil, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceBackupCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
ib := obj.(*models.SInstanceBackup)
self.SetStage("OnInstanceBackup", nil)
guest := models.GuestManager.FetchGuestById(ib.GuestId)
params := jsonutils.NewDict()
if err := ib.GetRegionDriver().RequestCreateInstanceBackup(ctx, guest, ib, self, params); err != nil {
self.taskFailed(ctx, ib, guest, jsonutils.NewString(err.Error()))
}
}
func (self *InstanceBackupCreateTask) OnKvmDisksSnapshot(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
subTasks := taskman.SubTaskManager.GetTotalSubtasks(self.Id, "OnKvmDisksSnapshot", "")
guest := models.GuestManager.FetchGuestById(ib.GuestId)
self.SetStage("OnInstanceBackup", nil)
for i := range subTasks {
log.Infof("subsTask %s result: %s", subTasks[i].SubtaskId, subTasks[i].Result)
result, err := jsonutils.ParseString(subTasks[i].Result)
if err != nil {
self.taskFailed(ctx, ib, guest, jsonutils.NewString(fmt.Sprintf("unable to parse %s", subTasks[i].Result)))
return
}
if subTasks[i].Status == taskman.SUBTASK_FAIL {
self.taskFailed(ctx, ib, guest, result)
return
}
snapshotId, _ := result.GetString("snapshot_id")
diskBakcupId, _ := result.GetString("disk_backup_id")
ibackup, err := models.DiskBackupManager.FetchById(diskBakcupId)
if err != nil {
self.taskFailed(ctx, ib, guest, jsonutils.NewString(err.Error()))
return
}
backup := ibackup.(*models.SDiskBackup)
params := jsonutils.NewDict()
params.Set("snapshot_id", jsonutils.NewString(snapshotId))
if err := backup.StartBackupCreateTask(ctx, self.UserCred, params, self.Id); err != nil {
self.taskFailed(ctx, ib, guest, jsonutils.NewString(err.Error()))
return
}
}
guest.StartSyncstatus(ctx, self.UserCred, "")
}
func (self *InstanceBackupCreateTask) OnKvmDisksSnapshotFailed(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, ib, nil, data)
}
func (self *InstanceBackupCreateTask) OnInstanceBackup(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
subTasks := taskman.SubTaskManager.GetTotalSubtasks(self.Id, "OnInstanceBackup", "")
for i := range subTasks {
if subTasks[i].Status == taskman.SUBTASK_SUCC {
continue
}
result, err := jsonutils.ParseString(subTasks[i].Result)
if err != nil {
self.taskFailed(ctx, ib, nil, jsonutils.NewString(fmt.Sprintf("unable to parse %s", subTasks[i].Result)))
return
}
self.taskFailed(ctx, ib, nil, result)
}
// update size_mb
backups, err := ib.GetBackups()
if err != nil {
self.taskFailed(ctx, ib, nil, jsonutils.NewString(err.Error()))
return
}
var sizeMb int
for i := range backups {
sizeMb += backups[i].SizeMb
}
db.Update(ib, func() error {
ib.SizeMb = sizeMb
return nil
})
self.taskSuccess(ctx, ib)
}
func (self *InstanceBackupCreateTask) OnInstanceBackupFailed(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, ib, nil, data)
}
@@ -0,0 +1,94 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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 InstanceBackupDeleteTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceBackupDeleteTask{})
}
func (self *InstanceBackupDeleteTask) taskFailed(ctx context.Context, ib *models.SInstanceBackup, reason jsonutils.JSONObject) {
reasonStr, _ := reason.GetString()
ib.SetStatus(self.UserCred, compute.INSTANCE_BACKUP_STATUS_DELETE_FAILED, reasonStr)
logclient.AddActionLogWithStartable(self, ib, logclient.ACT_CREATE, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceBackupDeleteTask) taskSuccess(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
ib.RealDelete(ctx, self.UserCred)
logclient.AddActionLogWithContext(ctx, ib, logclient.ACT_DELETE, nil, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceBackupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
ib := obj.(*models.SInstanceBackup)
self.SetStage("OnInstanceBackupDelete", nil)
if err := ib.GetRegionDriver().RequestDeleteInstanceBackup(ctx, ib, self); err != nil {
self.taskFailed(ctx, ib, jsonutils.NewString(err.Error()))
return
}
}
func (self *InstanceBackupDeleteTask) OnKvmDiskBackupDelete(
ctx context.Context, isp *models.SInstanceBackup, data jsonutils.JSONObject) {
backupId, _ := self.Params.GetString("del_backup_id")
// detach backup and instance
isjp := new(models.SInstanceBackupJoint)
err := models.InstanceBackupJointManager.Query().
Equals("instance_backup_id", isp.Id).Equals("disk_backup_id", backupId).First(isjp)
if err != nil {
self.taskFailed(ctx, isp, jsonutils.NewString(err.Error()))
return
}
isjp.SetModelManager(models.InstanceBackupJointManager, isjp)
err = isjp.Delete(ctx, self.UserCred)
if err != nil {
self.taskFailed(ctx, isp, jsonutils.NewString(err.Error()))
return
}
if err := isp.GetRegionDriver().RequestDeleteInstanceBackup(ctx, isp, self); err != nil {
self.taskFailed(ctx, isp, jsonutils.NewString(err.Error()))
return
}
}
func (self *InstanceBackupDeleteTask) OnKvmBackupDeleteFailed(
ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, ib, data)
}
func (self *InstanceBackupDeleteTask) OnInstanceBackupDelete(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskSuccess(ctx, ib, data)
}
func (self *InstanceBackupDeleteTask) OnInstanceBackupDeleteFailed(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, ib, data)
}
@@ -0,0 +1,113 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tasks
import (
"context"
"fmt"
"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/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
compute_modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type InstanceBackupRecoveryTask struct {
taskman.STask
}
func init() {
taskman.RegisterTask(InstanceBackupRecoveryTask{})
}
func (self *InstanceBackupRecoveryTask) taskFailed(ctx context.Context, ib *models.SInstanceBackup, reason jsonutils.JSONObject) {
reasonStr, _ := reason.GetString()
ib.SetStatus(self.UserCred, compute.INSTANCE_BACKUP_STATUS_CREATE_FAILED, reasonStr)
logclient.AddActionLogWithStartable(self, ib, logclient.ACT_RECOVERY, reason, self.UserCred, false)
self.SetStageFailed(ctx, reason)
}
func (self *InstanceBackupRecoveryTask) taskSuccess(ctx context.Context, ib *models.SInstanceBackup) {
ib.SetStatus(self.UserCred, compute.INSTANCE_BACKUP_STATUS_READY, "")
logclient.AddActionLogWithStartable(self, ib, logclient.ACT_RECOVERY, nil, self.UserCred, true)
self.SetStageComplete(ctx, nil)
}
func (self *InstanceBackupRecoveryTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
ib := obj.(*models.SInstanceBackup)
serverName, _ := self.Params.GetString("server_name")
if serverName == "" {
serverName, _ = ib.ServerConfig.GetString("name")
}
sourceInput := &compute.ServerCreateInput{}
sourceInput.ServerConfigs = &compute.ServerConfigs{}
sourceInput.GenerateName = serverName
sourceInput.Description = fmt.Sprintf("recovery from instance backup %s", ib.GetName())
sourceInput.InstanceBackupId = ib.GetId()
sourceInput.Hypervisor = compute.HYPERVISOR_KVM
taskHeader := self.GetTaskRequestHeader()
session := auth.GetSession(ctx, self.UserCred, "", "")
session.Header.Set(mcclient.TASK_NOTIFY_URL, taskHeader.Get(mcclient.TASK_NOTIFY_URL))
session.Header.Set(mcclient.TASK_ID, taskHeader.Get(mcclient.TASK_ID))
serverData, err := compute_modules.Servers.Create(session, jsonutils.Marshal(sourceInput))
if err != nil {
self.taskFailed(ctx, ib, jsonutils.NewString(err.Error()))
return
}
guestId, _ := serverData.GetString("id")
params := jsonutils.NewDict()
params.Set("guest_id", jsonutils.NewString(guestId))
self.SetStage("OnCreateGuest", params)
}
func (self *InstanceBackupRecoveryTask) OnCreateGuest(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
guestId, _ := self.Params.GetString("guest_id")
guest := models.GuestManager.FetchGuestById(guestId)
if guest == nil {
self.taskFailed(ctx, ib, jsonutils.NewString(fmt.Sprintf("no such guest %s", guest.GetId())))
return
}
disks, err := guest.GetDisks()
if err != nil {
self.taskFailed(ctx, ib, jsonutils.NewString(err.Error()))
return
}
if len(disks) == 0 {
self.taskFailed(ctx, ib, jsonutils.NewString(fmt.Sprintf("no disks in guest %s?", guestId)))
return
}
sysDisk := &disks[0]
backups, err := ib.GetBackups()
if err != nil {
self.taskFailed(ctx, ib, jsonutils.NewString(fmt.Sprintf(err.Error())))
return
}
db.Update(sysDisk, func() error {
sysDisk.TemplateId = backups[0].DiskConfig.ImageId
sysDisk.SnapshotId = backups[0].DiskConfig.SnapshotId
return nil
})
self.taskSuccess(ctx, ib)
}
func (self *InstanceBackupRecoveryTask) OnCreateGuestFailed(ctx context.Context, ib *models.SInstanceBackup, data jsonutils.JSONObject) {
self.taskFailed(ctx, ib, data)
}
@@ -21,6 +21,7 @@ import (
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
@@ -493,6 +494,7 @@ func guestSnapshot(ctx context.Context, sid string, body jsonutils.JSONObject) (
if !ok {
return nil, httperrors.NewNotFoundError("guest %s not found", sid)
}
log.Infof("guest info: %s", jsonutils.Marshal(guest))
var disk storageman.IDisk
disks, _ := guest.Desc.GetArray("disks")
+7
View File
@@ -84,6 +84,13 @@ type SDiskSnapshot struct {
Disk storageman.IDisk
}
type SDiskBackup struct {
Sid string
SnapshotId string
BackupId string
Disk storageman.IDisk
}
type SDeleteDiskSnapshot struct {
Sid string
DeleteSnapshot string
+2
View File
@@ -160,6 +160,8 @@ type SHostOptions struct {
EnableVmUuid bool `help:"enable vm UUID" default:"true" json:"enable_vm_uuid"`
EnableVirtioRngDevice bool `help:"enable qemu virtio-rng device" default:"false"`
RestrictQemuImgConvertWorker bool `help:"restrict qemu-img convert worker" default:"false"`
}
var (
@@ -0,0 +1,44 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package backupstorage
import (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
type IBackupStorage interface {
CopyBackupFrom(srcFilename string, bakcupId string) error
CopyBackupTo(targetFilename string, backupId string) error
RemoveBackup(backupId string) error
IsExists(backupId string) (bool, error)
ConvertTo(destPath string, format qemuimg.TImageFormat, backupId string) error
ConvertFrom(srcPath string, format qemuimg.TImageFormat, backupId string) (int, error)
}
func NewBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) {
nfsHost, err := backupStorageAccessInfo.GetString("nfs_host")
if err != nil {
return nil, fmt.Errorf("need nfs_host in backup_storage_access_info")
}
nfsSharedDir, err := backupStorageAccessInfo.GetString("nfs_shared_dir")
if err != nil {
return nil, fmt.Errorf("need nfs_shared_dir in backup_storage_access_info")
}
return NewNFSBackupStorage(backupStroageId, nfsHost, nfsSharedDir), nil
}
@@ -0,0 +1,222 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package backupstorage
import (
"context"
"fmt"
"path"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
const BackupStoragePath = "/opt/cloud/workspace/backupstorage"
type SNFSBackupStorage struct {
BackupStorageId string
Path string
NfsHost string
NfsSharedDir string
}
func NewNFSBackupStorage(backupStorageId, nfsHost, nfsSharedDir string) *SNFSBackupStorage {
return &SNFSBackupStorage{
BackupStorageId: backupStorageId,
NfsHost: nfsHost,
NfsSharedDir: nfsSharedDir,
Path: path.Join(BackupStoragePath, backupStorageId),
}
}
func (s *SNFSBackupStorage) getBackupDir() string {
return path.Join(s.Path, "backups")
}
func (s *SNFSBackupStorage) checkAndMount() error {
lockman.LockRawObject(context.Background(), "backupstorage", s.BackupStorageId)
defer lockman.ReleaseRawObject(context.Background(), "backupstorage", s.BackupStorageId)
if !fileutils2.Exists(s.Path) {
output, err := procutils.NewCommand("mkdir", "-p", s.Path).Output()
if err != nil {
log.Errorf("mkdir %s failed: %s", s.Path, output)
return errors.Wrapf(err, "mkdir %s failed: %s", s.Path, output)
}
}
if err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", s.Path).Run(); err == nil {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := procutils.NewRemoteCommandContextAsFarAsPossible(ctx,
"mount", "-t", "nfs", fmt.Sprintf("%s:%s", s.NfsHost, s.NfsSharedDir), s.Path).Run()
if err != nil {
return err
}
backupDir := s.getBackupDir()
if !fileutils2.Exists(backupDir) {
output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
if err != nil {
log.Errorf("mkdir %s failed: %s", backupDir, output)
return errors.Wrapf(err, "mkdir %s failed: %s", backupDir, output)
}
}
return nil
}
func (s *SNFSBackupStorage) unMount() error {
lockman.LockRawObject(context.Background(), "backupstorage", s.BackupStorageId)
defer lockman.ReleaseRawObject(context.Background(), "backupstorage", s.BackupStorageId)
out, err := procutils.NewRemoteCommandAsFarAsPossible("umount", s.Path).Output()
if err != nil {
return errors.Wrapf(err, "umount %s failed %s", s.Path, out)
}
return nil
}
func (s *SNFSBackupStorage) CopyBackupFrom(srcFilename string, backupId string) error {
err := s.checkAndMount()
if err != nil {
return errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
targetFilename := path.Join(backupDir, backupId)
if output, err := procutils.NewCommand("cp", srcFilename, targetFilename).Output(); err != nil {
log.Errorf("unable to cp %s to %s: %s", srcFilename, targetFilename, output)
return errors.Wrapf(err, "cp %s to %s failed and output is %q", srcFilename, targetFilename, output)
}
return nil
}
func (s *SNFSBackupStorage) CopyBackupTo(targetFilename string, backupId string) error {
err := s.checkAndMount()
if err != nil {
return errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
srcFilename := path.Join(backupDir, backupId)
if output, err := procutils.NewCommand("cp", srcFilename, targetFilename).Output(); err != nil {
log.Errorf("unable to cp %s to %s: %s", srcFilename, targetFilename, output)
return errors.Wrapf(err, "cp %s to %s failed and output is %q", srcFilename, targetFilename, output)
}
return nil
}
func (s *SNFSBackupStorage) ConvertFrom(srcPath string, format qemuimg.TImageFormat, backupId string) (int, error) {
err := s.checkAndMount()
if err != nil {
return 0, errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
destPath := path.Join(backupDir, backupId)
srcInfo := qemuimg.SConvertInfo{
Path: srcPath,
Format: qemuimg.RAW,
IoLevel: qemuimg.IONiceNone,
Password: "",
}
destInfo := qemuimg.SConvertInfo{
Path: destPath,
Format: qemuimg.QCOW2,
IoLevel: qemuimg.IONiceNone,
Password: "",
}
err = qemuimg.Convert(srcInfo, destInfo, nil, true, nil)
if err != nil {
return 0, err
}
newImage, err := qemuimg.NewQemuImage(destPath)
if err != nil {
return 0, err
}
return newImage.GetActualSizeMB(), nil
}
func (s *SNFSBackupStorage) ConvertTo(destPath string, format qemuimg.TImageFormat, backupId string) error {
err := s.checkAndMount()
if err != nil {
return errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
srcPath := path.Join(backupDir, backupId)
srcInfo := qemuimg.SConvertInfo{
Path: srcPath,
Format: qemuimg.QCOW2,
IoLevel: qemuimg.IONiceNone,
Password: "",
}
destInfo := qemuimg.SConvertInfo{
Path: destPath,
Format: format,
IoLevel: qemuimg.IONiceNone,
Password: "",
}
var opts []string
if format == qemuimg.QCOW2 {
opts = qemuimg.Qcow2SparseOptions()
}
var workerOpts []string
if options.HostOptions.RestrictQemuImgConvertWorker {
workerOpts = nil
} else {
workerOpts = []string{"-W", "-m", "16"}
}
return qemuimg.Convert(srcInfo, destInfo, opts, false, workerOpts)
}
func (s *SNFSBackupStorage) GetBackupPath(backupId string) string {
backupDir := s.getBackupDir()
return path.Join(backupDir, backupId)
}
func (s *SNFSBackupStorage) RemoveBackup(backupId string) error {
err := s.checkAndMount()
if err != nil {
return errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
filename := path.Join(backupDir, backupId)
if !fileutils2.Exists(filename) {
return nil
}
if output, err := procutils.NewCommand("rm", filename).Output(); err != nil {
log.Errorf("unable to rm %s: %s", filename, output)
return errors.Wrapf(err, "rm %s failed and output is %q", filename, output)
}
return nil
}
func (s *SNFSBackupStorage) IsExists(backupId string) (bool, error) {
err := s.checkAndMount()
if err != nil {
return false, errors.Wrap(err, "unable to checkAndMount")
}
defer s.unMount()
backupDir := s.getBackupDir()
filename := path.Join(backupDir, backupId)
return fileutils2.Exists(filename), nil
}
@@ -0,0 +1,15 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package backupstorage // import "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
+11
View File
@@ -63,6 +63,9 @@ type IDisk interface {
DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error
DeployGuestFs(diskPath string, guestDesc *jsonutils.JSONDict,
deployInfo *deployapi.DeployInfo) (jsonutils.JSONObject, error)
GetBackupDir() string
DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
}
type SBaseDisk struct {
@@ -169,6 +172,10 @@ func (d *SBaseDisk) DiskSnapshot(ctx context.Context, params interface{}) (jsonu
return nil, fmt.Errorf("Not implement disk.DiskSnapshot")
}
func (d *SBaseDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
return nil, fmt.Errorf("Not implement disk.DiskDeleteSnapshot")
}
func (d *SBaseDisk) DiskDeleteSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
return nil, fmt.Errorf("Not implement disk.DiskDeleteSnapshot")
}
@@ -180,3 +187,7 @@ func (d *SBaseDisk) CreateFromRbdSnapshot(ctx context.Context, napshotUrl, srcDi
func (d *SBaseDisk) DoDeleteSnapshot(snapshotId string) error {
return fmt.Errorf("Not implement disk.DoDeleteSnapshot")
}
func (d *SBaseDisk) GetBackupDir() string {
return ""
}
+41
View File
@@ -78,6 +78,10 @@ func (d *SLocalDisk) GetSnapshotDir() string {
return path.Join(d.Storage.GetSnapshotDir(), d.Id+options.HostOptions.SnapshotDirSuffix)
}
func (d *SLocalDisk) GetBackupDir() string {
return d.Storage.GetBackupDir()
}
func (d *SLocalDisk) GetSnapshotLocation() string {
return d.GetSnapshotDir()
}
@@ -341,8 +345,45 @@ func (d *SLocalDisk) PostCreateFromImageFuse() {
}
}
func (d *SLocalDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
diskBakcup := params.(*SDiskBakcup)
snapshotDir := d.GetSnapshotDir()
snapshotPath := path.Join(snapshotDir, diskBakcup.SnapshotId)
backupDir := d.GetBackupDir()
if !fileutils2.Exists(backupDir) {
output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
if err != nil {
log.Errorf("mkdir %s failed: %s", backupDir, output)
return nil, errors.Wrapf(err, "mkdir %s failed: %s", backupDir, output)
}
}
backupPath := path.Join(backupDir, diskBakcup.BackupId)
img, err := qemuimg.NewQemuImage(snapshotPath)
if err != nil {
log.Errorln(err)
procutils.NewCommand("mv", "-f", backupPath, d.getPath()).Run()
return nil, err
}
newImage, err := img.Clone(backupPath, qemuimg.QCOW2, true)
if err != nil {
return nil, errors.Wrap(err, "unable to backup snapshot")
}
_, err = d.Storage.StorageBackup(ctx, &SStorageBackup{
BackupId: diskBakcup.BackupId,
BackupStorageId: diskBakcup.BackupStorageId,
BackupStorageAccessInfo: diskBakcup.BackupStorageAccessInfo,
})
if err != nil {
return nil, errors.Wrap(err, "unable to SStorageBackup")
}
data := jsonutils.NewDict()
data.Set("size_mb", jsonutils.NewInt(int64(newImage.GetActualSizeMB())))
return data, nil
}
func (d *SLocalDisk) CreateSnapshot(snapshotId string) error {
snapshotDir := d.GetSnapshotDir()
log.Infof("snapshotDir of LocalDisk %s: %s", d.Id, snapshotDir)
if !fileutils2.Exists(snapshotDir) {
output, err := procutils.NewCommand("mkdir", "-p", snapshotDir).Output()
if err != nil {
+13
View File
@@ -221,6 +221,19 @@ func (d *SRBDDisk) PostCreateFromImageFuse() {
log.Errorf("Not support PostCreateFromImageFuse")
}
func (d *SRBDDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
diskBackup := params.(*SDiskBakcup)
storage := d.Storage.(*SRbdStorage)
pool, _ := storage.StorageConf.GetString("pool")
sizeMb, err := storage.createBackup(pool, d.Id, diskBackup.SnapshotId, diskBackup.BackupId, diskBackup.BackupStorageId, diskBackup.BackupStorageAccessInfo)
if err != nil {
return nil, err
}
data := jsonutils.NewDict()
data.Set("size_mb", jsonutils.NewInt(int64(sizeMb)))
return data, nil
}
func (d *SRBDDisk) CreateSnapshot(snapshotId string) error {
storage := d.Storage.(*SRbdStorage)
pool, _ := storage.StorageConf.GetString("pool")
@@ -48,6 +48,7 @@ var (
"snapshot": diskSnapshot,
"delete-snapshot": diskDeleteSnapshot,
"cleanup-snapshots": diskCleanupSnapshots,
"backup": diskBackup,
}
)
@@ -325,6 +326,74 @@ func diskSnapshot(ctx context.Context, storage storageman.IStorage, diskId strin
return nil, nil
}
func diskStorageBackup(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) {
backupId, err := body.GetString("backup_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_id")
}
backupStorageId, err := body.GetString("backup_storage_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_id")
}
backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_access_info")
}
hostutils.DelayTask(ctx, storage.StorageBackup, &storageman.SStorageBackup{
BackupId: backupId,
BackupStorageId: backupStorageId,
BackupStorageAccessInfo: backupStorageAccessInfo.(*jsonutils.JSONDict),
})
return nil, nil
}
func diskStorageBackupRecovery(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) {
backupId, err := body.GetString("backup_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_id")
}
backupStorageId, err := body.GetString("backup_storage_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_id")
}
backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_access_info")
}
hostutils.DelayTask(ctx, storage.StorageBackupRecovery, storageman.SStorageBackup{
BackupId: backupId,
BackupStorageId: backupStorageId,
BackupStorageAccessInfo: backupStorageAccessInfo.(*jsonutils.JSONDict),
})
return nil, nil
}
func diskBackup(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) {
snapshotId, err := body.GetString("snapshot_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("snapshot_id")
}
backupId, err := body.GetString("backup_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_id")
}
backupStorageId, err := body.GetString("backup_storage_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_id")
}
backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
if err != nil {
return nil, httperrors.NewMissingParameterError("backup_storage_access_info")
}
hostutils.DelayTask(ctx, disk.DiskBackup, &storageman.SDiskBakcup{
BackupId: backupId,
SnapshotId: snapshotId,
BackupStorageId: backupStorageId,
BackupStorageAccessInfo: backupStorageAccessInfo.(*jsonutils.JSONDict),
})
return nil, nil
}
func diskDeleteSnapshot(ctx context.Context, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) {
snapshotId, err := body.GetString("snapshot_id")
if err != nil {
+48
View File
@@ -46,6 +46,7 @@ const (
_RECYCLE_BIN_ = "recycle_bin"
_IMGSAVE_BACKUPS_ = "imgsave_backups"
_SNAPSHOT_PATH_ = "snapshots"
_BACKUP_PATH_ = "backups"
ErrStorageTimeout = constError("storage accessible check timeout")
TempBindMountPath = "/opt/cloud/workspace/temp-bind"
@@ -102,6 +103,10 @@ type IStorage interface {
DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
IsSnapshotExist(diskId, snapshotId string) (bool, error)
GetBackupDir() string
StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
GetFreeSizeMb() int
GetCapacity() int
@@ -117,6 +122,8 @@ type IStorage interface {
SaveToGlance(context.Context, interface{}) (jsonutils.JSONObject, error)
CreateDiskFromSnapshot(context.Context, IDisk, *SDiskCreateByDiskinfo) error
CreateDiskFromBackup(context.Context, IDisk, *SDiskCreateByDiskinfo) error
// GetCloneTargetDiskPath generate target disk path by target disk id
GetCloneTargetDiskPath(ctx context.Context, targetDiskId string) string
// CloneDiskFromStorage clone disk from other storage
@@ -174,6 +181,14 @@ func (s *SBaseStorage) SetStoragecacheId(storagecacheId string) {
s.StoragecacheId = storagecacheId
}
func (s *SBaseStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
return nil, nil
}
func (s *SBaseStorage) StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
return nil, nil
}
func (s *SBaseStorage) GetName(generateName func() string) string {
if len(s.StorageName) > 0 {
return s.StorageName
@@ -329,6 +344,9 @@ func (s *SBaseStorage) CreateDiskByDiskinfo(ctx context.Context, params interfac
case len(createParams.DiskInfo.ImageId) > 0:
log.Infof("CreateDiskFromTemplate %s", createParams)
return s.CreateDiskFromTemplate(ctx, disk, createParams)
case createParams.DiskInfo.Backup != nil:
log.Infof("CreateDiskFromBackup %s", createParams)
return s.createDiskFromBackup(ctx, disk, createParams)
case createParams.DiskInfo.DiskSizeMb > 0:
log.Infof("CreateRawDisk %s", createParams)
return s.CreateRawDisk(ctx, disk, createParams)
@@ -363,6 +381,20 @@ func (s *SBaseStorage) CreateDiskFromSnpashot(ctx context.Context, disk IDisk, i
return disk.GetDiskDesc(), nil
}
func (s *SBaseStorage) createDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) {
var storage = input.Storage
if input.DiskInfo.Backup == nil {
return nil, httperrors.NewMissingParameterError("Backup")
}
err := storage.CreateDiskFromBackup(ctx, disk, input)
if err != nil {
return nil, errors.Wrapf(err, "CreateDiskFromBackup")
}
return disk.GetDiskDesc(), nil
}
func (s *SBaseStorage) DestinationPrepareMigrate(
ctx context.Context, liveMigrate bool, disksUri string, snapshotsUri string,
disksBackingFile, srcSnapshots jsonutils.JSONObject, rebaseDisks bool, diskinfo jsonutils.JSONObject, serverId string, idx, totalDiskCount int,
@@ -378,6 +410,22 @@ func (s *SBaseStorage) CloneDiskFromStorage(ctx context.Context, srcStorage ISto
return nil, httperrors.ErrNotImplemented
}
func (s *SBaseStorage) GetBackupDir() string {
return path.Join(s.Path, _BACKUP_PATH_)
}
func (s *SBaseStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
info := input.DiskInfo
backupPath := path.Join(s.GetBackupDir(), info.Backup.BackupId)
img, err := qemuimg.NewQemuImage(backupPath)
if err != nil {
log.Errorln("unable to new qemu image for %s: %s", backupPath, err.Error())
return err
}
_, err = img.Clone(disk.GetPath(), qemuimg.QCOW2, false)
return err
}
/*************************Background delete snapshot job****************************/
func StartSnapshotRecycle(storage IStorage) {
+56
View File
@@ -36,6 +36,7 @@ import (
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/hostutils/kubelet"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
"yunion.io/x/onecloud/pkg/hostman/storageman/remotefile"
"yunion.io/x/onecloud/pkg/httperrors"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
@@ -99,6 +100,61 @@ func (s *SLocalStorage) SyncStorageSize() error {
s.StorageId, content)
return err
}
func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
info := input.DiskInfo
backupPath := path.Join(s.GetBackupDir(), info.Backup.BackupId)
if !fileutils2.Exists(backupPath) {
_, err := s.storageBackupRecovery(ctx, &SStorageBackup{
BackupId: input.DiskInfo.Backup.BackupId,
BackupStorageId: input.DiskInfo.Backup.BackupStorageId,
BackupStorageAccessInfo: input.DiskInfo.Backup.BackupStorageAccessInfo.Copy(),
})
if err != nil {
return errors.Wrap(err, "unable to storageBackupRecovery")
}
}
img, err := qemuimg.NewQemuImage(backupPath)
if err != nil {
log.Errorln("unable to new qemu image for %s: %s", backupPath, err.Error())
return err
}
_, err = img.Clone(disk.GetPath(), qemuimg.QCOW2, false)
return err
}
func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
sbParams := params.(*SStorageBackup)
backupStorage, err := backupstorage.NewBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
if err != nil {
return nil, err
}
backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
err = backupStorage.CopyBackupFrom(backupPath, sbParams.BackupId)
if err != nil {
return nil, err
}
// remove local backup
output, err := procutils.NewCommand("rm", backupPath).Output()
if err != nil {
log.Errorf("rm %s failed %s", backupPath, output)
return nil, errors.Wrapf(err, "rm %s failed %s", backupPath, output)
}
return nil, nil
}
func (s *SLocalStorage) storageBackupRecovery(ctx context.Context, sbParams *SStorageBackup) (jsonutils.JSONObject, error) {
backupStorage, err := backupstorage.NewBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
if err != nil {
return nil, err
}
backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
return nil, backupStorage.CopyBackupTo(backupPath, sbParams.BackupId)
}
func (s *SLocalStorage) StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
sbParams := params.(*SStorageBackup)
return s.storageBackupRecovery(ctx, sbParams)
}
func (s *SLocalStorage) GetAvailSizeMb() int {
sizeMb := s.SBaseStorage.GetAvailSizeMb()
+59
View File
@@ -33,6 +33,7 @@ import (
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
"yunion.io/x/onecloud/pkg/mcclient/modules/image"
"yunion.io/x/onecloud/pkg/util/cephutils"
@@ -290,6 +291,45 @@ func (s *SRbdStorage) resetDisk(pool string, diskId string, snapshotId string) e
return snap.Rollback()
}
func (s *SRbdStorage) createBackup(pool string, diskId string, snapshotId string, backupId string, backupStorageId string, backupStorageAccessInfo *jsonutils.JSONDict) (int, error) {
client, err := s.GetClient()
if err != nil {
return 0, errors.Wrapf(err, "GetClient")
}
client.SetPool(pool)
defer client.Close()
img, err := client.GetImage(diskId)
if err != nil {
return 0, errors.Wrapf(err, "GetImage")
}
snap, err := img.GetSnapshot(snapshotId)
if err != nil {
return 0, errors.Wrapf(err, "unable to GetSnapshot %s of Image %s", snapshotId, diskId)
}
backupName := fmt.Sprintf("backup_%s", backupId)
err = snap.Clone(pool, backupName)
if err != nil {
return 0, errors.Wrapf(err, "unable to Clone snap %s", fmt.Sprintf("%s@%s", diskId, snapshotId))
}
backupImg, err := client.GetImage(backupName)
if err != nil {
return 0, errors.Wrapf(err, "GetImage")
}
defer backupImg.Delete()
// convert backupStorage
backupStorage, err := backupstorage.NewBackupStorage(backupStorageId, backupStorageAccessInfo)
if err != nil {
return 0, errors.Wrap(err, "unable to NewNFSBackupStorage")
}
srcPath := fmt.Sprintf("rbd:%s/%s%s", pool, backupName, s.getStorageConfString())
// convert
sizeMb, err := backupStorage.ConvertFrom(srcPath, qemuimg.RAW, backupId)
if err != nil {
return 0, errors.Wrapf(err, "unable to ConvertFrom with srcPath %s and format %s", srcPath, qemuimg.RAW.String())
}
return sizeMb, nil
}
func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId string) error {
client, err := s.GetClient()
if err != nil {
@@ -522,6 +562,25 @@ func (s *SRbdStorage) CreateDiskFromSnapshot(ctx context.Context, disk IDisk, in
return disk.CreateFromRbdSnapshot(ctx, info.SnapshotUrl, info.SrcDiskId, info.SrcPool)
}
func (s *SRbdStorage) GetBackupDir() string {
return ""
}
func (s *SRbdStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
backup := input.DiskInfo.Backup
pool, _ := s.StorageConf.GetString("pool")
destPath := fmt.Sprintf("rbd:%s/%s%s", pool, disk.GetId(), s.getStorageConfString())
backupStorage, err := backupstorage.NewBackupStorage(backup.BackupStorageId, backup.BackupStorageAccessInfo)
if err != nil {
return errors.Wrap(err, "unable to NewNFSBackupStorage")
}
err = backupStorage.ConvertTo(destPath, qemuimg.RAW, backup.BackupId)
if err != nil {
return errors.Wrapf(err, "unable to Convert to with destPath %s and format %s", destPath, qemuimg.RAW.String())
}
return nil
}
func (s *SRbdStorage) getDiskPath(diskId string) string {
storageConf := s.GetStorageConf()
pool, _ := storageConf.GetString("pool")
@@ -23,9 +23,11 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/storageman"
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient/auth"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
@@ -57,6 +59,12 @@ func AddStorageHandler(prefix string, app *appsrv.Application) {
app.AddHandler("GET",
fmt.Sprintf("%s/%s/is-mount-point", prefix, keyWords),
auth.Authenticate(storageVerifyMountPoint))
app.AddHandler("POST",
fmt.Sprintf("%s/%s/delete-backup", prefix, keyWords),
auth.Authenticate(storageDeleteBackup))
app.AddHandler("POST",
fmt.Sprintf("%s/%s/sync-backup", prefix, keyWords),
auth.Authenticate(storageSyncBackup))
}
}
@@ -183,6 +191,87 @@ func storageUpdate(ctx context.Context, body jsonutils.JSONObject) (interface{},
return nil, nil
}
func storageSyncBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
_, _, body := appsrv.FetchEnv(ctx, w, r)
backupId, err := body.GetString("backup_id")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_id"))
return
}
backupStorageId, err := body.GetString("backup_storage_id")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_id"))
return
}
backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_access_info"))
return
}
backupStorage, err := backupstorage.NewBackupStorage(backupStorageId, backupStorageAccessInfo.(*jsonutils.JSONDict))
if err != nil {
hostutils.Response(ctx, w, err)
return
}
exist, err := backupStorage.IsExists(backupId)
if err != nil {
hostutils.Response(ctx, w, err)
return
}
var (
ret = jsonutils.NewDict()
status string
)
if exist {
status = compute.BACKUP_EXIST
} else if !exist && err == nil {
status = compute.BACKUP_NOT_EXIST
} else {
log.Errorf("fetch snapshot exist failed %s", err)
status = compute.BACKUP_STATUS_UNKNOWN
}
ret.Set("status", jsonutils.NewString(status))
hostutils.Response(ctx, w, ret)
}
func storageDeleteBackup(ctx context.Context, w http.ResponseWriter, r *http.Request) {
_, _, body := appsrv.FetchEnv(ctx, w, r)
backupId, err := body.GetString("backup_id")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_id"))
return
}
backupStorageId, err := body.GetString("backup_storage_id")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_id"))
return
}
backupStorageAccessInfo, err := body.Get("backup_storage_access_info")
if err != nil {
hostutils.Response(ctx, w, httperrors.NewMissingParameterError("backup_storage_access_info"))
return
}
hostutils.DelayTask(ctx, deleteBackup, &storageman.SStorageBackup{
BackupId: backupId,
BackupStorageId: backupStorageId,
BackupStorageAccessInfo: backupStorageAccessInfo.(*jsonutils.JSONDict),
})
hostutils.ResponseOk(ctx, w)
}
func deleteBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
sbParams := params.(*storageman.SStorageBackup)
backupStorage, err := backupstorage.NewBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
if err != nil {
return nil, err
}
err = backupStorage.RemoveBackup(sbParams.BackupId)
if err != nil {
return nil, err
}
return nil, nil
}
func storageDeleteSnapshots(ctx context.Context, w http.ResponseWriter, r *http.Request) {
params, _, body := appsrv.FetchEnv(ctx, w, r)
var storageId = params["<storageId>"]
+13
View File
@@ -43,3 +43,16 @@ type SDiskCleanupSnapshots struct {
ConvertSnapshots []jsonutils.JSONObject
DeleteSnapshots []jsonutils.JSONObject
}
type SDiskBakcup struct {
SnapshotId string
BackupId string
BackupStorageId string
BackupStorageAccessInfo *jsonutils.JSONDict
}
type SStorageBackup struct {
BackupId string
BackupStorageId string
BackupStorageAccessInfo *jsonutils.JSONDict
}
@@ -0,0 +1,64 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compute
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
type DiskBackupManager struct {
modulebase.ResourceManager
}
type BackupStorageManager struct {
modulebase.ResourceManager
}
type InstanceBackupManager struct {
modulebase.ResourceManager
}
var (
DiskBackups DiskBackupManager
BackupStorages BackupStorageManager
InstanceBackups InstanceBackupManager
)
func init() {
DiskBackups = DiskBackupManager{modules.NewComputeManager(
"diskbackup",
"diskbackups",
[]string{},
[]string{},
)}
modules.RegisterCompute(&DiskBackups)
BackupStorages = BackupStorageManager{modules.NewComputeManager(
"backupstorage",
"backupstorages",
[]string{},
[]string{},
)}
modules.RegisterCompute(&BackupStorages)
InstanceBackups = InstanceBackupManager{modules.NewComputeManager(
"instancebackup",
"instancebackups",
[]string{},
[]string{},
)}
modules.RegisterCompute(&InstanceBackups)
}
+146
View File
@@ -0,0 +1,146 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compute
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type DiskBackupListOptions struct {
options.BaseListOptions
DiskId string `help:"disk id" json:"disk_id"`
BackupStorageId string `help:"backup storage id" json:"backup_storage_id"`
IsInstanceBackup *bool `help:"if part of instance backup" json:"is_instance_backup"`
}
func (opts *DiskBackupListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(opts)
}
type DiskBackupIdOptions struct {
ID string `help:"disk backup id" json:"-"`
}
func (opts *DiskBackupIdOptions) GetId() string {
return opts.ID
}
func (opts *DiskBackupIdOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type DiskBackupCreateOptions struct {
options.BaseCreateOptions
DISKID string `help:"disk id" json:"disk_id"`
BACKUPSTORAGEID string `help:"back storage id" json:"backup_storage_id"`
}
func (opts *DiskBackupCreateOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(opts), nil
}
type DiskBackupRecoveryOptions struct {
DiskBackupIdOptions
Name string `help:"disk name" json:"name"`
}
func (opt *DiskBackupRecoveryOptions) GetId() string {
return opt.ID
}
func (opt *DiskBackupRecoveryOptions) Params() (jsonutils.JSONObject, error) {
params := jsonutils.NewDict()
params.Set("name", jsonutils.NewString(opt.Name))
return params, nil
}
type DiskBackupSyncstatusOptions struct {
DiskBackupIdOptions
}
func (opt *DiskBackupSyncstatusOptions) GetId() string {
return opt.ID
}
func (opt *DiskBackupSyncstatusOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type BackupStorageListOptions struct {
options.BaseListOptions
}
func (opts *BackupStorageListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(opts)
}
type BackupStorageIdOptions struct {
ID string `help:"backup storage id"`
}
func (opts *BackupStorageIdOptions) GetId() string {
return opts.ID
}
func (opts *BackupStorageIdOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type BackupStorageCreateOptions struct {
options.BaseCreateOptions
StorageType string `help:"storage type" choices:"nfs"`
NfsHost string `help:"nfs host, required when storage_type is nfs"`
NfsSharedDir string `help:"nfs shared dir, required when storage_type is nfs" `
CapacityMb int `help:"capacity, unit mb"`
}
func (opts *BackupStorageCreateOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(opts), nil
}
type InstanceBackupListOptions struct {
options.BaseListOptions
}
func (opts *InstanceBackupListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(opts)
}
type InstanceBackupIdOptions struct {
ID string `help:"instance backup id"`
}
func (opts *InstanceBackupIdOptions) GetId() string {
return opts.ID
}
func (opts *InstanceBackupIdOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type InstanceBackupRecoveryOptions struct {
DiskBackupIdOptions
Name string `help:"server name" json:"name"`
}
func (opts *InstanceBackupRecoveryOptions) GetId() string {
return opts.ID
}
func (opts *InstanceBackupRecoveryOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(opts), nil
}
+3
View File
@@ -34,6 +34,8 @@ type DiskCreateOptions struct {
Backend string `help:"Backend of this disk"`
Schedtag []string `help:"Schedule policy, key = aggregate name, value = require|exclude|prefer|avoid" metavar:"<KEY:VALUE>"`
TaskNotify bool `help:"Setup task notify"`
SnapshotId string `help:"snapshot id"`
BackupId string `help:"backupid"`
}
func (o DiskCreateOptions) Params() (*api.DiskCreateInput, error) {
@@ -65,5 +67,6 @@ func (o DiskCreateOptions) Params() (*api.DiskCreateInput, error) {
if o.Storage != "" {
params.Storage = o.Storage
}
params.BackupId = o.BackupId
return params, nil
}
+2
View File
@@ -214,4 +214,6 @@ const (
ACT_MERGE_NETWORK = "merge_network"
ACT_RESTART_NETWORK = "restart_network"
ACT_RECOVERY = "recovery"
)
+4
View File
@@ -68,3 +68,7 @@ func qcow2SparseOptions() []string {
return []string{}
}
}
func Qcow2SparseOptions() []string {
return qcow2SparseOptions()
}
+43 -19
View File
@@ -174,21 +174,29 @@ func (img *SQemuImage) IsChained() bool {
return len(img.BackFilePath) > 0
}
func (img *SQemuImage) doConvert(name string, format TImageFormat, options []string, compact bool, password string) error {
if !img.IsValid() {
return fmt.Errorf("self is not valid")
}
cmdline := []string{"-c", strconv.Itoa(int(img.IoLevel)),
type SConvertInfo struct {
Path string
Format TImageFormat
IoLevel TIONiceLevel
Password string
}
func Convert(srcInfo, destInfo SConvertInfo, options []string, compact bool, workerOpions []string) error {
cmdline := []string{"-c", strconv.Itoa(int(srcInfo.IoLevel)),
qemutils.GetQemuImg(), "convert"}
if compact {
cmdline = append(cmdline, "-c")
}
// https://bugzilla.redhat.com/show_bug.cgi?id=1969848
// https://bugs.launchpad.net/qemu/+bug/1805256
// qemu-img convert may hang on aarch64, fix: add -m 1
cmdline = append(cmdline, "-m", "1")
cmdline = append(cmdline, "-f", img.Format.String(), "-O", format.String())
if len(password) > 0 {
if workerOpions == nil {
// https://bugzilla.redhat.com/show_bug.cgi?id=1969848
// https://bugs.launchpad.net/qemu/+bug/1805256
// qemu-img convert may hang on aarch64, fix: add -m 1
cmdline = append(cmdline, "-m", "1")
} else {
cmdline = append(cmdline, workerOpions...)
}
cmdline = append(cmdline, "-f", srcInfo.Format.String(), "-O", destInfo.Format.String())
if len(destInfo.Password) > 0 {
if options == nil {
options = make([]string, 0)
}
@@ -197,12 +205,12 @@ func (img *SQemuImage) doConvert(name string, format TImageFormat, options []str
if len(options) > 0 {
cmdline = append(cmdline, "-o", strings.Join(options, ","))
}
cmdline = append(cmdline, img.Path, name)
cmdline = append(cmdline, srcInfo.Path, destInfo.Path)
log.Infof("XXXX qemu-img command: %s", cmdline)
cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", cmdline...)
var stdin io.WriteCloser
var err error
if len(img.Password) > 0 || len(password) > 0 {
if len(srcInfo.Password) > 0 || len(destInfo.Password) > 0 {
stdin, err = cmd.StdinPipe()
if err != nil {
return errors.Wrap(err, "convert stdin")
@@ -212,25 +220,41 @@ func (img *SQemuImage) doConvert(name string, format TImageFormat, options []str
if err != nil {
return errors.Wrap(err, "do convert")
}
if len(img.Password) > 0 || len(password) > 0 {
if len(srcInfo.Password) > 0 || len(destInfo.Password) > 0 {
input := ""
if len(img.Password) > 0 {
input = fmt.Sprintf("%s%s\r", input, img.Password)
if len(srcInfo.Password) > 0 {
input = fmt.Sprintf("%s%s\r", input, srcInfo.Password)
}
if len(password) > 0 {
input = fmt.Sprintf("%s%s\r", input, password)
if len(destInfo.Password) > 0 {
input = fmt.Sprintf("%s%s\r", input, destInfo.Password)
}
io.WriteString(stdin, input+"\n")
}
err = cmd.Wait()
if err != nil {
log.Errorf("clone fail %s", err)
os.Remove(name)
os.Remove(destInfo.Path)
return err
}
return nil
}
func (img *SQemuImage) doConvert(name string, format TImageFormat, options []string, compact bool, password string) error {
if !img.IsValid() {
return fmt.Errorf("self is not valid")
}
return Convert(SConvertInfo{
Path: img.Path,
Format: img.Format,
IoLevel: img.IoLevel,
Password: img.Password,
}, SConvertInfo{
Path: name,
Format: format,
Password: password,
}, options, compact, nil)
}
func (img *SQemuImage) Clone(name string, format TImageFormat, compact bool) (*SQemuImage, error) {
switch format {
case QCOW2: