From b6917c56042ed56ffe6a09a30ea62132d7ffd959 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Thu, 19 Nov 2020 10:16:43 +0800 Subject: [PATCH] feat: rds recovery new instance from backup supported --- pkg/apis/compute/dbinstance.go | 6 ++ pkg/cloudcommon/validators/validators.go | 13 +++ pkg/cloudprovider/dbinstance.go | 11 ++ pkg/cloudprovider/resources.go | 3 + pkg/compute/models/dbinstance_backups.go | 111 +++++++++++++++++++- pkg/compute/models/dbinstances.go | 49 ++++++++- pkg/compute/models/regiondrivers.go | 1 + pkg/compute/regiondrivers/aliyun.go | 35 +++--- pkg/compute/regiondrivers/base.go | 4 + pkg/compute/regiondrivers/managedvirtual.go | 76 ++++++++++++-- pkg/compute/tasks/dbinstance_create_task.go | 13 ++- pkg/mcclient/options/dbinstances.go | 66 ++++++++++++ pkg/multicloud/aliyun/dbinstance_backup.go | 33 +++++- pkg/multicloud/dbinstance_backup_base.go | 11 ++ pkg/multicloud/google/dbinstance_backup.go | 2 + 15 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 pkg/mcclient/options/dbinstances.go diff --git a/pkg/apis/compute/dbinstance.go b/pkg/apis/compute/dbinstance.go index d62a55cd4b..94f5302819 100644 --- a/pkg/apis/compute/dbinstance.go +++ b/pkg/apis/compute/dbinstance.go @@ -142,6 +142,9 @@ type DBInstanceCreateInput struct { // required: true DiskSizeGB int `json:"disk_size_gb"` + // 指定连接端口 + Port int `json:"port"` + // rds初始化密码 // 阿里云不需要此参数 // 华为云会默认创建一个用户,若不传此参数, 则为随机密码 @@ -163,6 +166,9 @@ type DBInstanceCreateInput struct { // swagger:ignore Provider string + + // 从备份中创建新实例 + DBInstancebackupId string `json:"dbinstancebackup_id"` } type SDBInstanceChangeConfigInput struct { diff --git a/pkg/cloudcommon/validators/validators.go b/pkg/cloudcommon/validators/validators.go index f82f830ae2..de862d5c8b 100644 --- a/pkg/cloudcommon/validators/validators.go +++ b/pkg/cloudcommon/validators/validators.go @@ -30,6 +30,7 @@ import ( "strings" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" @@ -821,3 +822,15 @@ func NewIPv4AddrValidator(key string) *ValidatorIPv4Addr { v.SetParent(v) return v } + +var ValidateModel = func(userCred mcclient.TokenCredential, manager db.IStandaloneModelManager, id *string) (db.IModel, error) { + model, err := manager.FetchByIdOrName(userCred, *id) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2(manager.Keyword(), *id) + } + return nil, httperrors.NewGeneralError(err) + } + *id = model.GetId() + return model, nil +} diff --git a/pkg/cloudprovider/dbinstance.go b/pkg/cloudprovider/dbinstance.go index 076107208f..74e0483e99 100644 --- a/pkg/cloudprovider/dbinstance.go +++ b/pkg/cloudprovider/dbinstance.go @@ -16,6 +16,14 @@ package cloudprovider import "yunion.io/x/onecloud/pkg/util/billing" +type TBackupMethod string + +const ( + BackupMethodLogical = TBackupMethod("Logical") + BackupMethodPhysical = TBackupMethod("Physical") + BackupMethodUnknown = TBackupMethod("") +) + type SDBInstanceNetwork struct { IP string NetworkId string @@ -55,6 +63,9 @@ type SManagedDBInstanceCreateConfig struct { ProjectId string BillingCycle *billing.SBillingCycle + + // 仅从备份恢复到新实例用到 + RdsId string } type SManagedDBInstanceChangeConfig struct { diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 67e093fe89..ceedf270e1 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -816,6 +816,9 @@ type ICloudDBInstanceBackup interface { GetBackupSizeMb() int GetDBNames() string GetBackupMode() string + GetBackupMethod() TBackupMethod + + CreateICloudDBInstance(opts *SManagedDBInstanceCreateConfig) (ICloudDBInstance, error) Delete() error } diff --git a/pkg/compute/models/dbinstance_backups.go b/pkg/compute/models/dbinstance_backups.go index 56967a69ee..58296fe994 100644 --- a/pkg/compute/models/dbinstance_backups.go +++ b/pkg/compute/models/dbinstance_backups.go @@ -85,9 +85,8 @@ type SDBInstanceBackup struct { // example: 32 BackupSizeMb int `nullable:"false" list:"user" json:"backup_size_mb"` - // RDS实例Id - // example: 239b9663-6d06-4ef4-8cfc-320a7fb6660d - // DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` + // 备份方式 Logical|Physical + BackupMethod string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional" json:"backup_method"` } func (manager *SDBInstanceBackupManager) GetContextManagers() [][]db.IModelManager { @@ -369,6 +368,46 @@ func (backup *SDBInstanceBackup) GetIRegion() (cloudprovider.ICloudRegion, error } func (backup *SDBInstanceBackup) GetIDBInstanceBackup() (cloudprovider.ICloudDBInstanceBackup, error) { + if len(backup.ExternalId) == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty external id") + } + if len(backup.DBInstanceId) > 0 { + rds, err := backup.GetDBInstance() + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstance") + } + iRds, err := rds.GetIDBInstance() + if err != nil { + return nil, errors.Wrapf(err, "GetIDBInstance") + } + err = cloudprovider.Wait(time.Second*3, time.Second*15, func() (bool, error) { + backups, err := iRds.GetIDBInstanceBackups() + if err != nil { + return false, errors.Wrapf(err, "GetIDBInstanceBackups") + } + for i := range backups { + if backups[i].GetGlobalId() == backup.ExternalId { + return true, nil + } + } + log.Warningf("failed to found backup %s", backup.ExternalId) + return false, nil + }) + if err != nil { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "timeout for search backup %s", backup.ExternalId) + } + backups, err := iRds.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrapf(err, "GetIDBInstanceBackups") + } + for i := range backups { + if backups[i].GetGlobalId() == backup.ExternalId { + return backups[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "search backup %s", backup.ExternalId) + } + iRegion, err := backup.GetIRegion() if err != nil { return nil, errors.Wrap(err, "backup.GetIRegion") @@ -439,6 +478,7 @@ func (self *SDBInstanceBackup) SyncWithCloudDBInstanceBackup( self.Engine = extBackup.GetEngine() self.EngineVersion = extBackup.GetEngineVersion() self.DBNames = extBackup.GetDBNames() + self.BackupMethod = string(extBackup.GetBackupMethod()) if dbinstanceId := extBackup.GetDBInstanceId(); len(dbinstanceId) > 0 { //有可能云上删除了实例,未删除备份 @@ -497,6 +537,7 @@ func (manager *SDBInstanceBackupManager) newFromCloudDBInstanceBackup( backup.BackupSizeMb = extBackup.GetBackupSizeMb() backup.DBNames = extBackup.GetDBNames() backup.BackupMode = extBackup.GetBackupMode() + backup.BackupMethod = string(extBackup.GetBackupMethod()) backup.ExternalId = extBackup.GetGlobalId() if dbinstanceId := extBackup.GetDBInstanceId(); len(dbinstanceId) > 0 { @@ -591,3 +632,67 @@ func (manager *SDBInstanceBackupManager) ListItemExportKeys(ctx context.Context, func (self *SDBInstanceBackup) GetChangeOwnerCandidateDomainIds() []string { return self.SManagedResourceBase.GetChangeOwnerCandidateDomainIds() } + +func (self *SDBInstanceBackup) fillRdsConfig(output *api.DBInstanceCreateInput) error { + if self.Status != api.DBINSTANCE_BACKUP_READY { + return fmt.Errorf("backup %s status is %s require %s", self.Name, self.Status, api.DBINSTANCE_BACKUP_READY) + } + if len(self.DBInstanceId) == 0 { + if len(self.Engine) == 0 { + return fmt.Errorf("backup engine %s is unknown", self.Name) + } + output.Engine = self.Engine + if len(self.EngineVersion) == 0 { + return fmt.Errorf("backup engine version %s is unknown", self.Name) + } + output.EngineVersion = self.EngineVersion + return nil + } + rds, err := self.GetDBInstance() + if err != nil { + return errors.Wrapf(err, "backup.GetDBInstance") + } + if len(output.NetworkId) == 0 { + network, _ := rds.GetDBNetwork() + if network != nil { + output.NetworkId = network.NetworkId + } + } + + if output.VcpuCount == 0 { + output.VcpuCount = rds.VcpuCount + } + if output.VmemSizeMb == 0 { + output.VmemSizeMb = rds.VmemSizeMb + } + if output.DiskSizeGB == 0 { + output.DiskSizeGB = rds.DiskSizeGB + } + if output.Port == 0 { + output.Port = rds.Port + } + if len(output.Category) == 0 { + output.Category = rds.Category + } + if len(output.StorageType) == 0 { + output.StorageType = rds.StorageType + } + output.Engine = rds.Engine + output.EngineVersion = rds.EngineVersion + if len(output.InstanceType) == 0 { + output.InstanceType = rds.InstanceType + } + if len(output.VpcId) == 0 { + output.VpcId = rds.VpcId + } + if len(output.Zone1) == 0 { + output.Zone1 = rds.Zone1 + } + if len(output.Zone2) == 0 { + output.Zone2 = rds.Zone2 + } + if len(output.Zone3) == 0 { + output.Zone3 = rds.Zone3 + } + return nil +} diff --git a/pkg/compute/models/dbinstances.go b/pkg/compute/models/dbinstances.go index da74807fab..b7bf719405 100644 --- a/pkg/compute/models/dbinstances.go +++ b/pkg/compute/models/dbinstances.go @@ -18,6 +18,7 @@ import ( "context" "database/sql" "fmt" + "net" "strings" "time" @@ -135,6 +136,9 @@ type SDBInstance struct { Zone2 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` // 可用区3 Zone3 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` + + // 从备份创建新实例 + DBInstancebackupId string `width:"36" name:"dbinstancebackup_id" charset:"ascii" nullable:"false" create:"optional"` } func (manager *SDBInstanceManager) GetContextManagers() [][]db.IModelManager { @@ -274,8 +278,18 @@ func (man *SDBInstanceManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field } func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.DBInstanceCreateInput) (*jsonutils.JSONDict, error) { + if len(input.DBInstancebackupId) > 0 { + _backup, err := validators.ValidateModel(userCred, DBInstanceBackupManager, &input.DBInstancebackupId) + if err != nil { + return nil, err + } + backup := _backup.(*SDBInstanceBackup) + err = backup.fillRdsConfig(&input) + if err != nil { + return nil, err + } + } data := input.JSON(input) - networkV := validators.NewModelIdOrNameValidator("network", "network", ownerId) addressV := validators.NewIPv4AddrValidator("address") secgroupV := validators.NewModelIdOrNameValidator("secgroup", "secgroup", ownerId) masterV := validators.NewModelIdOrNameValidator("master_instance", "dbinstance", ownerId) @@ -283,7 +297,6 @@ func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred zone2V := validators.NewModelIdOrNameValidator("zone2", "zone", ownerId) zone3V := validators.NewModelIdOrNameValidator("zone3", "zone", ownerId) keyV := map[string]validators.IValidator{ - "network": networkV, "address": addressV.Optional(true), "master": masterV.ModelIdKey("master_instance_id").Optional(true), "secgroup": secgroupV.Optional(true), @@ -318,10 +331,36 @@ func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred } } - network := networkV.Model.(*SNetwork) - input.NetworkExternalId = network.ExternalId + var vpc *SVpc + var network *SNetwork + if len(input.NetworkId) > 0 { + _network, err := validators.ValidateModel(userCred, NetworkManager, &input.NetworkId) + if err != nil { + return nil, err + } + network = _network.(*SNetwork) + input.NetworkExternalId = network.ExternalId + if len(input.Address) > 0 { + ip := net.ParseIP(input.Address).To4() + if ip == nil { + return nil, httperrors.NewInputParameterError("invalid address: %s", input.Address) + } + addr, _ := netutils.NewIPV4Addr(input.Address) + if !network.IsAddressInRange(addr) { + return nil, httperrors.NewInputParameterError("Ip %s not in network %s(%s) range", input.Address, network.Name, network.Id) + } + } + vpc = network.GetVpc() + } else if len(input.VpcId) > 0 { + _vpc, err := validators.ValidateModel(userCred, VpcManager, &input.VpcId) + if err != nil { + return nil, err + } + vpc = _vpc.(*SVpc) + } else { + return nil, httperrors.NewMissingParameterError("vpc_id") + } - vpc := network.GetVpc() input.VpcId = vpc.Id input.ManagerId = vpc.ManagerId cloudprovider := vpc.GetCloudprovider() diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 006a470ed6..88ba6a9375 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -154,6 +154,7 @@ type IDBInstanceDriver interface { ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, account string) error RequestCreateDBInstance(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, task taskman.ITask) error + RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, task taskman.ITask) error RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, backup *SDBInstanceBackup, task taskman.ITask) error RequestChangeDBInstanceConfig(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, task taskman.ITask) error diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index ce5710c01b..3be983d9b5 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -1000,25 +1000,26 @@ func (self *SAliyunRegionDriver) ValidateCreateDBInstanceData(ctx context.Contex return input, httperrors.NewInputParameterError("slave dbinstance not support prepaid billing type") } - wire := network.GetWire() - if wire == nil { - return input, httperrors.NewGeneralError(fmt.Errorf("failed to found wire for network %s(%s)", network.Name, network.Id)) - } - zone := wire.GetZone() - if zone == nil { - return input, httperrors.NewGeneralError(fmt.Errorf("failed to found zone for wire %s(%s)", wire.Name, wire.Id)) - } - - match := false - for _, sku := range skus { - if utils.IsInStringArray(zone.Id, []string{sku.Zone1, sku.Zone2, sku.Zone3}) { - match = true - break + if network != nil { + wire := network.GetWire() + if wire == nil { + return input, httperrors.NewGeneralError(fmt.Errorf("failed to found wire for network %s(%s)", network.Name, network.Id)) + } + zone := wire.GetZone() + if zone == nil { + return input, httperrors.NewGeneralError(fmt.Errorf("failed to found zone for wire %s(%s)", wire.Name, wire.Id)) } - } - if !match { - return input, httperrors.NewInputParameterError("failed to match any skus in the network %s(%s) zone %s(%s)", network.Name, network.Id, zone.Name, zone.Id) + match := false + for _, sku := range skus { + if utils.IsInStringArray(zone.Id, []string{sku.Zone1, sku.Zone2, sku.Zone3}) { + match = true + break + } + } + if !match { + return input, httperrors.NewInputParameterError("failed to match any skus in the network %s(%s) zone %s(%s)", network.Name, network.Id, zone.Name, zone.Id) + } } var master *models.SDBInstance diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index 14bb88979a..a69914b2c7 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -285,6 +285,10 @@ func (self *SBaseRegionDriver) RequestCreateDBInstance(ctx context.Context, user return fmt.Errorf("Not Implement RequestCreateDBInstance") } +func (self *SBaseRegionDriver) RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateDBInstanceFromBackup") +} + func (self *SBaseRegionDriver) RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, backup *models.SDBInstanceBackup, task taskman.ITask) error { return fmt.Errorf("Not Implement RequestCreateDBInstanceBackup") } diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 5f335096f2..e873ed040a 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -1738,6 +1738,73 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont return nil } +func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceFromBackup(ctx context.Context, userCred mcclient.TokenCredential, rds *models.SDBInstance, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + _backup, err := models.DBInstanceBackupManager.FetchById(rds.DBInstancebackupId) + if err != nil { + return nil, errors.Wrapf(err, "DBInstanceBackupManager.FetchById(%s)", rds.DBInstancebackupId) + } + backup := _backup.(*models.SDBInstanceBackup) + iBackup, err := backup.GetIDBInstanceBackup() + if err != nil { + return nil, errors.Wrapf(err, "backup.GetIDBInstanceBackup") + } + vpc, err := rds.GetVpc() + if err != nil { + return nil, errors.Wrap(err, "rds.GetVpc()") + } + desc := cloudprovider.SManagedDBInstanceCreateConfig{ + Name: rds.Name, + Description: rds.Description, + StorageType: rds.StorageType, + DiskSizeGB: rds.DiskSizeGB, + VcpuCount: rds.VcpuCount, + VmemSizeMb: rds.VmemSizeMb, + VpcId: vpc.ExternalId, + Engine: rds.Engine, + EngineVersion: rds.EngineVersion, + Category: rds.Category, + Port: rds.Port, + } + if len(backup.DBInstanceId) > 0 { + parentRds, err := backup.GetDBInstance() + if err != nil { + return nil, errors.Wrapf(err, "backup.GetDBInstance") + } + desc.RdsId = parentRds.ExternalId + } + + log.Debugf("create from backup params: %s", jsonutils.Marshal(desc).String()) + + if rds.BillingType == billing_api.BILLING_TYPE_PREPAID { + bc, err := billing.ParseBillingCycle(rds.BillingCycle) + if err != nil { + log.Errorf("failed to parse billing cycle %s: %v", rds.BillingCycle, err) + } else if bc.IsValid() { + desc.BillingCycle = &bc + desc.BillingCycle.AutoRenew = rds.AutoRenew + } + } + + iRds, err := iBackup.CreateICloudDBInstance(&desc) + if err != nil { + return nil, errors.Wrapf(err, "iBackup.CreateICloudDBInstance") + } + + err = db.SetExternalId(rds, userCred, iRds.GetGlobalId()) + if err != nil { + return nil, errors.Wrapf(err, "db.SetExternalId") + } + + err = cloudprovider.WaitStatus(iRds, api.DBINSTANCE_RUNNING, time.Second*5, time.Hour*1) + if err != nil { + return nil, errors.Wrapf(err, "cloudprovider.WaitStatus runing") + } + return nil, nil + }) + return nil +} + func (self *SManagedVirtualizationRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask) error { task.ScheduleRun(nil) return nil @@ -2347,14 +2414,9 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceBackup(ct return nil, errors.Wrapf(err, "iRegion.GetIDBInstanceBackupById(%s)", backupId) } - _, err = db.Update(backup, func() error { - backup.StartTime = iBackup.GetStartTime() - backup.EndTime = iBackup.GetEndTime() - backup.BackupSizeMb = iBackup.GetBackupSizeMb() - return nil - }) + err = backup.SyncWithCloudDBInstanceBackup(ctx, userCred, iBackup, instance.GetCloudprovider()) if err != nil { - return nil, errors.Wrap(err, "db.Update") + log.Warningf("sync backup info error: %v", err) } instance.SetStatus(userCred, api.DBINSTANCE_RUNNING, "") diff --git a/pkg/compute/tasks/dbinstance_create_task.go b/pkg/compute/tasks/dbinstance_create_task.go index 245fb01309..262666cdb3 100644 --- a/pkg/compute/tasks/dbinstance_create_task.go +++ b/pkg/compute/tasks/dbinstance_create_task.go @@ -48,12 +48,17 @@ func (self *DBInstanceCreateTask) OnInit(ctx context.Context, obj db.IStandalone self.CreateDBInstance(ctx, dbinstance) } -func (self *DBInstanceCreateTask) CreateDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { - region := dbinstance.GetRegion() +func (self *DBInstanceCreateTask) CreateDBInstance(ctx context.Context, rds *models.SDBInstance) { + region := rds.GetRegion() self.SetStage("OnCreateDBInstanceComplete", nil) - err := region.GetDriver().RequestCreateDBInstance(ctx, self.UserCred, dbinstance, self) + var err error + if len(rds.DBInstancebackupId) > 0 { + err = region.GetDriver().RequestCreateDBInstanceFromBackup(ctx, self.UserCred, rds, self) + } else { + err = region.GetDriver().RequestCreateDBInstance(ctx, self.UserCred, rds, self) + } if err != nil { - self.taskFailed(ctx, dbinstance, err) + self.taskFailed(ctx, rds, err) return } } diff --git a/pkg/mcclient/options/dbinstances.go b/pkg/mcclient/options/dbinstances.go new file mode 100644 index 0000000000..f901883b3c --- /dev/null +++ b/pkg/mcclient/options/dbinstances.go @@ -0,0 +1,66 @@ +// 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 options + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" +) + +type DBInstanceCreateOptions struct { + NAME string `help:"DBInstance Name"` + InstanceType string `help:"InstanceType for DBInstance"` + VcpuCount int `help:"Core of cpu for DBInstance"` + VmemSizeMb int `help:"Memory size of DBInstance"` + Port int `help:"Port of DBInstance"` + Category string `help:"Category of DBInstance"` + Network string `help:"Network of DBInstance"` + Address string `help:"Address of DBInstance"` + Engine string `help:"Engine of DBInstance"` + EngineVersion string `help:"EngineVersion of DBInstance Engine"` + StorageType string `help:"StorageTyep of DBInstance"` + Secgroup string `help:"Secgroup name or Id for DBInstance"` + Zone string `help:"ZoneId or name for DBInstance"` + DiskSizeGB int `help:"Storage size for DBInstance"` + Duration string `help:"Duration for DBInstance"` + AllowDelete *bool `help:"not lock dbinstance" ` + Tags []string `help:"Tags info,prefix with 'user:', eg: user:project=default" json:"-"` + DBInstancebackupId string `help:"create dbinstance from backup" json:"dbinstancebackup_id"` +} + +func (opts *DBInstanceCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := StructToParams(opts) + if err != nil { + return nil, err + } + Tagparams := jsonutils.NewDict() + for _, tag := range opts.Tags { + info := strings.Split(tag, "=") + if len(info) == 2 { + if len(info[0]) == 0 { + return nil, fmt.Errorf("invalidate tag info %s", tag) + } + Tagparams.Add(jsonutils.NewString(info[1]), info[0]) + } else if len(info) == 1 { + Tagparams.Add(jsonutils.NewString(info[0]), info[0]) + } else { + return nil, fmt.Errorf("invalidate tag info %s", tag) + } + } + params.Add(Tagparams, "__meta__") + return params, nil +} diff --git a/pkg/multicloud/aliyun/dbinstance_backup.go b/pkg/multicloud/aliyun/dbinstance_backup.go index b548cb4785..8abc1d8394 100644 --- a/pkg/multicloud/aliyun/dbinstance_backup.go +++ b/pkg/multicloud/aliyun/dbinstance_backup.go @@ -19,8 +19,7 @@ import ( "strings" "time" - "github.com/coredns/coredns/plugin/pkg/log" - + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" @@ -325,3 +324,33 @@ func (region *SRegion) waitBackupCreateComplete(instanceId, jobId string) (strin } return "", fmt.Errorf("failed to found backup job %s backupid", jobId) } + +func (self *SDBInstanceBackup) GetBackupMethod() cloudprovider.TBackupMethod { + return cloudprovider.TBackupMethod(self.BackupMethod) +} + +func (self *SDBInstanceBackup) CreateICloudDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + rdsId, err := self.region.CreateDBInstanceByBackup(self.BackupId, opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateDBInstanceByBackup") + } + return self.region.GetDBInstanceDetail(rdsId) +} + +func (self *SRegion) CreateDBInstanceByBackup(backupId string, opts *cloudprovider.SManagedDBInstanceCreateConfig) (string, error) { + params := map[string]string{ + "DBInstanceId": opts.RdsId, + "DBInstanceStorageType": opts.StorageType, + "PayType": "Postpaid", + "BackupId": backupId, + } + resp, err := self.rdsRequest("CloneDBInstance", params) + if err != nil { + return "", errors.Wrapf(err, "rdsRequest") + } + rdsId, err := resp.GetString("DBInstanceId") + if err != nil { + return "", fmt.Errorf("missing DBInstanceId after CloneDBInstance") + } + return rdsId, nil +} diff --git a/pkg/multicloud/dbinstance_backup_base.go b/pkg/multicloud/dbinstance_backup_base.go index 411ff8f6b8..c23b140d16 100644 --- a/pkg/multicloud/dbinstance_backup_base.go +++ b/pkg/multicloud/dbinstance_backup_base.go @@ -17,7 +17,10 @@ package multicloud import ( "fmt" + "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" ) type SDBInstanceBackupBase struct { @@ -35,3 +38,11 @@ func (backup *SDBInstanceBackupBase) Delete() error { func (backup *SDBInstanceBackupBase) GetProjectId() string { return "" } + +func (backup *SDBInstanceBackupBase) CreateICloudDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + return nil, errors.Wrap(cloudprovider.ErrNotImplemented, "CreateICloudDBInstance") +} + +func (backup *SDBInstanceBackupBase) GetBackupMethod() cloudprovider.TBackupMethod { + return cloudprovider.BackupMethodUnknown +} diff --git a/pkg/multicloud/google/dbinstance_backup.go b/pkg/multicloud/google/dbinstance_backup.go index e24b68fbb9..9af5c191dc 100644 --- a/pkg/multicloud/google/dbinstance_backup.go +++ b/pkg/multicloud/google/dbinstance_backup.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" ) type OperationError struct { @@ -32,6 +33,7 @@ type OperationError struct { } type SDBInstanceBackup struct { + multicloud.SDBInstanceBackupBase rds *SDBInstance Kind string