diff --git a/cmd/climc/shell/compute/dbinstance_skus.go b/cmd/climc/shell/compute/dbinstance_skus.go index f977085d2c..4febdbb481 100644 --- a/cmd/climc/shell/compute/dbinstance_skus.go +++ b/cmd/climc/shell/compute/dbinstance_skus.go @@ -15,44 +15,13 @@ package compute import ( - "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/cmd/climc/shell" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" ) func init() { - type DBInstanceSkuListOption struct { - options.BaseListOptions - Engine string - EngineVersion string - Category string - StorageType string - Cloudregion string - } - R(&DBInstanceSkuListOption{}, "dbinstance-sku-list", "List dbinstance skus", func(s *mcclient.ClientSession, args *DBInstanceSkuListOption) error { - params, err := options.ListStructToParams(args) - if err != nil { - return err - } - result, err := modules.DBInstanceSkus.List(s, params) - if err != nil { - return err - } - printList(result, modules.DBInstanceSkus.GetColumns(s)) - return nil - }) - - type DBInstanceSkuIdOption struct { - ID string `help:"DBInstance Id or name"` - } - - R(&DBInstanceSkuIdOption{}, "dbinstance-sku-show", "Show dbinstance sku details", func(s *mcclient.ClientSession, args *DBInstanceSkuIdOption) error { - result, err := modules.DBInstanceSkus.Get(s, args.ID, nil) - if err != nil { - return err - } - printObject(result) - return nil - }) - + cmd := shell.NewResourceCmd(&modules.DBInstanceSkus).WithKeyword("dbinstance-sku") + cmd.List(&options.DBInstanceSkuListOption{}) + cmd.Show(&options.DBInstanceSkuIdOption{}) } diff --git a/pkg/apis/compute/dbinstance.go b/pkg/apis/compute/dbinstance.go index d62a55cd4b..c911051048 100644 --- a/pkg/apis/compute/dbinstance.go +++ b/pkg/apis/compute/dbinstance.go @@ -107,19 +107,10 @@ type DBInstanceCreateInput struct { // // | 平台 | 支持类型 | 说明 | // | ----- | ------ | --- | - // | 华为云 |ha, single, replica| | - // | 阿里云 |basic, high_availability, always_on, finance|| - // | Google |Zonal, Regional | | - // 翻译: - // basic: 基础版 - // high_availability: 高可用 - // always_on: 集群版 - // finance: 金融版, 三节点 - // ha: 高可用 - // single: 单机 - // replica: 只读 - // Zonal: 单区域 - // Regional: 区域级 + // | 华为云 |ha, single, replica| ha: 高可用, single: 单机, replica: 只读| + // | 阿里云 |basic, high_availability, always_on, finance|basic: 基础版, high_availability: 高可用, always_on: 集群版, finance: 金融版, 三节点| + // | Google |Zonal, Regional | Zonal: 单区域, Regional: 区域级| + // | 腾讯云 |fe, ha, basic | ha: 高可用, basic: 基础版, fe: 金融版| // required: true Category string `json:"category"` @@ -127,13 +118,12 @@ type DBInstanceCreateInput struct { // // // - // | 平台 | 支持类型 | - // | 华为云 |SSD, SAS, SATA| - // | 阿里云 |local_ssd, cloud_essd, cloud_ssd| - // | Google |PD_SSD, PD_HDD| - // 翻译: - // PD_SSD: SSD - // PD_HDD: HDD + // | 平台 | 支持类型 | 说明 | + // | ----- | ------ | --- | + // | 华为云 |SSD, SAS, SATA| | + // | 阿里云 |local_ssd, cloud_essd, cloud_ssd| | + // | Google |PD_SSD, PD_HDD| PD_SSD: SSD, PD_HDD: HDD| + // | 腾讯云 |cloud_ssd, local_ssd| | // required: true StorageType string `json:"storage_type"` @@ -146,6 +136,7 @@ type DBInstanceCreateInput struct { // 阿里云不需要此参数 // 华为云会默认创建一个用户,若不传此参数, 则为随机密码 // 谷歌云会默认创建一个用户,若不传此参数, 则为随机密码 + // 腾讯云会默认创建一个用户,若不传此参数, 则为随机密码 Password string `json:"password"` // 是否不设置初始密码 diff --git a/pkg/apis/compute/dbinstance_account.go b/pkg/apis/compute/dbinstance_account.go index 067405dc7f..2f612a8047 100644 --- a/pkg/apis/compute/dbinstance_account.go +++ b/pkg/apis/compute/dbinstance_account.go @@ -48,6 +48,7 @@ type DBInstanceAccountCreateInput struct { // | ---- |------- | // | Aliyun |MySQL, MariaBD, SQLServer | // | 华为云 |MySQL, MariaBD | + // | 腾讯云 |MySQL | // required: true // 阿里云SQL Server 2017集群版不支持创建账号 // 实例状态必须是运行中 diff --git a/pkg/apis/compute/dbinstance_const.go b/pkg/apis/compute/dbinstance_const.go index 7ce8054332..5b77264dd6 100644 --- a/pkg/apis/compute/dbinstance_const.go +++ b/pkg/apis/compute/dbinstance_const.go @@ -16,6 +16,7 @@ package compute const ( //实例状态 + DBINSTANCE_INIT = "init" //初始化 DBINSTANCE_DEPLOYING = "deploying" //部署中 DBINSTANCE_RUNNING = "running" //运行中 DBINSTANCE_REBOOTING = "rebooting" //重启中 @@ -99,6 +100,11 @@ const ( ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON = "always_on" //集群版 ALIYUN_DBINSTANCE_CATEGORY_FINANCE = "finance" //金融版 + //腾讯云实例类型 + QCLOUD_DBINSTANCE_CATEGORY_BASIC = "basic" //基础版 + QCLOUD_DBINSTANCE_CATEGORY_HA = "ha" //高可用 + QCLOUD_DBINSTANCE_CATEGORY_FINANCE = "fe" //金融版 + //华为云实例类型 HUAWEI_DBINSTANCE_CATEGORY_HA = "ha" //主备 HUAWEI_DBINSTANCE_CATEGORY_SINGLE = "single" //单机 @@ -118,6 +124,11 @@ const ( HUAWEI_DBINSTANCE_STORAGE_TYPE_ULTRAHIGHPRO = "ULTRAHIGHPRO" HUAWEI_DBINSTANCE_STORAGE_TYPE_COMMON = "COMMON" HUAWEI_DBINSTANCE_STORAGE_TYPE_HIGH = "HIGH" + + //腾讯云 + QCLOUD_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD = "local_ssd" //本地盘SSD盘 + QCLOUD_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD = "cloud_ssd" //SSD云盘 + ) var ( @@ -128,4 +139,20 @@ var ( ALIYUN_SQL_SERVER_DENY_KEYWORD []string = []string{ "root", " admin", " eagleye", " master", " aurora", " sa", " sysadmin", " administrator", " mssqld", " public", " securityadmin", " serveradmin", " setupadmin", " processadmin", " diskadmin", " dbcreator", " bulkadmin", " tempdb", " msdb", " model", " distribution", " mssqlsystemresource", " guest", " add", " except", " percent", " all", " exec", " plan", " alter", " execute", " precision", " and", " exists", " primary", " any", " exit", " print", " as", " fetch", " proc", " asc", " file", " procedure", " authorization", " fillfactor", " public", " backup", " for", " raiserror", " begin", " foreign", " read", " between", " freetext", " readtext", " break", " freetexttable", " reconfigure", " browse", " from", " references", " bulk", " full", " replication", " by", " function", " restore", " cascade", " goto", " restrict", " case", " grant", " return", " check", " group", " revoke", " checkpoint", " having", " right", " close", " holdlock", " rollback", " clustered", " identity", " rowcount", " coalesce", " identity_insert", " rowguidcol", " collate", " identitycol", " rule", " column", " if", " save", " commit", " in", " schema", " compute", " index", " select", " constraint", " inner", " session_user", " contains", " insert", " set", " containstable", " intersect", " setuser", " continue", " into", " shutdown", " convert", " is", " some", " create", " join", " statistics", " cross", " key", " system_user", " current", " kill", " table", " current_date", " left", " textsize", " current_time", " like", " then", " current_timestamp", " lineno", " to", " current_user", " load", " top", " cursor", " national", " tran", " database", " nocheck", " transaction", " dbcc", " nonclustered", " trigger", " deallocate", " not", " truncate", " declare", " null", " tsequal", " default", " nullif", " union", " delete", " of", " unique", " deny", " off", " update", " desc", " offsets", " updatetext", " disk", " on", " use", " distinct", " open", " user", " distributed", " opendatasource", " values", " double", " openquery", " varying", " drop", " openrowset", " view", " dummy", " openxml", " waitfor", " dump", " option", " when", " else", " or", " where", " end", " order", " while", " errlvl", " outer", " with", " escape", " over", " writetext", " galaxy", } + + RW_PRIVILEGE_SET = []string{ + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", + "DROP", "REFERENCES", "INDEX", "ALTER", "CREATE TEMPORARY TABLES", + "LOCK TABLES", "EXECUTE", "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", + "ALTER ROUTINE", "EVENT", "TRIGGER", "PROCESS", "REPLICATION SLAVE", + "REPLICATION CLIENT", + } + R_PRIVILEGE_SET = []string{"SELECT", "LOCK TABLES", "SHOW VIEW", "PROCESS", "REPLICATION SLAVE", "REPLICATION CLIENT"} + QCLOUD_RW_PRIVILEGE_SET = []string{ + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", + "DROP", "REFERENCES", "INDEX", "ALTER", "CREATE TEMPORARY TABLES", + "LOCK TABLES", "EXECUTE", "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", + "ALTER ROUTINE", "EVENT", "TRIGGER", + } + QCLOUD_R_PRIVILEGE_SET = []string{"SELECT", "LOCK TABLES", "SHOW VIEW"} ) diff --git a/pkg/apis/compute/dbinstance_database.go b/pkg/apis/compute/dbinstance_database.go index adb9e2399f..3563e94c76 100644 --- a/pkg/apis/compute/dbinstance_database.go +++ b/pkg/apis/compute/dbinstance_database.go @@ -44,6 +44,7 @@ type DBInstanceDatabaseCreateInput struct { // | ---- |------- | // | Aliyun |MySQL, MariaBD, SQLServer | // | 华为云 |MySQL, MariaBD | + // | 腾讯云 | | // required: true // 阿里云SQL Server 2017集群版不支持创建数据库 // 阿里云只读实例不支持创建数据库 diff --git a/pkg/apis/compute/dbinstance_sku_const.go b/pkg/apis/compute/dbinstance_sku_const.go index f4d9dc903a..979d56f473 100644 --- a/pkg/apis/compute/dbinstance_sku_const.go +++ b/pkg/apis/compute/dbinstance_sku_const.go @@ -16,4 +16,5 @@ package compute const ( DBINSTANCE_SKU_AVAILABLE = "available" + DBINSTANCE_SKU_SOLDOUT = "soldout" ) diff --git a/pkg/cloudprovider/dbinstance.go b/pkg/cloudprovider/dbinstance.go index 076107208f..3bd5ac1417 100644 --- a/pkg/cloudprovider/dbinstance.go +++ b/pkg/cloudprovider/dbinstance.go @@ -26,19 +26,24 @@ type SExtraIp struct { URL string } +type SZoneInfo struct { + Zone1 string + Zone2 string + Zone3 string + ZoneId string +} + type SInstanceType struct { InstanceType string - ZoneIds []string + SZoneInfo } type SManagedDBInstanceCreateConfig struct { + Name string + Description string + StorageType string + DiskSizeGB int SInstanceType - Name string - Description string - StorageType string - DiskSizeGB int - InstanceType string - InstanceTypes []SInstanceType VcpuCount int VmemSizeMb int VpcId string @@ -61,6 +66,8 @@ type SManagedDBInstanceChangeConfig struct { DiskSizeGB int StorageType string InstanceType string + VcpuCount int + VmemSizeMb int } type SDBInstanceDatabaseCreateConfig struct { @@ -77,6 +84,7 @@ type SDBInstancePrivilege struct { type SDBInstanceAccountCreateConfig struct { Name string + Host string Description string Password string } diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 91055ab145..8c9679ecf4 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -833,7 +833,9 @@ type ICloudDBInstanceDatabase interface { } type ICloudDBInstanceAccount interface { - ICloudResource + GetName() string + GetStatus() string + GetHost() string GetIDBInstanceAccountPrivileges() ([]ICloudDBInstanceAccountPrivilege, error) diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index ae571c4b22..6c546b0c55 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -813,24 +813,38 @@ func syncRegionDBInstances(ctx context.Context, userCred mcclient.TokenCredentia return } - syncDBInstanceNetwork(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) - syncDBInstanceParameters(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) - syncDBInstanceDatabases(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) - syncDBInstanceAccounts(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) + syncDBInstanceResource(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) }() } } -func syncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) { +func syncDBInstanceResource(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) { + err := syncDBInstanceNetwork(ctx, userCred, syncResults, localInstance, remoteInstance) + if err != nil { + log.Errorf("syncDBInstanceNetwork error: %v", err) + } + err = syncDBInstanceParameters(ctx, userCred, syncResults, localInstance, remoteInstance) + if err != nil { + log.Errorf("syncDBInstanceParameters error: %v", err) + } + err = syncDBInstanceDatabases(ctx, userCred, syncResults, localInstance, remoteInstance) + if err != nil { + log.Errorf("syncDBInstanceParameters error: %v", err) + } + err = syncDBInstanceAccounts(ctx, userCred, syncResults, localInstance, remoteInstance) + if err != nil { + log.Errorf("syncDBInstanceAccounts: %v", err) + } +} + +func syncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { network, err := remoteInstance.GetDBNetwork() if err != nil { - msg := fmt.Sprintf("GetDBNetwork for dbinstance %s failed %s", remoteInstance.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetDBNetwork") } if network == nil { - return + return nil } result := DBInstanceNetworkManager.SyncDBInstanceNetwork(ctx, userCred, localInstance, network) @@ -839,16 +853,15 @@ func syncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredentia msg := result.Result() log.Infof("SyncDBInstanceNetwork for dbinstance %s result: %s", localInstance.Name, msg) if result.IsError() { - return + return result.AllError() } + return nil } -func syncDBInstanceParameters(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) { +func syncDBInstanceParameters(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { parameters, err := remoteInstance.GetIDBInstanceParameters() if err != nil { - msg := fmt.Sprintf("GetIDBInstanceParameters for dbinstance %s failed %s", remoteInstance.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetIDBInstanceParameters") } result := DBInstanceParameterManager.SyncDBInstanceParameters(ctx, userCred, localInstance, parameters) @@ -857,16 +870,15 @@ func syncDBInstanceParameters(ctx context.Context, userCred mcclient.TokenCreden msg := result.Result() log.Infof("SyncDBInstanceParameters for dbinstance %s result: %s", localInstance.Name, msg) if result.IsError() { - return + return result.AllError() } + return nil } -func syncRegionDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) { +func syncRegionDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) error { backups, err := remoteRegion.GetIDBInstanceBackups() if err != nil { - msg := fmt.Sprintf("GetIDBInstanceBackups for dbinstance %s failed %s", remoteRegion.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetIDBInstanceBackups") } result := DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, provider, nil, localRegion, backups) @@ -875,17 +887,16 @@ func syncRegionDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCre msg := result.Result() log.Infof("SyncDBInstanceBackups for region %s result: %s", localRegion.Name, msg) if result.IsError() { - return + return result.AllError() } + return nil } -func syncDBInstanceDatabases(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) { +func syncDBInstanceDatabases(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { databases, err := remoteInstance.GetIDBInstanceDatabases() if err != nil { - msg := fmt.Sprintf("GetIDBInstanceDatabases for dbinstance %s failed %s", remoteInstance.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetIDBInstanceDatabases") } result := DBInstanceDatabaseManager.SyncDBInstanceDatabases(ctx, userCred, localInstance, databases) @@ -894,16 +905,15 @@ func syncDBInstanceDatabases(ctx context.Context, userCred mcclient.TokenCredent msg := result.Result() log.Infof("SyncDBInstanceDatabases for dbinstance %s result: %s", localInstance.Name, msg) if result.IsError() { - return + return result.AllError() } + return nil } -func syncDBInstanceAccounts(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) { +func syncDBInstanceAccounts(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SDBInstance, remoteInstance cloudprovider.ICloudDBInstance) error { accounts, err := remoteInstance.GetIDBInstanceAccounts() if err != nil { - msg := fmt.Sprintf("GetIDBInstanceAccounts for dbinstance %s failed %s", remoteInstance.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetIDBInstanceAccounts") } localAccounts, remoteAccounts, result := DBInstanceAccountManager.SyncDBInstanceAccounts(ctx, userCred, localInstance, accounts) @@ -912,7 +922,7 @@ func syncDBInstanceAccounts(ctx context.Context, userCred mcclient.TokenCredenti msg := result.Result() log.Infof("SyncDBInstanceAccounts for dbinstance %s result: %s", localInstance.Name, msg) if result.IsError() { - return + return result.AllError() } for i := 0; i < len(localAccounts); i++ { @@ -924,18 +934,20 @@ func syncDBInstanceAccounts(ctx context.Context, userCred mcclient.TokenCredenti return } - syncDBInstanceAccountPrivileges(ctx, userCred, syncResults, &localAccounts[i], remoteAccounts[i]) + err = syncDBInstanceAccountPrivileges(ctx, userCred, syncResults, &localAccounts[i], remoteAccounts[i]) + if err != nil { + log.Errorf("syncDBInstanceAccountPrivileges error: %v", err) + } }() } + return nil } -func syncDBInstanceAccountPrivileges(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localAccount *SDBInstanceAccount, remoteAccount cloudprovider.ICloudDBInstanceAccount) { +func syncDBInstanceAccountPrivileges(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localAccount *SDBInstanceAccount, remoteAccount cloudprovider.ICloudDBInstanceAccount) error { privileges, err := remoteAccount.GetIDBInstanceAccountPrivileges() if err != nil { - msg := fmt.Sprintf("GetIDBInstanceAccountPrivileges for dbinstance account %s failed %s", remoteAccount.GetName(), err) - log.Errorf(msg) - return + return errors.Wrapf(err, "GetIDBInstanceAccountPrivileges for %s(%s)", localAccount.Name, localAccount.Id) } result := DBInstancePrivilegeManager.SyncDBInstanceAccountPrivileges(ctx, userCred, localAccount, privileges) @@ -944,8 +956,9 @@ func syncDBInstanceAccountPrivileges(ctx context.Context, userCred mcclient.Toke msg := result.Result() log.Infof("SyncDBInstanceAccountPrivileges for account %s result: %s", localAccount.Name, msg) if result.IsError() { - return + return result.AllError() } + return nil } func syncRegionSnapshots(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion, syncRange *SSyncRange) { diff --git a/pkg/compute/models/dbinstance_accounts.go b/pkg/compute/models/dbinstance_accounts.go index 5d3b6bdb2c..35ccb19a19 100644 --- a/pkg/compute/models/dbinstance_accounts.go +++ b/pkg/compute/models/dbinstance_accounts.go @@ -17,6 +17,7 @@ package models import ( "context" "database/sql" + "fmt" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -39,7 +40,6 @@ import ( type SDBInstanceAccountManager struct { db.SStatusStandaloneResourceBaseManager - db.SExternalizedResourceBaseManager SDBInstanceResourceBaseManager } @@ -59,15 +59,13 @@ func init() { type SDBInstanceAccount struct { db.SStatusStandaloneResourceBase - db.SExternalizedResourceBase + + Host string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"optional" default:"%"` SDBInstanceResourceBase `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` // 数据库密码 Secret string `width:"256" charset:"ascii" nullable:"false" list:"user" create:"optional"` - - // RDS实例Id - // DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` } func (manager *SDBInstanceAccountManager) GetContextManagers() [][]db.IModelManager { @@ -224,10 +222,6 @@ func (manager *SDBInstanceAccountManager) ListItemFilter( if err != nil { return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.ListItemFilter") } - q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) - if err != nil { - return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") - } q, err = manager.SDBInstanceResourceBaseManager.ListItemFilter(ctx, q, userCred, query.DBInstanceFilterListInput) if err != nil { return nil, errors.Wrap(err, "SDBInstanceResourceBaseManager.ListItemFilter") @@ -264,19 +258,33 @@ func (manager *SDBInstanceAccountManager) QueryDistinctExtraField(q *sqlchemy.SQ return q, httperrors.ErrNotFound } +type sRdsAccount struct { + Name string + DBInstanceId string `json:"dbinstance_id"` + Host string +} + func (self *SDBInstanceAccount) GetUniqValues() jsonutils.JSONObject { - return jsonutils.Marshal(map[string]string{"dbinstance_id": self.DBInstanceId}) + return jsonutils.Marshal(sRdsAccount{Name: self.Name, DBInstanceId: self.DBInstanceId, Host: self.Host}) } func (manager *SDBInstanceAccountManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject { - dbinstanceId, _ := data.GetString("dbinstance_id") - return jsonutils.Marshal(map[string]string{"dbinstance_id": dbinstanceId}) + info := sRdsAccount{} + data.Unmarshal(&info) + return jsonutils.Marshal(info) } func (manager *SDBInstanceAccountManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery { - dbinstanceId, _ := values.GetString("dbinstance_id") - if len(dbinstanceId) > 0 { - q = q.Equals("dbinstance_id", dbinstanceId) + info := sRdsAccount{} + values.Unmarshal(&info) + if len(info.DBInstanceId) > 0 { + q = q.Equals("dbinstance_id", info.DBInstanceId) + } + if len(info.Name) > 0 { + q = q.Equals("name", info.Name) + } + if len(info.Host) > 0 { + q = q.Equals("host", info.Host) } return q } @@ -612,54 +620,57 @@ func (manager *SDBInstanceAccountManager) SyncDBInstanceAccounts(ctx context.Con defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, instance.GetOwnerId())) result := compare.SyncResult{} + localAccounts := []SDBInstanceAccount{} + remoteAccounts := []cloudprovider.ICloudDBInstanceAccount{} dbAccounts, err := instance.GetDBInstanceAccounts() if err != nil { result.Error(err) return nil, nil, result } - - localAccounts := []SDBInstanceAccount{} - remoteAccounts := []cloudprovider.ICloudDBInstanceAccount{} - - removed := make([]SDBInstanceAccount, 0) - commondb := make([]SDBInstanceAccount, 0) - commonext := make([]cloudprovider.ICloudDBInstanceAccount, 0) - added := make([]cloudprovider.ICloudDBInstanceAccount, 0) - if err := compare.CompareSets(dbAccounts, cloudAccounts, &removed, &commondb, &commonext, &added); err != nil { - result.Error(err) - return nil, nil, result + accountMaps := map[string]SDBInstanceAccount{} + for i := range dbAccounts { + accountMaps[fmt.Sprintf("%s:%s", dbAccounts[i].Name, dbAccounts[i].Host)] = dbAccounts[i] + } + remoteMaps := map[string]cloudprovider.ICloudDBInstanceAccount{} + for i := range cloudAccounts { + remoteMaps[fmt.Sprintf("%s:%s", cloudAccounts[i].GetName(), cloudAccounts[i].GetHost())] = cloudAccounts[i] } - for i := 0; i < len(removed); i++ { - err := removed[i].Purge(ctx, userCred) + for key, account := range remoteMaps { + local, ok := accountMaps[key] + if !ok { + _account, err := manager.newFromCloudDBInstanceAccount(ctx, userCred, instance, account) + if err != nil { + result.AddError(err) + continue + } + result.Add() + remoteAccounts = append(remoteAccounts, account) + localAccounts = append(localAccounts, *_account) + continue + } + err = local.SyncWithCloudDBInstanceAccount(ctx, userCred, instance, account) if err != nil { - result.DeleteError(err) - } else { + result.UpdateError(err) + continue + } + result.Update() + remoteAccounts = append(remoteAccounts, account) + localAccounts = append(localAccounts, local) + } + + for key, account := range accountMaps { + _, ok := remoteMaps[key] + if !ok { + err := account.Purge(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } result.Delete() } } - for i := 0; i < len(commondb); i++ { - err := commondb[i].SyncWithCloudDBInstanceAccount(ctx, userCred, instance, commonext[i]) - if err != nil { - result.UpdateError(err) - } else { - result.Update() - localAccounts = append(localAccounts, commondb[i]) - remoteAccounts = append(remoteAccounts, commonext[i]) - } - } - - for i := 0; i < len(added); i++ { - account, err := manager.newFromCloudDBInstanceAccount(ctx, userCred, instance, added[i]) - if err != nil { - result.AddError(err) - } else { - localAccounts = append(localAccounts, *account) - remoteAccounts = append(remoteAccounts, added[i]) - result.Add() - } - } return localAccounts, remoteAccounts, result } @@ -684,7 +695,6 @@ func (manager *SDBInstanceAccountManager) newFromCloudDBInstanceAccount(ctx cont account.Name = extAccount.GetName() account.DBInstanceId = instance.Id account.Status = extAccount.GetStatus() - account.ExternalId = extAccount.GetGlobalId() err := manager.TableSpec().Insert(ctx, &account) if err != nil { diff --git a/pkg/compute/models/dbinstance_backups.go b/pkg/compute/models/dbinstance_backups.go index 56967a69ee..6c323791e1 100644 --- a/pkg/compute/models/dbinstance_backups.go +++ b/pkg/compute/models/dbinstance_backups.go @@ -369,6 +369,30 @@ 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") + } + 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, "externalId: %s", backup.ExternalId) + } + iRegion, err := backup.GetIRegion() if err != nil { return nil, errors.Wrap(err, "backup.GetIRegion") @@ -432,6 +456,7 @@ func (self *SDBInstanceBackup) SyncWithCloudDBInstanceBackup( provider *SCloudprovider, ) error { _, err := db.UpdateWithLock(ctx, self, func() error { + self.ExternalId = extBackup.GetGlobalId() self.Status = extBackup.GetStatus() self.StartTime = extBackup.GetStartTime() self.EndTime = extBackup.GetEndTime() diff --git a/pkg/compute/models/dbinstance_skus.go b/pkg/compute/models/dbinstance_skus.go index d180b8dcc8..15a830a92d 100644 --- a/pkg/compute/models/dbinstance_skus.go +++ b/pkg/compute/models/dbinstance_skus.go @@ -30,6 +30,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/stringutils2" @@ -628,3 +629,27 @@ func (manager *SDBInstanceSkuManager) ListItemExportKeys(ctx context.Context, return q, nil } + +func (self *SDBInstanceSku) GetZoneInfo() (cloudprovider.SZoneInfo, error) { + zoneInfo := cloudprovider.SZoneInfo{ZoneId: self.ZoneId} + region := self.GetRegion() + if region == nil { + return zoneInfo, fmt.Errorf("empyt region for rds sku %s(%s)", self.Name, self.Id) + } + var cloudZoneId = func(id string) (string, error) { + if len(id) == 0 { + return "", nil + } + _zone, err := ZoneManager.FetchById(id) + if err != nil { + log.Errorf("ZoneManager.FetchById(%s) error: %v", id, err) + return "", errors.Wrapf(err, "ZoneManager.FetchById(%s)", id) + } + zone := _zone.(*SZone) + return strings.TrimPrefix(zone.ExternalId, region.ExternalId+"/"), nil + } + zoneInfo.Zone1, _ = cloudZoneId(self.Zone1) + zoneInfo.Zone2, _ = cloudZoneId(self.Zone2) + zoneInfo.Zone3, _ = cloudZoneId(self.Zone3) + return zoneInfo, nil +} diff --git a/pkg/compute/models/dbinstances.go b/pkg/compute/models/dbinstances.go index 079cd574fd..85216298a7 100644 --- a/pkg/compute/models/dbinstances.go +++ b/pkg/compute/models/dbinstances.go @@ -111,7 +111,7 @@ type SDBInstance struct { EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` // 套餐名称 // example: mysql.x4.large.2c - InstanceType string `width:"64" charset:"ascii" nullable:"true" list:"user" create:"optional"` + InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"` // 维护时间 MaintainTime string `width:"64" charset:"ascii" nullable:"true" list:"user" create:"optional"` @@ -666,7 +666,11 @@ func (self *SDBInstance) GetIDBInstance() (cloudprovider.ICloudDBInstance, error if err != nil { return nil, errors.Wrap(err, "self.GetIRegion") } - return iregion.GetIDBInstanceById(self.ExternalId) + iRds, err := iregion.GetIDBInstanceById(self.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "GetIDBInstanceById(%s)", self.ExternalId) + } + return iRds, nil } func (self *SDBInstance) PerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformChangeProjectOwnerInput) (jsonutils.JSONObject, error) { @@ -1413,26 +1417,19 @@ func (self *SDBInstance) GetAvailableZoneIds() ([]string, error) { } func (self *SDBInstance) GetAvailableInstanceTypes() ([]cloudprovider.SInstanceType, error) { - instanceTypes := map[string]cloudprovider.SInstanceType{} + instanceTypes := []cloudprovider.SInstanceType{} skus, err := self.GetAvailableDBInstanceSkus() if err != nil { return nil, errors.Wrap(err, "self.GetAvailableDBInstanceSkus") } for _, sku := range skus { - if instanceType, ok := instanceTypes[sku.Name]; !ok { - instanceTypes[sku.Name] = cloudprovider.SInstanceType{InstanceType: sku.Name, ZoneIds: []string{sku.ZoneId}} - } else if !utils.IsInStringArray(sku.ZoneId, instanceType.ZoneIds) { - instanceType.ZoneIds = append(instanceType.ZoneIds, sku.ZoneId) - } + instanceType := cloudprovider.SInstanceType{} + instanceType.InstanceType = sku.Name + instanceType.SZoneInfo, _ = sku.GetZoneInfo() + instanceTypes = append(instanceTypes, instanceType) } - - result := []cloudprovider.SInstanceType{} - for _, instanceType := range instanceTypes { - result = append(result, instanceType) - } - - return result, nil + return instanceTypes, nil } func (self *SDBInstance) setZoneInfo() error { @@ -1491,8 +1488,18 @@ func (self *SDBInstance) SetZoneIds(extInstance cloudprovider.ICloudDBInstance) } } +func (self *SDBInstance) SyncAllWithCloudDBInstance(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, extInstance cloudprovider.ICloudDBInstance) error { + err := self.SyncWithCloudDBInstance(ctx, userCred, provider, extInstance) + if err != nil { + return errors.Wrapf(err, "SyncWithCloudDBInstance") + } + syncDBInstanceResource(ctx, userCred, SSyncResultSet{}, self, extInstance) + return nil +} + func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, extInstance cloudprovider.ICloudDBInstance) error { diff, err := db.UpdateWithLock(ctx, self, func() error { + self.ExternalId = extInstance.GetGlobalId() self.Engine = extInstance.GetEngine() self.EngineVersion = extInstance.GetEngineVersion() self.InstanceType = extInstance.GetInstanceType() diff --git a/pkg/compute/regiondrivers/google.go b/pkg/compute/regiondrivers/google.go index d22fbc2791..18cbcc0c0b 100644 --- a/pkg/compute/regiondrivers/google.go +++ b/pkg/compute/regiondrivers/google.go @@ -219,7 +219,6 @@ func (self *SGoogleRegionDriver) InitDBInstanceUser(ctx context.Context, instanc account.DBInstanceId = instance.Id account.Name = user account.Status = api.DBINSTANCE_USER_AVAILABLE - account.ExternalId = user account.SetModelManager(models.DBInstanceAccountManager, &account) err := models.DBInstanceAccountManager.TableSpec().Insert(ctx, &account) if err != nil { diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go index fe6d277027..eb1699c37b 100644 --- a/pkg/compute/regiondrivers/huawei.go +++ b/pkg/compute/regiondrivers/huawei.go @@ -2187,7 +2187,6 @@ func (self *SHuaWeiRegionDriver) InitDBInstanceUser(ctx context.Context, instanc account.DBInstanceId = instance.Id account.Name = user account.Status = api.DBINSTANCE_USER_AVAILABLE - account.ExternalId = user account.SetModelManager(models.DBInstanceAccountManager, &account) err := models.DBInstanceAccountManager.TableSpec().Insert(ctx, &account) if err != nil { diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index edb3a1ef73..eebda49aea 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -1610,7 +1610,6 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont Description: dbinstance.Description, StorageType: dbinstance.StorageType, DiskSizeGB: dbinstance.DiskSizeGB, - InstanceType: dbinstance.InstanceType, VcpuCount: dbinstance.VcpuCount, VmemSizeMb: dbinstance.VmemSizeMb, VpcId: vpc.ExternalId, @@ -1629,12 +1628,6 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont log.Errorf("failed to sync project %s for create %s rds %s error: %v", dbinstance.ProjectId, _cloudprovider.Provider, dbinstance.Name, err) } - if len(dbinstance.InstanceType) > 0 { - desc.ZoneIds, _ = dbinstance.GetAvailableZoneIds() - } else { - desc.InstanceTypes, _ = dbinstance.GetAvailableInstanceTypes() - } - region := dbinstance.GetRegion() err = region.GetDriver().InitDBInstanceUser(ctx, dbinstance, task, &desc) @@ -1673,49 +1666,46 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont desc.MasterInstanceId = master.ExternalId } - log.Debugf("create dbinstance params: %s", jsonutils.Marshal(desc).String()) - - idbinstance, err := iregion.CreateIDBInstance(&desc) - if idbinstance != nil { //避免创建失败后,删除本地的未能同步删除云上失败的RDS - db.SetExternalId(dbinstance, userCred, idbinstance.GetGlobalId()) - } + instanceTypes, err := dbinstance.GetAvailableInstanceTypes() if err != nil { - return nil, err + return nil, errors.Wrapf(err, "GetAvailableInstanceTypes") + } + if len(instanceTypes) == 0 { + return nil, fmt.Errorf("no avaiable sku for create") } - err = cloudprovider.WaitStatus(idbinstance, api.DBINSTANCE_RUNNING, time.Second*5, time.Hour*1) + var createFunc = func() (cloudprovider.ICloudDBInstance, error) { + errMsgs := []string{} + for i := range instanceTypes { + desc.SInstanceType = instanceTypes[i] + log.Debugf("create dbinstance params: %s", jsonutils.Marshal(desc).String()) + + iRds, err := iregion.CreateIDBInstance(&desc) + if err != nil { + errMsgs = append(errMsgs, err.Error()) + continue + } + return iRds, nil + } + if len(errMsgs) > 0 { + return nil, fmt.Errorf(strings.Join(errMsgs, "\n")) + } + return nil, fmt.Errorf("no avaiable skus %s(%dC%d) for create", dbinstance.InstanceType, desc.VcpuCount, desc.VmemSizeMb) + } + + iRds, err := createFunc() + if err != nil { + return nil, errors.Wrapf(err, "create") + } + + err = cloudprovider.WaitStatus(iRds, api.DBINSTANCE_RUNNING, time.Second*5, time.Hour*1) if err != nil { log.Errorf("timeout for waiting dbinstance running error: %v", err) } - dbinstance.SyncWithCloudDBInstance(ctx, userCred, dbinstance.GetCloudprovider(), idbinstance) - - network, err := idbinstance.GetDBNetwork() + err = dbinstance.SyncAllWithCloudDBInstance(ctx, userCred, dbinstance.GetCloudprovider(), iRds) if err != nil { - log.Errorf("failed to get get network for dbinstance %s(%s) error: %v", dbinstance.Name, dbinstance.Id, err) - } else { - models.DBInstanceNetworkManager.SyncDBInstanceNetwork(ctx, userCred, dbinstance, network) - } - - parameters, err := idbinstance.GetIDBInstanceParameters() - if err != nil { - log.Errorf("failed to get parameters for dbinstance %s(%s) error: %v", dbinstance.Name, dbinstance.Id, err) - } else { - models.DBInstanceParameterManager.SyncDBInstanceParameters(ctx, userCred, dbinstance, parameters) - } - - backups, err := idbinstance.GetIDBInstanceBackups() - if err != nil { - log.Errorf("failed to get backups for dbinstance %s(%s) error: %v", dbinstance.Name, dbinstance.Id, err) - } else { - models.DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, dbinstance.GetCloudprovider(), dbinstance, dbinstance.GetRegion(), backups) - } - - databases, err := idbinstance.GetIDBInstanceDatabases() - if err != nil { - log.Errorf("failed to get databases for databases %s(%s) error: %v", dbinstance.Name, dbinstance.Id, err) - } else { - models.DBInstanceDatabaseManager.SyncDBInstanceDatabases(ctx, userCred, dbinstance, databases) + log.Errorf("SyncAllWithCloudDBInstance error: %v", err) } return nil, nil @@ -2223,37 +2213,36 @@ func (self *SManagedVirtualizationRegionDriver) RequestChangeDBInstanceConfig(ct instance.StorageType = input.StorageType } - conf := &cloudprovider.SManagedDBInstanceChangeConfig{ + conf := cloudprovider.SManagedDBInstanceChangeConfig{ DiskSizeGB: input.DiskSizeGB, StorageType: instance.StorageType, } - instanceTypes := []string{} + opts := []cloudprovider.SManagedDBInstanceChangeConfig{} - if len(input.InstanceType) > 0 { - conf.InstanceType = input.InstanceType - } else if input.VCpuCount == 0 && input.VmemSizeMb == 0 { - conf.InstanceType = instance.InstanceType - } else { - instance.InstanceType = "" + if len(input.InstanceType) > 0 || input.VCpuCount > 0 || input.VmemSizeMb > 0 { + instance.InstanceType = input.InstanceType if input.VCpuCount > 0 { instance.VcpuCount = input.VCpuCount } if input.VmemSizeMb > 0 { instance.VmemSizeMb = input.VmemSizeMb } - skus, err := instance.GetDBInstanceSkus() if err != nil { return nil, errors.Wrap(err, "instance.GetDBInstanceSkus") } - for _, sku := range skus { - instanceTypes = append(instanceTypes, sku.Name) + for i := range skus { + conf.InstanceType = skus[i].Name + conf.VcpuCount = skus[i].VcpuCount + conf.VmemSizeMb = skus[i].VmemSizeMb + opts = append(opts, conf) } - } - - if len(conf.InstanceType) == 0 && len(instanceTypes) == 0 { - return nil, fmt.Errorf("No available dbinstance sku for change config") + } else { + conf.InstanceType = instance.InstanceType + conf.VcpuCount = instance.VcpuCount + conf.VmemSizeMb = instance.VmemSizeMb + opts = append(opts, conf) } iRds, err := instance.GetIDBInstance() @@ -2261,29 +2250,38 @@ func (self *SManagedVirtualizationRegionDriver) RequestChangeDBInstanceConfig(ct return nil, errors.Wrap(err, "instance.GetIDBInstance") } - log.Infof("change config: %s", jsonutils.Marshal(conf).String()) - - if len(conf.InstanceType) > 0 { - err = iRds.ChangeConfig(ctx, conf) - if err != nil { - return nil, errors.Wrapf(err, "iRds.ChangeConfig(%s)", conf.InstanceType) - } - } else { - for _, instanceType := range instanceTypes { - conf.InstanceType = instanceType - log.Infof("try change instance type to %s", instance.InstanceType) - err = iRds.ChangeConfig(ctx, conf) + var changeConfig = func() error { + errMsgs := []string{} + for i := range opts { + log.Infof("change config: %s", jsonutils.Marshal(opts[i]).String()) + err = iRds.ChangeConfig(ctx, &opts[i]) if err != nil { - log.Warningf("change failed: %v try another", err) + errMsgs = append(errMsgs, err.Error()) + continue } + return nil } - return nil, fmt.Errorf("no available dbinstance sku to change") + if len(errMsgs) > 0 { + return fmt.Errorf(strings.Join(errMsgs, "\n")) + } + return fmt.Errorf("no available dbinstance sku to change") + } + + err = changeConfig() + if err != nil { + return nil, err } err = cloudprovider.WaitStatus(iRds, api.DBINSTANCE_RUNNING, time.Second*10, time.Minute*40) if err != nil { - log.Errorf("failed to wait rds %s(%s) status running", instance.Name, instance.Id) + return nil, errors.Wrapf(err, "cloudprovider.WaitStatus") } + + err = iRds.Refresh() + if err != nil { + return nil, errors.Wrapf(err, "iRds.Refresh") + } + _, err = db.Update(instance, func() error { instance.InstanceType = iRds.GetInstanceType() instance.Category = iRds.GetCategory() @@ -2308,11 +2306,6 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceBackup(ct return nil, errors.Wrap(err, "instance.GetIDBInstance") } - iRegion, err := backup.GetIRegion() - if err != nil { - return nil, errors.Wrap(err, "backup.GetIRegion") - } - desc := &cloudprovider.SDBInstanceBackupCreateConfig{ Name: backup.Name, } @@ -2326,11 +2319,14 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstanceBackup(ct return nil, errors.Wrap(err, "iRds.CreateBackup") } - db.SetExternalId(backup, userCred, backupId) - - iBackup, err := iRegion.GetIDBInstanceBackupById(backupId) + err = db.SetExternalId(backup, userCred, backupId) if err != nil { - return nil, errors.Wrapf(err, "iRegion.GetIDBInstanceBackupById(%s)", backupId) + return nil, errors.Wrapf(err, "db.SetExternalId") + } + + iBackup, err := backup.GetIDBInstanceBackup() + if err != nil { + return nil, errors.Wrapf(err, "backup.GetIDBInstanceBackup") } _, err = db.Update(backup, func() error { diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go index 0a01850b5f..f8e7030e20 100644 --- a/pkg/compute/regiondrivers/qcloud.go +++ b/pkg/compute/regiondrivers/qcloud.go @@ -1358,3 +1358,57 @@ func (self *SQcloudRegionDriver) RequestSyncLoadbalancerListener(ctx context.Con }) return nil } + +func (self *SQcloudRegionDriver) InitDBInstanceUser(ctx context.Context, instance *models.SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error { + user := "root" + account := models.SDBInstanceAccount{} + account.DBInstanceId = instance.Id + account.Name = user + account.Host = "%" + if instance.Engine == api.DBINSTANCE_TYPE_MYSQL && instance.Category == api.QCLOUD_DBINSTANCE_CATEGORY_BASIC { + account.Host = "localhost" + } + account.Status = api.DBINSTANCE_USER_AVAILABLE + account.SetModelManager(models.DBInstanceAccountManager, &account) + err := models.DBInstanceAccountManager.TableSpec().Insert(ctx, &account) + if err != nil { + return errors.Wrapf(err, "Insert") + } + return account.SetPassword(desc.Password) +} + +func (self *SQcloudRegionDriver) IsSupportedDBInstance() bool { + return true +} + +func (self *SQcloudRegionDriver) IsDBInstanceNeedSecgroup() bool { + return true +} + +func (self *SQcloudRegionDriver) ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceBackupCreateInput) (api.DBInstanceBackupCreateInput, error) { + switch instance.Engine { + case api.DBINSTANCE_TYPE_MYSQL: + if instance.Category == api.QCLOUD_DBINSTANCE_CATEGORY_BASIC { + return input, httperrors.NewNotSupportedError("Qcloud Basic MySQL instance not support create backup") + } + } + return input, nil +} + +func (self *SQcloudRegionDriver) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceAccountCreateInput) (api.DBInstanceAccountCreateInput, error) { + return input, nil +} + +func (self *SQcloudRegionDriver) ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceDatabaseCreateInput) (api.DBInstanceDatabaseCreateInput, error) { + return input, httperrors.NewNotSupportedError("Not support create Qcloud databases") +} + +func (self *SQcloudRegionDriver) ValidateDBInstanceAccountPrivilege(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, account string, privilege string) error { + switch privilege { + case api.DATABASE_PRIVILEGE_RW: + case api.DATABASE_PRIVILEGE_R: + default: + return httperrors.NewInputParameterError("Unknown privilege %s", privilege) + } + return nil +} diff --git a/pkg/compute/tasks/dbinstance_account_create_task.go b/pkg/compute/tasks/dbinstance_account_create_task.go index c095a7f9d8..6e6ad9f89d 100644 --- a/pkg/compute/tasks/dbinstance_account_create_task.go +++ b/pkg/compute/tasks/dbinstance_account_create_task.go @@ -64,6 +64,7 @@ func (self *DBInstanceAccountCreateTask) CreateDBInstanceAccount(ctx context.Con desc := &cloudprovider.SDBInstanceAccountCreateConfig{ Name: account.Name, + Host: account.Host, } desc.Password, _ = account.GetPassword() @@ -85,7 +86,7 @@ func (self *DBInstanceAccountCreateTask) CreateDBInstanceAccount(ctx context.Con var iAccount cloudprovider.ICloudDBInstanceAccount = nil for i := range iAccounts { - if iAccounts[i].GetName() == account.Name { + if iAccounts[i].GetName() == account.Name && iAccounts[i].GetHost() == account.Host { iAccount = iAccounts[i] break } @@ -100,7 +101,7 @@ func (self *DBInstanceAccountCreateTask) CreateDBInstanceAccount(ctx context.Con return } - db.SetExternalId(account, self.UserCred, iAccount.GetGlobalId()) + //db.SetExternalId(account, self.UserCred, iAccount.GetGlobalId()) input := api.DBInstanceAccountCreateInput{} self.GetParams().Unmarshal(&input) diff --git a/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go b/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go index f07212178a..9913951a74 100644 --- a/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go +++ b/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go @@ -66,7 +66,7 @@ func (self *DBInstanceAccountRevokePrivilegeTask) OnInit(ctx context.Context, ob var iAccount cloudprovider.ICloudDBInstanceAccount = nil for _, ac := range accounts { - if ac.GetName() == account.Name { + if ac.GetName() == account.Name && ac.GetHost() == account.Host { iAccount = ac break } diff --git a/pkg/compute/tasks/dbinstance_backup_delete.go b/pkg/compute/tasks/dbinstance_backup_delete.go index 41dcf7b2c8..37295a7862 100644 --- a/pkg/compute/tasks/dbinstance_backup_delete.go +++ b/pkg/compute/tasks/dbinstance_backup_delete.go @@ -49,15 +49,9 @@ func (self *DBInstanceBackupDeleteTask) OnInit(ctx context.Context, obj db.IStan } func (self *DBInstanceBackupDeleteTask) DeleteDBInstanceBackup(ctx context.Context, backup *models.SDBInstanceBackup) { - iRegion, err := backup.GetIRegion() - if err != nil { - self.taskFailed(ctx, backup, errors.Wrap(err, "backup.GetIRegion")) - return - } - - iBackup, err := iRegion.GetIDBInstanceBackupById(backup.ExternalId) - if err != nil && err != cloudprovider.ErrNotFound { - self.taskFailed(ctx, backup, errors.Wrap(err, "iRegion.GetIDBInstanceBackupById")) + iBackup, err := backup.GetIDBInstanceBackup() + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + self.taskFailed(ctx, backup, errors.Wrap(err, "backup.GetIDBInstanceBackup")) return } diff --git a/pkg/compute/tasks/dbinstance_create_task.go b/pkg/compute/tasks/dbinstance_create_task.go index 245fb01309..a9121a78d4 100644 --- a/pkg/compute/tasks/dbinstance_create_task.go +++ b/pkg/compute/tasks/dbinstance_create_task.go @@ -19,7 +19,6 @@ import ( "fmt" "yunion.io/x/jsonutils" - "yunion.io/x/log" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -40,7 +39,7 @@ func (self *DBInstanceCreateTask) taskFailed(ctx context.Context, dbinstance *mo dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_CREATE_FAILED, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_CREATE, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CREATE, err, self.UserCred, false) - self.SetStageFailed(ctx, jsonutils.Marshal(err)) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } func (self *DBInstanceCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { @@ -62,35 +61,6 @@ func (self *DBInstanceCreateTask) OnCreateDBInstanceComplete(ctx context.Context dbinstance := obj.(*models.SDBInstance) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CREATE, nil, self.UserCred, true) - accounts, err := dbinstance.GetDBInstanceAccounts() - if err != nil { - log.Errorf("failed to get dbinstance %s account error: %v", dbinstance.Name, err) - } - - if len(accounts) > 0 { - iRds, err := dbinstance.GetIDBInstance() - if err != nil { - log.Errorf("failed to found dbinstance %s error: %v", dbinstance.Name, err) - } else { - iAccounts, err := iRds.GetIDBInstanceAccounts() - if err != nil { - log.Errorf("failed to get accounts from cloud dbinstance %s error: %v", dbinstance.Name, err) - } - externalIds := map[string]string{} - for _, iAccount := range iAccounts { - externalIds[iAccount.GetName()] = iAccount.GetGlobalId() - } - for i := range accounts { - externalId, ok := externalIds[accounts[i].Name] - if !ok { - log.Errorf("failed to get dbinstance account %s from cloud dbinstance for set externalId", accounts[i].Name) - } else { - db.SetExternalId(&accounts[i], self.UserCred, externalId) - } - } - } - } - self.SetStage("OnSyncDBInstanceStatusComplete", nil) models.StartResourceSyncStatusTask(ctx, self.UserCred, dbinstance, "DBInstanceSyncStatusTask", self.GetTaskId()) } diff --git a/pkg/compute/tasks/dbinstance_public_connection_task.go b/pkg/compute/tasks/dbinstance_public_connection_task.go index 48ae5d4534..c38374fbb2 100644 --- a/pkg/compute/tasks/dbinstance_public_connection_task.go +++ b/pkg/compute/tasks/dbinstance_public_connection_task.go @@ -48,7 +48,7 @@ func (self *DBInstancePublicConnectionTask) getAction() string { func (self *DBInstancePublicConnectionTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_FAILE, err.Error()) logclient.AddActionLogWithStartable(self, dbinstance, self.getAction(), err, self.UserCred, false) - self.SetStageFailed(ctx, jsonutils.Marshal(err)) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } func (self *DBInstancePublicConnectionTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { @@ -63,7 +63,9 @@ func (self *DBInstancePublicConnectionTask) DBInstancePublicConnectionOperation( return } - if isOpen, _ := self.GetParams().Bool("open"); isOpen { + isOpen := jsonutils.QueryBoolean(self.GetParams(), "open", false) + + if isOpen { err = idbinstance.OpenPublicConnection() } else { err = idbinstance.ClosePublicConnection() @@ -73,14 +75,27 @@ func (self *DBInstancePublicConnectionTask) DBInstancePublicConnectionOperation( return } - err = cloudprovider.WaitStatus(idbinstance, api.DBINSTANCE_RUNNING, 10*time.Second, time.Minute*30) + connectionStr := "" + + err = cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) { + iRds, err := instance.GetIDBInstance() + if err != nil { + return false, errors.Wrapf(err, "GetIDBInstance") + } + connectionStr = iRds.GetConnectionStr() + if (isOpen && len(connectionStr) > 0) || (!isOpen && len(connectionStr) == 0) { + return true, nil + } + return false, nil + }) + if err != nil { - self.taskFailed(ctx, instance, errors.Wrap(err, "cloudprovider.WaitStatus")) + self.taskFailed(ctx, instance, errors.Wrapf(err, "cloudprovider.Wait")) return } _, err = db.Update(instance, func() error { - instance.ConnectionStr = idbinstance.GetConnectionStr() + instance.ConnectionStr = connectionStr return nil }) diff --git a/pkg/compute/tasks/dbinstance_sync_status_task.go b/pkg/compute/tasks/dbinstance_sync_status_task.go index 81c579d3a9..6d0183653b 100644 --- a/pkg/compute/tasks/dbinstance_sync_status_task.go +++ b/pkg/compute/tasks/dbinstance_sync_status_task.go @@ -39,7 +39,7 @@ func (self *DBInstanceSyncStatusTask) taskFailed(ctx context.Context, dbinstance dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_UNKNOWN, err.Error()) db.OpsLog.LogEvent(dbinstance, db.ACT_SYNC_STATUS, err, self.GetUserCred()) logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) - self.SetStageFailed(ctx, jsonutils.Marshal(err)) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) } func (self *DBInstanceSyncStatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { diff --git a/pkg/compute/tasks/dbinstance_sync_task.go b/pkg/compute/tasks/dbinstance_sync_task.go index d677acb6ab..434818f6f9 100644 --- a/pkg/compute/tasks/dbinstance_sync_task.go +++ b/pkg/compute/tasks/dbinstance_sync_task.go @@ -53,7 +53,7 @@ func (self *DBInstanceSyncTask) SyncDBInstance(ctx context.Context, dbinstance * self.taskFailed(ctx, dbinstance, errors.Wrapf(err, "dbinstance.GetIDBInstance")) return } - err = dbinstance.SyncWithCloudDBInstance(ctx, self.UserCred, dbinstance.GetCloudprovider(), idbinstance) + err = dbinstance.SyncAllWithCloudDBInstance(ctx, self.UserCred, dbinstance.GetCloudprovider(), idbinstance) if err != nil { self.taskFailed(ctx, dbinstance, errors.Wrapf(err, "dbinstance.GetIDBInstance")) return diff --git a/pkg/mcclient/modules/mod_dbinstance_skus.go b/pkg/mcclient/modules/mod_dbinstance_skus.go index 10c319e106..4fc1ab44d5 100644 --- a/pkg/mcclient/modules/mod_dbinstance_skus.go +++ b/pkg/mcclient/modules/mod_dbinstance_skus.go @@ -26,7 +26,7 @@ var ( func init() { DBInstanceSkus = DBInstanceSkusManager{NewComputeManager("dbinstance_sku", "dbinstance_skus", - []string{"Id", "Name", "Engine", "Engine_Version", "Category", "Storage_Type", "Status", "Enabled"}, + []string{}, []string{})} registerCompute(&DBInstanceSkus) diff --git a/pkg/mcclient/options/dbinstance_skus.go b/pkg/mcclient/options/dbinstance_skus.go new file mode 100644 index 0000000000..53f799b1c2 --- /dev/null +++ b/pkg/mcclient/options/dbinstance_skus.go @@ -0,0 +1,44 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options + +import "yunion.io/x/jsonutils" + +type DBInstanceSkuListOption struct { + BaseListOptions + Engine string + EngineVersion string + Category string + StorageType string + Cloudregion string + VcpuCount *int + VmemSizeMb *int +} + +func (opts *DBInstanceSkuListOption) Params() (jsonutils.JSONObject, error) { + return ListStructToParams(opts) +} + +type DBInstanceSkuIdOption struct { + ID string `help:"DBInstance Id or name"` +} + +func (opts *DBInstanceSkuIdOption) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +func (opts *DBInstanceSkuIdOption) GetId() string { + return opts.ID +} diff --git a/pkg/multicloud/aliyun/dbinstance.go b/pkg/multicloud/aliyun/dbinstance.go index 80b8705418..ca49a430e7 100644 --- a/pkg/multicloud/aliyun/dbinstance.go +++ b/pkg/multicloud/aliyun/dbinstance.go @@ -604,17 +604,18 @@ func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceC "RegionId": region.RegionId, "Engine": desc.Engine, "EngineVersion": desc.EngineVersion, - "DBInstanceClass": desc.InstanceType, "DBInstanceStorage": fmt.Sprintf("%d", desc.DiskSizeGB), "DBInstanceNetType": "Intranet", "PayType": "Postpaid", "SecurityIPList": "0.0.0.0/0", "DBInstanceDescription": desc.Name, - "ClientToken": utils.GenRequestId(20), "InstanceNetworkType": "VPC", "VPCId": desc.VpcId, "VSwitchId": desc.NetworkId, "DBInstanceStorageType": desc.StorageType, + "DBInstanceClass": desc.InstanceType, + "ZoneId": desc.ZoneId, + "ClientToken": utils.GenRequestId(20), } switch desc.Category { case api.ALIYUN_DBINSTANCE_CATEGORY_HA: @@ -651,43 +652,9 @@ func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceC params["DBInstanceId"] = desc.MasterInstanceId } - var err error - var resp jsonutils.JSONObject - if len(desc.InstanceType) > 0 { - params["DBInstanceClass"] = desc.InstanceType - for _, zoneId := range desc.ZoneIds { - params["ZoneId"] = zoneId - resp, err = region.rdsRequest(action, params) - if err == nil { - break - } - } - if len(desc.ZoneIds) == 0 { - resp, err = region.rdsRequest(action, params) - } - if err != nil { - return nil, errors.Wrapf(err, "region.rdsRequest.%s", action) - } - } else { - for _, spec := range desc.InstanceTypes { - params["DBInstanceClass"] = spec.InstanceType - for _, zoneId := range spec.ZoneIds { - params["ZoneId"] = zoneId - resp, err = region.rdsRequest(action, params) - if err == nil { - break - } - } - if err == nil { - break - } - } - if err != nil { - return nil, errors.Wrapf(err, "region.rdsRequest.%s", action) - } - if resp == nil { - return nil, fmt.Errorf("dbinstance type %dC%dMB not avaiable", desc.VcpuCount, desc.VmemSizeMb) - } + resp, err := region.rdsRequest(action, params) + if err != nil { + return nil, errors.Wrapf(err, "rdsRequest") } instanceId, err := resp.GetString("DBInstanceId") if err != nil { diff --git a/pkg/multicloud/aliyun/dbinstance_account.go b/pkg/multicloud/aliyun/dbinstance_account.go index ad5d3b6f04..e399eb8356 100644 --- a/pkg/multicloud/aliyun/dbinstance_account.go +++ b/pkg/multicloud/aliyun/dbinstance_account.go @@ -41,14 +41,6 @@ type SDBInstanceAccount struct { PrivExceeded string } -func (account *SDBInstanceAccount) GetId() string { - return account.AccountName -} - -func (account *SDBInstanceAccount) GetGlobalId() string { - return account.AccountName -} - func (account *SDBInstanceAccount) GetName() string { return account.AccountName } diff --git a/pkg/multicloud/aliyun/dbinstance_privilege.go b/pkg/multicloud/aliyun/dbinstance_privilege.go index 6a2c6c623c..5826987113 100644 --- a/pkg/multicloud/aliyun/dbinstance_privilege.go +++ b/pkg/multicloud/aliyun/dbinstance_privilege.go @@ -29,7 +29,7 @@ type SDatabasePrivilege struct { } func (privilege *SDatabasePrivilege) GetGlobalId() string { - return fmt.Sprintf("%s/%s", privilege.account.GetGlobalId(), privilege.DBName) + return fmt.Sprintf("%s/%s", privilege.account.GetName(), privilege.DBName) } func (privilege *SDatabasePrivilege) GetPrivilege() string { diff --git a/pkg/multicloud/aws/dbinstance_account.go b/pkg/multicloud/aws/dbinstance_account.go index 511e0e7c94..0f1522c2ad 100644 --- a/pkg/multicloud/aws/dbinstance_account.go +++ b/pkg/multicloud/aws/dbinstance_account.go @@ -15,7 +15,6 @@ package aws import ( - api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/multicloud" ) @@ -26,18 +25,6 @@ type SDBInstanceAccount struct { AccountName string } -func (account *SDBInstanceAccount) GetId() string { - return account.AccountName -} - -func (account *SDBInstanceAccount) GetGlobalId() string { - return account.AccountName -} - func (account *SDBInstanceAccount) GetName() string { return account.AccountName } - -func (account *SDBInstanceAccount) GetStatus() string { - return api.DBINSTANCE_USER_AVAILABLE -} diff --git a/pkg/multicloud/dbinstance_account_base.go b/pkg/multicloud/dbinstance_account_base.go index 98471a8cb9..dc5ad83f95 100644 --- a/pkg/multicloud/dbinstance_account_base.go +++ b/pkg/multicloud/dbinstance_account_base.go @@ -30,11 +30,11 @@ package multicloud import ( "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" ) type SDBInstanceAccountBase struct { - SResourceBase } func (account *SDBInstanceAccountBase) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { @@ -45,6 +45,14 @@ func (account *SDBInstanceAccountBase) Delete() error { return errors.Wrapf(cloudprovider.ErrNotImplemented, "Delete") } +func (account *SDBInstanceAccountBase) GetHost() string { + return "%" +} + +func (account *SDBInstanceAccountBase) GetStatus() string { + return api.DBINSTANCE_USER_AVAILABLE +} + func (account *SDBInstanceAccountBase) ResetPassword(password string) error { return errors.Wrapf(cloudprovider.ErrNotImplemented, "ResetPassword") } diff --git a/pkg/multicloud/google/dbinstance.go b/pkg/multicloud/google/dbinstance.go index 3169d490ba..1f833eeb4f 100644 --- a/pkg/multicloud/google/dbinstance.go +++ b/pkg/multicloud/google/dbinstance.go @@ -625,45 +625,9 @@ func (region *SRegion) CreateDBInstance(desc *cloudprovider.SManagedDBInstanceCr if _, ok := EngineVersions[databaseVersion]; !ok { return nil, fmt.Errorf("Unsupport %s version %s", desc.Engine, desc.EngineVersion) } - var err error - var rds *SDBInstance = nil - if len(desc.InstanceType) > 0 { - if len(desc.ZoneIds) == 0 { - desc.ZoneIds = append(desc.ZoneIds, "") - } - for _, zoneId := range desc.ZoneIds { - rds, err = region.CreateRds(desc.Name, desc.Engine, databaseVersion, desc.Category, desc.InstanceType, desc.StorageType, desc.DiskSizeGB, desc.VpcId, zoneId, desc.Password) - if err == nil { - break - } else { - log.Errorf("failed to create dbinstance %s at %s error: %v", desc.Name, zoneId, err) - } - } - if err != nil { - return nil, errors.Wrap(err, "CreateRds") - } - } else if len(desc.InstanceTypes) > 0 { - for _, spec := range desc.InstanceTypes { - if len(spec.ZoneIds) == 0 { - desc.ZoneIds = append(desc.ZoneIds, "") - } - for _, zoneId := range spec.ZoneIds { - rds, err = region.CreateRds(desc.Name, desc.Engine, databaseVersion, desc.Category, desc.InstanceType, desc.StorageType, desc.DiskSizeGB, desc.VpcId, zoneId, desc.Password) - if err == nil { - break - } else { - log.Errorf("failed to create dbinstance %s at %s error: %v", desc.Name, zoneId, err) - } - } - if err == nil { - break - } - } - if err != nil { - return nil, errors.Wrap(err, "CreateRds") - } - } else { - return nil, fmt.Errorf("Missing instance type info") + rds, err := region.CreateRds(desc.Name, desc.Engine, databaseVersion, desc.Category, desc.InstanceType, desc.StorageType, desc.DiskSizeGB, desc.VpcId, desc.ZoneId, desc.Password) + if err != nil { + return nil, errors.Wrapf(err, "CreateRds") } return rds, nil } diff --git a/pkg/multicloud/google/dbinstance_account.go b/pkg/multicloud/google/dbinstance_account.go index c899297455..f105f8d80c 100644 --- a/pkg/multicloud/google/dbinstance_account.go +++ b/pkg/multicloud/google/dbinstance_account.go @@ -21,8 +21,8 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" - api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" ) type SSqlserverUserDetails struct { @@ -30,6 +30,7 @@ type SSqlserverUserDetails struct { } type SDBInstanceAccount struct { + multicloud.SDBInstanceAccountBase rds *SDBInstance Kind string @@ -71,30 +72,14 @@ func (account *SDBInstanceAccount) Delete() error { return account.rds.region.DeleteDBInstanceAccount(account.rds.Name, account.Name, account.Host) } -func (account *SDBInstanceAccount) GetGlobalId() string { - return account.Name -} - -func (account *SDBInstanceAccount) GetId() string { - return account.SelfLink -} - -func (account *SDBInstanceAccount) GetMetadata() *jsonutils.JSONDict { - return nil +func (account *SDBInstanceAccount) GetHost() string { + return account.Host } func (account *SDBInstanceAccount) GetName() string { return account.Name } -func (account *SDBInstanceAccount) GetStatus() string { - return api.DBINSTANCE_USER_AVAILABLE -} - -func (account *SDBInstanceAccount) IsEmulated() bool { - return false -} - func (account *SDBInstanceAccount) Refresh() error { _account, err := account.rds.region.GetDBInstanceAccount(account.SelfLink) if err != nil { diff --git a/pkg/multicloud/huawei/dbinstance.go b/pkg/multicloud/huawei/dbinstance.go index acbdc38428..641f409524 100644 --- a/pkg/multicloud/huawei/dbinstance.go +++ b/pkg/multicloud/huawei/dbinstance.go @@ -469,17 +469,7 @@ func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceC "replication_mode": "sync", } } - if len(desc.ZoneIds) == 0 { - for _, masterZoneId := range zoneIds { - for _, slaveZoneId := range zoneIds { - desc.ZoneIds = append(desc.ZoneIds, fmt.Sprintf("%s,%s", masterZoneId, slaveZoneId)) - } - } - } case api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE: - if len(desc.ZoneIds) == 0 { - desc.ZoneIds = zoneIds - } case api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA: } @@ -497,42 +487,11 @@ func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceC "is_auto_renew": false, } } - var resp jsonutils.JSONObject = nil - - if len(desc.InstanceType) > 0 { - params["flavor_ref"] = desc.InstanceType - for _, zoneId := range desc.ZoneIds { - params["availability_zone"] = zoneId - resp, err = region.ecsClient.DBInstance.Create(jsonutils.Marshal(params)) - if err == nil { - break - } - } - if err != nil { - log.Debugf("params: %s", jsonutils.Marshal(params).PrettyString()) - return nil, errors.Wrap(err, "DBInstance.Create") - } - } else { - for _, spec := range desc.InstanceTypes { - params["flavor_ref"] = spec.InstanceType - for _, zoneId := range spec.ZoneIds { - params["availability_zone"] = zoneId - resp, err = region.ecsClient.DBInstance.Create(jsonutils.Marshal(params)) - if err == nil { - break - } - } - if err == nil { - break - } - } - if err != nil { - log.Debugf("params: %s", jsonutils.Marshal(params).PrettyString()) - return nil, errors.Wrap(err, "region.ecsClient.DBInstance.Create") - } - if resp == nil { - return nil, fmt.Errorf("dbinstance type %dC%dMB not avaiable", desc.VcpuCount, desc.VmemSizeMb) - } + params["flavor_ref"] = desc.InstanceType + params["availability_zone"] = desc.ZoneId + resp, err := region.ecsClient.DBInstance.Create(jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrapf(err, "Create") } instance := &SDBInstance{region: region} diff --git a/pkg/multicloud/huawei/dbinstance_account.go b/pkg/multicloud/huawei/dbinstance_account.go index 587b0b4074..36f54fa529 100644 --- a/pkg/multicloud/huawei/dbinstance_account.go +++ b/pkg/multicloud/huawei/dbinstance_account.go @@ -30,15 +30,6 @@ type SDBInstanceAccount struct { Name string } -func (account *SDBInstanceAccount) GetId() string { - return account.Name - -} - -func (account *SDBInstanceAccount) GetGlobalId() string { - return account.Name -} - func (account *SDBInstanceAccount) GetName() string { return account.Name } @@ -51,10 +42,6 @@ func (region *SRegion) DeleteDBInstanceAccount(instanceId string, account string return DoDeleteWithSpec(region.ecsClient.DBInstance.DeleteInContextWithSpec, nil, instanceId, fmt.Sprintf("db_user/%s", account), nil, nil) } -func (account *SDBInstanceAccount) GetStatus() string { - return api.DBINSTANCE_USER_AVAILABLE -} - func (account *SDBInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { privileges, err := account.instance.region.GetDBInstancePrivvileges(account.instance.Id, account.Name) if err != nil { diff --git a/pkg/multicloud/huawei/dbinstance_privilege.go b/pkg/multicloud/huawei/dbinstance_privilege.go index 18fefa7dd8..fcab4995c9 100644 --- a/pkg/multicloud/huawei/dbinstance_privilege.go +++ b/pkg/multicloud/huawei/dbinstance_privilege.go @@ -28,7 +28,7 @@ type SDatabasePrivilege struct { } func (privilege *SDatabasePrivilege) GetGlobalId() string { - return fmt.Sprintf("%s/%s", privilege.account.GetGlobalId(), privilege.Name) + return fmt.Sprintf("%s/%s", privilege.account.Name, privilege.Name) } func (privilege *SDatabasePrivilege) GetPrivilege() string { diff --git a/pkg/multicloud/qcloud/dbinstance.go b/pkg/multicloud/qcloud/dbinstance.go new file mode 100644 index 0000000000..e51df2be79 --- /dev/null +++ b/pkg/multicloud/qcloud/dbinstance.go @@ -0,0 +1,63 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qcloud + +import ( + "strings" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +func (self *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { + ret := []cloudprovider.ICloudDBInstance{} + mysql := []SMySQLInstance{} + for { + part, total, err := self.ListMySQLInstances([]string{}, len(mysql), 50) + if err != nil { + return nil, errors.Wrapf(err, "ListMySQLInstances") + } + mysql = append(mysql, part...) + if len(mysql) >= total { + break + } + } + for i := range mysql { + mysql[i].region = self + ret = append(ret, &mysql[i]) + } + return ret, nil +} + +func (self *SRegion) GetIDBInstanceById(id string) (cloudprovider.ICloudDBInstance, error) { + if strings.HasPrefix(id, "cdb-") { + return self.GetMySQLInstanceById(id) + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) CreateIDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + switch opts.Engine { + case api.DBINSTANCE_TYPE_MYSQL: + rds, err := self.CreateMySQLDBInstance(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateMySQLDBInstance") + } + return rds, nil + } + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "For %s", opts.Engine) +} diff --git a/pkg/multicloud/qcloud/doc.go b/pkg/multicloud/qcloud/doc.go index 494fbada82..d6ac5e19e7 100644 --- a/pkg/multicloud/qcloud/doc.go +++ b/pkg/multicloud/qcloud/doc.go @@ -41,6 +41,22 @@ clb.tencentcloudapi.com clb.ap-shanghai-fsi.tencentcloudapi.com clb.ap-shenzhen-fsi.tencentcloudapi.com +cdb.tencentcloudapi.com +cdb.ap-shanghai-fsi.tencentcloudapi.com +cdb.ap-shenzhen-fsi.tencentcloudapi.com + +mariadb.tencentcloudapi.com +mariadb.ap-shanghai-fsi.tencentcloudapi.com +mariadb.ap-shenzhen-fsi.tencentcloudapi.com + +postgres.tencentcloudapi.com +postgres.ap-shanghai-fsi.tencentcloudapi.com +postgres.ap-shenzhen-fsi.tencentcloudapi.com + +sqlserver.tencentcloudapi.com +sqlserver.ap-shanghai-fsi.tencentcloudapi.com +sqlserver.ap-shenzhen-fsi.tencentcloudapi.com + lb.api.qcloud.com wss.api.qcloud.com cns.api.qcloud.com diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index 981e85cbbc..563b32b282 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -46,11 +46,15 @@ const ( QCLOUD_DEFAULT_REGION = "ap-beijing" - QCLOUD_API_VERSION = "2017-03-12" - QCLOUD_CLB_API_VERSION = "2018-03-17" - QCLOUD_BILLING_API_VERSION = "2018-07-09" - QCLOUD_AUDIT_API_VERSION = "2019-03-19" - QCLOUD_CAM_API_VERSION = "2019-01-16" + QCLOUD_API_VERSION = "2017-03-12" + QCLOUD_CLB_API_VERSION = "2018-03-17" + QCLOUD_BILLING_API_VERSION = "2018-07-09" + QCLOUD_AUDIT_API_VERSION = "2019-03-19" + QCLOUD_CAM_API_VERSION = "2019-01-16" + QCLOUD_CDB_API_VERSION = "2017-03-20" + QCLOUD_MARIADB_API_VERSION = "2017-03-12" + QCLOUD_POSTGRES_API_VERSION = "2017-03-12" + QCLOUD_SQLSERVER_API_VERSION = "2018-03-28" ) type QcloudClientConfig struct { @@ -167,6 +171,30 @@ func lbRequest(client *common.Client, apiName string, params map[string]string, return _phpJsonRequest(client, &lbJsonResponse{}, domain, "/v2/index.php", "", apiName, params, debug) } +// cdb +func cdbRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("cdb", params) + return _jsonRequest(client, domain, QCLOUD_CDB_API_VERSION, apiName, params, debug, true) +} + +// mariadb +func mariadbRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("mariadb", params) + return _jsonRequest(client, domain, QCLOUD_MARIADB_API_VERSION, apiName, params, debug, true) +} + +// postgres +func postgresRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("postgres", params) + return _jsonRequest(client, domain, QCLOUD_POSTGRES_API_VERSION, apiName, params, debug, true) +} + +// sqlserver +func sqlserverRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("sqlserver", params) + return _jsonRequest(client, domain, QCLOUD_SQLSERVER_API_VERSION, apiName, params, debug, true) +} + // ssl 证书服务 func wssRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { domain := "wss.api.qcloud.com" @@ -514,6 +542,38 @@ func (client *SQcloudClient) lbRequest(apiName string, params map[string]string) return lbRequest(cli, apiName, params, client.debug) } +func (client *SQcloudClient) cdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return cdbRequest(cli, apiName, params, client.debug) +} + +func (client *SQcloudClient) mariadbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return mariadbRequest(cli, apiName, params, client.debug) +} + +func (client *SQcloudClient) postgresRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return postgresRequest(cli, apiName, params, client.debug) +} + +func (client *SQcloudClient) sqlserverRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return sqlserverRequest(cli, apiName, params, client.debug) +} + func (client *SQcloudClient) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient() if err != nil { @@ -830,7 +890,7 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_NETWORK, cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, - // cloudprovider.CLOUD_CAPABILITY_RDS, + cloudprovider.CLOUD_CAPABILITY_RDS, // cloudprovider.CLOUD_CAPABILITY_CACHE, cloudprovider.CLOUD_CAPABILITY_EVENT, cloudprovider.CLOUD_CAPABILITY_CLOUDID, diff --git a/pkg/multicloud/qcloud/rds_mariadb_sku.go b/pkg/multicloud/qcloud/rds_mariadb_sku.go new file mode 100644 index 0000000000..525082121d --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mariadb_sku.go @@ -0,0 +1,137 @@ +// 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 qcloud + +import ( + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +var ( + SUPPORTED_ENGINE_VERSION = []string{"5.7", "10.0", "10.1"} +) + +type SaleZoneInfo struct { + Zone string + ZoneId string + ZoneName string +} + +type SAvailableChoice struct { + MasterZone SaleZoneInfo + SlaveZones []SaleZoneInfo +} + +type SRegionSaleInfo struct { + AvailableChoice []SAvailableChoice + Region string + RegionId string + RegionName string + ZoneList []SaleZoneInfo +} + +func (self *SRegion) DescribeSaleInfo() ([]SRegionSaleInfo, error) { + resp, err := self.mariadbRequest("DescribeSaleInfo", map[string]string{}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeSaleInfo") + } + saleInfo := []SRegionSaleInfo{} + err = resp.Unmarshal(&saleInfo, "RegionList") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return saleInfo, nil +} + +type SInstanceSpec struct { + Cpu int + Machine string + MaxStorage int + Memory int + MinStorage int + NodeCount int + Pid int + Qps int + SuitInfo string +} + +type SInstanceSpecs struct { + Machine string + SpecInfos []SInstanceSpec +} + +func (self *SRegion) DescribeDBInstanceSpecs() ([]SInstanceSpecs, error) { + resp, err := self.mariadbRequest("DescribeDBInstanceSpecs", map[string]string{}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBInstanceSpecs") + } + specs := []SInstanceSpecs{} + err = resp.Unmarshal(&specs, "Specs") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return specs, nil +} + +func (self *SRegion) ListMariadbSkus() ([]SDBInstanceSku, error) { + skus := []SDBInstanceSku{} + + saleRegions, err := self.DescribeSaleInfo() + if err != nil { + return nil, errors.Wrapf(err, "DescribeSaleInfo") + } + + for _, saleRegion := range saleRegions { + if saleRegion.Region == self.Region { + specs, err := self.DescribeDBInstanceSpecs() + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBInstanceSpecs") + } + for _, spec := range specs { + for _, info := range spec.SpecInfos { + sku := SDBInstanceSku{ + Region: self.Region, + Engine: api.DBINSTANCE_TYPE_MARIADB, + Cpu: info.Cpu, + StorageMax: info.MaxStorage, + StorageMin: info.MinStorage, + StorageStep: 10, + MemoryMb: info.Memory * 1024, + Qps: info.Qps, + Description: info.SuitInfo, + Category: "标准版", + Status: api.DBINSTANCE_SKU_AVAILABLE, + } + if info.NodeCount == 3 { + sku.Category = "金融版" + } + for _, engineVersion := range SUPPORTED_ENGINE_VERSION { + sku.EngineVersion = engineVersion + sku.Zone2 = "" + for _, zone := range saleRegion.AvailableChoice { + sku.Zone1 = zone.MasterZone.Zone + for _, slaveZone := range zone.SlaveZones { + sku.Zone2 = slaveZone.Zone + skus = append(skus, sku) + } + } + } + } + } + } + } + return skus, nil +} diff --git a/pkg/multicloud/qcloud/rds_mysql.go b/pkg/multicloud/qcloud/rds_mysql.go new file mode 100644 index 0000000000..fbf187ba04 --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mysql.go @@ -0,0 +1,811 @@ +// 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 qcloud + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/timeutils" + "yunion.io/x/pkg/utils" + + billingapi "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" +) + +type SlaveInstanceInfo struct { + Region string + Vip string + VpcId int + Vport int + Zone string +} + +type SlaveInfo struct { + First SlaveInstanceInfo + Second SlaveInstanceInfo +} + +type SDrInfo struct { + Status int + Zone string + InstanceId string + Region string + SyncStatus string + InstanceName string + InstanceType string +} + +type SMasterInfo struct { + Region string + RegionId int + ZoneId int + Zone string + InstanceId string + ResourceId string + Status int + InstanceName string + InstanceType int + TaskStatus int + Memory int + Volume int + DeviceType string + Qps int + VpcId int + SubnetId int + ExClusterId string + ExClusterName string +} + +type SRoGroup struct { + RoGroupMode string + RoGroupId string + RoGroupName string + RoOfflineDelay int + RoMaxDelayTime int + MinRoInGroup int + WeightMode string + Weight int + // RoInstances + Vip string + Vport int + UniqVpcId string + UniqSubnetId string + RoGroupRegion string + RoGroupZone string +} + +type SRoVipInfo struct { + RoVipStatus int + RoSubnetId int + RoVpcId int + RoVport int + RoVip string +} + +type SMySQLInstance struct { + region *SRegion + multicloud.SDBInstanceBase + + AutoRenew int + CdbError int + Cpu int + CreateTime time.Time + DeadlineTime string + DeployGroupId string + DeployMode int + DeviceClass string + DeviceType string + DrInfo []SDrInfo + EngineVersion string + ExClusterId string + HourFeeStatus int + InitFlag int + InstanceId string + InstanceName string + InstanceType int + IsolateTime string + MasterInfo SMasterInfo + Memory int + OfflineTime string + PayType int + PhysicalId string + ProjectId int + ProtectMode string + Qps int + Region string + RegionId string + ResourceId string + RoGroups []SRoGroup + RoVipInfo SRoVipInfo + SecurityGroupIds []string + SlaveInfo SlaveInfo + Status int + SubnetId int + //TagList": null, + TaskStatus int + UniqSubnetId string + UniqVpcId string + Vip string + Volume int + VpcId int + Vport int + WanDomain string + WanPort int + WanStatus int + Zone string + ZoneId int + ZoneName string +} + +func (self *SMySQLInstance) GetId() string { + return self.InstanceId +} + +func (self *SMySQLInstance) GetGlobalId() string { + return self.InstanceId +} + +func (self *SMySQLInstance) GetName() string { + if len(self.InstanceName) > 0 { + return self.InstanceName + } + return self.InstanceId +} + +func (self *SMySQLInstance) GetDiskSizeGB() int { + return self.Volume +} + +func (self *SMySQLInstance) GetEngine() string { + return api.DBINSTANCE_TYPE_MYSQL +} + +func (self *SMySQLInstance) GetEngineVersion() string { + return self.EngineVersion +} + +func (self *SMySQLInstance) GetIVpcId() string { + return self.UniqVpcId +} + +func (self *SMySQLInstance) Refresh() error { + rds, err := self.region.GetMySQLInstanceById(self.InstanceId) + if err != nil { + return errors.Wrapf(err, "GetMySQLInstanceById(%s)", self.InstanceId) + } + return jsonutils.Update(self, rds) +} + +func (self *SMySQLInstance) GetInstanceType() string { + return fmt.Sprintf("%d核%dMB", self.Cpu, self.Memory) +} + +func (self *SMySQLInstance) GetMaintainTime() string { + timeWindow, err := self.region.DescribeMySQLTimeWindow(self.InstanceId) + if err != nil { + log.Errorf("DescribeMySQLTimeWindow %s error: %v", self.InstanceId, err) + return "" + } + return timeWindow.String() +} + +func (self *SMySQLInstance) GetDBNetwork() (*cloudprovider.SDBInstanceNetwork, error) { + return &cloudprovider.SDBInstanceNetwork{NetworkId: self.UniqSubnetId, IP: self.Vip}, nil +} + +func (self *SMySQLInstance) GetConnectionStr() string { + if self.WanStatus == 1 { + return fmt.Sprintf("%s:%d", self.WanDomain, self.WanPort) + } + return "" +} + +func (self *SMySQLInstance) GetInternalConnectionStr() string { + return fmt.Sprintf("%s:%d", self.Vip, self.Vport) +} + +func (self *SMySQLInstance) Reboot() error { + return self.region.RebootMySQLInstance(self.InstanceId) +} + +func (self *SMySQLInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedDBInstanceChangeConfig) error { + return self.region.UpgradeMySQLDBInstance(self.InstanceId, opts.VmemSizeMb, opts.DiskSizeGB) +} + +func (self *SMySQLInstance) GetMasterInstanceId() string { + return self.MasterInfo.InstanceId +} + +func (self *SMySQLInstance) GetSecurityGroupId() string { + if len(self.SecurityGroupIds) > 0 { + return self.SecurityGroupIds[0] + } + if self.DeviceType == "BASIC" { + return "" + } + secgroups, err := self.region.DescribeMySQLDBSecurityGroups(self.InstanceId) + if err != nil { + log.Errorf("failed to get instance %s security group info %v", self.InstanceId, err) + return "" + } + if len(secgroups) > 0 { + return secgroups[0].SecurityGroupId + } + return "" +} + +func (self *SMySQLInstance) Renew(bc billing.SBillingCycle) error { + month := bc.GetMonths() + return self.region.RenewMySQLDBInstance(self.InstanceId, month) +} + +func (self *SMySQLInstance) OpenPublicConnection() error { + if self.WanStatus == 0 { + return self.region.OpenMySQLWanService(self.InstanceId) + } + return nil +} + +func (self *SMySQLInstance) ClosePublicConnection() error { + if self.WanStatus == 1 { + return self.region.CloseMySQLWanService(self.InstanceId) + } + return nil +} + +func (self *SMySQLInstance) GetPort() int { + return self.Vport +} + +func (self *SMySQLInstance) GetStatus() string { + if self.InitFlag == 0 { + return api.DBINSTANCE_INIT + } + switch self.TaskStatus { + case 0: + switch self.Status { + case 0: + return api.DBINSTANCE_DEPLOYING + case 1: + return api.DBINSTANCE_RUNNING + case 4, 5: + return api.DBINSTANCE_DELETING + } + case 1: + case 2: //数据导入中 + return api.DBINSTANCE_IMPORTING + case 3, 4: //开放关闭外网地址 + return api.DBINSTANCE_DEPLOYING + case 10: + return api.DBINSTANCE_REBOOTING + case 12: + return api.DBINSTANCE_MIGRATING + default: + return api.DBINSTANCE_DEPLOYING + } + return api.DBINSTANCE_UNKNOWN +} + +func (self *SMySQLInstance) GetCategory() string { + return strings.ToLower(self.DeviceType) +} + +func (self *SMySQLInstance) GetStorageType() string { + switch self.DeviceType { + case "BASIC": + return api.QCLOUD_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD + default: + return api.QCLOUD_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD + } +} + +func (self *SMySQLInstance) GetCreatedAt() time.Time { + // 2019-12-25 09:00:43 #非UTC时间 + return self.CreateTime.Add(time.Hour * -8) +} + +func (self *SMySQLInstance) GetBillingType() string { + if self.PayType == 0 { + return billingapi.BILLING_TYPE_PREPAID + } + return billingapi.BILLING_TYPE_POSTPAID +} + +func (self *SMySQLInstance) SetAutoRenew(autoRenew bool) error { + return self.region.ModifyMySQLAutoRenewFlag([]string{self.InstanceId}, autoRenew) +} + +func (self *SMySQLInstance) IsAutoRenew() bool { + return self.AutoRenew == 1 +} + +func (self *SMySQLInstance) GetExpiredAt() time.Time { + t, _ := timeutils.ParseTimeStr(self.DeadlineTime) + if t.IsZero() { + return t + } + return t.Add(time.Hour * -8) +} + +func (self *SMySQLInstance) GetVcpuCount() int { + return self.Cpu +} + +func (self *SMySQLInstance) GetVmemSizeMB() int { + return self.Memory +} + +func (self *SMySQLInstance) GetZone1Id() string { + return self.Zone +} + +func (self *SMySQLInstance) GetZone2Id() string { + return self.SlaveInfo.First.Zone +} + +func (self *SMySQLInstance) GetZone3Id() string { + return self.SlaveInfo.Second.Zone +} + +func (self *SMySQLInstance) GetProjectId() string { + return fmt.Sprintf("%d", self.ProjectId) +} + +func (self *SMySQLInstance) Delete() error { + err := self.region.IsolateMySQLDBInstance(self.InstanceId) + if err != nil { + return errors.Wrapf(err, "IsolateMySQLDBInstance") + } + return self.region.OfflineIsolatedMySQLInstances([]string{self.InstanceId}) +} + +func (self *SRegion) ListMySQLInstances(ids []string, offset, limit int) ([]SMySQLInstance, int, error) { + if limit < 1 || limit > 50 { + limit = 50 + } + params := map[string]string{ + "Offset": fmt.Sprintf("%d", offset), + "Limit": fmt.Sprintf("%d", limit), + } + for idx, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", idx)] = id + } + resp, err := self.cdbRequest("DescribeDBInstances", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeDBInstances") + } + items := []SMySQLInstance{} + err = resp.Unmarshal(&items, "Items") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + total, _ := resp.Float("TotalCount") + return items, int(total), nil +} + +type SAsyncRequestResult struct { + Info string + Status string +} + +func (self *SRegion) DescribeMySQLAsyncRequestInfo(id string) (*SAsyncRequestResult, error) { + resp, err := self.cdbRequest("DescribeAsyncRequestInfo", map[string]string{"AsyncRequestId": id}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeAsyncRequestInfo") + } + result := SAsyncRequestResult{} + err = resp.Unmarshal(&result) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return &result, nil +} + +func (self *SRegion) waitAsyncAction(action string, resId, asyncRequestId string) error { + if len(asyncRequestId) == 0 { + return errors.Error("Missing AsyncRequestId") + } + return cloudprovider.Wait(time.Second*10, time.Minute*20, func() (bool, error) { + result, err := self.DescribeMySQLAsyncRequestInfo(asyncRequestId) + if err != nil { + return false, errors.Wrapf(err, action) + } + log.Debugf("task %s(%s) for mysql instance %s status: %s", action, asyncRequestId, resId, result.Status) + switch result.Status { + case "FAILED", "KILLED", "REMOVED", "PAUSED": + return true, errors.Errorf(result.Info) + case "SUCCESS": + return true, nil + default: + return false, nil + } + }) +} + +func (self *SRegion) RebootMySQLInstance(id string) error { + resp, err := self.cdbRequest("RestartDBInstances", map[string]string{"InstanceIds.0": id}) + if err != nil { + return errors.Wrapf(err, "RestartDBInstances") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("RestartDBInstances", id, asyncRequestId) +} + +func (self *SRegion) DescribeMySQLDBInstanceInfo(id string) (*SMySQLInstance, error) { + resp, err := self.cdbRequest("DescribeDBInstanceInfo", map[string]string{"InstanceId": id}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBInstanceInfo") + } + result := SMySQLInstance{region: self} + err = resp.Unmarshal(&result) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return &result, nil +} + +func (self *SRegion) RenewMySQLDBInstance(id string, month int) error { + params := map[string]string{ + "InstanceId": id, + "TimeSpan": fmt.Sprintf("%d", month), + } + _, err := self.cdbRequest("RenewDBInstance", params) + if err != nil { + return errors.Wrapf(err, "RenewDBInstance") + } + return nil +} + +func (self *SRegion) OfflineIsolatedMySQLInstances(ids []string) error { + params := map[string]string{} + for idx, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", idx)] = id + } + _, err := self.cdbRequest("OfflineIsolatedInstances", params) + if err != nil { + return errors.Wrapf(err, "OfflineIsolatedInstances") + } + return nil +} + +func (self *SRegion) ReleaseIsolatedMySQLDBInstances(ids []string) error { + params := map[string]string{} + for idx, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", idx)] = id + } + resp, err := self.cdbRequest("ReleaseIsolatedDBInstances", params) + if err != nil { + return errors.Wrapf(err, "ReleaseIsolatedDBInstances") + } + result := []struct { + InstanceId string + Code int + Message string + }{} + err = resp.Unmarshal(&result, "Items") + if err != nil { + return errors.Wrapf(err, "resp.Unmarshal") + } + msg := []string{} + for i := range result { + if result[i].Code != 0 { + msg = append(msg, fmt.Sprintf("instance %s release isolate error: %s", result[i].InstanceId, result[i].Message)) + } + } + if len(msg) > 0 { + return errors.Error(strings.Join(msg, " ")) + } + return cloudprovider.Wait(time.Second, time.Minute*10, func() (bool, error) { + instances, _, err := self.ListMySQLInstances(ids, 0, len(ids)) + if err != nil { + return false, errors.Wrapf(err, "ListMySQLInstances") + } + for i := range instances { + if instances[i].Status == 4 || instances[i].Status == 5 { + log.Debugf("mysql instance %s(%s) current be isolate", instances[i].InstanceName, instances[i].InstanceId) + return false, nil + } + } + return true, nil + }) +} + +func (self *SRegion) IsolateMySQLDBInstance(id string) error { + params := map[string]string{"InstanceId": id} + resp, err := self.cdbRequest("IsolateDBInstance", params) + if err != nil { + return errors.Wrapf(err, "IsolateDBInstance") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + if len(asyncRequestId) > 0 { + return self.waitAsyncAction("IsolateDBInstance", id, asyncRequestId) + } + return cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) { + instances, _, err := self.ListMySQLInstances([]string{id}, 0, 1) + if err != nil { + return false, errors.Wrapf(err, "ListMySQLInstances(%s)", id) + } + statusMap := map[int]string{0: "创建中", 1: "运行中", 4: "隔离中", 5: "已隔离"} + for _, rds := range instances { + status, _ := statusMap[rds.Status] + log.Debugf("instance %s(%s) status %d(%s)", rds.InstanceName, rds.InstanceId, rds.Status, status) + if rds.Status != 5 { + return false, nil + } + } + return true, nil + }) +} + +func (self *SRegion) CloseMySQLWanService(id string) error { + params := map[string]string{"InstanceId": id} + resp, err := self.cdbRequest("CloseWanService", params) + if err != nil { + return errors.Wrapf(err, "CloseWanService") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("CloseWanService", id, asyncRequestId) +} + +func (self *SRegion) OpenMySQLWanService(id string) error { + params := map[string]string{"InstanceId": id} + resp, err := self.cdbRequest("OpenWanService", params) + if err != nil { + return errors.Wrapf(err, "OpenWanService") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("OpenWanService", id, asyncRequestId) +} + +func (self *SRegion) InitMySQLDBInstances(ids []string, password string, parameters map[string]string, vport int) error { + params := map[string]string{"NewPassword": password} + for idx, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", idx)] = id + } + i := 0 + for k, v := range parameters { + params[fmt.Sprintf("Parameters.%d.name", i)] = k + params[fmt.Sprintf("Parameters.%d.value", i)] = v + i++ + } + if vport >= 1024 && vport <= 65535 { + params["Vport"] = fmt.Sprintf("%d", vport) + } + resp, err := self.cdbRequest("InitDBInstances", params) + if err != nil { + return errors.Wrapf(err, "InitDBInstances") + } + asyncRequestIds := []string{} + err = resp.Unmarshal(&asyncRequestIds, "AsyncRequestIds") + if err != nil { + return errors.Wrapf(err, "resp.Unmarshal") + } + for idx, requestId := range asyncRequestIds { + err = self.waitAsyncAction("InitDBInstances", fmt.Sprintf("%d", idx), requestId) + if err != nil { + return err + } + } + return nil +} + +func (self *SRegion) UpgradeMySQLDBInstance(id string, memoryMb int, volumeGb int) error { + params := map[string]string{ + "InstanceId": id, + "Memory": fmt.Sprintf("%d", memoryMb), + "Volume": fmt.Sprintf("%d", volumeGb), + } + resp, err := self.cdbRequest("UpgradeDBInstance", params) + if err != nil { + return errors.Wrapf(err, "UpgradeDBInstance") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("UpgradeDBInstance", id, asyncRequestId) +} + +func (self *SRegion) ModifyMySQLAutoRenewFlag(ids []string, autoRenew bool) error { + params := map[string]string{} + for idx, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", idx)] = id + } + params["AutoRenew"] = fmt.Sprintf("%d", autoRenew) + _, err := self.cdbRequest("ModifyAutoRenewFlag", params) + return err +} + +type SMaintenanceTime struct { + Monday []string + Tuesday []string + Wednesday []string + Thursday []string + Friday []string + Saturday []string + Sunday []string +} + +func (w SMaintenanceTime) String() string { + windows := []string{} + for k, v := range map[string][]string{ + "Monday": w.Monday, + "Tuesday": w.Tuesday, + "Wednesday": w.Wednesday, + "Thursday": w.Thursday, + "Friday": w.Friday, + "Saturday": w.Saturday, + "Sunday": w.Sunday, + } { + if len(v) > 0 { + windows = append(windows, fmt.Sprintf("%s: %s", k, strings.Join(v, " "))) + } + } + return strings.Join(windows, "\n") +} + +func (self *SRegion) DescribeMySQLTimeWindow(id string) (*SMaintenanceTime, error) { + params := map[string]string{"InstanceId": id} + resp, err := self.cdbRequest("DescribeTimeWindow", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeTimeWindow") + } + timeWindow := &SMaintenanceTime{} + err = resp.Unmarshal(timeWindow) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return timeWindow, nil +} + +type SDBSecgroup struct { + ProjectId int + CreateTime time.Time + SecurityGroupId string + SecurityGroupName string + SecurityGroupRemark string +} + +func (self *SRegion) DescribeMySQLDBSecurityGroups(instanceId string) ([]SDBSecgroup, error) { + params := map[string]string{ + "InstanceId": instanceId, + } + resp, err := self.cdbRequest("DescribeDBSecurityGroups", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBSecurityGroups") + } + result := []SDBSecgroup{} + err = resp.Unmarshal(&result, "Groups") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return result, nil +} + +func (self *SRegion) CreateMySQLDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (*SMySQLInstance, error) { + params := map[string]string{ + "InstanceName": opts.Name, + "GoodsNum": "1", + "Memory": fmt.Sprintf("%d", opts.VmemSizeMb), + "Volume": fmt.Sprintf("%d", opts.DiskSizeGB), + "EngineVersion": opts.EngineVersion, + } + if len(opts.VpcId) > 0 { + params["UniqVpcId"] = opts.VpcId + } + if len(opts.NetworkId) > 0 { + params["UniqSubnetId"] = opts.NetworkId + } + if len(opts.ProjectId) > 0 { + params["ProjectId"] = opts.ProjectId + } + if opts.Port > 1024 && opts.Port < 65535 { + params["Port"] = fmt.Sprintf("%d", opts.Port) + } + if len(opts.Password) > 0 { + params["Password"] = opts.Password + } + if len(opts.SecgroupId) > 0 { + params["SecurityGroup.0"] = opts.SecgroupId + } + action := "CreateDBInstanceHour" + if opts.BillingCycle != nil { + params["Period"] = fmt.Sprintf("%d", opts.BillingCycle.GetMonths()) + action = "CreateDBInstance" + } + switch opts.Category { + case api.QCLOUD_DBINSTANCE_CATEGORY_HA, api.QCLOUD_DBINSTANCE_CATEGORY_BASIC: + params["DeviceType"] = strings.ToUpper(opts.Category) + case api.QCLOUD_DBINSTANCE_CATEGORY_FINANCE: + params["DeviceType"] = "HA" + params["ProtectMode"] = "2" + } + if len(opts.Zone1) > 0 { + params["Zone"] = opts.Zone1 + } + if len(opts.Zone2) > 0 { + params["SlaveZone"] = opts.Zone2 + } + if len(opts.Zone3) > 0 { + params["BackupZone"] = opts.Zone3 + } + params["ClientToken"] = utils.GenRequestId(20) + resp, err := self.cdbRequest(action, params) + if err != nil { + return nil, errors.Wrapf(err, "cdbRequest") + } + instanceIds := []string{} + err = resp.Unmarshal(&instanceIds, "InstanceIds") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + if len(instanceIds) == 0 { + return nil, fmt.Errorf("%s not return InstanceIds", action) + } + err = cloudprovider.Wait(time.Second*10, time.Minute*20, func() (bool, error) { + instances, _, err := self.ListMySQLInstances(instanceIds, 0, 1) + if err != nil { + return false, errors.Wrapf(err, "ListMySQLInstances(%s)", instanceIds) + } + for _, rds := range instances { + log.Debugf("instance %s(%s) task status: %d", rds.InstanceName, rds.InstanceId, rds.TaskStatus) + if rds.TaskStatus == 1 { + return false, nil + } + } + return true, nil + }) + if err != nil { + return nil, errors.Wrapf(err, "cloudprovider.Wait After create") + } + return self.GetMySQLInstanceById(instanceIds[0]) +} + +func (self *SRegion) GetMySQLInstanceById(id string) (*SMySQLInstance, error) { + part, total, err := self.ListMySQLInstances([]string{id}, 0, 20) + if err != nil { + return nil, errors.Wrapf(err, "ListMySQLInstances") + } + if total > 1 { + return nil, errors.Wrapf(cloudprovider.ErrDuplicateId, "id: [%s]", id) + } + if total < 1 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) + } + part[0].region = self + return &part[0], nil +} + +func (self *SMySQLInstance) CreateDatabase(opts *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return cloudprovider.ErrNotSupported +} + +func (self *SMySQLInstance) CreateAccount(opts *cloudprovider.SDBInstanceAccountCreateConfig) error { + return self.region.CreateMySQLAccount(self.InstanceId, opts) +} + +func (self *SMySQLInstance) CreateIBackup(opts *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + tables := map[string]string{} + for _, d := range opts.Databases { + tables[d] = "" + } + return self.region.CreateMySQLBackup(self.InstanceId, tables) +} diff --git a/pkg/multicloud/qcloud/rds_mysql_account.go b/pkg/multicloud/qcloud/rds_mysql_account.go new file mode 100644 index 0000000000..7dbf7cd0ea --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mysql_account.go @@ -0,0 +1,367 @@ +// 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 qcloud + +import ( + "fmt" + "strings" + + "gopkg.in/fatih/set.v0" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMySQLInstanceAccount struct { + multicloud.SDBInstanceAccountBase + rds *SMySQLInstance + + Notes string + Host string + User string + ModifyTime string + ModifyPasswordTime string + CreateTime string +} + +func (self *SMySQLInstanceAccount) GetName() string { + return self.User +} + +func (self *SMySQLInstanceAccount) GetHost() string { + return self.Host +} + +func (self *SMySQLInstanceAccount) ResetPassword(password string) error { + return self.rds.region.ModifyMySQLAccountPassword(self.rds.InstanceId, password, map[string]string{self.User: self.Host}) +} + +func (self *SMySQLInstanceAccount) Delete() error { + return self.rds.region.DeleteMySQLAccounts(self.rds.InstanceId, map[string]string{self.User: self.Host}) +} + +type sPrivilege struct { + Database string + Privilege string + User string + Host string +} + +func (p sPrivilege) GetGlobalId() string { + return fmt.Sprintf("%s-%s-%s-%s", p.User, p.Host, p.Database, p.Privilege) +} + +func (p sPrivilege) GetDBName() string { + return p.Database +} + +func (p sPrivilege) GetPrivilege() string { + return p.Privilege +} + +func (self *SRegion) GrantAccountPrivilege(instanceId, user, host, database, privilege string) error { + privileges := []string{} + switch privilege { + case api.DATABASE_PRIVILEGE_RW: + privileges = api.QCLOUD_RW_PRIVILEGE_SET + case api.DATABASE_PRIVILEGE_R: + privileges = api.QCLOUD_R_PRIVILEGE_SET + default: + return fmt.Errorf("unknow privilege %s", privilege) + } + priv, err := self.DescribeAccountPrivileges(instanceId, user, host) + if err != nil { + return errors.Wrapf(err, "DescribeAccountPrivileges") + } + params := map[string]string{ + "InstanceId": instanceId, + "Accounts.0.User": user, + "Accounts.0.Host": host, + } + for i, p := range priv.GlobalPrivileges { + params[fmt.Sprintf("GlobalPrivileges.%d", i)] = p + } + find := false + for i, p := range priv.DatabasePrivileges { + params[fmt.Sprintf("DatabasePrivileges.%d.Database", i)] = p.Database + if database == p.Database { + p.Privileges = privileges + find = true + } + for j, v := range p.Privileges { + params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", i, j)] = v + } + } + if !find { + params[fmt.Sprintf("DatabasePrivileges.%d.Database", len(priv.DatabasePrivileges))] = database + for j, v := range privileges { + params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", len(priv.DatabasePrivileges), j)] = v + } + } + for i, p := range priv.TablePrivileges { + params[fmt.Sprintf("TablePrivileges.%d.Database", i)] = p.Database + params[fmt.Sprintf("TablePrivileges.%d.Table", i)] = p.Table + for j, v := range p.Privileges { + params[fmt.Sprintf("TablePrivileges.%d.Privileges.%d", i, j)] = v + } + } + for i, p := range priv.ColumnPrivileges { + params[fmt.Sprintf("ColumnPrivileges.%d.Database", i)] = p.Database + params[fmt.Sprintf("ColumnPrivileges.%d.Table", i)] = p.Table + params[fmt.Sprintf("ColumnPrivileges.%d.Column", i)] = p.Column + for j, v := range p.Privileges { + params[fmt.Sprintf("ColumnPrivileges.%d.Privileges.%d", i, j)] = v + } + } + resp, err := self.cdbRequest("ModifyAccountPrivileges", params) + if err != nil { + return errors.Wrapf(err, "ModifyAccountPrivileges") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("ModifyAccountPrivileges", instanceId, asyncRequestId) +} + +func (self *SRegion) RevokeAccountPrivilege(instanceId, user, host, database string) error { + priv, err := self.DescribeAccountPrivileges(instanceId, user, host) + if err != nil { + return errors.Wrapf(err, "DescribeAccountPrivileges") + } + params := map[string]string{ + "InstanceId": instanceId, + "Accounts.0.User": user, + "Accounts.0.Host": host, + } + for i, p := range priv.GlobalPrivileges { + params[fmt.Sprintf("GlobalPrivileges.%d", i)] = p + } + idx := 0 + for _, p := range priv.DatabasePrivileges { + if p.Database == database { + continue + } + params[fmt.Sprintf("DatabasePrivileges.%d.Database", idx)] = p.Database + for i, v := range p.Privileges { + params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", idx, i)] = v + } + } + for i, p := range priv.TablePrivileges { + params[fmt.Sprintf("TablePrivileges.%d.Database", i)] = p.Database + params[fmt.Sprintf("TablePrivileges.%d.Table", i)] = p.Table + for j, v := range p.Privileges { + params[fmt.Sprintf("TablePrivileges.%d.Privileges.%d", i, j)] = v + } + } + for i, p := range priv.ColumnPrivileges { + params[fmt.Sprintf("ColumnPrivileges.%d.Database", i)] = p.Database + params[fmt.Sprintf("ColumnPrivileges.%d.Table", i)] = p.Table + params[fmt.Sprintf("ColumnPrivileges.%d.Column", i)] = p.Column + for j, v := range p.Privileges { + params[fmt.Sprintf("ColumnPrivileges.%d.Privileges.%d", i, j)] = v + } + } + resp, err := self.cdbRequest("ModifyAccountPrivileges", params) + if err != nil { + return errors.Wrapf(err, "ModifyAccountPrivileges") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("ModifyAccountPrivileges", instanceId, asyncRequestId) +} + +func (self *SMySQLInstanceAccount) GrantPrivilege(database, privilege string) error { + return self.rds.region.GrantAccountPrivilege(self.rds.InstanceId, self.User, self.Host, database, privilege) +} + +func (self *SMySQLInstanceAccount) RevokePrivilege(database string) error { + return self.rds.region.RevokeAccountPrivilege(self.rds.InstanceId, self.User, self.Host, database) +} + +func (self *SMySQLInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { + if utils.IsInStringArray(self.User, []string{"mysql.infoschema", "mysql.session", "mysql.sys"}) { + return []cloudprovider.ICloudDBInstanceAccountPrivilege{}, nil + } + priv, err := self.rds.region.DescribeAccountPrivileges(self.rds.InstanceId, self.User, self.Host) + if err != nil { + return nil, errors.Wrapf(err, "DescribeAccountPrivileges") + } + ret := []cloudprovider.ICloudDBInstanceAccountPrivilege{} + rwSet := set.New(set.ThreadSafe) + for _, p := range api.QCLOUD_RW_PRIVILEGE_SET { + rwSet.Add(p) + } + rSet := set.New(set.ThreadSafe) + for _, p := range api.QCLOUD_R_PRIVILEGE_SET { + rSet.Add(p) + } + for _, p := range priv.DatabasePrivileges { + pSet := set.New(set.ThreadSafe) + for _, v := range p.Privileges { + pSet.Add(v) + } + priv := strings.Join(p.Privileges, ",") + if pSet.IsEqual(rSet) { + priv = api.DATABASE_PRIVILEGE_R + } else if pSet.IsEqual(rwSet) { + priv = api.DATABASE_PRIVILEGE_RW + } + privilege := &sPrivilege{ + Database: p.Database, + User: self.User, + Host: self.Host, + Privilege: priv, + } + ret = append(ret, privilege) + } + return ret, nil +} + +func (self *SRegion) ModifyMySQLAccountPassword(instanceId string, password string, users map[string]string) error { + params := map[string]string{ + "InstanceId": instanceId, + "NewPassword": password, + } + idx := 0 + for user, host := range users { + params[fmt.Sprintf("Accounts.%d.user", idx)] = user + params[fmt.Sprintf("Accounts.%d.host", idx)] = host + idx++ + } + resp, err := self.cdbRequest("ModifyAccountPassword", params) + if err != nil { + return errors.Wrapf(err, "ModifyAccountPassword") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("ModifyAccountPassword", instanceId, asyncRequestId) +} + +func (self *SRegion) DeleteMySQLAccounts(instanceId string, users map[string]string) error { + params := map[string]string{ + "InstanceId": instanceId, + } + idx := 0 + for user, host := range users { + params[fmt.Sprintf("Accounts.%d.user", idx)] = user + params[fmt.Sprintf("Accounts.%d.host", idx)] = host + idx++ + } + resp, err := self.cdbRequest("DeleteAccounts", params) + if err != nil { + return errors.Wrapf(err, "DeleteAccounts") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("DeleteAccounts", instanceId, asyncRequestId) +} + +func (self *SRegion) DescribeMySQLAccounts(instanceId string, offset, limit int) ([]SMySQLInstanceAccount, int, error) { + if limit < 1 || limit > 100 { + limit = 100 + } + params := map[string]string{ + "InstanceId": instanceId, + "Offset": fmt.Sprintf("%d", offset), + "Limit": fmt.Sprintf("%d", limit), + } + resp, err := self.cdbRequest("DescribeAccounts", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeAccounts") + } + ret := []SMySQLInstanceAccount{} + err = resp.Unmarshal(&ret, "Items") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Float("TotalCount") + return ret, int(totalCount), nil +} + +func (self *SMySQLInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) { + ret := []cloudprovider.ICloudDBInstanceAccount{} + for { + part, total, err := self.region.DescribeMySQLAccounts(self.InstanceId, len(ret), 100) + if err != nil { + return nil, errors.Wrapf(err, "DescribeMySQLAccounts") + } + for i := range part { + part[i].rds = self + ret = append(ret, &part[i]) + } + if len(ret) >= total { + break + } + } + return ret, nil +} + +func (self *SRegion) CreateMySQLAccount(instanceId string, opts *cloudprovider.SDBInstanceAccountCreateConfig) error { + params := map[string]string{ + "InstanceId": instanceId, + "Password": opts.Password, + "Accounts.0.User": opts.Name, + "Accounts.0.Host": opts.Host, + "Description": opts.Description, + } + resp, err := self.cdbRequest("CreateAccounts", params) + if err != nil { + return errors.Wrapf(err, "CreateAccounts") + } + asyncRequestId, _ := resp.GetString("AsyncRequestId") + return self.waitAsyncAction("CreateAccounts", instanceId, asyncRequestId) +} + +type SDatabasePrivilege struct { + Privileges []string + Database string +} + +type STablePrivilege struct { + Database string + Table string + Privileges []string +} + +type SColumnPrivilege struct { + Database string + Table string + Column string + Privileges []string +} + +type SAccountPrivilege struct { + GlobalPrivileges []string + DatabasePrivileges []SDatabasePrivilege + TablePrivileges []STablePrivilege + ColumnPrivileges []SColumnPrivilege +} + +func (self *SRegion) DescribeAccountPrivileges(instanceId string, user, host string) (*SAccountPrivilege, error) { + params := map[string]string{ + "InstanceId": instanceId, + "User": user, + "Host": host, + } + resp, err := self.cdbRequest("DescribeAccountPrivileges", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeAccountPrivileges") + } + priv := &SAccountPrivilege{} + err = resp.Unmarshal(priv) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return priv, nil +} diff --git a/pkg/multicloud/qcloud/rds_mysql_backup.go b/pkg/multicloud/qcloud/rds_mysql_backup.go new file mode 100644 index 0000000000..26ed9bb277 --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mysql_backup.go @@ -0,0 +1,240 @@ +// 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 qcloud + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/timeutils" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMySQLInstanceBackup struct { + multicloud.SDBInstanceBackupBase + rds *SMySQLInstance + + Name string + Size int + Date string + IntranetUrl string + InternetUrl string + Type string + BackupId int + Status string + FinishTime string + Creator string + StartTime string + Method string + Way string +} + +func (self *SMySQLInstanceBackup) GetId() string { + return fmt.Sprintf("%d", self.BackupId) +} + +func (self *SMySQLInstanceBackup) GetGlobalId() string { + return self.GetId() +} + +func (self *SMySQLInstanceBackup) GetName() string { + if len(self.Name) > 0 { + return self.Name + } + return self.GetId() +} + +func (self *SMySQLInstanceBackup) GetEngine() string { + return api.DBINSTANCE_TYPE_MYSQL +} + +func (self *SMySQLInstanceBackup) GetStatus() string { + switch self.Status { + case "SUCCESS": + return api.DBINSTANCE_BACKUP_READY + case "FAILED": + return api.DBINSTANCE_BACKUP_CREATE_FAILED + case "RUNNING": + return api.DBINSTANCE_BACKUP_CREATING + default: + return api.DBINSTANCE_BACKUP_UNKNOWN + } +} + +func (self *SMySQLInstanceBackup) GetEngineVersion() string { + return self.rds.EngineVersion +} + +func (self *SMySQLInstanceBackup) GetDBInstanceId() string { + return self.rds.InstanceId +} + +func (self *SMySQLInstanceBackup) GetStartTime() time.Time { + start, err := timeutils.ParseTimeStr(self.StartTime) + if err != nil { + return time.Time{} + } + return start.Add(time.Hour * -8) +} + +func (self *SMySQLInstanceBackup) GetEndTime() time.Time { + end, err := timeutils.ParseTimeStr(self.FinishTime) + if err != nil { + return time.Time{} + } + return end.Add(time.Hour * -8) +} + +func (self *SMySQLInstanceBackup) GetBackupSizeMb() int { + return self.Size / 1024 / 1024 +} + +func (self *SMySQLInstanceBackup) GetDBNames() string { + return "" +} + +func (self *SMySQLInstanceBackup) GetBackupMode() string { + if self.Way == "manual" { + return api.BACKUP_MODE_MANUAL + } + return api.BACKUP_MODE_AUTOMATED +} + +func (self *SMySQLInstanceBackup) Delete() error { + return self.rds.region.DeleteBackup(self.rds.InstanceId, fmt.Sprintf("%d", self.BackupId)) +} + +func (self *SMySQLInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + backups := []cloudprovider.ICloudDBInstanceBackup{} + for { + part, total, err := self.region.DescribeMySQLBackups(self.InstanceId, len(backups), 100) + if err != nil { + return nil, errors.Wrapf(err, "DescribeMySQLBackups") + } + for i := range part { + part[i].rds = self + backups = append(backups, &part[i]) + } + if len(backups) >= total { + break + } + } + return backups, nil +} + +func (self *SRegion) DescribeMySQLBackups(instanceId string, offset, limit int) ([]SMySQLInstanceBackup, int, error) { + if limit < 1 || limit > 100 { + limit = 100 + } + params := map[string]string{ + "Offset": fmt.Sprintf("%d", offset), + "Limit": fmt.Sprintf("%d", limit), + } + if len(instanceId) > 0 { + params["InstanceId"] = instanceId + } + resp, err := self.cdbRequest("DescribeBackups", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeBackups") + } + backups := []SMySQLInstanceBackup{} + err = resp.Unmarshal(&backups, "Items") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Float("TotalCount") + return backups, int(totalCount), nil +} + +func (self *SRegion) DeleteBackup(instanceId, id string) error { + params := map[string]string{ + "InstanceId": instanceId, + "BackupId": id, + } + _, err := self.cdbRequest("DeleteBackup", params) + if err != nil { + return errors.Wrapf(err, "DeleteBackup") + } + return nil +} + +func (self *SRegion) GetMySQLInstanceBackup(instanceId, backupId string) (*SMySQLInstanceBackup, error) { + backups := []SMySQLInstanceBackup{} + for { + part, total, err := self.DescribeMySQLBackups(instanceId, len(backups), 100) + if err != nil { + return nil, errors.Wrapf(err, "DescribeMySQLBackups") + } + for i := range part { + if fmt.Sprintf("%d", part[i].BackupId) == backupId { + return &part[i], nil + } + } + backups = append(backups, part...) + if len(backups) >= total { + break + } + } + return nil, fmt.Errorf("failed to found rds %s backup %s", instanceId, backupId) +} + +func (self *SRegion) waitMySQLBackupReady(instanceId, backupId string) error { + return cloudprovider.Wait(time.Second*20, time.Minute*30, func() (bool, error) { + backup, err := self.GetMySQLInstanceBackup(instanceId, backupId) + if err != nil { + return false, errors.Wrapf(err, "GetMySQLInstanceBackup") + } + log.Infof("backup %s for instance %s status %s", backup.GetName(), instanceId, backup.Status) + if utils.IsInStringArray(backup.Status, []string{"FAILED", "SUCCESS"}) { + return true, nil + } + return false, nil + }) +} + +func (self *SRegion) CreateMySQLBackup(instanceId string, tables map[string]string) (string, error) { + params := map[string]string{ + "InstanceId": instanceId, + "BackupMethod": "physical", + } + if len(tables) > 0 { + params["BackupMethod"] = "logical" + idx := 0 + for db, table := range tables { + params[fmt.Sprintf("BackupDBTableList.%d.Db", idx)] = db + if len(table) > 0 { + params[fmt.Sprintf("BackupDBTableList.%d.Table", idx)] = table + } + idx++ + } + } + resp, err := self.cdbRequest("CreateBackup", params) + if err != nil { + return "", errors.Wrapf(err, "CreateBackup") + } + _backupId, _ := resp.Float("BackupId") + backupId := fmt.Sprintf("%d", int(_backupId)) + err = self.waitMySQLBackupReady(instanceId, backupId) + if err != nil { + return "", errors.Wrapf(err, "waitBackupReady") + } + return backupId, nil +} diff --git a/pkg/multicloud/qcloud/rds_mysql_database.go b/pkg/multicloud/qcloud/rds_mysql_database.go new file mode 100644 index 0000000000..f71505bb9b --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mysql_database.go @@ -0,0 +1,97 @@ +// 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 qcloud + +import ( + "fmt" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMySQLInstanceDatabase struct { + rds *SMySQLInstance + multicloud.SResourceBase + + CharacterSet string + DatabaseName string +} + +func (self *SMySQLInstanceDatabase) GetStatus() string { + return api.DBINSTANCE_DATABASE_RUNNING +} + +func (self *SMySQLInstanceDatabase) GetId() string { + return self.DatabaseName +} + +func (self *SMySQLInstanceDatabase) GetName() string { + return self.DatabaseName +} + +func (self *SMySQLInstanceDatabase) GetGlobalId() string { + return self.DatabaseName +} + +func (self *SMySQLInstanceDatabase) GetCharacterSet() string { + return self.CharacterSet +} + +func (self *SMySQLInstanceDatabase) Delete() error { + return cloudprovider.ErrNotSupported +} + +func (self *SRegion) DescribeMySQLDatabases(instanceId string, offset, limit int) ([]SMySQLInstanceDatabase, int, error) { + if limit < 1 || limit > 100 { + limit = 100 + } + params := map[string]string{ + "Offset": fmt.Sprintf("%d", offset), + "Limit": fmt.Sprintf("%d", limit), + "InstanceId": instanceId, + } + resp, err := self.cdbRequest("DescribeDatabases", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeDatabases") + } + databases := []SMySQLInstanceDatabase{} + err = resp.Unmarshal(&databases, "DatabaseList") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Float("TotalCount") + return databases, int(totalCount), nil +} + +func (rds *SMySQLInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { + ret := []cloudprovider.ICloudDBInstanceDatabase{} + for { + part, total, err := rds.region.DescribeMySQLDatabases(rds.InstanceId, len(ret), 100) + if err != nil { + return nil, errors.Wrapf(err, "DescribeMySQLDatabases") + } + for i := range part { + part[i].rds = rds + ret = append(ret, &part[i]) + } + if len(ret) >= total { + break + } + } + return ret, nil +} diff --git a/pkg/multicloud/qcloud/rds_mysql_sku.go b/pkg/multicloud/qcloud/rds_mysql_sku.go new file mode 100644 index 0000000000..d5ebef2ef7 --- /dev/null +++ b/pkg/multicloud/qcloud/rds_mysql_sku.go @@ -0,0 +1,183 @@ +// 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 qcloud + +import ( + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SellTypeConfig struct { + Device string + Type string + CdbType string + Memory int + Cpu int + VolumeMin int + VolumeMax int + VolumeStep int + Connection int + Qps int + Iops int + Info string + Status string + Tag string +} + +type SSellType struct { + TypeName string + EngineVersion []string + Configs []SellTypeConfig +} + +type SZoneConf struct { + DeployMode []int + MasterZone []string + SlaveZone []string + BackupZone []string +} + +type SZoneSellConf struct { + Status int + ZoneName string + IsCustom bool + IsSupportDr bool + IsSupportVpc bool + HourInstanceSaleMaxNum int + IsDefaultZone bool + IsBm bool + PayType []string + ProtectMode string + Zone string + SellType []SSellType + ZoneConf SZoneConf + DrZone []string + IsSupportRemoteRo bool +} + +type SRegionSellConf struct { + RegionName string + Area string + IsDefaultRegion bool + Region string + ZonesConf []SZoneSellConf +} + +func (self *SRegion) DescribeDBZoneConfig() ([]SRegionSellConf, error) { + resp, err := self.cdbRequest("DescribeDBZoneConfig", map[string]string{}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBZoneConfig") + } + skus := []SRegionSellConf{} + err = resp.Unmarshal(&skus, "Items") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return skus, nil +} + +type SDBInstanceSku struct { + Region string + Engine string + EngineVersion string + Category string + Iops int + Qps int + MaxConnection int + Cpu int + MemoryMb int + StorageType string + StorageMin int + StorageMax int + StorageStep int + Status string + Description string + Zone1 string + Zone2 string + Zone3 string +} + +func (self *SRegion) ListMysqlSkus() ([]SDBInstanceSku, error) { + conf, err := self.DescribeDBZoneConfig() + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBZoneConfig") + } + skus := []SDBInstanceSku{} + for _, item := range conf { + if item.Region != self.Region { + continue + } + sku := SDBInstanceSku{ + Region: self.Region, + Engine: api.DBINSTANCE_TYPE_MYSQL, + Status: api.DBINSTANCE_SKU_SOLDOUT, + } + for _, zone := range item.ZonesConf { + //0-未上线;1-上线;2-开放;3-停售;4-不展示 + if zone.Status == 0 || zone.Status == 4 { + continue + } + if zone.Status == 1 || zone.Status == 2 { + sku.Status = api.DBINSTANCE_SKU_AVAILABLE + } + if zone.IsBm { // 跳过黑石 + continue + } + for _, sellType := range zone.SellType { + for _, sellConf := range sellType.Configs { + sku.Cpu = sellConf.Cpu + sku.MemoryMb = sellConf.Memory + sku.Iops = sellConf.Iops + sku.MaxConnection = sellConf.Connection + sku.Qps = sellConf.Qps + sku.StorageMin = sellConf.VolumeMin + sku.StorageMax = sellConf.VolumeMax + sku.StorageStep = sellConf.VolumeStep + sku.Zone1 = zone.Zone + for _, engineVersion := range sellType.EngineVersion { + sku.EngineVersion = engineVersion + sku.Category = sellConf.Type + sku.Zone2, sku.Zone3 = "", "" + sku.StorageType = api.STORAGE_LOCAL_SSD + switch sellConf.Type { + case "高可用版": + for _, zone2 := range zone.ZoneConf.SlaveZone { + sku.Zone2 = zone2 + skus = append(skus, sku) + } + if utils.IsInStringArray(engineVersion, []string{"5.6", "5.7", "8.0"}) { + sku.Category = "金融版" + for _, zone2 := range zone.ZoneConf.SlaveZone { + sku.Zone2 = zone2 + sku.Zone3 = zone2 + skus = append(skus, sku) + } + } + case "基础版": + sku.StorageType = api.STORAGE_CLOUD_SSD + skus = append(skus, sku) + default: + log.Errorf("unknow %s", sellConf.Type) + } + } + } + } + } + } + return skus, nil +} diff --git a/pkg/multicloud/qcloud/rds_postgre_sku.go b/pkg/multicloud/qcloud/rds_postgre_sku.go new file mode 100644 index 0000000000..02dcb4454f --- /dev/null +++ b/pkg/multicloud/qcloud/rds_postgre_sku.go @@ -0,0 +1,83 @@ +// 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 qcloud + +import ( + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SpecItemInfoList struct { + SpecCode string + Version string + VersionName string + Cpu int + Memory int + MaxStorage int + MinStorage int + Qps int + Pid int + Type string +} + +type SpecInfoList struct { + Region string + Zone string + SpecItemInfoList []SpecItemInfoList +} + +func (self *SRegion) DescribeProductConfig() ([]SpecInfoList, error) { + resp, err := self.postgresRequest("DescribeProductConfig", map[string]string{}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeProductConfig") + } + products := []SpecInfoList{} + err = resp.Unmarshal(&products, "SpecInfoList") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return products, nil +} + +func (self *SRegion) ListPostgreSQLSkus() ([]SDBInstanceSku, error) { + skus := []SDBInstanceSku{} + products, err := self.DescribeProductConfig() + if err != nil { + return nil, errors.Wrapf(err, "DescribeProductConfig") + } + for _, product := range products { + sku := SDBInstanceSku{ + Region: self.Region, + Zone1: product.Zone, + Engine: api.DBINSTANCE_TYPE_POSTGRESQL, + Status: api.DBINSTANCE_SKU_AVAILABLE, + Category: "双机高可用", + } + + for _, spec := range product.SpecItemInfoList { + sku.EngineVersion = spec.Version + sku.Qps = spec.Qps + sku.Cpu = spec.Cpu + sku.MemoryMb = spec.Memory * 1024 + sku.StorageMax = spec.MaxStorage + sku.StorageMin = spec.MinStorage + sku.StorageStep = 10 + + skus = append(skus, sku) + } + } + return skus, nil +} diff --git a/pkg/multicloud/qcloud/rds_sqlserver_sku.go b/pkg/multicloud/qcloud/rds_sqlserver_sku.go new file mode 100644 index 0000000000..a3392704a2 --- /dev/null +++ b/pkg/multicloud/qcloud/rds_sqlserver_sku.go @@ -0,0 +1,76 @@ +// 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 qcloud + +import ( + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SqlServerSpecInfoList struct { + Cpu int + MachineType string + MachineTypeName string + MaxStorage int + Memory int + MinStorage int + PayModeStatus string + Pid int + PostPid []int + Qps int + RoPid int + SpecId int + SuitInfo string + Version string + VersionName string +} + +func (self *SRegion) DescribeSqlServerProductConfig(zoneId string) ([]SqlServerSpecInfoList, error) { + resp, err := self.sqlserverRequest("DescribeProductConfig", map[string]string{"Zone": zoneId}) + if err != nil { + return nil, errors.Wrapf(err, "DescribeProductConfig") + } + specs := []SqlServerSpecInfoList{} + err = resp.Unmarshal(&specs, "SpecInfoList") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return specs, nil +} + +func (self *SRegion) ListSQLServerSkus() ([]SDBInstanceSku, error) { + zones, err := self.GetIZones() + if err != nil { + return nil, errors.Wrapf(err, "GetIZones") + } + skus := []SDBInstanceSku{} + for _, zone := range zones { + products, err := self.DescribeSqlServerProductConfig(zone.GetId()) + if err != nil { + return nil, errors.Wrapf(err, "DescribeSqlServerProductConfig") + } + for _, product := range products { + sku := SDBInstanceSku{ + Region: self.Region, + Zone1: zone.GetId(), + Engine: api.DBINSTANCE_TYPE_SQLSERVER, + EngineVersion: product.Version, + } + skus = append(skus, sku) + } + } + return skus, nil +} diff --git a/pkg/multicloud/qcloud/region.go b/pkg/multicloud/qcloud/region.go index 83009a0627..83b2f19633 100644 --- a/pkg/multicloud/qcloud/region.go +++ b/pkg/multicloud/qcloud/region.go @@ -674,6 +674,26 @@ func (self *SRegion) lbRequest(apiName string, params map[string]string) (jsonut return self.client.lbRequest(apiName, params) } +func (self *SRegion) cdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.cdbRequest(apiName, params) +} + +func (self *SRegion) mariadbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.mariadbRequest(apiName, params) +} + +func (self *SRegion) postgresRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.postgresRequest(apiName, params) +} + +func (self *SRegion) sqlserverRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.sqlserverRequest(apiName, params) +} + func (self *SRegion) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { return self.client.wssRequest(apiName, params) } diff --git a/pkg/multicloud/qcloud/shell/rds_mariadb.go b/pkg/multicloud/qcloud/shell/rds_mariadb.go new file mode 100644 index 0000000000..4738a670f2 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/rds_mariadb.go @@ -0,0 +1,60 @@ +// 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 shell + +import ( + "github.com/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MariadbRegionListOptions struct { + } + shellutils.R(&MariadbRegionListOptions{}, "mariadb-region-list", "List mariadb region list", func(cli *qcloud.SRegion, args *MariadbRegionListOptions) error { + regions, err := cli.DescribeSaleInfo() + if err != nil { + return errors.Wrapf(err, "DescribeSaleInfo") + } + printList(regions, 0, 0, 0, nil) + return nil + }) + + type MariadbSpecListOptions struct { + } + + shellutils.R(&MariadbSpecListOptions{}, "mariadb-spec-list", "List mariadb specs", func(cli *qcloud.SRegion, args *MariadbSpecListOptions) error { + specs, err := cli.DescribeDBInstanceSpecs() + if err != nil { + return errors.Wrapf(err, "DescribeDBInstanceSpecs") + } + printList(specs, 0, 0, 0, nil) + return nil + }) + + type MariadbSkuListOptions struct { + } + + shellutils.R(&MariadbSkuListOptions{}, "mariadb-sku-list", "List mariadb skus", func(cli *qcloud.SRegion, args *MariadbSkuListOptions) error { + skus, err := cli.ListMariadbSkus() + if err != nil { + return errors.Wrapf(err, "ListMariadbSkus") + } + printList(skus, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/qcloud/shell/rds_mysql.go b/pkg/multicloud/qcloud/shell/rds_mysql.go new file mode 100644 index 0000000000..16c4a9ef1a --- /dev/null +++ b/pkg/multicloud/qcloud/shell/rds_mysql.go @@ -0,0 +1,171 @@ +// 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 shell + +import ( + "fmt" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MysqlSkuListOptions struct { + } + shellutils.R(&MysqlSkuListOptions{}, "mysql-sku-list", "List mysql instance types", func(cli *qcloud.SRegion, args *MysqlSkuListOptions) error { + skus, err := cli.ListMysqlSkus() + if err != nil { + return errors.Wrapf(err, "ListMysqlSkus") + } + printList(skus, 0, 0, 0, nil) + return nil + }) + + type MysqlInstanceListOptions struct { + Ids []string + Offset int + Limit int + } + + shellutils.R(&MysqlInstanceListOptions{}, "mysql-instance-list", "List mysql instance", func(cli *qcloud.SRegion, args *MysqlInstanceListOptions) error { + result, _, err := cli.ListMySQLInstances(args.Ids, args.Offset, args.Limit) + if err != nil { + return errors.Wrapf(err, "ListMySQLInstances") + } + printList(result, 0, 0, 0, nil) + return nil + }) + + type MySQLInstanceIdOptions struct { + ID string + } + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-reboot", "Reboot mysql instance", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + return cli.RebootMySQLInstance(args.ID) + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-isolate", "Isolate mysql instance", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + return cli.IsolateMySQLDBInstance(args.ID) + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-offline-isolate", "Offline Isolate mysql instance", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + return cli.OfflineIsolatedMySQLInstances([]string{args.ID}) + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-release-isolate", "Release Isolate mysql instance", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + return cli.ReleaseIsolatedMySQLDBInstances([]string{args.ID}) + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-secgroup-list", "List mysql instance secgroups", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + secgroups, err := cli.DescribeMySQLDBSecurityGroups(args.ID) + if err != nil { + return err + } + printList(secgroups, 0, 0, 0, nil) + return nil + }) + + type MySQLInstanceDBListOptions struct { + MySQLInstanceIdOptions + Offset int + Limit int + } + + shellutils.R(&MySQLInstanceDBListOptions{}, "mysql-instance-database-list", "List mysql instance database", func(cli *qcloud.SRegion, args *MySQLInstanceDBListOptions) error { + databases, totalCount, err := cli.DescribeMySQLDatabases(args.ID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(databases, 0, 0, 0, nil) + fmt.Println("TotalCount: ", totalCount) + return nil + }) + + shellutils.R(&MySQLInstanceDBListOptions{}, "mysql-instance-account-list", "List mysql instance accounts", func(cli *qcloud.SRegion, args *MySQLInstanceDBListOptions) error { + accounts, totalCount, err := cli.DescribeMySQLAccounts(args.ID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, nil) + fmt.Println("TotalCount: ", totalCount) + return nil + }) + + shellutils.R(&MySQLInstanceDBListOptions{}, "mysql-instance-backup-list", "List mysql instance backups", func(cli *qcloud.SRegion, args *MySQLInstanceDBListOptions) error { + backups, totalCount, err := cli.DescribeMySQLBackups(args.ID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(backups, 0, 0, 0, nil) + fmt.Println("TotalCount: ", totalCount) + return nil + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-show", "Show mysql instance", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + result, err := cli.DescribeMySQLDBInstanceInfo(args.ID) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type MySQLRenewOptions struct { + MySQLInstanceIdOptions + MONTH int `choices:"1|2|3|4|5|6|7|8|9|10|11|12|24|36"` + } + + shellutils.R(&MySQLRenewOptions{}, "mysql-instance-renew", "Renew mysql instance", func(cli *qcloud.SRegion, args *MySQLRenewOptions) error { + return cli.RenewMySQLDBInstance(args.ID, args.MONTH) + }) + + shellutils.R(&MySQLInstanceIdOptions{}, "mysql-instance-backup-create", "Create mysql instance backup", func(cli *qcloud.SRegion, args *MySQLInstanceIdOptions) error { + backup, err := cli.CreateMySQLBackup(args.ID, nil) + if err != nil { + return err + } + printObject(backup) + return nil + }) + + type RestAccountPasswordOptions struct { + INSTANCE_ID string + PASSWORD string + USER string + Host string `default:"%"` + } + + shellutils.R(&RestAccountPasswordOptions{}, "mysql-account-reset-password", "Reset mysql account password", func(cli *qcloud.SRegion, args *RestAccountPasswordOptions) error { + return cli.ModifyMySQLAccountPassword(args.INSTANCE_ID, args.PASSWORD, map[string]string{args.USER: args.Host}) + }) + + type MySQLAccountPrivilegeShowOptions struct { + INSTANCE_ID string + USER string + Host string `default:"%"` + } + + shellutils.R(&MySQLAccountPrivilegeShowOptions{}, "mysql-account-privilege-show", "Show account privileges", func(cli *qcloud.SRegion, args *MySQLAccountPrivilegeShowOptions) error { + result, err := cli.DescribeAccountPrivileges(args.INSTANCE_ID, args.USER, args.Host) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/pkg/multicloud/qcloud/shell/rds_postgre.go b/pkg/multicloud/qcloud/shell/rds_postgre.go new file mode 100644 index 0000000000..66e2c58cf7 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/rds_postgre.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 shell + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type PostgreSQLProductListOptions struct { + } + shellutils.R(&PostgreSQLProductListOptions{}, "postgre-product-list", "List postgre sql products", func(cli *qcloud.SRegion, args *PostgreSQLProductListOptions) error { + products, err := cli.DescribeProductConfig() + if err != nil { + return errors.Wrapf(err, "DescribeProductConfig") + } + printList(products, 0, 0, 0, nil) + return nil + }) + + type SPostgreSQLSkuListOptions struct { + } + + shellutils.R(&SPostgreSQLSkuListOptions{}, "postgre-sku-list", "List postgre sql skus", func(cli *qcloud.SRegion, args *SPostgreSQLSkuListOptions) error { + skus, err := cli.ListPostgreSQLSkus() + if err != nil { + return errors.Wrapf(err, "ListPostgreSQLSkus") + } + printList(skus, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/qcloud/shell/rds_sqlserver.go b/pkg/multicloud/qcloud/shell/rds_sqlserver.go new file mode 100644 index 0000000000..e9d854c331 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/rds_sqlserver.go @@ -0,0 +1,49 @@ +// 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 shell + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SQLServerSQLProductListOptions struct { + ZONE string + } + shellutils.R(&SQLServerSQLProductListOptions{}, "sqlserver-product-list", "List sql server products", func(cli *qcloud.SRegion, args *SQLServerSQLProductListOptions) error { + products, err := cli.DescribeSqlServerProductConfig(args.ZONE) + if err != nil { + return errors.Wrapf(err, "DescribeProductConfig") + } + printList(products, 0, 0, 0, nil) + return nil + }) + + type SSQLServerSQLSkuListOptions struct { + } + + shellutils.R(&SSQLServerSQLSkuListOptions{}, "sqlserver-sku-list", "List sqlserver skus", func(cli *qcloud.SRegion, args *SSQLServerSQLSkuListOptions) error { + skus, err := cli.ListSQLServerSkus() + if err != nil { + return errors.Wrapf(err, "ListSQLServerSkus") + } + printList(skus, 0, 0, 0, nil) + return nil + }) + +}