From 6c15b555965163a49a617e47445cffcb86720431 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sat, 25 Nov 2023 18:26:39 +0800 Subject: [PATCH 1/3] feature: backup support object storage --- pkg/apis/compute/backup.go | 38 ++- pkg/apis/compute/bucket.go | 8 + pkg/apis/compute/guests.go | 2 - pkg/apis/compute/zz_generated.model.go | 6 +- pkg/apis/image/image.go | 2 + pkg/cloudcommon/db/metadata.go | 5 +- pkg/compute/models/backup_storage.go | 69 ++-- pkg/compute/models/guests.go | 11 - pkg/compute/models/instance_backup.go | 4 + pkg/compute/models/instance_snapshots.go | 4 +- pkg/hostman/storageman/backup.go | 294 ++++++++++++++++ .../backupstorage/backup_storage.go | 90 +++-- .../storageman/backupstorage/nfs/doc.go | 15 + .../storageman/backupstorage/nfs/factory.go | 45 +++ .../storageman/backupstorage/nfs/nfs.go | 226 ++++++++++++ .../storageman/backupstorage/object/doc.go | 15 + .../backupstorage/object/factory.go | 48 +++ .../storageman/backupstorage/object/object.go | 212 ++++++++++++ pkg/hostman/storageman/disk_base.go | 12 +- pkg/hostman/storageman/disk_local.go | 61 +--- pkg/hostman/storageman/disk_lvm.go | 6 + pkg/hostman/storageman/disk_nas.go | 2 + pkg/hostman/storageman/disk_nvme.go | 7 + pkg/hostman/storageman/disk_rbd.go | 45 ++- .../storageman/diskhandlers/diskhandler.go | 2 +- pkg/hostman/storageman/imagecache_rbd.go | 44 ++- .../storageman/imagecachemanager_rbd.go | 16 +- pkg/hostman/storageman/storage_local.go | 21 +- pkg/hostman/storageman/storage_rbd.go | 188 +++++----- .../storagehandler/storagehandler.go | 23 +- pkg/hostman/storageman/storagehelper.go | 2 +- pkg/image/models/image_guest.go | 3 + pkg/mcclient/options/compute/backup.go | 10 +- pkg/util/cephutils/ceph.go | 323 +++++++++--------- 34 files changed, 1418 insertions(+), 441 deletions(-) create mode 100644 pkg/hostman/storageman/backup.go create mode 100644 pkg/hostman/storageman/backupstorage/nfs/doc.go create mode 100644 pkg/hostman/storageman/backupstorage/nfs/factory.go create mode 100644 pkg/hostman/storageman/backupstorage/nfs/nfs.go create mode 100644 pkg/hostman/storageman/backupstorage/object/doc.go create mode 100644 pkg/hostman/storageman/backupstorage/object/factory.go create mode 100644 pkg/hostman/storageman/backupstorage/object/object.go diff --git a/pkg/apis/compute/backup.go b/pkg/apis/compute/backup.go index 4fd31cf8f2..f154917f67 100644 --- a/pkg/apis/compute/backup.go +++ b/pkg/apis/compute/backup.go @@ -20,8 +20,12 @@ import ( "yunion.io/x/onecloud/pkg/apis" ) +type TBackupStorageType string + const ( - BACKUPSTORAGE_TYPE_NFS = "nfs" + BACKUPSTORAGE_TYPE_NFS = TBackupStorageType("nfs") + BACKUPSTORAGE_TYPE_OBJECT_STORAGE = TBackupStorageType("object") + BACKUPSTORAGE_STATUS_ONLINE = "online" BACKUPSTORAGE_STATUS_OFFLINE = "offline" @@ -63,19 +67,26 @@ type BackupStorageCreateInput struct { // example: /nfs_root/ NfsSharedDir string `json:"nfs_shared_dir"` + // description: access url of object storage bucket + // example: https://qxxxxxo.tos-cn-beijing.volces.com + ObjectBucketUrl string `json:"object_bucket_url"` + // description: access key of object storage + ObjectAccessKey string `json:"object_access_key"` + // description: secret of object storage + ObjectSecret string `json:"object_secret"` + // description: Capacity size in MB CapacityMb int `json:"capacity_mb"` } -type BackupStorageAccessInfo struct { +/*type BackupStorageAccessInfo struct { AccessUrl string -} +}*/ type BackupStorageDetails struct { apis.EnabledStatusInfrasResourceBaseDetails - NfsHost string - NfsSharedDir string + SBackupStorageAccessInfo } type BackupStorageListInput struct { @@ -162,3 +173,20 @@ type InstanceBackupPackMetadata struct { type InstanceBackupManagerSyncstatusInput struct { } + +type SBackupStorageAccessInfo struct { + NfsHost string `json:"nfs_host"` + NfsSharedDir string `json:"nfs_shared_dir"` + + ObjectBucketUrl string `json:"object_bucket_url"` + ObjectAccessKey string `json:"object_access_key"` + ObjectSecret string `json:"object_secret"` +} + +func (ba *SBackupStorageAccessInfo) String() string { + return jsonutils.Marshal(ba).String() +} + +func (ba *SBackupStorageAccessInfo) IsZero() bool { + return ba == nil +} diff --git a/pkg/apis/compute/bucket.go b/pkg/apis/compute/bucket.go index 8696e7a5c8..8ae94edb70 100644 --- a/pkg/apis/compute/bucket.go +++ b/pkg/apis/compute/bucket.go @@ -16,10 +16,12 @@ package compute import ( "net/http" + "reflect" "yunion.io/x/cloudmux/pkg/apis/compute" "yunion.io/x/cloudmux/pkg/cloudprovider" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/utils" @@ -360,3 +362,9 @@ type BucketRefererConf struct { func (input *BucketRefererConf) Validate() error { return nil } + +func init() { + gotypes.RegisterSerializable(reflect.TypeOf(&SBackupStorageAccessInfo{}), func() gotypes.ISerializable { + return &SBackupStorageAccessInfo{} + }) +} diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 6077a65a1b..0157109b9b 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -73,8 +73,6 @@ type ServerListInput struct { // 列出操作系统为指定值的主机 // enum: linux,windows,vmware OsType []string `json:"os_type"` - // 操作系统发行版 - OsDist []string `json:"os_dist"` // 对列表结果按照磁盘大小进行排序 // enum: asc,desc diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index f609b5126f..8cdc4b6f6a 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -95,11 +95,7 @@ type SBackupStorage struct { CapacityMb int `json:"capacity_mb"` } -// SBackupStorageAccessInfo is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SBackupStorageAccessInfo. -type SBackupStorageAccessInfo struct { - NfsHost string `json:"nfs_host"` - NfsSharedDir string `json:"nfs_shared_dir"` -} + // SBaremetalagent is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SBaremetalagent. type SBaremetalagent struct { diff --git a/pkg/apis/image/image.go b/pkg/apis/image/image.go index b6e61e5c7a..b681043f05 100644 --- a/pkg/apis/image/image.go +++ b/pkg/apis/image/image.go @@ -66,6 +66,8 @@ type GuestImageListInput struct { // 是否删除保护 Protected *bool `json:"protected"` + + DiskFormat []string `json:"disk_format"` } type ImageDetails struct { diff --git a/pkg/cloudcommon/db/metadata.go b/pkg/cloudcommon/db/metadata.go index 6e1ffcc510..10d4af9469 100644 --- a/pkg/cloudcommon/db/metadata.go +++ b/pkg/cloudcommon/db/metadata.go @@ -544,7 +544,6 @@ func (manager *SMetadataManager) rawSetValues(ctx context.Context, objType strin keys := make([]string, 0, len(store)) changes := make([]sMetadataChange, 0) for key, value := range store { - key = strings.TrimSpace(key) keys = append(keys, key) record := SMetadata{} @@ -709,7 +708,7 @@ func (manager *SMetadataManager) rawGetAll(objType, objId string, keys []string, ret := make(map[string]string) for _, rec := range records { if len(rec.Value) > 0 || strings.HasPrefix(rec.Key, USER_TAG_PREFIX) || strings.HasPrefix(rec.Key, CLOUD_TAG_PREFIX) { - ret[strings.TrimSpace(rec.Key)] = rec.Value + ret[rec.Key] = rec.Value } } return ret, nil @@ -772,7 +771,7 @@ func metaList2Map(manager IMetadataBaseModelManager, userCred mcclient.TokenCred hiddenKeys := manager.GetMetadataHiddenKeys() for _, meta := range metaList { if IsMetadataKeyVisible(meta.Key) && !utils.IsInStringArray(meta.Key, hiddenKeys) { - metaMap[strings.TrimSpace(meta.Key)] = meta.Value + metaMap[meta.Key] = meta.Value } } diff --git a/pkg/compute/models/backup_storage.go b/pkg/compute/models/backup_storage.go index af494b0c7b..21a1e3c174 100644 --- a/pkg/compute/models/backup_storage.go +++ b/pkg/compute/models/backup_storage.go @@ -16,12 +16,12 @@ package models import ( "context" - "reflect" + "net/url" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/pkg/gotypes" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -40,17 +40,14 @@ type SBackupStorageManager struct { 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"` + AccessInfo *api.SBackupStorageAccessInfo + StorageType api.TBackupStorageType `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{}, @@ -62,48 +59,58 @@ func init() { 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}) { + if !utils.IsInArray(input.StorageType, []string{string(api.BACKUPSTORAGE_TYPE_NFS), string(api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE)}) { return input, httperrors.NewInputParameterError("Invalid storage type %s", input.StorageType) } switch input.StorageType { - case api.BACKUPSTORAGE_TYPE_NFS: + case string(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") } + case string(api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE): + if input.ObjectBucketUrl == "" { + return input, httperrors.NewInputParameterError("object_bucket_url is required when storage type is object") + } + _, err := url.Parse(input.ObjectBucketUrl) + if err != nil { + return input, httperrors.NewInputParameterError("invalid object_bucket_url %s: %s", input.ObjectBucketUrl, err) + } + if input.ObjectAccessKey == "" { + return input, httperrors.NewInputParameterError("object_access_key is required when storage type is object") + } + if input.ObjectSecret == "" { + return input, httperrors.NewInputParameterError("object_secret is required when storage type is object") + } } 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") + input := api.BackupStorageCreateInput{} + err := data.Unmarshal(&input) + if err != nil { + return errors.Wrap(err, "Unmarshal BackupStorageCreateInput") + } + // nfsHost, _ := data.GetString("nfs_host") + // nfsSharedDir, _ := data.GetString("nfs_shared_dir") bs.Status = api.BACKUPSTORAGE_STATUS_ONLINE - bs.AccessInfo = &SBackupStorageAccessInfo{ - NfsHost: nfsHost, - NfsSharedDir: nfsSharedDir, + bs.AccessInfo = &api.SBackupStorageAccessInfo{ + NfsHost: input.NfsHost, + NfsSharedDir: input.NfsSharedDir, + + ObjectBucketUrl: input.ObjectBucketUrl, + ObjectAccessKey: input.ObjectAccessKey, + ObjectSecret: input.ObjectSecret, } return bs.SEnabledStatusInfrasResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) } @@ -118,7 +125,7 @@ func (bs *SBackupStorage) ValidateDeleteCondition(ctx context.Context, info json return httperrors.NewInternalServerError("BackupCount fail %s", err) } if cnt > 0 { - return httperrors.NewNotEmptyError("storage has backup") + return httperrors.NewNotEmptyError("storage has been used") } return bs.SEnabledStatusInfrasResourceBase.ValidateDeleteCondition(ctx, nil) } @@ -135,6 +142,10 @@ func (bs *SBackupStorage) PostCreate(ctx context.Context, userCred mcclient.Toke func (bs *SBackupStorage) getMoreDetails(ctx context.Context, out api.BackupStorageDetails) api.BackupStorageDetails { out.NfsHost = bs.AccessInfo.NfsHost out.NfsSharedDir = bs.AccessInfo.NfsSharedDir + out.ObjectBucketUrl = bs.AccessInfo.ObjectBucketUrl + out.ObjectAccessKey = bs.AccessInfo.ObjectAccessKey + // should not return secret + out.ObjectSecret = "" // bs.AccessInfo.ObjectSecret return out } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index bb4a4f6809..53121cad9a 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -655,10 +655,6 @@ func (manager *SGuestManager) ListItemFilter( if len(query.OsType) > 0 { q = q.In("os_type", query.OsType) } - if len(query.OsDist) > 0 { - metaSQ := db.Metadata.Query().Equals("key", "os_distribution").In("value", query.OsDist).SubQuery() - q = q.Join(metaSQ, sqlchemy.Equals(q.Field("id"), metaSQ.Field("obj_id"))) - } if len(query.VcpuCount) > 0 { q = q.In("vcpu_count", query.VcpuCount) } @@ -784,13 +780,6 @@ func (manager *SGuestManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field if err == nil { return q, nil } - if field == "os_dist" { - metaQuery := db.Metadata.Query("obj_id", "value").Equals("key", "os_distribution").SubQuery() - q = q.AppendField(metaQuery.Field("value", field)).Distinct() - q = q.Join(metaQuery, sqlchemy.Equals(q.Field("id"), metaQuery.Field("obj_id"))) - q.GroupBy(metaQuery.Field("value")) - return q, nil - } guestnets := GuestnetworkManager.Query("guest_id", "network_id").SubQuery() q = q.LeftJoin(guestnets, sqlchemy.Equals(q.Field("id"), guestnets.Field("guest_id"))) q, err = manager.SNetworkResourceBaseManager.QueryDistinctExtraField(q, field) diff --git a/pkg/compute/models/instance_backup.go b/pkg/compute/models/instance_backup.go index a3fdd6294d..639172a83d 100644 --- a/pkg/compute/models/instance_backup.go +++ b/pkg/compute/models/instance_backup.go @@ -225,6 +225,7 @@ func (self *SInstanceBackup) getMoreDetails(userCred mcclient.TokenCredential, o CreatedAt: backups[i].CreatedAt, }) } + out.Size = self.SizeMb * 1024 * 1024 return out } @@ -366,6 +367,9 @@ func (self *SInstanceBackup) ToInstanceCreateInput(sourceInput *api.ServerCreate createInput.Disks[i].BackupId = isjs[index].DiskBackupId createInput.Disks[i].ImageId = "" createInput.Disks[i].SnapshotId = "" + if i < len(sourceInput.Disks) { + createInput.Disks[i].Backend = sourceInput.Disks[i].Backend + } } } diff --git a/pkg/compute/models/instance_snapshots.go b/pkg/compute/models/instance_snapshots.go index 51c92fb506..a9fdd4ffc8 100644 --- a/pkg/compute/models/instance_snapshots.go +++ b/pkg/compute/models/instance_snapshots.go @@ -79,7 +79,7 @@ type SInstanceSnapshot struct { // 套餐名称 InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"` // 主机快照磁盘容量和 - SizeMb int `nullable:"false"` + SizeMb int `nullable:"false" list:"user"` // 镜像ID ImageId string `width:"36" charset:"ascii" nullable:"true" list:"user"` // 是否保存内存 @@ -280,7 +280,7 @@ func (self *SInstanceSnapshot) getMoreDetails(userCred mcclient.TokenCredential, } } } else if guest != nil { - out.Size = self.SizeMb + out.Size = self.SizeMb * 1024 * 1024 disk, err := guest.GetSystemDisk() if err != nil { log.Errorf("unable to GetSystemDisk of guest %q", guest.GetId()) diff --git a/pkg/hostman/storageman/backup.go b/pkg/hostman/storageman/backup.go new file mode 100644 index 0000000000..f54a8ecda6 --- /dev/null +++ b/pkg/hostman/storageman/backup.go @@ -0,0 +1,294 @@ +// 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 storageman + +import ( + "context" + "fmt" + "io/ioutil" + "path" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/qemuimgfmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/consts" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage" + _ "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/nfs" + _ "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/object" + "yunion.io/x/onecloud/pkg/mcclient/auth" + identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +func ensureBackupDir() (string, error) { + backupTmpDir := options.HostOptions.LocalBackupTempPath + if !fileutils2.Exists(backupTmpDir) { + output, err := procutils.NewCommand("mkdir", "-p", backupTmpDir).Output() + if err != nil { + log.Errorf("mkdir %s failed: %s", backupTmpDir, output) + return "", errors.Wrapf(err, "mkdir %s failed: %s", backupTmpDir, output) + } + } + tmpFileDir, err := ioutil.TempDir(backupTmpDir, "backuptmp*") + if err != nil { + return "", errors.Wrap(err, "ioutil.TempDir") + } + return tmpFileDir, nil +} + +func cleanupDirOrFile(path string) { + log.Debugf("cleanup backup %s", path) + if output, err := procutils.NewCommand("rm", "-rf", path).Output(); err != nil { + log.Errorf("unable to rm %s: %s", path, output) + } +} + +func doBackupDisk(ctx context.Context, snapshotPath string, diskBackup *SDiskBackup) (int, error) { + backupTmpDir, err := ensureBackupDir() + if err != nil { + return 0, errors.Wrap(err, "ensureBackupDir") + } + defer cleanupDirOrFile(backupTmpDir) + + backupPath := path.Join(backupTmpDir, diskBackup.BackupId) + img, err := qemuimg.NewQemuImage(snapshotPath) + if err != nil { + return 0, errors.Wrap(err, "NewQemuImage snapshot") + } + encKey := "" + if len(diskBackup.EncryptKeyId) > 0 { + session := auth.GetSession(ctx, diskBackup.UserCred, consts.GetRegion()) + secKey, err := identity_modules.Credentials.GetEncryptKey(session, diskBackup.EncryptKeyId) + if err != nil { + return 0, errors.Wrap(err, "GetEncryptKey") + } + encKey = secKey.Key + } + if len(encKey) > 0 { + img.SetPassword(encKey) + } + newImage, err := img.Clone(backupPath, qemuimgfmt.QCOW2, true) + if err != nil { + return 0, errors.Wrap(err, "unable to backup snapshot") + } + + newImageSizeMb := newImage.GetActualSizeMB() + + backupStorage, err := backupstorage.GetBackupStorage(diskBackup.BackupStorageId, diskBackup.BackupStorageAccessInfo) + if err != nil { + return 0, errors.Wrap(err, "GetBackupStorage") + } + + err = backupStorage.SaveBackupFrom(ctx, backupPath, diskBackup.BackupId) + if err != nil { + return 0, errors.Wrap(err, "SaveBackupFrom") + } + + return newImageSizeMb, nil +} + +func doRestoreDisk(ctx context.Context, diskInfo api.DiskAllocateInput, destImgPath string, format string) error { + backupTmpDir, err := ensureBackupDir() + if err != nil { + return errors.Wrap(err, "ensureBackupDir") + } + defer cleanupDirOrFile(backupTmpDir) + + backupStorage, err := backupstorage.GetBackupStorage(diskInfo.Backup.BackupStorageId, diskInfo.Backup.BackupStorageAccessInfo) + if err != nil { + return errors.Wrap(err, "GetBackupStorage") + } + backupPath := path.Join(backupTmpDir, diskInfo.Backup.BackupId) + err = backupStorage.RestoreBackupTo(ctx, backupPath, diskInfo.Backup.BackupId) + if err != nil { + return errors.Wrap(err, "RestoreBackupTo") + } + img, err := qemuimg.NewQemuImage(backupPath) + if err != nil { + return errors.Wrap(err, "NewQemuImage") + } + if diskInfo.Encryption { + img.SetPassword(diskInfo.EncryptInfo.Key) + } + if len(format) == 0 { + format = qemuimgfmt.QCOW2.String() + } + _, err = img.Clone(destImgPath, qemuimgfmt.String2ImageFormat(format), false) + if err != nil { + return errors.Wrapf(err, "Clone %s", destImgPath) + } + return nil +} + +const ( + PackageDiskFilename = "disk" + PackageMetadataFilename = "metadata" +) + +func DoInstancePackBackup(ctx context.Context, backupInfo SStoragePackInstanceBackup) (string, error) { + backupTmpDir, err := ensureBackupDir() + if err != nil { + return "", errors.Wrap(err, "ensureBackupDir") + } + defer cleanupDirOrFile(backupTmpDir) + + backupStorage, err := backupstorage.GetBackupStorage(backupInfo.BackupStorageId, backupInfo.BackupStorageAccessInfo) + if err != nil { + return "", errors.Wrap(err, "GetBackupStorage") + } + + packagePath := path.Join(backupTmpDir, backupInfo.PackageName) + { + // prepare package Path + output, err := procutils.NewCommand("mkdir", "-p", packagePath).Output() + if err != nil { + log.Errorf("mkdir %s failed: %s", packagePath, output) + return "", errors.Wrapf(err, "mkdir %s failed: %s", packagePath, output) + } + } + { + // download disk files + for i, backupId := range backupInfo.BackupIds { + packageDiskPath := path.Join(packagePath, fmt.Sprintf("%s_%d", PackageDiskFilename, i)) + err := backupStorage.RestoreBackupTo(ctx, packageDiskPath, backupId) + if err != nil { + return "", errors.Wrapf(err, "RestoreBackupTo %s %s", backupId, packageDiskPath) + } + } + } + { + // save snapshot metadata + packageMetadataPath := path.Join(packagePath, PackageMetadataFilename) + err = ioutil.WriteFile(packageMetadataPath, []byte(jsonutils.Marshal(backupInfo.Metadata).PrettyString()), 0644) + if err != nil { + return "", errors.Wrapf(err, "unable to write to %s", packageMetadataPath) + } + } + tmpPkgFilename := path.Join(backupTmpDir, backupInfo.PackageName+".tar") + { + // tar + if output, err := procutils.NewRemoteCommandAsFarAsPossible("tar", "-cf", tmpPkgFilename, "-C", backupTmpDir, backupInfo.PackageName).Output(); err != nil { + log.Errorf("unable to 'tar -cf %s -C %s %s': %s", tmpPkgFilename, backupTmpDir, backupInfo.PackageName, output) + return "", errors.Wrap(err, "unable to tar") + } + } + + var finalPackageName string + tried := 0 + for { + var finalPackageFileName string + if tried == 0 { + finalPackageFileName = fmt.Sprintf("%s.tar", backupInfo.PackageName) + } else { + finalPackageFileName = fmt.Sprintf("%s-%d.tar", backupInfo.PackageName, tried) + } + exists, err := backupStorage.IsBackupInstanceExists(finalPackageFileName) + if err != nil { + return "", errors.Wrap(err, "IsBackupInstanceExists") + } + if exists { + tried++ + } else { + err := backupStorage.SaveBackupInstanceFrom(ctx, tmpPkgFilename, finalPackageFileName) + if err != nil { + return "", errors.Wrap(err, "SaveBackupInstanceFrom") + } + finalPackageName = finalPackageFileName + break + } + } + + return finalPackageName, nil +} + +func DoInstanceUnpackBackup(ctx context.Context, backupInfo SStorageUnpackInstanceBackup) ([]string, *api.InstanceBackupPackMetadata, error) { + backupTmpDir, err := ensureBackupDir() + if err != nil { + return nil, nil, errors.Wrap(err, "ensureBackupDir") + } + defer cleanupDirOrFile(backupTmpDir) + + packageName := backupInfo.PackageName + metadataOnly := false + if backupInfo.MetadataOnly != nil && *backupInfo.MetadataOnly { + metadataOnly = true + } + + backupStorage, err := backupstorage.GetBackupStorage(backupInfo.BackupStorageId, backupInfo.BackupStorageAccessInfo) + if err != nil { + return nil, nil, errors.Wrap(err, "GetBackupStorage") + } + + packageFilename := path.Join(backupTmpDir, packageName+".tar") + err = backupStorage.RestoreBackupInstanceTo(ctx, packageFilename, backupInfo.PackageName) + if err != nil { + return nil, nil, errors.Wrap(err, "RestoreBackupInstanceTo") + } + + // untar to temp dir + packagePath := path.Join(backupTmpDir, packageName) + log.Infof("unpack to %s", packagePath) + untarArgs := []string{ + "-xf", packageFilename, "-C", backupTmpDir, + } + if metadataOnly { + untarArgs = append(untarArgs, fmt.Sprintf("%s/metadata", packageName)) + } else { + untarArgs = append(untarArgs, packageName) + } + if output, err := procutils.NewCommand("tar", untarArgs...).Output(); err != nil { + log.Errorf("unable to 'tar -xf %s -C %s %s': %s", packageFilename, backupTmpDir, packageName, output) + return nil, nil, errors.Wrap(err, "unable to untar") + } + + // unpack metadata + packageMetadataPath := path.Join(packagePath, PackageMetadataFilename) + metadataBytes, err := ioutil.ReadFile(packageMetadataPath) + if err != nil { + return nil, nil, errors.Wrap(err, "unable to read metadata file") + } + metadataJson, err := jsonutils.Parse(metadataBytes) + if err != nil { + return nil, nil, errors.Wrap(err, "unable to parse string to json") + } + metadata := &api.InstanceBackupPackMetadata{} + err = metadataJson.Unmarshal(metadata) + if err != nil { + return nil, nil, errors.Wrap(err, "unmarshal backup metadata") + } + + // copy disk files only if !metadataOnly + backupIds := make([]string, len(metadata.DiskMetadatas)) + if !metadataOnly { + for i := 0; i < len(metadata.DiskMetadatas); i++ { + backupId := db.DefaultUUIDGenerator() + backupIds[i] = backupId + packageDiskPath := path.Join(packagePath, fmt.Sprintf("%s_%d", PackageDiskFilename, i)) + err := backupStorage.SaveBackupFrom(ctx, packageDiskPath, backupId) + if err != nil { + return nil, nil, errors.Wrapf(err, "SaveBackupFrom %s %s", packageDiskPath, backupId) + } + } + } + + return backupIds, metadata, nil +} diff --git a/pkg/hostman/storageman/backupstorage/backup_storage.go b/pkg/hostman/storageman/backupstorage/backup_storage.go index a77d8af9bb..cc6472a227 100644 --- a/pkg/hostman/storageman/backupstorage/backup_storage.go +++ b/pkg/hostman/storageman/backupstorage/backup_storage.go @@ -16,46 +16,82 @@ package backupstorage import ( "context" - "fmt" "sync" "yunion.io/x/jsonutils" - "yunion.io/x/pkg/util/qemuimgfmt" - - api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/pkg/errors" ) +type IBackupStorageFactory interface { + NewBackupStore(storeId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) +} + 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 qemuimgfmt.TImageFormat, backupId string) error - ConvertFrom(srcPath string, format qemuimgfmt.TImageFormat, backupId string) (int, error) - InstancePack(ctx context.Context, packageName string, backupIds []string, metadata *api.InstanceBackupPackMetadata) (string, error) - InstanceUnpack(ctx context.Context, packageName string, metadataOnly bool) ([]string, *api.InstanceBackupPackMetadata, error) + // 从指定路径拷贝磁盘文件到备份存储 + SaveBackupFrom(ctx context.Context, srcFilename string, bakcupId string) error + // 将备份backupId对应的备份文件拷贝到指定的文件路径 + RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error + // 删除备份 + RemoveBackup(ctx context.Context, backupId string) error + // 备份是否存在 + IsBackupExists(backupId string) (bool, error) + + // 从指定路径拷贝主机备份文件到备份存储 + SaveBackupInstanceFrom(ctx context.Context, srcFilename string, bakcupInstanceId string) error + // 将备份backupId对应的备份文件拷贝到指定的文件路径 + RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupInstanceId string) error + // 删除备份 + RemoveBackupInstance(ctx context.Context, backupInstanceId string) error + // 备份是否存在 + IsBackupInstanceExists(backupInstanceId string) (bool, error) + + // ConvertTo(destPath string, format qemuimgfmt.TImageFormat, backupId string) error + // ConvertFrom(srcPath string, format qemuimgfmt.TImageFormat, backupId string) (int, error) + // InstancePack(ctx context.Context, packageName string, backupIds []string, metadata *api.InstanceBackupPackMetadata) (string, error) + // InstanceUnpack(ctx context.Context, packageName string, metadataOnly bool) ([]string, *api.InstanceBackupPackMetadata, error) + + // 存储是否在线 IsOnline() (bool, string, error) } -var backupStoragePool *sync.Map = &sync.Map{} +var factories []IBackupStorageFactory +var backupStoragePool map[string]IBackupStorage +var backupStorageLock *sync.Mutex -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") +func init() { + backupStorageLock = &sync.Mutex{} + backupStoragePool = make(map[string]IBackupStorage) +} + +func RegisterFactory(factory IBackupStorageFactory) { + factories = append(factories, factory) +} + +func newBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) { + errs := make([]error, 0) + for _, factory := range factories { + store, err := factory.NewBackupStore(backupStroageId, backupStorageAccessInfo) + if err == nil { + return store, nil + } else { + errs = append(errs, err) + } } - 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 + return nil, errors.NewAggregate(errs) } func GetBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) { - bs, err := NewBackupStorage(backupStroageId, backupStorageAccessInfo) - if err != nil { - return nil, err + backupStorageLock.Lock() + defer backupStorageLock.Unlock() + + if ibs, ok := backupStoragePool[backupStroageId]; !ok { + bs, err := newBackupStorage(backupStroageId, backupStorageAccessInfo) + if err != nil { + return nil, errors.Wrap(err, "newBackupStorage") + } + backupStoragePool[backupStroageId] = bs + return bs, nil + } else { + return ibs, nil } - ibs, _ := backupStoragePool.LoadOrStore(backupStroageId, bs) - return ibs.(IBackupStorage), nil } diff --git a/pkg/hostman/storageman/backupstorage/nfs/doc.go b/pkg/hostman/storageman/backupstorage/nfs/doc.go new file mode 100644 index 0000000000..f6eb94de88 --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/nfs/doc.go @@ -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 nfs // import "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/nfs" diff --git a/pkg/hostman/storageman/backupstorage/nfs/factory.go b/pkg/hostman/storageman/backupstorage/nfs/factory.go new file mode 100644 index 0000000000..9484f0296b --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/nfs/factory.go @@ -0,0 +1,45 @@ +// 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 nfs + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage" + "yunion.io/x/onecloud/pkg/httperrors" +) + +type sNfsBackupStorageFactory struct{} + +func (factory *sNfsBackupStorageFactory) NewBackupStore(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (backupstorage.IBackupStorage, error) { + accessInfo := api.SBackupStorageAccessInfo{} + err := backupStorageAccessInfo.Unmarshal(&accessInfo) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal access info") + } + if len(accessInfo.NfsHost) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "need nfs_host in backup_storage_access_info") + } + if len(accessInfo.NfsSharedDir) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "need nfs_shared_dir in backup_storage_access_info") + } + return newNFSBackupStorage(backupStroageId, accessInfo.NfsHost, accessInfo.NfsSharedDir), nil +} + +func init() { + backupstorage.RegisterFactory(&sNfsBackupStorageFactory{}) +} diff --git a/pkg/hostman/storageman/backupstorage/nfs/nfs.go b/pkg/hostman/storageman/backupstorage/nfs/nfs.go new file mode 100644 index 0000000000..69a5b35065 --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/nfs/nfs.go @@ -0,0 +1,226 @@ +// 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 nfs + +import ( + "context" + "fmt" + "path" + "sync" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/procutils" +) + +var ErrorBackupStorageOffline error = errors.Error(api.BackupStorageOffline) + +type SNFSBackupStorage struct { + BackupStorageId string + Path string + NfsHost string + NfsSharedDir string + lock *sync.Mutex + userNumber int +} + +func newNFSBackupStorage(backupStorageId, nfsHost, nfsSharedDir string) *SNFSBackupStorage { + return &SNFSBackupStorage{ + BackupStorageId: backupStorageId, + NfsHost: nfsHost, + NfsSharedDir: nfsSharedDir, + Path: path.Join(options.HostOptions.LocalBackupStoragePath, backupStorageId), + lock: &sync.Mutex{}, + } +} + +func (s *SNFSBackupStorage) getBackupDir() string { + return path.Join(s.Path, "backups") +} + +func (s *SNFSBackupStorage) getBackupDiskPath(backupId string) string { + return path.Join(s.getBackupDir(), backupId) +} + +func (s *SNFSBackupStorage) getPackageDir() string { + return path.Join(s.Path, "backuppacks") +} + +func (s *SNFSBackupStorage) getBackupInstancePath(backupInstanceId string) string { + return path.Join(s.getPackageDir(), backupInstanceId) +} + +func (s *SNFSBackupStorage) checkAndMount() error { + s.lock.Lock() + defer s.lock.Unlock() + 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 { + s.userNumber++ + 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 errors.Wrap(ErrorBackupStorageOffline, err.Error()) + } + 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) + } + } + packageDir := s.getPackageDir() + if !fileutils2.Exists(packageDir) { + output, err := procutils.NewCommand("mkdir", "-p", packageDir).Output() + if err != nil { + log.Errorf("mkdir %s failed: %s", packageDir, output) + return errors.Wrapf(err, "mkdir %s failed: %s", packageDir, output) + } + } + s.userNumber++ + return nil +} + +func (s *SNFSBackupStorage) unMount() error { + s.lock.Lock() + defer s.lock.Unlock() + s.userNumber-- + if s.userNumber > 0 { + return nil + } + 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) SaveBackupFrom(ctx context.Context, srcFilename string, backupId string) error { + return s.saveFile(ctx, srcFilename, backupId, s.getBackupDiskPath) +} + +func (s *SNFSBackupStorage) SaveBackupInstanceFrom(ctx context.Context, srcFilename string, backupId string) error { + return s.saveFile(ctx, srcFilename, backupId, s.getBackupDiskPath) +} + +func (s *SNFSBackupStorage) saveFile(ctx context.Context, srcFilename string, id string, getPathFunc func(string) string) error { + err := s.checkAndMount() + if err != nil { + return errors.Wrap(err, "unable to checkAndMount") + } + defer s.unMount() + + targetFilename := getPathFunc(id) + 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) RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error { + return s.restoreFile(ctx, targetFilename, backupId, s.getBackupDiskPath) +} + +func (s *SNFSBackupStorage) RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupId string) error { + return s.restoreFile(ctx, targetFilename, backupId, s.getBackupInstancePath) +} + +func (s *SNFSBackupStorage) restoreFile(ctx context.Context, targetFilename string, id string, getPathFunc func(string) string) error { + err := s.checkAndMount() + if err != nil { + return errors.Wrap(err, "unable to checkAndMount") + } + defer s.unMount() + + srcFilename := getPathFunc(id) + 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) RemoveBackup(ctx context.Context, backupId string) error { + return s.removeFile(ctx, backupId, s.getBackupDiskPath) +} + +func (s *SNFSBackupStorage) RemoveBackupInstance(ctx context.Context, backupId string) error { + return s.removeFile(ctx, backupId, s.getBackupInstancePath) +} + +func (s *SNFSBackupStorage) removeFile(ctx context.Context, id string, getPathFunc func(id string) string) error { + err := s.checkAndMount() + if err != nil { + return errors.Wrap(err, "unable to checkAndMount") + } + defer s.unMount() + + filename := getPathFunc(id) + 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) IsBackupExists(backupId string) (bool, error) { + return s.isFileExists(backupId, s.getBackupDiskPath) +} + +func (s *SNFSBackupStorage) IsBackupInstanceExists(backupId string) (bool, error) { + return s.isFileExists(backupId, s.getBackupInstancePath) +} + +func (s *SNFSBackupStorage) isFileExists(id string, getPathFunc func(id string) string) (bool, error) { + err := s.checkAndMount() + if err != nil { + return false, errors.Wrap(err, "unable to checkAndMount") + } + defer s.unMount() + + filename := getPathFunc(id) + return fileutils2.Exists(filename), nil +} + +func (s *SNFSBackupStorage) IsOnline() (bool, string, error) { + err := s.checkAndMount() + if errors.Cause(err) == ErrorBackupStorageOffline { + return false, err.Error(), nil + } + if err != nil { + return false, "", err + } + s.unMount() + return true, "", nil +} diff --git a/pkg/hostman/storageman/backupstorage/object/doc.go b/pkg/hostman/storageman/backupstorage/object/doc.go new file mode 100644 index 0000000000..3adcdd8b99 --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/object/doc.go @@ -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 object // import "yunion.io/x/onecloud/pkg/hostman/storageman/object" diff --git a/pkg/hostman/storageman/backupstorage/object/factory.go b/pkg/hostman/storageman/backupstorage/object/factory.go new file mode 100644 index 0000000000..5c2c4b8f80 --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/object/factory.go @@ -0,0 +1,48 @@ +// 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 object + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage" + "yunion.io/x/onecloud/pkg/httperrors" +) + +type sObjectBackupStorageFactory struct{} + +func (factory *sObjectBackupStorageFactory) NewBackupStore(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (backupstorage.IBackupStorage, error) { + accessInfo := api.SBackupStorageAccessInfo{} + err := backupStorageAccessInfo.Unmarshal(&accessInfo) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal access info") + } + if len(accessInfo.ObjectBucketUrl) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_bucket_url in backup_storage_access_info") + } + if len(accessInfo.ObjectAccessKey) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_access_key in backup_storage_access_info") + } + if len(accessInfo.ObjectSecret) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_secret in backup_storage_access_info") + } + return newObjectBackupStorage(backupStroageId, accessInfo.ObjectBucketUrl, accessInfo.ObjectAccessKey, accessInfo.ObjectSecret) +} + +func init() { + backupstorage.RegisterFactory(&sObjectBackupStorageFactory{}) +} diff --git a/pkg/hostman/storageman/backupstorage/object/object.go b/pkg/hostman/storageman/backupstorage/object/object.go new file mode 100644 index 0000000000..ef8fc22869 --- /dev/null +++ b/pkg/hostman/storageman/backupstorage/object/object.go @@ -0,0 +1,212 @@ +// 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 object + +import ( + "context" + "fmt" + "net/url" + "os" + "strings" + + "yunion.io/x/cloudmux/pkg/cloudprovider" + "yunion.io/x/cloudmux/pkg/multicloud/objectstore" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/streamutils" +) + +type SObjectBackupStorage struct { + BackupStorageId string + + endpoint string + bucket string + accessKey string + secret string + + store *objectstore.SObjectStoreClient +} + +func newObjectBackupStorage(backupStorageId, bucketUrl, accessKey, secret string) (*SObjectBackupStorage, error) { + bucket, endpoint, err := parseBucketUrl(bucketUrl) + if err != nil { + return nil, errors.Wrapf(err, "parseBucketUrl %s", bucketUrl) + } + cfg := objectstore.NewObjectStoreClientConfig(endpoint, accessKey, secret) + store, err := objectstore.NewObjectStoreClient(cfg) + if err != nil { + return nil, errors.Wrap(err, "NewObjectStoreClient") + } + return &SObjectBackupStorage{ + BackupStorageId: backupStorageId, + + endpoint: endpoint, + bucket: bucket, + accessKey: accessKey, + secret: secret, + + store: store, + }, nil +} + +func parseBucketUrl(bucketUrl string) (string, string, error) { + bu, err := url.Parse(bucketUrl) + if err != nil { + return "", "", errors.Wrapf(err, "ur.Parse %s", bucketUrl) + } + for len(bu.Path) > 0 && bu.Path[0] == '/' { + bu.Path = bu.Path[1:] + } + if len(bu.Path) > 0 { + bucket := strings.TrimRight(bu.Path, "/") + bu.Path = "" + return bucket, fmt.Sprintf("%s://%s", bu.Scheme, bu.Host), nil + } else { + parts := strings.Split(bu.Host, ".") + if len(parts) < 3 { + return "", "", errors.Wrapf(errors.ErrInvalidFormat, "host %s should have at least 3 segments", bu.Host) + } + return parts[0], fmt.Sprintf("%s//%s", bu.Scheme, bu.Host), nil + } +} + +const backupPathPrefix = "backups" +const backupInstancePathPrefix = "backuppacks" + +func (s *SObjectBackupStorage) getBackupKey(backupId string) string { + return fmt.Sprintf("%s/%s", backupPathPrefix, backupId) +} + +func (s *SObjectBackupStorage) getBackupInstanceKey(backupInstancePackName string) string { + return fmt.Sprintf("%s/%s", backupInstancePathPrefix, backupInstancePackName) +} + +func (s *SObjectBackupStorage) getBucket() (cloudprovider.ICloudBucket, error) { + bucket, err := s.store.GetIRegion().GetIBucketByName(s.bucket) + if err != nil { + return nil, errors.Wrap(err, "IBucketExist") + } + return bucket, nil +} + +func (s *SObjectBackupStorage) SaveBackupFrom(ctx context.Context, srcFilename string, backupId string) error { + return s.saveObject(ctx, srcFilename, backupId, s.getBackupKey) +} + +func (s *SObjectBackupStorage) SaveBackupInstanceFrom(ctx context.Context, srcFilename string, backupId string) error { + return s.saveObject(ctx, srcFilename, backupId, s.getBackupInstanceKey) +} + +func (s *SObjectBackupStorage) saveObject(ctx context.Context, srcFilename string, id string, getKeyFunc func(string) string) error { + bucket, err := s.getBucket() + if err != nil { + return errors.Wrap(err, "getBucket") + } + fileInfo, err := os.Stat(srcFilename) + if err != nil { + return errors.Wrapf(err, "stat %s", srcFilename) + } + file, err := os.Open(srcFilename) + if err != nil { + return errors.Wrapf(err, "Open %s", srcFilename) + } + defer file.Close() + + err = cloudprovider.UploadObject(ctx, bucket, getKeyFunc(id), 200*1024*1024, file, fileInfo.Size(), cloudprovider.ACLPrivate, "", nil, false) + if err != nil { + return errors.Wrapf(err, "UploadObject %s %s", srcFilename, getKeyFunc(id)) + } + + return nil +} + +func (s *SObjectBackupStorage) RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error { + return s.restoreObject(ctx, targetFilename, backupId, s.getBackupKey) +} + +func (s *SObjectBackupStorage) RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupId string) error { + return s.restoreObject(ctx, targetFilename, backupId, s.getBackupInstanceKey) +} + +func (s *SObjectBackupStorage) restoreObject(ctx context.Context, targetFilename string, id string, getKeyFunc func(string) string) error { + bucket, err := s.getBucket() + if err != nil { + return errors.Wrap(err, "getBucket") + } + reader, err := bucket.GetObject(ctx, getKeyFunc(id), nil) + if err != nil { + return errors.Wrap(err, "GetObject") + } + file, err := os.OpenFile(targetFilename, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return errors.Wrapf(err, "OpenFile %s", targetFilename) + } + defer file.Close() + _, err = streamutils.StreamPipe(reader, file, false, nil) + if err != nil { + return errors.Wrap(err, "StreamPipe") + } + return nil +} + +func (s *SObjectBackupStorage) RemoveBackup(ctx context.Context, backupId string) error { + return s.removeObject(ctx, backupId, s.getBackupKey) +} + +func (s *SObjectBackupStorage) RemoveBackupInstance(ctx context.Context, backupId string) error { + return s.removeObject(ctx, backupId, s.getBackupInstanceKey) +} + +func (s *SObjectBackupStorage) removeObject(ctx context.Context, id string, getKeyFunc func(string) string) error { + bucket, err := s.getBucket() + if err != nil { + return errors.Wrap(err, "getBucket") + } + err = bucket.DeleteObject(ctx, getKeyFunc(id)) + if err != nil { + return errors.Wrap(err, "DeleteObject") + } + return nil +} + +func (s *SObjectBackupStorage) IsBackupExists(backupId string) (bool, error) { + return s.isObjectExists(backupId, s.getBackupKey) +} + +func (s *SObjectBackupStorage) IsBackupInstanceExists(backupId string) (bool, error) { + return s.isObjectExists(backupId, s.getBackupInstanceKey) +} + +func (s *SObjectBackupStorage) isObjectExists(id string, getKeyFunc func(string) string) (bool, error) { + bucket, err := s.getBucket() + if err != nil { + return false, errors.Wrap(err, "getBucket") + } + _, err = cloudprovider.GetIObject(bucket, getKeyFunc(id)) + if err != nil { + if errors.Cause(err) == errors.ErrNotFound { + return false, nil + } + return false, errors.Wrap(err, "GetIObject") + } + return true, nil +} + +func (s *SObjectBackupStorage) IsOnline() (bool, string, error) { + exist, err := s.store.GetIRegion().IBucketExist(s.bucket) + if err != nil { + return false, "", errors.Wrap(err, "IBucketExist") + } + return exist, "", nil +} diff --git a/pkg/hostman/storageman/disk_base.go b/pkg/hostman/storageman/disk_base.go index 0b60dad986..f720bb160b 100644 --- a/pkg/hostman/storageman/disk_base.go +++ b/pkg/hostman/storageman/disk_base.go @@ -38,12 +38,14 @@ type IDisk interface { Probe() error GetPath() string GetFormat() (string, error) - GetSnapshotDir() string GetDiskDesc() jsonutils.JSONObject GetDiskSetupScripts(idx int) string - GetSnapshotLocation() string OnRebuildRoot(ctx context.Context, params api.DiskAllocateInput) error + + GetSnapshotDir() string DoDeleteSnapshot(snapshotId string) error + GetSnapshotLocation() string + GetStorage() IStorage DeleteAllSnapshot(skipRecycle bool) error @@ -69,7 +71,7 @@ type IDisk interface { DeployGuestFs(diskInfo *deployapi.DiskInfo, guestDesc *desc.SGuestDesc, deployInfo *deployapi.DeployInfo) (jsonutils.JSONObject, error) - GetBackupDir() string + // GetBackupDir() string DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) IsFile() bool @@ -212,10 +214,6 @@ 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") } diff --git a/pkg/hostman/storageman/disk_local.go b/pkg/hostman/storageman/disk_local.go index 5cb62f5167..7e779d5eb7 100644 --- a/pkg/hostman/storageman/disk_local.go +++ b/pkg/hostman/storageman/disk_local.go @@ -27,12 +27,10 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/appctx" "yunion.io/x/pkg/errors" - "yunion.io/x/pkg/util/qemuimgfmt" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/compute" - "yunion.io/x/onecloud/pkg/cloudcommon/consts" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" "yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient" "yunion.io/x/onecloud/pkg/hostman/hostutils" @@ -40,7 +38,6 @@ import ( "yunion.io/x/onecloud/pkg/hostman/storageman/remotefile" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient/auth" - identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/fuseutils" "yunion.io/x/onecloud/pkg/util/procutils" @@ -48,6 +45,8 @@ import ( "yunion.io/x/onecloud/pkg/util/seclib2" ) +var _ IDisk = (*SLocalDisk)(nil) + var _ALTER_SUFFIX_ = ".alter" type SLocalDisk struct { @@ -93,10 +92,6 @@ 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() } @@ -408,52 +403,18 @@ func (d *SLocalDisk) PostCreateFromImageFuse() { } func (d *SLocalDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - diskBackup := params.(*SDiskBakcup) - - encKey := "" - if len(diskBackup.EncryptKeyId) > 0 { - session := auth.GetSession(ctx, diskBackup.UserCred, consts.GetRegion()) - secKey, err := identity_modules.Credentials.GetEncryptKey(session, diskBackup.EncryptKeyId) - if err != nil { - return nil, errors.Wrap(err, "GetEncryptKey") - } - encKey = secKey.Key - } + diskBackup := params.(*SDiskBackup) snapshotDir := d.GetSnapshotDir() snapshotPath := path.Join(snapshotDir, diskBackup.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, diskBackup.BackupId) - img, err := qemuimg.NewQemuImage(snapshotPath) + + size, err := doBackupDisk(ctx, snapshotPath, diskBackup) if err != nil { - log.Errorln(err) - procutils.NewCommand("mv", "-f", backupPath, d.getPath()).Run() - return nil, err - } - if len(encKey) > 0 { - img.SetPassword(encKey) - } - newImage, err := img.Clone(backupPath, qemuimgfmt.QCOW2, true) - if err != nil { - return nil, errors.Wrap(err, "unable to backup snapshot") - } - _, err = d.Storage.StorageBackup(ctx, &SStorageBackup{ - BackupId: diskBackup.BackupId, - BackupStorageId: diskBackup.BackupStorageId, - BackupStorageAccessInfo: diskBackup.BackupStorageAccessInfo, - }) - if err != nil { - return nil, errors.Wrap(err, "unable to SStorageBackup") + return nil, errors.Wrap(err, "doBackupDisk") } + data := jsonutils.NewDict() - data.Set("size_mb", jsonutils.NewInt(int64(newImage.GetActualSizeMB()))) + data.Set("size_mb", jsonutils.NewInt(int64(size))) return data, nil } @@ -712,8 +673,12 @@ func (d *SLocalDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, e return nil, "", false, nil } +func (d *SLocalDisk) GetSnapshotPath(snapshotId string) string { + return path.Join(d.GetSnapshotDir(), snapshotId) +} + func (d *SLocalDisk) DoDeleteSnapshot(snapshotId string) error { - snapshotPath := path.Join(d.GetSnapshotDir(), snapshotId) + snapshotPath := d.GetSnapshotPath(snapshotId) return d.Storage.DeleteDiskfile(snapshotPath, false) } diff --git a/pkg/hostman/storageman/disk_lvm.go b/pkg/hostman/storageman/disk_lvm.go index 2077ff78b3..30258b771a 100644 --- a/pkg/hostman/storageman/disk_lvm.go +++ b/pkg/hostman/storageman/disk_lvm.go @@ -38,6 +38,8 @@ import ( "yunion.io/x/onecloud/pkg/util/qemuimg" ) +var _ IDisk = (*SLVMDisk)(nil) + type SLVMDisk struct { SBaseDisk } @@ -332,6 +334,10 @@ func (d *SLVMDisk) createFromTemplate( return d.GetDiskDesc(), nil } +func (d *SLVMDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + return nil, errors.ErrNotImplemented +} + func NewLVMDisk(storage IStorage, id string) *SLVMDisk { return &SLVMDisk{ SBaseDisk: *NewBaseDisk(storage, id), diff --git a/pkg/hostman/storageman/disk_nas.go b/pkg/hostman/storageman/disk_nas.go index e4cbefabb6..2c13998d61 100644 --- a/pkg/hostman/storageman/disk_nas.go +++ b/pkg/hostman/storageman/disk_nas.go @@ -31,6 +31,8 @@ import ( "yunion.io/x/onecloud/pkg/util/qemuimg" ) +var _ IDisk = (*SNasDisk)(nil) + type SNasDisk struct { SLocalDisk } diff --git a/pkg/hostman/storageman/disk_nvme.go b/pkg/hostman/storageman/disk_nvme.go index f077bb48d0..15ca00b636 100644 --- a/pkg/hostman/storageman/disk_nvme.go +++ b/pkg/hostman/storageman/disk_nvme.go @@ -18,11 +18,14 @@ import ( "context" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/qemuimgfmt" "yunion.io/x/onecloud/pkg/apis" ) +var _ IDisk = (*SNVMEDisk)(nil) + type SNVMEDisk struct { SBaseDisk } @@ -61,6 +64,10 @@ func (d *SNVMEDisk) Probe() error { return nil } +func (d *SNVMEDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { + return nil, errors.ErrNotImplemented +} + func NewNVMEDisk(storage IStorage, id string) *SNVMEDisk { return &SNVMEDisk{ SBaseDisk: *NewBaseDisk(storage, id), diff --git a/pkg/hostman/storageman/disk_rbd.go b/pkg/hostman/storageman/disk_rbd.go index 771434acb7..b89defeaf3 100644 --- a/pkg/hostman/storageman/disk_rbd.go +++ b/pkg/hostman/storageman/disk_rbd.go @@ -34,6 +34,8 @@ import ( "yunion.io/x/onecloud/pkg/util/seclib2" ) +var _ IDisk = (*SRBDDisk)(nil) + type SRBDDisk struct { SBaseDisk } @@ -79,9 +81,8 @@ func (d *SRBDDisk) GetSnapshotDir() string { func (d *SRBDDisk) GetDiskDesc() jsonutils.JSONObject { storage := d.Storage.(*SRbdStorage) - storageConf := d.Storage.GetStorageConf() - pool, _ := storageConf.GetString("pool") - sizeMb, _ := storage.getImageSizeMb(pool, d.Id) + + sizeMb, _ := storage.getImageSizeMb(d.Id) desc := map[string]interface{}{ "disk_id": d.Id, "disk_format": "raw", @@ -102,9 +103,8 @@ func (d *SRBDDisk) DeleteAllSnapshot(skipRecycle bool) error { func (d *SRBDDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { p := params.(api.DiskDeleteInput) storage := d.Storage.(*SRbdStorage) - storageConf := d.Storage.GetStorageConf() - pool, _ := storageConf.GetString("pool") - return nil, storage.deleteImage(pool, d.Id, p.SkipRecycle != nil && *p.SkipRecycle) + + return nil, storage.deleteImage(d.Id, p.SkipRecycle != nil && *p.SkipRecycle) } func (d *SRBDDisk) OnRebuildRoot(ctx context.Context, params api.DiskAllocateInput) error { @@ -124,10 +124,8 @@ func (d *SRBDDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JS return nil, hostutils.ParamsError } storage := d.Storage.(*SRbdStorage) - storageConf := d.Storage.GetStorageConf() - pool, _ := storageConf.GetString("pool") sizeMb, _ := diskInfo.Int("size") - if err := storage.resizeImage(pool, d.Id, uint64(sizeMb)); err != nil { + if err := storage.resizeImage(d.Id, uint64(sizeMb)); err != nil { return nil, err } @@ -161,8 +159,7 @@ func (d *SRBDDisk) PrepareSaveToGlance(ctx context.Context, params interface{}) func (d *SRBDDisk) CleanupSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - return nil, storage.deleteSnapshot(pool, d.Id, "") + return nil, storage.deleteSnapshot(d.Id, "") } func (d *SRBDDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, error) { @@ -203,9 +200,9 @@ func (d *SRBDDisk) createFromTemplate(ctx context.Context, imageId, format strin defer imageCacheManager.ReleaseImage(ctx, imageId) storage := d.Storage.(*SRbdStorage) - destPool, _ := storage.StorageConf.GetString("pool") - storage.deleteImage(destPool, d.Id, false) //重装系统时,需要删除以前的系统盘 - err = storage.cloneImage(ctx, imageCacheManager.GetPath(), imageCache.GetName(), destPool, d.Id) + + storage.deleteImage(d.Id, false) //重装系统时,需要删除以前的系统盘 + err = storage.cloneImage(ctx, imageCacheManager.GetPath(), imageCache.GetName(), storage.Pool, d.Id) if err != nil { return nil, errors.Wrapf(err, "cloneImage(%s)", imageCache.GetName()) } @@ -221,8 +218,8 @@ func (d *SRBDDisk) CreateRaw(ctx context.Context, sizeMb int, diskFromat string, return nil, errors.Wrap(httperrors.ErrNotSupported, "rbd not support encryptInfo") } storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - if err := storage.createImage(pool, diskId, uint64(sizeMb)); err != nil { + + if err := storage.createImage(diskId, uint64(sizeMb)); err != nil { return nil, err } @@ -241,10 +238,10 @@ func (d *SRBDDisk) PostCreateFromImageFuse() { } func (d *SRBDDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { - diskBackup := params.(*SDiskBakcup) + diskBackup := params.(*SDiskBackup) storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - sizeMb, err := storage.createBackup(pool, d.Id, diskBackup.SnapshotId, diskBackup.BackupId, diskBackup.BackupStorageId, diskBackup.BackupStorageAccessInfo) + + sizeMb, err := storage.createBackup(ctx, d.Id, diskBackup) if err != nil { return nil, err } @@ -255,14 +252,12 @@ func (d *SRBDDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutil func (d *SRBDDisk) CreateSnapshot(snapshotId string, encryptKey string, encFormat qemuimg.TEncryptFormat, encAlg seclib2.TSymEncAlg) error { storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - return storage.createSnapshot(pool, d.Id, snapshotId) + return storage.createSnapshot(d.Id, snapshotId) } func (d *SRBDDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error { storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - return storage.deleteSnapshot(pool, d.Id, snapshotId) + return storage.deleteSnapshot(d.Id, snapshotId) } func (d *SRBDDisk) DiskSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { @@ -298,8 +293,8 @@ func (d *SRBDDisk) ResetFromSnapshot(ctx context.Context, params interface{}) (j diskId = d.GetId() } storage := d.Storage.(*SRbdStorage) - pool, _ := storage.StorageConf.GetString("pool") - return nil, storage.resetDisk(pool, diskId, resetParams.SnapshotId) + + return nil, storage.resetDisk(diskId, resetParams.SnapshotId) } func (d *SRBDDisk) CreateFromRbdSnapshot(ctx context.Context, snapshot, srcDiskId, srcPool string) error { diff --git a/pkg/hostman/storageman/diskhandlers/diskhandler.go b/pkg/hostman/storageman/diskhandlers/diskhandler.go index fdba8dabbd..452e63b830 100644 --- a/pkg/hostman/storageman/diskhandlers/diskhandler.go +++ b/pkg/hostman/storageman/diskhandlers/diskhandler.go @@ -379,7 +379,7 @@ func diskStorageBackupRecovery(ctx context.Context, storage storageman.IStorage, } func diskBackup(ctx context.Context, userCred mcclient.TokenCredential, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) { - backupInfo := &storageman.SDiskBakcup{} + backupInfo := &storageman.SDiskBackup{} err := body.Unmarshal(backupInfo) if err != nil { return nil, errors.Wrap(err, "JsonUnmarshal") diff --git a/pkg/hostman/storageman/imagecache_rbd.go b/pkg/hostman/storageman/imagecache_rbd.go index d3c7b44863..14b3f395c2 100644 --- a/pkg/hostman/storageman/imagecache_rbd.go +++ b/pkg/hostman/storageman/imagecache_rbd.go @@ -102,9 +102,22 @@ func (r *SRbdImageCache) Release() { func (r *SRbdImageCache) Remove(ctx context.Context) error { imageCacheManger := r.Manager.(*SRbdImageCacheManager) - storage := imageCacheManger.storage.(*SRbdStorage) - if err := storage.deleteImage(r.Manager.GetPath(), r.GetName(), false); err != nil { - return err + + cli, err := imageCacheManger.getCephClient() + if err != nil { + return errors.Wrap(err, "getCephClient") + } + + img, err := cli.GetImage(r.GetName()) + if err != nil { + if errors.Cause(err) == errors.ErrNotFound { + return nil + } + return errors.Wrapf(err, "GetImage") + } + err = img.Delete() + if err != nil { + return errors.Wrap(err, "Delete") } go func() { @@ -119,13 +132,30 @@ func (r *SRbdImageCache) Remove(ctx context.Context) error { func (r *SRbdImageCache) GetDesc() *remotefile.SImageDesc { imageCacheManger := r.Manager.(*SRbdImageCacheManager) - storage := imageCacheManger.storage.(*SRbdStorage) - size, _ := storage.getImageSizeMb(imageCacheManger.Pool, r.GetName()) - return &remotefile.SImageDesc{ - Size: int64(size), + desc := &remotefile.SImageDesc{ + Size: -1, Name: r.imageName, } + + cli, err := imageCacheManger.getCephClient() + if err != nil { + log.Errorf("getCephClient fail %s", err) + return desc + } + img, err := cli.GetImage(r.GetName()) + if err != nil { + log.Errorf("getName fail %s", err) + return desc + } + info, err := img.GetInfo() + if err != nil { + log.Errorf("GetInfo fail %s", err) + return desc + } + desc.Size = info.SizeByte / 1024 / 1024 + + return desc } func (r *SRbdImageCache) GetImageId() string { diff --git a/pkg/hostman/storageman/imagecachemanager_rbd.go b/pkg/hostman/storageman/imagecachemanager_rbd.go index 4def819083..d853d1b6b3 100644 --- a/pkg/hostman/storageman/imagecachemanager_rbd.go +++ b/pkg/hostman/storageman/imagecachemanager_rbd.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/hostman/hostutils" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/util/cephutils" ) type SRbdImageCacheManager struct { @@ -69,12 +70,23 @@ func init() { registerimageCacheManagerFactory(&SRbdImageCacheManagerFactory{}) } +func (c *SRbdImageCacheManager) getCephClient() (*cephutils.CephClient, error) { + storage := c.storage.(*SRbdStorage) + return storage.getCephClient(c.Pool) +} + func (c *SRbdImageCacheManager) loadCache(ctx context.Context) { lockman.LockRawObject(ctx, "RBD", "image-cache") defer lockman.ReleaseRawObject(ctx, "RBD", "image-cache") - storage := c.storage.(*SRbdStorage) - images, err := storage.listImages(c.Pool) + cli, err := c.getCephClient() + if err != nil { + log.Errorf("getCephClient %s fail %s", c.storage.GetStorageName(), err) + return + } + defer cli.Close() + + images, err := cli.ListImages() if err != nil { log.Errorf("get storage %s images error; %v", c.storage.GetStorageName(), err) return diff --git a/pkg/hostman/storageman/storage_local.go b/pkg/hostman/storageman/storage_local.go index 90825a0cd4..9d0f8001e1 100644 --- a/pkg/hostman/storageman/storage_local.go +++ b/pkg/hostman/storageman/storage_local.go @@ -106,7 +106,11 @@ func (s *SLocalStorage) GetComposedName() string { } func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error { - info := input.DiskInfo + err := doRestoreDisk(ctx, input.DiskInfo, disk.GetPath(), input.DiskInfo.Format) + if err != nil { + return errors.Wrap(err, "doRestoreDisk") + } + /*info := input.DiskInfo backupDir := s.GetBackupDir() if !fileutils2.Exists(backupDir) { output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output() @@ -124,7 +128,7 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in if err != nil { return errors.Wrap(err, "unable to storageBackupRecovery") } - } + }*/ /*img, err := qemuimg.NewQemuImage(backupPath) if err != nil { log.Errorf("unable to new qemu image for %s: %s", backupPath, err.Error()) @@ -134,7 +138,7 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in img.SetPassword(info.EncryptInfo.Key) } _, err = img.Clone(disk.GetLvPath(), qemuimg.QCOW2, false)*/ - img, err := qemuimg.NewQemuImage(disk.GetPath()) + /*img, err := qemuimg.NewQemuImage(disk.GetPath()) if err != nil { log.Errorf("NewQemuImage fail %s %s", disk.GetPath(), err) return errors.Wrapf(err, "unable to new qemu image for %s", disk.GetPath()) @@ -153,18 +157,18 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in if err != nil { log.Errorf("CreateQcow2 fail %s", err) return errors.Wrapf(err, "CreateQcow2 %s fail", backupPath) - } + }*/ return nil } -func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { +/*func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { sbParams := params.(*SStorageBackup) backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo) if err != nil { return nil, err } backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId) - err = backupStorage.CopyBackupFrom(backupPath, sbParams.BackupId) + err = backupStorage.SaveBackupFrom(ctx, backupPath, sbParams.BackupId) if err != nil { return nil, err } @@ -175,7 +179,7 @@ func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) ( 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.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo) @@ -183,7 +187,7 @@ func (s *SLocalStorage) storageBackupRecovery(ctx context.Context, sbParams *SSt return nil, err } backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId) - return nil, backupStorage.CopyBackupTo(backupPath, sbParams.BackupId) + return nil, backupStorage.RestoreBackupTo(ctx, backupPath, sbParams.BackupId) } func (s *SLocalStorage) StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { @@ -518,6 +522,7 @@ func (s *SLocalStorage) SaveToGlance(ctx context.Context, params interface{}) (j if err := s.saveToGlance(ctx, imageId, imagePath, compress, format, encKey, encFormat, encAlg); err != nil { log.Errorf("Save to glance failed: %s", err) s.onSaveToGlanceFailed(ctx, imageId, err.Error()) + return nil, errors.Wrap(err, "saveToGlance") } imagecacheManager := s.Manager.LocalStorageImagecacheManager diff --git a/pkg/hostman/storageman/storage_rbd.go b/pkg/hostman/storageman/storage_rbd.go index d16ade8a3b..a3b4e14c8e 100644 --- a/pkg/hostman/storageman/storage_rbd.go +++ b/pkg/hostman/storageman/storage_rbd.go @@ -17,7 +17,9 @@ package storageman import ( "context" "fmt" + "io/ioutil" "os" + "path/filepath" "strings" "yunion.io/x/jsonutils" @@ -33,13 +35,11 @@ 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" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/qemuimg" - "yunion.io/x/onecloud/pkg/util/qemutils" ) const ( @@ -95,16 +95,24 @@ func (s *SRbdStorage) GetSnapshotPathByIds(diskId, snapshotId string) string { return "" } -func (s *SRbdStorage) GetClient() (*cephutils.CephClient, error) { - return cephutils.NewClient(s.MonHost, s.Key, s.Pool) +func (s *SRbdStorage) getCephClient(pool string) (*cephutils.CephClient, error) { + if pool == "" { + pool = s.Pool + } + return cephutils.NewClient(s.MonHost, s.Key, pool) +} + +func (s *SRbdStorage) getClient() (*cephutils.CephClient, error) { + return s.getCephClient("") } func (s *SRbdStorage) IsSnapshotExist(diskId, snapshotId string) (bool, error) { - client, err := s.GetClient() + client, err := s.getClient() if err != nil { return false, errors.Wrapf(err, "GetClient") } defer client.Close() + img, err := client.GetImage(diskId) if err != nil { return false, errors.Wrapf(err, "GetImage") @@ -149,18 +157,17 @@ func (s *SRbdStorage) getStorageConfString() string { return ":" + strings.Join(conf, ":") } -func (s *SRbdStorage) listImages(pool string) ([]string, error) { - client, err := s.GetClient() +func (s *SRbdStorage) listImages() ([]string, error) { + client, err := s.getClient() if err != nil { return nil, errors.Wrapf(err, "GetClient") } - client.SetPool(pool) defer client.Close() return client.ListImages() } func (s *SRbdStorage) IsImageExist(name string) (bool, error) { - images, err := s.listImages(s.Pool) + images, err := s.listImages() if err != nil { return false, errors.Wrapf(err, "listImages") } @@ -170,13 +177,13 @@ func (s *SRbdStorage) IsImageExist(name string) (bool, error) { return false, nil } -func (s *SRbdStorage) getImageSizeMb(pool string, name string) (uint64, error) { - client, err := s.GetClient() +func (s *SRbdStorage) getImageSizeMb(name string) (uint64, error) { + client, err := s.getClient() if err != nil { return 0, errors.Wrapf(err, "GetClient") } defer client.Close() - client.SetPool(pool) + img, err := client.GetImage(name) if err != nil { return 0, errors.Wrapf(err, "GetImage") @@ -188,13 +195,13 @@ func (s *SRbdStorage) getImageSizeMb(pool string, name string) (uint64, error) { return uint64(info.SizeByte) / 1024 / 1024, nil } -func (s *SRbdStorage) resizeImage(pool string, name string, sizeMb uint64) error { - client, err := s.GetClient() +func (s *SRbdStorage) resizeImage(name string, sizeMb uint64) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } defer client.Close() - client.SetPool(pool) + img, err := client.GetImage(name) if err != nil { return errors.Wrapf(err, "GetImage") @@ -209,13 +216,13 @@ func (s *SRbdStorage) resizeImage(pool string, name string, sizeMb uint64) error return img.Resize(int64(sizeMb)) } -func (s *SRbdStorage) deleteImage(pool string, name string, skipRecycle bool) error { - client, err := s.GetClient() +func (s *SRbdStorage) deleteImage(name string, skipRecycle bool) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } defer client.Close() - client.SetPool(pool) + img, err := client.GetImage(name) if err != nil { if errors.Cause(err) == errors.ErrNotFound { @@ -226,29 +233,35 @@ func (s *SRbdStorage) deleteImage(pool string, name string, skipRecycle bool) er return img.Delete() } -// 速度快 func (s *SRbdStorage) cloneImage(ctx context.Context, srcPool string, srcImage string, destPool string, destImage string) error { - client, err := s.GetClient() + cli, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - defer client.Close() - client.SetPool(srcPool) - img, err := client.GetImage(srcImage) + defer cli.Close() + srcCli := cli.Child(srcPool) + + img, err := srcCli.GetImage(srcImage) if err != nil { return errors.Wrapf(err, "GetImage") } - return img.Clone(ctx, destPool, destImage) + _, err = img.Clone(ctx, destPool, destImage) + if err != nil { + return errors.Wrap(err, "Clone") + } + return nil } func (s *SRbdStorage) cloneFromSnapshot(srcImage, srcPool, srcSnapshot, newImage, pool string) error { - client, err := s.GetClient() + cli, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - defer client.Close() - client.SetPool(srcPool) - img, err := client.GetImage(srcImage) + defer cli.Close() + + srcCli := cli.Child(srcPool) + + img, err := srcCli.GetImage(srcImage) if err != nil { return errors.Wrapf(err, "GetImage(%s/%s)", srcPool, srcImage) } @@ -256,41 +269,46 @@ func (s *SRbdStorage) cloneFromSnapshot(srcImage, srcPool, srcSnapshot, newImage if err != nil { return errors.Wrapf(err, "GetSnapshot(%s)", srcSnapshot) } - return snap.Clone(pool, newImage) + _, err = snap.Clone(pool, newImage, true) + if err != nil { + return errors.Wrap(err, "Clone") + } + return nil } -func (s *SRbdStorage) createImage(pool string, name string, sizeMb uint64) error { - client, err := s.GetClient() +func (s *SRbdStorage) createImage(name string, sizeMb uint64) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } defer client.Close() - client.SetPool(pool) + _, err = client.CreateImage(name, int64(sizeMb)) return err } func (s *SRbdStorage) renameImage(pool string, src string, dest string) error { - client, err := s.GetClient() + cli, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - defer client.Close() - client.SetPool(pool) - img, err := client.GetImage(src) + defer cli.Close() + + srcCli := cli.Child(pool) + img, err := srcCli.GetImage(src) if err != nil { return errors.Wrapf(err, "GetImage") } return img.Rename(dest) } -func (s *SRbdStorage) resetDisk(pool string, diskId string, snapshotId string) error { - client, err := s.GetClient() +func (s *SRbdStorage) resetDisk(diskId string, snapshotId string) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - client.SetPool(pool) defer client.Close() + img, err := client.GetImage(diskId) if err != nil { return errors.Wrapf(err, "GetImage") @@ -302,51 +320,44 @@ 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() +func (s *SRbdStorage) createBackup(ctx context.Context, diskId string, diskBackup *SDiskBackup) (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) + snap, err := img.GetSnapshot(diskBackup.SnapshotId) if err != nil { - return 0, errors.Wrapf(err, "unable to GetSnapshot %s of Image %s", snapshotId, diskId) + return 0, errors.Wrapf(err, "unable to GetSnapshot %s of Image %s", diskBackup.SnapshotId, diskId) } - backupName := fmt.Sprintf("backup_%s", backupId) - err = snap.Clone(pool, backupName) + backupName := fmt.Sprintf("backup_%s", diskBackup.BackupId) + // no need to flattern, as image will be delete right after backup + backupImg, err := snap.Clone(s.sStorageConf.Pool, backupName, false) 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") + return 0, errors.Wrapf(err, "unable to Clone snap %s", fmt.Sprintf("%s@%s", diskId, diskBackup.SnapshotId)) } defer backupImg.Delete() - // convert backupStorage - backupStorage, err := backupstorage.GetBackupStorage(backupStorageId, backupStorageAccessInfo) + + srcPath := fmt.Sprintf("rbd:%s%s", backupImg.GetName(), s.getStorageConfString()) + + size, err := doBackupDisk(ctx, srcPath, diskBackup) if err != nil { - return 0, errors.Wrap(err, "unable to GetBackupStorage") + return 0, errors.Wrap(err, "doBackupDisk") } - srcPath := fmt.Sprintf("rbd:%s/%s%s", pool, backupName, s.getStorageConfString()) - // convert - sizeMb, err := backupStorage.ConvertFrom(srcPath, qemuimgfmt.RAW, backupId) - if err != nil { - return 0, errors.Wrapf(err, "unable to ConvertFrom with srcPath %s and format %s", srcPath, qemuimgfmt.RAW.String()) - } - return sizeMb, nil + + return size, nil } -func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId string) error { - client, err := s.GetClient() +func (s *SRbdStorage) createSnapshot(diskId string, snapshotId string) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - client.SetPool(pool) defer client.Close() img, err := client.GetImage(diskId) if err != nil { @@ -356,12 +367,11 @@ func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId stri return err } -func (s *SRbdStorage) deleteSnapshot(pool string, diskId string, snapshotId string) error { - client, err := s.GetClient() +func (s *SRbdStorage) deleteSnapshot(diskId string, snapshotId string) error { + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } - client.SetPool(pool) defer client.Close() img, err := client.GetImage(diskId) if err != nil { @@ -379,7 +389,7 @@ func (s *SRbdStorage) SyncStorageSize() (api.SHostStorageStat, error) { StorageId: s.StorageId, } - client, err := s.GetClient() + client, err := s.getClient() if err != nil { return stat, errors.Wrapf(err, "GetClient") } @@ -396,7 +406,7 @@ func (s *SRbdStorage) SyncStorageSize() (api.SHostStorageStat, error) { func (s *SRbdStorage) SyncStorageInfo() (jsonutils.JSONObject, error) { content := map[string]interface{}{} if len(s.StorageId) > 0 { - client, err := s.GetClient() + client, err := s.getClient() if err != nil { reason := jsonutils.Marshal(map[string]string{"reason": errors.Wrapf(err, "GetClient").Error()}) return modules.Storages.PerformAction(hostutils.GetComputeSession(context.Background()), s.StorageId, api.STORAGE_OFFLINE, reason) @@ -449,7 +459,7 @@ func (s *SRbdStorage) CreateDisk(diskId string) IDisk { } func (s *SRbdStorage) Accessible() error { - client, err := s.GetClient() + client, err := s.getClient() if err != nil { return errors.Wrapf(err, "GetClient") } @@ -508,15 +518,32 @@ func (s *SRbdStorage) saveToGlance(ctx context.Context, imageId, imagePath strin return err } - tmpImageFile := fmt.Sprintf("/tmp/%s.img", imageId) + tmpFileDir, err := ioutil.TempDir(options.HostOptions.TempPath, "ceph_save_images") + if err != nil { + log.Errorf("fail to obtain tempFile for ceph save glance image: %s", err) + return errors.Wrap(err, "ioutil.TempDir") + } + defer func() { + log.Debugf("clean up temp dir for glance image save %s", tmpFileDir) + output, err := procutils.NewRemoteCommandAsFarAsPossible("rm", "-fr", tmpFileDir).Output() + if err != nil { + log.Errorf("rm %s fail %s %s", tmpFileDir, output, err) + } + }() + + tmpImageFile := filepath.Join(tmpFileDir, imageId) + if len(format) == 0 { format = options.HostOptions.DefaultImageSaveFormat } - err = procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuImg(), - "convert", "-f", "raw", "-O", format, imagePath, tmpImageFile).Run() + img, err := qemuimg.NewQemuImage(imagePath) if err != nil { - return err + return errors.Wrapf(err, "NewQemuImage %s", imagePath) + } + _, err = img.Clone(tmpImageFile, qemuimgfmt.String2ImageFormat(format), compress) + if err != nil { + return errors.Wrapf(err, "Clone %s", tmpImageFile) } f, err := os.Open(tmpImageFile) @@ -551,8 +578,7 @@ func (s *SRbdStorage) saveToGlance(ctx context.Context, imageId, imagePath strin } params.Set("image_id", jsonutils.NewString(imageId)) - _, err = image.Images.Upload(hostutils.GetImageSession(ctx), - params, f, size) + _, err = image.Images.Upload(hostutils.GetImageSession(ctx), params, f, size) return err } @@ -578,9 +604,13 @@ func (s *SRbdStorage) CreateDiskFromExistingPath(context.Context, IDisk, *SDiskC } 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()) + err := doRestoreDisk(ctx, input.DiskInfo, destPath, input.DiskInfo.Format) + if err != nil { + return errors.Wrap(err, "doRestore") + } + /*backup := input.DiskInfo.Backup backupStorage, err := backupstorage.GetBackupStorage(backup.BackupStorageId, backup.BackupStorageAccessInfo) if err != nil { return errors.Wrap(err, "unable to GetBackupStorage") @@ -588,7 +618,7 @@ func (s *SRbdStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, inpu err = backupStorage.ConvertTo(destPath, qemuimgfmt.RAW, backup.BackupId) if err != nil { return errors.Wrapf(err, "unable to Convert to with destPath %s and format %s", destPath, qemuimgfmt.RAW.String()) - } + }*/ return nil } @@ -621,7 +651,7 @@ func (s *SRbdStorage) CloneDiskFromStorage( return nil, errors.Wrap(err, "Clone source disk to target rbd storage") } } else { - err = s.createImage(s.Pool, targetDiskId, uint64(srcImg.GetSizeMB())) + err = s.createImage(targetDiskId, uint64(srcImg.GetSizeMB())) if err != nil { return nil, errors.Wrap(err, "Create rbd image") } diff --git a/pkg/hostman/storageman/storagehandler/storagehandler.go b/pkg/hostman/storageman/storagehandler/storagehandler.go index 902edd096d..d29ac6dc1b 100644 --- a/pkg/hostman/storageman/storagehandler/storagehandler.go +++ b/pkg/hostman/storageman/storagehandler/storagehandler.go @@ -253,7 +253,7 @@ func storageSyncBackup(ctx context.Context, w http.ResponseWriter, r *http.Reque hostutils.Response(ctx, w, err) return } - exist, err := backupStorage.IsExists(backupId) + exist, err := backupStorage.IsBackupExists(backupId) if err != nil { hostutils.Response(ctx, w, err) return @@ -344,13 +344,9 @@ func storageUnpackInstanceBackup(ctx context.Context, w http.ResponseWriter, r * func packInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { sbParams := params.(*storageman.SStoragePackInstanceBackup) - backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo) + packFileName, err := storageman.DoInstancePackBackup(ctx, *sbParams) if err != nil { - return nil, errors.Wrap(err, "GetBackupStorage") - } - packFileName, err := backupStorage.InstancePack(ctx, sbParams.PackageName, sbParams.BackupIds, &sbParams.Metadata) - if err != nil { - return nil, errors.Wrap(err, "InstancePack") + return nil, errors.Wrap(err, "DoInstancePackBackup") } ret := jsonutils.NewDict() ret.Set("pack_file_name", jsonutils.NewString(packFileName)) @@ -359,15 +355,12 @@ func packInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSON func unpackInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) { sbParams := params.(*storageman.SStorageUnpackInstanceBackup) - backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo) + + diskBackupIds, metadata, err := storageman.DoInstanceUnpackBackup(ctx, *sbParams) if err != nil { - return nil, errors.Wrap(err, "GetBackupStorage") - } - metadataOnly := (sbParams.MetadataOnly != nil && *sbParams.MetadataOnly) - diskBackupIds, metadata, err := backupStorage.InstanceUnpack(ctx, sbParams.PackageName, metadataOnly) - if err != nil { - return nil, errors.Wrap(err, "InstanceUnpack") + return nil, errors.Wrap(err, "DoInstanceUnpackBackup") } + ret := jsonutils.NewDict() if diskBackupIds != nil { ret.Set("disk_backup_ids", jsonutils.Marshal(diskBackupIds)) @@ -418,7 +411,7 @@ func deleteBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject if err != nil { return nil, err } - err = backupStorage.RemoveBackup(sbParams.BackupId) + err = backupStorage.RemoveBackup(ctx, sbParams.BackupId) if err != nil { return nil, err } diff --git a/pkg/hostman/storageman/storagehelper.go b/pkg/hostman/storageman/storagehelper.go index 77836d7e87..61328ca7fd 100644 --- a/pkg/hostman/storageman/storagehelper.go +++ b/pkg/hostman/storageman/storagehelper.go @@ -46,7 +46,7 @@ type SDiskCleanupSnapshots struct { DeleteSnapshots []jsonutils.JSONObject } -type SDiskBakcup struct { +type SDiskBackup struct { SnapshotId string `json:"snapshot_id"` BackupId string `json:"backup_id"` BackupStorageId string `json:"backup_storage_id"` diff --git a/pkg/image/models/image_guest.go b/pkg/image/models/image_guest.go index a7f6431e47..300fdda648 100644 --- a/pkg/image/models/image_guest.go +++ b/pkg/image/models/image_guest.go @@ -678,6 +678,9 @@ func (manager *SGuestImageManager) ListItemFilter( q = q.IsFalse("protected") } } + if len(query.DiskFormat) > 0 { + q = q.In("disk_format", query.DiskFormat) + } return q, nil } diff --git a/pkg/mcclient/options/compute/backup.go b/pkg/mcclient/options/compute/backup.go index b93b54dd34..62f43ab03d 100644 --- a/pkg/mcclient/options/compute/backup.go +++ b/pkg/mcclient/options/compute/backup.go @@ -112,10 +112,16 @@ func (opts *BackupStorageIdOptions) Params() (jsonutils.JSONObject, error) { type BackupStorageCreateOptions struct { options.BaseCreateOptions - StorageType string `help:"storage type" choices:"nfs"` + StorageType string `help:"storage type" choices:"nfs|object"` + 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"` + + ObjectBucketUrl string `help:"object bucket url, required when storage_type is object"` + ObjectAccessKey string `help:"object storage access key, required when storage_type is object"` + ObjectSecret string `help:"object storage secret, required when storage_type is object"` + + CapacityMb int `help:"capacity, unit mb"` } func (opts *BackupStorageCreateOptions) Params() (jsonutils.JSONObject, error) { diff --git a/pkg/util/cephutils/ceph.go b/pkg/util/cephutils/ceph.go index 555bdcc6a0..0049589a26 100644 --- a/pkg/util/cephutils/ceph.go +++ b/pkg/util/cephutils/ceph.go @@ -42,15 +42,11 @@ type CephClient struct { timeout int } -func (self *CephClient) Close() error { - if len(self.keyConf) > 0 { - os.Remove(self.keyConf) +func (cli *CephClient) Close() error { + if len(cli.keyConf) > 0 { + os.Remove(cli.keyConf) } - return os.Remove(self.cephConf) -} - -func (self *CephClient) SetPool(pool string) { - self.pool = pool + return os.Remove(cli.cephConf) } type cephStats struct { @@ -91,15 +87,15 @@ type SCapacity struct { UsedCapacitySizeKb int64 } -func (self *CephClient) Output(name string, opts []string) (jsonutils.JSONObject, error) { - return self.output(name, opts, false) +func (cli *CephClient) Output(name string, opts []string) (jsonutils.JSONObject, error) { + return cli.output(name, opts, false) } -func (self *CephClient) output(name string, opts []string, timeout bool) (jsonutils.JSONObject, error) { +func (cli *CephClient) output(name string, opts []string, timeout bool) (jsonutils.JSONObject, error) { cmds := []string{name, "--format", "json"} cmds = append(cmds, opts...) if timeout { - cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", self.timeout)}, cmds...) + cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", cli.timeout)}, cmds...) } proc := procutils.NewRemoteCommandAsFarAsPossible(cmds[0], cmds[1:]...) outb, err := proc.StdoutPipe() @@ -133,10 +129,10 @@ func (self *CephClient) output(name string, opts []string, timeout bool) (jsonut return jsonutils.Parse(stdoutPut) } -func (self *CephClient) run(name string, opts []string, timeout bool) error { +func (cli *CephClient) run(name string, opts []string, timeout bool) error { cmds := append([]string{name}, opts...) if timeout { - cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", self.timeout)}, cmds...) + cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", cli.timeout)}, cmds...) } output, err := procutils.NewRemoteCommandAsFarAsPossible(cmds[0], cmds[1:]...).Output() if err != nil { @@ -145,26 +141,26 @@ func (self *CephClient) run(name string, opts []string, timeout bool) error { return nil } -func (self *CephClient) options() []string { - opts := []string{"--conf", self.cephConf} - if len(self.keyConf) > 0 { - opts = append(opts, []string{"--keyring", self.keyConf}...) +func (cli *CephClient) options() []string { + opts := []string{"--conf", cli.cephConf} + if len(cli.keyConf) > 0 { + opts = append(opts, []string{"--keyring", cli.keyConf}...) } return opts } -func (self *CephClient) CreateImage(name string, sizeMb int64) (*SImage, error) { - opts := self.options() - image := &SImage{name: name, client: self} +func (cli *CephClient) CreateImage(name string, sizeMb int64) (*SImage, error) { + opts := cli.options() + image := &SImage{name: name, client: cli} opts = append(opts, []string{"create", image.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...) - return image, self.run("rbd", opts, false) + return image, cli.run("rbd", opts, false) } -func (self *CephClient) GetCapacity() (*SCapacity, error) { +func (cli *CephClient) GetCapacity() (*SCapacity, error) { result := &SCapacity{} - opts := self.options() + opts := cli.options() opts = append(opts, "df") - resp, err := self.output("ceph", opts, true) + resp, err := cli.output("ceph", opts, true) if err != nil { return nil, errors.Wrapf(err, "output") } @@ -176,7 +172,7 @@ func (self *CephClient) GetCapacity() (*SCapacity, error) { result.CapacitySizeKb = stats.Stats.TotalBytes / 1024 result.UsedCapacitySizeKb = stats.Stats.TotalUsedBytes / 1024 for _, pool := range stats.Pools { - if pool.Name == self.pool { + if pool.Name == cli.pool { result.UsedCapacitySizeKb = int64(pool.Stats.Stored / 1024) if pool.Stats.MaxAvail > 0 { result.CapacitySizeKb = int64(pool.Stats.MaxAvail/1024 + int64(pool.Stats.Stored/1024)) @@ -270,20 +266,26 @@ keyring = %s return client, nil } +func (cli *CephClient) Child(pool string) *CephClient { + newCli := *cli + newCli.pool = pool + return &newCli +} + type SImage struct { name string client *CephClient } -func (self *SImage) GetName() string { - return fmt.Sprintf("%s/%s", self.client.pool, self.name) +func (img *SImage) GetName() string { + return fmt.Sprintf("%s/%s", img.client.pool, img.name) } -func (self *CephClient) ListImages() ([]string, error) { +func (cli *CephClient) ListImages() ([]string, error) { result := []string{} - opts := self.options() - opts = append(opts, []string{"ls", self.pool}...) - resp, err := self.output("rbd", opts, true) + opts := cli.options() + opts = append(opts, []string{"ls", cli.pool}...) + resp, err := cli.output("rbd", opts, true) if err != nil { return nil, err } @@ -294,15 +296,15 @@ func (self *CephClient) ListImages() ([]string, error) { return result, nil } -func (self *CephClient) GetImage(name string) (*SImage, error) { - images, err := self.ListImages() +func (cli *CephClient) GetImage(name string) (*SImage, error) { + images, err := cli.ListImages() if err != nil { return nil, errors.Wrapf(err, "ListImages") } if !utils.IsInStringArray(name, images) { return nil, cloudprovider.ErrNotFound } - return &SImage{name: name, client: self}, nil + return &SImage{name: name, client: cli}, nil } type SImageInfo struct { @@ -323,14 +325,14 @@ type SImageInfo struct { ModifyTimestamp string `json:"modify_timestamp"` } -func (self *SImage) options() []string { - return self.client.options() +func (img *SImage) options() []string { + return img.client.options() } -func (self *SImage) GetInfo() (*SImageInfo, error) { - opts := self.options() - opts = append(opts, []string{"info", self.GetName()}...) - resp, err := self.client.output("rbd", opts, true) +func (img *SImage) GetInfo() (*SImageInfo, error) { + opts := img.options() + opts = append(opts, []string{"info", img.GetName()}...) + resp, err := img.client.output("rbd", opts, true) if err != nil { return nil, err } @@ -338,10 +340,10 @@ func (self *SImage) GetInfo() (*SImageInfo, error) { return info, resp.Unmarshal(info) } -func (self *SImage) ListSnapshots() ([]SSnapshot, error) { - opts := self.options() - opts = append(opts, []string{"snap", "ls", self.GetName()}...) - resp, err := self.client.output("rbd", opts, true) +func (img *SImage) ListSnapshots() ([]SSnapshot, error) { + opts := img.options() + opts = append(opts, []string{"snap", "ls", img.GetName()}...) + resp, err := img.client.output("rbd", opts, true) if err != nil { return nil, err } @@ -351,13 +353,13 @@ func (self *SImage) ListSnapshots() ([]SSnapshot, error) { return nil, errors.Wrapf(err, "ret.Unmarshal") } for i := range result { - result[i].image = self + result[i].image = img } return result, nil } -func (self *SImage) GetSnapshot(name string) (*SSnapshot, error) { - snaps, err := self.ListSnapshots() +func (img *SImage) GetSnapshot(name string) (*SSnapshot, error) { + snaps, err := img.ListSnapshots() if err != nil { return nil, errors.Wrapf(err, "ListSnapshots") } @@ -369,8 +371,8 @@ func (self *SImage) GetSnapshot(name string) (*SSnapshot, error) { return nil, cloudprovider.ErrNotFound } -func (self *SImage) IsSnapshotExist(name string) (bool, error) { - _, err := self.GetSnapshot(name) +func (img *SImage) IsSnapshotExist(name string) (bool, error) { + _, err := img.GetSnapshot(name) if err != nil { if errors.Cause(err) == cloudprovider.ErrNotFound { return false, nil @@ -381,74 +383,78 @@ func (self *SImage) IsSnapshotExist(name string) (bool, error) { } type SSnapshot struct { - Name string - Id string - Size int64 - Protected bool + Name string + Id string + Size int64 + // Protected bool Timestamp string image *SImage } -func (self *SSnapshot) Rollback() error { - opts := self.options() - opts = append(opts, []string{"snap", "rollback", self.GetName()}...) - return self.image.client.run("rbd", opts, false) +func (snap *SSnapshot) Rollback() error { + opts := snap.options() + opts = append(opts, []string{"snap", "rollback", snap.GetName()}...) + return snap.image.client.run("rbd", opts, false) } -func (self *SSnapshot) options() []string { - return self.image.options() +func (snap *SSnapshot) options() []string { + return snap.image.options() } -func (self *SSnapshot) GetName() string { - return fmt.Sprintf("%s@%s", self.image.GetName(), self.Name) +func (snap *SSnapshot) GetName() string { + return fmt.Sprintf("%s@%s", snap.image.GetName(), snap.Name) } -func (self *SSnapshot) Unprotect() error { - opts := self.options() - opts = append(opts, []string{"snap", "unprotect", self.GetName()}...) - err := self.image.client.run("rbd", opts, true) +func (snap *SSnapshot) Unprotect() error { + opts := snap.options() + opts = append(opts, []string{"snap", "unprotect", snap.GetName()}...) + err := snap.image.client.run("rbd", opts, true) if err != nil { if strings.Contains(err.Error(), "snap is already unprotected") { + // snap.Protected = false return nil } - return errors.Wrapf(err, "Unprotect") + return errors.Wrapf(err, "unprotect") } - self.Protected = false + // snap.Protected = false return nil } -func (self *SSnapshot) Protect() error { - if self.Protected { - return nil - } - opts := self.options() - opts = append(opts, []string{"snap", "protect", self.GetName()}...) - err := self.image.client.run("rbd", opts, true) - if err == nil { - self.Protected = true +func (snap *SSnapshot) protect() error { + // if snap.Protected { + // return nil + // } + opts := snap.options() + opts = append(opts, []string{"snap", "protect", snap.GetName()}...) + err := snap.image.client.run("rbd", opts, true) + if err != nil { + if strings.Contains(err.Error(), "snap is already protected") { + return nil + } + return errors.Wrap(err, "protect") } + // if err == nil { + // snap.Protected = true + // } return err } -func (self *SSnapshot) Remove() error { - opts := self.options() - opts = append(opts, []string{"snap", "rm", self.GetName()}...) - return self.image.client.run("rbd", opts, false) +func (snap *SSnapshot) Remove() error { + opts := snap.options() + opts = append(opts, []string{"snap", "rm", snap.GetName()}...) + return snap.image.client.run("rbd", opts, false) } -func (self *SSnapshot) Delete() error { - pool := self.image.client.pool - defer self.image.client.SetPool(pool) - - children, err := self.ListChildren() +func (snap *SSnapshot) Delete() error { + children, err := snap.ListChildren() if err != nil { return errors.Wrapf(err, "ListChildren") } for i := range children { - self.image.client.SetPool(children[i].Pool) - image, err := self.image.client.GetImage(children[i].Image) + tmpCli := snap.image.client.Child(children[i].Pool) + image, err := tmpCli.GetImage(children[i].Image) if err != nil { return errors.Wrapf(err, "GetImage(%s/%s)", children[i].Pool, children[i].Image) } @@ -458,12 +464,13 @@ func (self *SSnapshot) Delete() error { } } - err = self.Unprotect() + // always try to unprotect + err = snap.Unprotect() if err != nil { - log.Errorf("Unprotect %s failed: %s", self.GetName(), err) + log.Errorf("Unprotect %s failed: %s", snap.GetName(), err) } - return self.Remove() + return snap.Remove() } type SChildren struct { @@ -472,10 +479,10 @@ type SChildren struct { Image string } -func (self *SSnapshot) ListChildren() ([]SChildren, error) { - opts := self.options() - opts = append(opts, []string{"children", self.GetName()}...) - resp, err := self.image.client.output("rbd", opts, true) +func (snap *SSnapshot) ListChildren() ([]SChildren, error) { + opts := snap.options() + opts = append(opts, []string{"children", snap.GetName()}...) + resp, err := snap.image.client.output("rbd", opts, true) if err != nil { return nil, errors.Wrapf(err, "ListChildren") } @@ -483,26 +490,26 @@ func (self *SSnapshot) ListChildren() ([]SChildren, error) { return chidren, resp.Unmarshal(&chidren) } -func (self *SImage) Resize(sizeMb int64) error { - opts := self.options() - opts = append(opts, []string{"resize", self.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...) - return self.client.run("rbd", opts, false) +func (img *SImage) Resize(sizeMb int64) error { + opts := img.options() + opts = append(opts, []string{"resize", img.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...) + return img.client.run("rbd", opts, false) } -func (self *SImage) Remove() error { - opts := self.options() - opts = append(opts, []string{"rm", self.GetName()}...) - return self.client.run("rbd", opts, false) +func (img *SImage) Remove() error { + opts := img.options() + opts = append(opts, []string{"rm", img.GetName()}...) + return img.client.run("rbd", opts, false) } -func (self *SImage) Flatten() error { - opts := self.options() - opts = append(opts, []string{"flatten", self.GetName()}...) - return self.client.run("rbd", opts, false) +func (img *SImage) Flatten() error { + opts := img.options() + opts = append(opts, []string{"flatten", img.GetName()}...) + return img.client.run("rbd", opts, false) } -func (self *SImage) Delete() error { - snapshots, err := self.ListSnapshots() +func (img *SImage) Delete() error { + snapshots, err := img.ListSnapshots() if err != nil { return errors.Wrapf(err, "ListSnapshots") } @@ -512,77 +519,69 @@ func (self *SImage) Delete() error { return errors.Wrapf(err, "delete snapshot %s", snapshots[i].GetName()) } } - return self.Remove() + return img.Remove() } -func (self *SImage) Rename(name string) error { - opts := self.options() - opts = append(opts, []string{"rename", self.GetName(), fmt.Sprintf("%s/%s", self.client.pool, name)}...) - return self.client.run("rbd", opts, false) +func (img *SImage) Rename(name string) error { + opts := img.options() + opts = append(opts, []string{"rename", img.GetName(), fmt.Sprintf("%s/%s", img.client.pool, name)}...) + return img.client.run("rbd", opts, false) } -func (self *SImage) CreateSnapshot(name string) (*SSnapshot, error) { - snap := &SSnapshot{Name: name, image: self} - opts := self.options() +func (img *SImage) CreateSnapshot(name string) (*SSnapshot, error) { + snap := &SSnapshot{Name: name, image: img} + opts := img.options() opts = append(opts, []string{"snap", "create", snap.GetName()}...) - if err := self.client.run("rbd", opts, false); err != nil { + if err := img.client.run("rbd", opts, false); err != nil { return nil, errors.Wrap(err, "snap create") } - if err := snap.Protect(); err != nil { - log.Errorf("failed protect snap %s: %s", snap.GetName(), err) - } return snap, nil } -func (self *SImage) Clone(ctx context.Context, pool, name string) error { - lockman.LockRawObject(ctx, "rbd_image_cache", self.GetName()) - defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", self.GetName()) +func (img *SImage) Clone(ctx context.Context, pool, name string) (*SImage, error) { + lockman.LockRawObject(ctx, "rbd_image_cache", img.GetName()) + defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", img.GetName()) - var findOrCreateSnap = func() (*SSnapshot, error) { - snaps, err := self.ListSnapshots() - if err != nil { - return nil, errors.Wrapf(err, "ListSnapshots") - } - for i := range snaps { - if snaps[i].Name == name { - return &snaps[i], nil - } - } - snap, err := self.CreateSnapshot(name) - if err != nil { - return nil, errors.Wrapf(err, "CreateSnapshot") - } - return snap, nil - } - snap, err := findOrCreateSnap() + tmpSnapName := "snap-" + utils.GenRequestId(12) + tmpSnap, err := img.CreateSnapshot(tmpSnapName) if err != nil { - return errors.Wrapf(err, "findOrCreateSnap") + return nil, errors.Wrapf(err, "CreateSnapshot") } - err = snap.Clone(pool, name) + defer tmpSnap.Delete() + + newimg, err := tmpSnap.Clone(pool, name, true) if err != nil { - return errors.Wrapf(err, "clone %s/%s", pool, name) + return nil, errors.Wrapf(err, "clone %s/%s", pool, name) } - _pool := self.client.pool - - // use current pool - self.client.SetPool(pool) - // recover previous pool - defer self.client.SetPool(_pool) - - img, err := self.client.GetImage(name) - if err != nil { - return errors.Wrapf(err, "GetImage(%s) after clone", name) - } - return img.Flatten() + return newimg, nil } -func (self *SSnapshot) Clone(pool, name string) error { - err := self.Protect() +func (snap *SSnapshot) Clone(pool, name string, flattern bool) (*SImage, error) { + err := snap.protect() if err != nil { - log.Warningf("protect %s error: %v", self.GetName(), err) + log.Warningf("protect %s error: %v", snap.GetName(), err) + return nil, errors.Wrap(err, "Protect") } - opts := self.options() - opts = append(opts, []string{"clone", self.GetName(), fmt.Sprintf("%s/%s", pool, name)}...) - return self.image.client.run("rbd", opts, false) + if flattern { + defer snap.Unprotect() + } + + opts := snap.options() + opts = append(opts, []string{"clone", snap.GetName(), fmt.Sprintf("%s/%s", pool, name)}...) + err = snap.image.client.run("rbd", opts, false) + if err != nil { + return nil, errors.Wrap(err, "clone") + } + newimg, err := snap.image.client.Child(pool).GetImage(name) + if err != nil { + return nil, errors.Wrapf(err, "GetImage(%s) after clone", name) + } + if flattern { + err = newimg.Flatten() + if err != nil { + return nil, errors.Wrap(err, "flattern") + } + } + return newimg, nil } From 409a61dbd93e9150a464c870556316809e9de2e2 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sun, 26 Nov 2023 16:38:22 +0800 Subject: [PATCH 2/3] fix: update model api --- pkg/apis/compute/dnstrafficpolicy.go | 1 - pkg/apis/compute/dnszonecache.go | 1 - pkg/apis/compute/hostwire.go | 2 +- pkg/apis/compute/zz_generated.model.go | 145 ++++++++++++++---------- pkg/apis/identity/zz_generated.model.go | 29 ++++- pkg/apis/zz_generated.model.go | 14 ++- 6 files changed, 122 insertions(+), 70 deletions(-) diff --git a/pkg/apis/compute/dnstrafficpolicy.go b/pkg/apis/compute/dnstrafficpolicy.go index c394620ebf..6293929547 100644 --- a/pkg/apis/compute/dnstrafficpolicy.go +++ b/pkg/apis/compute/dnstrafficpolicy.go @@ -32,7 +32,6 @@ type DnsTrafficPolicyCreateInput struct { type DnsTrafficPolicyDetails struct { apis.EnabledStatusInfrasResourceBaseDetails - SDnsTrafficPolicy } type DnsTrafficPolicyListInput struct { diff --git a/pkg/apis/compute/dnszonecache.go b/pkg/apis/compute/dnszonecache.go index 6302c963db..fef2864042 100644 --- a/pkg/apis/compute/dnszonecache.go +++ b/pkg/apis/compute/dnszonecache.go @@ -30,7 +30,6 @@ type DnsZoneCacheCreateInput struct { type DnsZoneCacheDetails struct { apis.StatusStandaloneResourceDetails - SDnsZoneCache Account string Brand string diff --git a/pkg/apis/compute/hostwire.go b/pkg/apis/compute/hostwire.go index c76dad5d67..a9c9535cec 100644 --- a/pkg/apis/compute/hostwire.go +++ b/pkg/apis/compute/hostwire.go @@ -17,7 +17,7 @@ package compute type HostwireDetails struct { HostJointResourceDetails - SHostwire + SHostwireDeprecated // 二层网络名称 Wire string `json:"wire"` diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index 8cdc4b6f6a..fa2f386c62 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -95,8 +95,6 @@ type SBackupStorage struct { CapacityMb int `json:"capacity_mb"` } - - // SBaremetalagent is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SBaremetalagent. type SBaremetalagent struct { apis.SStandaloneResourceBase @@ -305,6 +303,7 @@ type SCloudimage struct { type SCloudprovider struct { apis.SEnabledStatusStandaloneResourceBase apis.SProjectizedResourceBase + apis.SExternalizedResourceBase SSyncableBaseResource // 云端服务健康状态。例如欠费、项目冻结都属于不健康状态。 // | HealthStatus | 说明 | @@ -325,6 +324,9 @@ type SCloudprovider struct { CloudaccountId string `json:"cloudaccount_id"` // 云账号的平台信息 Provider string `json:"provider"` + // 云上同步资源是否在本地被更改过配置, local: 更改过, cloud: 未更改过 + // example: local + ProjectSrc string `json:"project_src"` SProjectMappingResourceBase } @@ -348,9 +350,9 @@ type SCloudproviderQuota struct { QuotaRange string `json:"quota_range"` // 已使用的配额 // -1代表未从云平台拿到已使用配额信息 - UsedCount int `json:"used_count"` + UsedCount int64 `json:"used_count"` // 最大配额限制 - MaxCount int `json:"max_count"` + MaxCount int64 `json:"max_count"` // 配额类型 QuotaType string `json:"quota_type"` } @@ -626,6 +628,12 @@ type SDisk struct { // swap: 交换盘 // example: sys DiskType string `json:"disk_type"` + // 预分配策略 + // off: 关闭预分配,默认关闭 + // metadata: 精简制备 + // falloc: 厚制制备延迟置零 + // full: 厚制备快速置零 + Preallocation string `json:"preallocation"` // # is persistent Nonpersistent bool `json:"nonpersistent"` // 是否标记为SSD磁盘 @@ -660,48 +668,29 @@ type SDiskResourceBase struct { DiskId string `json:"disk_id"` } -// SDnsRecordis an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecord. +// SDnsRecord is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecord. type SDnsRecord struct { apis.SEnabledStatusStandaloneResourceBase + apis.SExternalizedResourceBase SDnsZoneResourceBase DnsType string `json:"dns_type"` DnsValue string `json:"dns_value"` TTL int64 `json:"ttl"` MxPriority int64 `json:"mx_priority"` -} - -// SDnsRecordSetTrafficPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecordSetTrafficPolicy. -type SDnsRecordSetTrafficPolicy struct { - apis.SJointResourceBase - DnsRecordsetId string `json:"dns_recordset_id"` - DnsTrafficPolicyId string `json:"dns_traffic_policy_id"` -} - -// SDnsTrafficPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsTrafficPolicy. -type SDnsTrafficPolicy struct { - apis.SEnabledStatusInfrasResourceBase - Provider string `json:"provider"` - PolicyType string `json:"policy_type"` - PolicyValue string `json:"policy_value"` - Options *jsonutils.JSONDict `json:"options"` + // 解析线路类型 + PolicyType string `json:"policy_type"` + // 解析线路 + PolicyValue string `json:"policy_value"` } // SDnsZone is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZone. type SDnsZone struct { - apis.SEnabledStatusInfrasResourceBase - IsDirty bool `json:"is_dirty"` - ZoneType string `json:"zone_type"` - Options *jsonutils.JSONDict `json:"options"` -} - -// SDnsZoneCache is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZoneCache. -type SDnsZoneCache struct { - apis.SStatusStandaloneResourceBase + apis.SSharableVirtualResourceBase + apis.SEnabledResourceBase apis.SExternalizedResourceBase - SDnsZoneResourceBase - // 归属云账号ID - CloudaccountId string `json:"cloudaccount_id"` - ProductType string `json:"product_type"` + SManagedResourceBase + ZoneType string `json:"zone_type"` + ProductType string `json:"product_type"` } // SDnsZoneResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZoneResourceBase. @@ -766,10 +755,12 @@ type SElasticSearch struct { type SElasticcache struct { apis.SVirtualResourceBase apis.SExternalizedResourceBase + SCloudregionResourceBase + SManagedResourceBase SBillingResourceBase SDeletePreventableResourceBase - SVpcResourceBase SZoneResourceBase + VpcId string `json:"vpc_id"` // 备可用区 SlaveZones string `json:"slave_zones"` // 实例规格 @@ -797,9 +788,9 @@ type SElasticcache struct { NetworkId string `json:"network_id"` // 带宽 Bandwidth int `json:"bandwidth"` - // 内网DNS + // 内网DNS PrivateDNS string `json:"private_dns"` - // 内网IP地址 + // 内网IP地址 PrivateIpAddr string `json:"private_ip_addr"` // 内网访问端口 PrivateConnectPort int `json:"private_connect_port"` @@ -984,6 +975,9 @@ type SExternalProject struct { apis.SVirtualResourceBase apis.SExternalizedResourceBase SManagedResourceBase + // 优先级,同一个本地项目映射多个云上项目,优先级高的优先选择 + // 数值越高,优先级越大 + Priority int `json:"priority"` ExternalDomainId string `json:"external_domain_id"` // 归属云账号ID CloudaccountId string `json:"cloudaccount_id"` @@ -1083,8 +1077,10 @@ type SGuest struct { SHostResourceBase apis.SEncryptedResource // CPU大小 + CpuSockets int `json:"cpu_sockets"` + // CPU大小 VcpuCount int `json:"vcpu_count"` - // 内存大小, 单位Mb + // 内存大小, 单位MB VmemSize int `json:"vmem_size"` // 启动顺序 BootOrder string `json:"boot_order"` @@ -1129,6 +1125,8 @@ type SGuest struct { QgaStatus string `json:"qga_status"` // power_states limit in [on, off, unknown] PowerStates string `json:"power_states"` + // Used for guest rescue + RescueMode bool `json:"rescue_mode"` } // SGuestJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestJointsBase. @@ -1202,6 +1200,12 @@ type SGuestnetwork struct { NumQueues int `json:"num_queues"` // 带宽限制,单位mbps BwLimit int `json:"bw_limit"` + // 下行流量限制,单位 bytes + RxTrafficLimit int64 `json:"rx_traffic_limit"` + RxTrafficUsed int64 `json:"rx_traffic_used"` + // 上行流量限制,单位 bytes + TxTrafficLimit int64 `json:"tx_traffic_limit"` + TxTrafficUsed int64 `json:"tx_traffic_used"` // 网卡序号 Index byte `json:"index"` // 是否为虚拟接口(无IP) @@ -1338,6 +1342,8 @@ type SHostnetwork struct { IpAddr string `json:"ip_addr"` // MAC地址 MacAddr string `json:"mac_addr"` + // VLAN + VlanId int `json:"vlan_id"` } // SHostschedtag is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostschedtag. @@ -1364,8 +1370,8 @@ type SHoststorage struct { RealCapacity int64 `json:"real_capacity"` } -// SHostwire is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostwire. -type SHostwire struct { +// SHostwireDeprecated is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostwireDeprecated. +type SHostwireDeprecated struct { SHostJointsBase Bridge string `json:"bridge"` // 接口名称 @@ -1512,7 +1518,25 @@ type SIsolatedDevice struct { // guest disk index DiskIndex byte `json:"disk_index"` // # pci address of `Bus:Device.Function` format, or usb bus address of `bus.addr` - Addr string `json:"addr"` + Addr string `json:"addr"` + // Is vgpu physical funcion, That means it cannot be attached to guest + // VGPUPhysicalFunction bool `nullable:"true" default:"false" list:"domain" create:"domain_optional"` + // nvidia vgpu config + // vgpu uuid generated on create + MdevId string `json:"mdev_id"` + // The frame rate limiter (FRL) configuration in frames per second + FRL string `json:"frl"` + // The frame buffer size in Mbytes + Framebuffer string `json:"framebuffer"` + // The maximum resolution per display head, eg: 5120x2880 + MaxResolution string `json:"max_resolution"` + // The maximum number of virtual display heads that the vGPU type supports + // In computer graphics and display technology, the term "head" is commonly used to + // describe the physical interface of a display device or display output. + // It refers to a connection point on the monitor, such as HDMI, DisplayPort, or VGA interface. + NumHeads string `json:"num_heads"` + // The maximum number of vGPU instances per physical GPU + MaxInstance string `json:"max_instance"` VendorDeviceId string `json:"vendor_device_id"` // reserved memory size for isolated device ReservedMemory int `json:"reserved_memory"` @@ -1520,6 +1544,8 @@ type SIsolatedDevice struct { ReservedCpu int `json:"reserved_cpu"` // reserved storage size for isolated device ReservedStorage int `json:"reserved_storage"` + // PciInfo stores extra PCIE information + PcieInfo *IsolatedDevicePCIEInfo `json:"pcie_info"` } // SIsolatedDeviceModel is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SIsolatedDeviceModel. @@ -1569,6 +1595,7 @@ type SKafka struct { // SKeypair is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SKeypair. type SKeypair struct { apis.SUserResourceBase + apis.SSharableBaseResource // 加密类型 // example: RSA Scheme string `json:"scheme"` @@ -1851,9 +1878,9 @@ type SLoadbalancerHealthCheck struct { // 健康检查路径 / HealthCheckHttpCode string `json:"health_check_http_code"` HealthCheckRise int `json:"health_check_rise"` - // 健康检查健康阈值 3秒 + // 健康检查健康阈值 3秒 HealthCheckFall int `json:"health_check_fall"` - // 健康检查不健康阈值 15秒 + // 健康检查不健康阈值 15秒 HealthCheckTimeout int `json:"health_check_timeout"` // 健康检查超时时间 10秒 HealthCheckInterval int `json:"health_check_interval"` @@ -2206,6 +2233,14 @@ type SNetwork struct { BgpType string `json:"bgp_type"` } +// SNetworkAdditionalWire is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetworkAdditionalWire. +type SNetworkAdditionalWire struct { + NetworkId string `json:"network_id"` + WireId string `json:"wire_id"` + Synced *bool `json:"synced"` + Marked *bool `json:"marked"` +} + // SNetworkAddress is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetworkAddress. type SNetworkAddress struct { apis.SStandaloneAnonResourceBase @@ -2476,7 +2511,7 @@ type SSchedtag struct { apis.SStandaloneResourceBase apis.SScopedResourceBase DefaultStrategy string `json:"default_strategy"` - // Column(VARCHAR(16, charset='ascii'), nullable=True, default=”) + // Column(VARCHAR(16, charset='ascii'), nullable=True, default='') ResourceType string `json:"resource_type"` } @@ -2495,21 +2530,12 @@ type SSchedtagResourceBase struct { // SSecurityGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroup. type SSecurityGroup struct { apis.SSharableVirtualResourceBase - IsDirty bool `json:"is_dirty"` -} - -// SSecurityGroupCache is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupCache. -type SSecurityGroupCache struct { - apis.SStatusStandaloneResourceBase apis.SExternalizedResourceBase - SCloudregionResourceBase + IsDirty bool `json:"is_dirty"` SManagedResourceBase - SSecurityGroupResourceBase - // 被其他安全组引用的次数 - ReferenceCount int `json:"reference_count"` - // 虚拟私有网络外部Id - VpcId string `json:"vpc_id"` - ExternalProjectId string `json:"external_project_id"` + SCloudregionResourceBase + SGlobalVpcResourceBase + SVpcResourceBase } // SSecurityGroupResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupResourceBase. @@ -2521,9 +2547,11 @@ type SSecurityGroupResourceBase struct { // SSecurityGroupRule is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupRule. type SSecurityGroupRule struct { apis.SResourceBase + apis.SStatusResourceBase + apis.SExternalizedResourceBase SSecurityGroupResourceBase Id string `json:"id"` - Priority int64 `json:"priority"` + Priority int `json:"priority"` Protocol string `json:"protocol"` Ports string `json:"ports"` Direction string `json:"direction"` @@ -2877,6 +2905,7 @@ type SWafRuleStatement struct { type SWire struct { apis.SStatusInfrasResourceBase apis.SExternalizedResourceBase + SManagedResourceBase SVpcResourceBase SZoneResourceBase // 带宽大小, 单位Mbps diff --git a/pkg/apis/identity/zz_generated.model.go b/pkg/apis/identity/zz_generated.model.go index 33545c0c7f..83fb2fb95e 100644 --- a/pkg/apis/identity/zz_generated.model.go +++ b/pkg/apis/identity/zz_generated.model.go @@ -19,7 +19,7 @@ import ( jsonutils "yunion.io/x/jsonutils" - apis "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/apis" tagutils "yunion.io/x/onecloud/pkg/util/tagutils" ) @@ -63,6 +63,7 @@ type SCredential struct { // SDomain is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SDomain. type SDomain struct { apis.SStandaloneResourceBase + apis.SPendingDeletedBase // 额外信息 Extra *jsonutils.JSONDict `json:"extra"` // 改域是否启用 @@ -77,7 +78,7 @@ type SDomain struct { // SEnabledIdentityBaseResource is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SEnabledIdentityBaseResource. type SEnabledIdentityBaseResource struct { SIdentityBaseResource - Enabled *bool `json:"enabled,omitempty"` + apis.SEnabledResourceBase } // SEndpoint is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SEndpoint. @@ -129,6 +130,7 @@ type SGroup struct { type SIdentityBaseResource struct { apis.SStandaloneResourceBase apis.SDomainizedResourceBase + apis.SPendingDeletedBase // 额外信息 Extra *jsonutils.JSONDict `json:"extra"` } @@ -200,7 +202,9 @@ type SNonlocalUser struct { // SOrganization is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganization. type SOrganization struct { - apis.SEnabledStatusInfrasResourceBase + SEnabledIdentityBaseResource + apis.SSharableBaseResource + apis.SStatusResourceBase Type string `json:"type"` Keys string `json:"keys"` Level int `json:"level"` @@ -208,17 +212,19 @@ type SOrganization struct { // SOrganizationDetails is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationDetails. type SOrganizationDetails struct { - apis.EnabledStatusInfrasResourceBaseDetails + EnabledIdentityBaseResourceDetails + apis.SharableResourceBaseInfo SOrganization } // SOrganizationNode is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationNode. type SOrganizationNode struct { apis.SStandaloneResourceBase + apis.SPendingDeletedBase OrgId string `json:"org_id"` FullLabel string `json:"full_label"` Level int `json:"level"` - Weight *int `json:"weight"` + Weight int `json:"weight"` } // SOrganizationNodeDetails is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationNodeDetails. @@ -339,6 +345,19 @@ type SServiceCertificate struct { CaPrivateKey string `json:"ca_private_key"` } +// STokenCache is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.STokenCache. +type STokenCache struct { + apis.SStandaloneAnonResourceBase + Valid bool `json:"valid"` + Method string `json:"method"` + AuditIds string `json:"audit_ids"` + UserId string `json:"user_id"` + ProjectId string `json:"project_id"` + DomainId string `json:"domain_id"` + Source string `json:"source"` + Ip string `json:"ip"` +} + // SUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SUser. type SUser struct { apis.SRecordChecksumResourceBase diff --git a/pkg/apis/zz_generated.model.go b/pkg/apis/zz_generated.model.go index 9bd798785a..89b3214729 100644 --- a/pkg/apis/zz_generated.model.go +++ b/pkg/apis/zz_generated.model.go @@ -152,6 +152,7 @@ type SJointResourceBase struct { // SKeystoneCacheObject is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SKeystoneCacheObject. type SKeystoneCacheObject struct { SStandaloneResourceBase + SPendingDeletedBase DomainId string `json:"domain_id"` Domain string `json:"domain"` Lang string `json:"lang"` @@ -212,6 +213,14 @@ type SOpsLog struct { OwnerProjectId string `json:"owner_tenant_id"` } +// SPendingDeletedBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SPendingDeletedBase. +type SPendingDeletedBase struct { + // 资源放入回收站时间 + PendingDeletedAt time.Time `json:"pending_deleted_at"` + // 资源是否处于回收站中 + PendingDeleted bool `json:"pending_deleted"` +} + // SProjectizedResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SProjectizedResourceBase. type SProjectizedResourceBase struct { SDomainizedResourceBase @@ -354,15 +363,12 @@ type SVirtualJointResourceBase struct { type SVirtualResourceBase struct { SStatusStandaloneResourceBase SProjectizedResourceBase + SPendingDeletedBase // 云上同步资源是否在本地被更改过配置, local: 更改过, cloud: 未更改过 // example: local ProjectSrc string `json:"project_src"` // 是否是系统资源 IsSystem bool `json:"is_system"` - // 资源放入回收站时间 - PendingDeletedAt time.Time `json:"pending_deleted_at"` - // 资源是否处于回收站中 - PendingDeleted bool `json:"pending_deleted"` // 资源是否被冻结 Freezed bool `json:"freezed"` } From 33ac7b1fdabc665a47b078613723a458512b36d4 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Sun, 26 Nov 2023 19:52:42 +0800 Subject: [PATCH 3/3] fix: encrypt object storage secret --- cmd/climc/shell/compute/backup.go | 1 + pkg/apis/compute/backup.go | 35 +++--- pkg/apis/compute/dnstrafficpolicy.go | 42 ------- pkg/apis/compute/dnszonecache.go | 45 ------- pkg/compute/models/backup_storage.go | 117 +++++++++++++++++- pkg/compute/models/disks.go | 6 +- pkg/compute/regiondrivers/kvm.go | 48 +++++-- .../tasks/backup_storage_syncstatus_task.go | 2 + pkg/mcclient/options/compute/backup.go | 15 +++ 9 files changed, 190 insertions(+), 121 deletions(-) delete mode 100644 pkg/apis/compute/dnstrafficpolicy.go delete mode 100644 pkg/apis/compute/dnszonecache.go diff --git a/cmd/climc/shell/compute/backup.go b/cmd/climc/shell/compute/backup.go index 7a5261fd57..29d677c76c 100644 --- a/cmd/climc/shell/compute/backup.go +++ b/cmd/climc/shell/compute/backup.go @@ -26,6 +26,7 @@ func init() { bsCmd.List(&compute.BackupStorageListOptions{}) bsCmd.Show(&compute.BackupStorageIdOptions{}) bsCmd.Create(&compute.BackupStorageCreateOptions{}) + bsCmd.Update(&compute.BackupStorageUpdateOptions{}) bsCmd.Delete(&compute.BackupStorageIdOptions{}) bsCmd.Perform("public", &options.BasePublicOptions{}) bsCmd.Perform("private", &options.BaseIdOptions{}) diff --git a/pkg/apis/compute/backup.go b/pkg/apis/compute/backup.go index f154917f67..cee934e0ef 100644 --- a/pkg/apis/compute/backup.go +++ b/pkg/apis/compute/backup.go @@ -59,26 +59,18 @@ type BackupStorageCreateInput struct { // 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: access url of object storage bucket - // example: https://qxxxxxo.tos-cn-beijing.volces.com - ObjectBucketUrl string `json:"object_bucket_url"` - // description: access key of object storage - ObjectAccessKey string `json:"object_access_key"` - // description: secret of object storage - ObjectSecret string `json:"object_secret"` + SBackupStorageAccessInfo // description: Capacity size in MB CapacityMb int `json:"capacity_mb"` } +type BackupStorageUpdateInput struct { + apis.EnabledStatusInfrasResourceBaseUpdateInput + + SBackupStorageAccessInfo +} + /*type BackupStorageAccessInfo struct { AccessUrl string }*/ @@ -175,12 +167,21 @@ type InstanceBackupManagerSyncstatusInput struct { } type SBackupStorageAccessInfo struct { - NfsHost string `json:"nfs_host"` + // 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: access url of object storage bucket + // example: https://qxxxxxo.tos-cn-beijing.volces.com ObjectBucketUrl string `json:"object_bucket_url"` + // description: access key of object storage ObjectAccessKey string `json:"object_access_key"` - ObjectSecret string `json:"object_secret"` + // description: secret of object storage + ObjectSecret string `json:"object_secret"` } func (ba *SBackupStorageAccessInfo) String() string { diff --git a/pkg/apis/compute/dnstrafficpolicy.go b/pkg/apis/compute/dnstrafficpolicy.go deleted file mode 100644 index 6293929547..0000000000 --- a/pkg/apis/compute/dnstrafficpolicy.go +++ /dev/null @@ -1,42 +0,0 @@ -// 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/apis" -) - -type DnsTrafficPolicyCreateInput struct { - apis.EnabledStatusInfrasResourceBaseCreateInput - - Provider string `json:"provider"` - PolicyType string `json:"policy_type"` - PolicyValue string `json:"policy_value"` - // 额外参数 - Options *jsonutils.JSONDict `json:"options"` -} - -type DnsTrafficPolicyDetails struct { - apis.EnabledStatusInfrasResourceBaseDetails -} - -type DnsTrafficPolicyListInput struct { - apis.EnabledStatusInfrasResourceBaseListInput - - Provider []string `json:"provider"` - PolicyType string `json:"policy_type"` -} diff --git a/pkg/apis/compute/dnszonecache.go b/pkg/apis/compute/dnszonecache.go deleted file mode 100644 index fef2864042..0000000000 --- a/pkg/apis/compute/dnszonecache.go +++ /dev/null @@ -1,45 +0,0 @@ -// 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 ( - DNS_ZONE_CACHE_STATUS_DELETING = "deleting" - DNS_ZONE_CACHE_STATUS_DELETE_FAILED = "delete_failed" - DNS_ZONE_CACHE_STATUS_CREATING = "creating" - DNS_ZONE_CACHE_STATUS_CREATE_FAILED = "create_failed" - DNS_ZONE_CACHE_STATUS_AVAILABLE = "available" - DNS_ZONE_CACHE_STATUS_UNKNOWN = "unknown" -) - -type DnsZoneCacheCreateInput struct { -} - -type DnsZoneCacheDetails struct { - apis.StatusStandaloneResourceDetails - - Account string - Brand string - Provider string -} - -type DnsZoneCacheListInput struct { - apis.StatusStandaloneResourceListInput - - DnsZoneFilterListBase - - CloudaccountId string `json:"cloudaccount_id"` -} diff --git a/pkg/compute/models/backup_storage.go b/pkg/compute/models/backup_storage.go index 21a1e3c174..abf51ce259 100644 --- a/pkg/compute/models/backup_storage.go +++ b/pkg/compute/models/backup_storage.go @@ -132,11 +132,31 @@ func (bs *SBackupStorage) ValidateDeleteCondition(ctx context.Context, info json 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) + bs.SetStatus(userCred, api.BACKUPSTORAGE_STATUS_OFFLINE, "") + if bs.StorageType == api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE { + err := bs.saveObjectSecret(bs.AccessInfo.ObjectSecret) + if err != nil { + log.Errorf("convert object secret fail %s", err) + } + } err := StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "") if err != nil { log.Errorf("unable to sync backup storage status") } - bs.SetStatus(userCred, api.BACKUPSTORAGE_STATUS_OFFLINE, "") +} + +func (bs *SBackupStorage) saveObjectSecret(secret string) error { + sec, err := utils.EncryptAESBase64(bs.Id, secret) + if err != nil { + return errors.Wrap(err, "EncryptAESBase64") + } + accessInfo := *bs.AccessInfo + accessInfo.ObjectSecret = sec + _, err = db.Update(bs, func() error { + bs.AccessInfo = &accessInfo + return nil + }) + return errors.Wrap(err, "Update") } func (bs *SBackupStorage) getMoreDetails(ctx context.Context, out api.BackupStorageDetails) api.BackupStorageDetails { @@ -173,9 +193,9 @@ func (bm *SBackupStorageManager) ListItemFilter(ctx context.Context, q *sqlchemy return q, nil } -func (self *SBackupStorage) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskBackupSyncstatusInput) (jsonutils.JSONObject, error) { +func (bs *SBackupStorage) 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() + count, err := taskman.TaskManager.QueryTasksOfObject(bs, time.Now().Add(-3*time.Minute), &openTask).CountWithError() if err != nil { return nil, err } @@ -183,5 +203,94 @@ func (self *SBackupStorage) PerformSyncstatus(ctx context.Context, userCred mccl return nil, httperrors.NewBadRequestError("Backup has %d task active, can't sync status", count) } - return nil, StartResourceSyncStatusTask(ctx, userCred, self, "BackupStorageSyncstatusTask", "") + return nil, StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "") +} + +func (bs *SBackupStorage) ValidateUpdateData( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input api.BackupStorageUpdateInput, +) (api.BackupStorageUpdateInput, error) { + var err error + if len(input.Name) > 0 { + err := isValidBucketName(input.Name) + if err != nil { + return input, httperrors.NewInputParameterError("invalid bucket name(%s): %s", input.Name, err) + } + } + input.EnabledStatusInfrasResourceBaseUpdateInput, err = bs.SEnabledStatusInfrasResourceBase.ValidateUpdateData(ctx, userCred, query, input.EnabledStatusInfrasResourceBaseUpdateInput) + if err != nil { + return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData") + } + return input, nil +} + +func (bs *SBackupStorage) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + bs.SEnabledStatusInfrasResourceBase.PostUpdate(ctx, userCred, query, data) + input := api.BackupStorageUpdateInput{} + err := data.Unmarshal(&input) + if err != nil { + log.Errorf("SBackupStorage.PostUpdate Unmarshal data %s fail %s", data, err) + return + } + // update accessinfo + accessInfoChanged := false + accessInfo := *bs.AccessInfo + switch bs.StorageType { + case api.BACKUPSTORAGE_TYPE_NFS: + if len(input.NfsHost) > 0 { + accessInfo.NfsHost = input.NfsHost + accessInfoChanged = true + } + if len(input.NfsSharedDir) > 0 { + accessInfo.NfsSharedDir = input.NfsSharedDir + accessInfoChanged = true + } + case api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE: + if len(input.ObjectBucketUrl) > 0 { + accessInfo.ObjectBucketUrl = input.ObjectBucketUrl + accessInfoChanged = true + } + if len(input.ObjectAccessKey) > 0 { + accessInfo.ObjectAccessKey = input.ObjectAccessKey + accessInfoChanged = true + } + if len(input.ObjectSecret) > 0 { + sec, err := utils.EncryptAESBase64(bs.Id, input.ObjectSecret) + if err != nil { + log.Errorf("EncryptAESBase64 fail %s", err) + return + } + accessInfo.ObjectSecret = sec + accessInfoChanged = true + } + } + if accessInfoChanged { + _, err = db.Update(bs, func() error { + bs.AccessInfo = &accessInfo + return nil + }) + if err != nil { + log.Errorf("update fail %s", err) + } else { + err := StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "") + if err != nil { + log.Errorf("unable to sync backup storage status") + } + } + } +} + +func (bs *SBackupStorage) GetAccessInfo() (*api.SBackupStorageAccessInfo, error) { + accessInfo := *bs.AccessInfo + switch bs.StorageType { + case api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE: + secret, err := utils.DescryptAESBase64(bs.Id, accessInfo.ObjectSecret) + if err != nil { + return nil, errors.Wrap(err, "DescryptAESBase64") + } + accessInfo.ObjectSecret = secret + } + return &accessInfo, nil } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 688f17e367..42d6824cdd 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -808,10 +808,14 @@ func (self *SDisk) getDiskAllocateFromBackupInput(ctx context.Context, backupId if err != nil { return nil, errors.Wrapf(err, "unable to get backupstorage of backup %s", backupId) } + accessInfo, err := bs.GetAccessInfo() + if err != nil { + return nil, errors.Wrap(err, "backupStorage.GetAccessInfo") + } return &api.DiskAllocateFromBackupInput{ BackupId: backupId, BackupStorageId: bs.GetId(), - BackupStorageAccessInfo: jsonutils.Marshal(bs.AccessInfo).(*jsonutils.JSONDict), + BackupStorageAccessInfo: jsonutils.Marshal(accessInfo).(*jsonutils.JSONDict), }, nil } diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index 2d6e018115..dc030ad07e 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -1005,7 +1005,11 @@ func (self *SKVMRegionDriver) RequestPackInstanceBackup(ctx context.Context, ib body := jsonutils.NewDict() body.Set("package_name", jsonutils.NewString(packageName)) body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId())) - body.Set("backup_storage_access_info", jsonutils.Marshal(backupStorage.AccessInfo)) + accessInfo, err := backupStorage.GetAccessInfo() + if err != nil { + return errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) body.Set("backup_ids", jsonutils.Marshal(backupIds)) body.Set("metadata", jsonutils.Marshal(metadata)) header := task.GetTaskRequestHeader() @@ -1031,7 +1035,11 @@ func (self *SKVMRegionDriver) RequestUnpackInstanceBackup(ctx context.Context, i body := jsonutils.NewDict() body.Set("package_name", jsonutils.NewString(packageName)) body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId())) - body.Set("backup_storage_access_info", jsonutils.Marshal(backupStorage.AccessInfo)) + accessInfo, err := backupStorage.GetAccessInfo() + if err != nil { + return errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) if metadataOnly { body.Set("metadata_only", jsonutils.JSONTrue) } @@ -1052,7 +1060,11 @@ func (self *SKVMRegionDriver) RequestSyncBackupStorageStatus(ctx context.Context url := fmt.Sprintf("%s/storages/sync-backup-storage", host.ManagerUri) body := jsonutils.NewDict() body.Set("backup_storage_id", jsonutils.NewString(bs.GetId())) - body.Set("backup_storage_access_info", jsonutils.Marshal(bs.AccessInfo)) + accessInfo, err := bs.GetAccessInfo() + if err != nil { + return nil, errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) header := task.GetTaskRequestHeader() _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) if err != nil { @@ -1109,7 +1121,7 @@ func (self *SKVMRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, u 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() + backupStorage, err := backup.GetBackupStorage() if err != nil { return nil, errors.Wrap(err, "unable to get backupStorage") } @@ -1128,8 +1140,12 @@ func (self *SKVMRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, u 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)) + body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId())) + accessInfo, err := backupStorage.GetAccessInfo() + if err != nil { + return nil, errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) header := task.GetTaskRequestHeader() _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) if err != nil { @@ -1332,7 +1348,7 @@ func (self *SKVMRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { } func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *models.SDiskBackup, task taskman.ITask) error { - backupStroage, err := backup.GetBackupStorage() + backupStorage, err := backup.GetBackupStorage() if err != nil { return errors.Wrap(err, "unable to get backupStorage") } @@ -1350,8 +1366,12 @@ func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *m 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)) + body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId())) + accessInfo, err := backupStorage.GetAccessInfo() + if err != nil { + return errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) header := task.GetTaskRequestHeader() _, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) if err != nil { @@ -1361,7 +1381,7 @@ func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *m } func (self *SKVMRegionDriver) RequestCreateBackup(ctx context.Context, backup *models.SDiskBackup, snapshotId string, task taskman.ITask) error { - backupStroage, err := backup.GetBackupStorage() + backupStorage, err := backup.GetBackupStorage() if err != nil { return errors.Wrap(err, "unable to get backupStorage") } @@ -1382,8 +1402,12 @@ func (self *SKVMRegionDriver) RequestCreateBackup(ctx context.Context, backup *m 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)) + body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId())) + accessInfo, err := backupStorage.GetAccessInfo() + if err != nil { + return errors.Wrap(err, "GetAccessInfo") + } + body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo)) if len(backup.EncryptKeyId) > 0 { body.Set("encrypt_key_id", jsonutils.NewString(backup.EncryptKeyId)) } diff --git a/pkg/compute/tasks/backup_storage_syncstatus_task.go b/pkg/compute/tasks/backup_storage_syncstatus_task.go index c2fe124fc1..341a467a1f 100644 --- a/pkg/compute/tasks/backup_storage_syncstatus_task.go +++ b/pkg/compute/tasks/backup_storage_syncstatus_task.go @@ -19,6 +19,7 @@ import ( "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" @@ -35,6 +36,7 @@ func init() { func (self *BackupStorageSyncstatusTask) taskFailed(ctx context.Context, bs *models.SBackupStorage, err jsonutils.JSONObject) { logclient.AddActionLogWithContext(ctx, bs, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + bs.SetStatus(self.UserCred, api.BACKUPSTORAGE_STATUS_OFFLINE, err.String()) self.SetStageFailed(ctx, err) } diff --git a/pkg/mcclient/options/compute/backup.go b/pkg/mcclient/options/compute/backup.go index 62f43ab03d..b5def5b986 100644 --- a/pkg/mcclient/options/compute/backup.go +++ b/pkg/mcclient/options/compute/backup.go @@ -128,6 +128,21 @@ func (opts *BackupStorageCreateOptions) Params() (jsonutils.JSONObject, error) { return jsonutils.Marshal(opts), nil } +type BackupStorageUpdateOptions struct { + options.BaseUpdateOptions + + NfsHost string `help:"nfs host, required when storage_type is nfs"` + NfsSharedDir string `help:"nfs shared dir, required when storage_type is nfs" ` + + ObjectBucketUrl string `help:"object bucket url, required when storage_type is object"` + ObjectAccessKey string `help:"object storage access key, required when storage_type is object"` + ObjectSecret string `help:"object storage secret, required when storage_type is object"` +} + +func (opts *BackupStorageUpdateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type InstanceBackupListOptions struct { options.BaseListOptions