mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #13239 from rainzm/vmbackup/init
Instance/Disk Backup Support
This commit is contained in:
@@ -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{})
|
||||
}
|
||||
@@ -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:"-"`
|
||||
|
||||
@@ -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列表
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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", "")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -214,4 +214,6 @@ const (
|
||||
ACT_MERGE_NETWORK = "merge_network"
|
||||
|
||||
ACT_RESTART_NETWORK = "restart_network"
|
||||
|
||||
ACT_RECOVERY = "recovery"
|
||||
)
|
||||
|
||||
@@ -68,3 +68,7 @@ func qcow2SparseOptions() []string {
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func Qcow2SparseOptions() []string {
|
||||
return qcow2SparseOptions()
|
||||
}
|
||||
|
||||
+43
-19
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user