From 3b0a437a12198b442167862086bb6063fa078d73 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Mon, 2 Mar 2020 18:10:41 +0800 Subject: [PATCH] feature: gcp rds sync --- cmd/climc/shell/dbinstances.go | 9 + pkg/apis/compute/dbinstance.go | 21 +- pkg/apis/compute/dbinstance_const.go | 2 + pkg/compute/models/dbinstancenetworks.go | 3 + pkg/compute/models/dbinstances.go | 37 ++ pkg/compute/models/regiondrivers.go | 1 + pkg/compute/regiondrivers/base.go | 4 + pkg/compute/regiondrivers/google.go | 88 +++ pkg/compute/regiondrivers/huawei.go | 12 +- pkg/compute/regiondrivers/managedvirtual.go | 20 +- pkg/compute/tasks/dbinstance_delete_task.go | 2 +- pkg/compute/tasks/dbinstance_sync_task.go | 62 ++ pkg/multicloud/google/dbinstance.go | 622 ++++++++++++++++++ pkg/multicloud/google/dbinstance_account.go | 139 ++++ pkg/multicloud/google/dbinstance_backup.go | 172 +++++ pkg/multicloud/google/dbinstance_database.go | 106 +++ pkg/multicloud/google/dbinstance_parameter.go | 40 ++ pkg/multicloud/google/google.go | 109 ++- pkg/multicloud/google/operation.go | 26 + pkg/multicloud/google/region.go | 149 +++++ pkg/multicloud/google/shell/dbinstance.go | 101 +++ .../google/shell/dbinstance_account.go | 44 ++ .../google/shell/dbinstance_backup.go | 44 ++ .../google/shell/dbinstance_database.go | 45 ++ pkg/util/seclib2/aes_test.go | 4 +- 25 files changed, 1840 insertions(+), 22 deletions(-) create mode 100644 pkg/compute/tasks/dbinstance_sync_task.go create mode 100644 pkg/multicloud/google/dbinstance.go create mode 100644 pkg/multicloud/google/dbinstance_account.go create mode 100644 pkg/multicloud/google/dbinstance_backup.go create mode 100644 pkg/multicloud/google/dbinstance_database.go create mode 100644 pkg/multicloud/google/dbinstance_parameter.go create mode 100644 pkg/multicloud/google/shell/dbinstance.go create mode 100644 pkg/multicloud/google/shell/dbinstance_account.go create mode 100644 pkg/multicloud/google/shell/dbinstance_backup.go create mode 100644 pkg/multicloud/google/shell/dbinstance_database.go diff --git a/cmd/climc/shell/dbinstances.go b/cmd/climc/shell/dbinstances.go index 187fdc4c33..104591de9a 100644 --- a/cmd/climc/shell/dbinstances.go +++ b/cmd/climc/shell/dbinstances.go @@ -267,6 +267,15 @@ func init() { return nil }) + R(&DBInstanceIdOptions{}, "dbinstance-sync", "Sync conf from cloud dbinstance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + result, err := modules.DBInstance.PerformAction(s, opts.ID, "sync", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + R(&DBInstanceIdOptions{}, "dbinstance-sync-status", "Sync status for DB instance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { result, err := modules.DBInstance.PerformAction(s, opts.ID, "sync-status", nil) if err != nil { diff --git a/pkg/apis/compute/dbinstance.go b/pkg/apis/compute/dbinstance.go index 648e807b62..5f74cc8e03 100644 --- a/pkg/apis/compute/dbinstance.go +++ b/pkg/apis/compute/dbinstance.go @@ -21,6 +21,7 @@ type DBInstanceCreateInput struct { DeletePreventableCreateInput // Ip子网名称或Id,建议使用Id + // 谷歌云并不实际使用Ip子网,仅仅通过Ip子网确定Vpc // required: true Network string `json:"network"` // swagger:ignore @@ -96,10 +97,11 @@ type DBInstanceCreateInput struct { // // // - // | 平台 | 支持类型 | - // | ----- | ------ | - // | 华为云 |ha, single, replica| - // | 阿里云 |basic, high_availability, always_on, finance| + // | 平台 | 支持类型 | 说明 | + // | ----- | ------ | --- | + // | 华为云 |ha, single, replica| | + // | 阿里云 |basic, high_availability, always_on, finance|| + // | Google |SECOND_GEN | FIRST_GEN 目前谷歌已弃用| // 翻译: // basic: 基础版 // high_availability: 高可用 @@ -108,6 +110,7 @@ type DBInstanceCreateInput struct { // ha: 高可用 // single: 单机 // replica: 只读 + // SECNOD_GEN: 第二代 // required: true Category string `json:"category"` @@ -118,6 +121,10 @@ type DBInstanceCreateInput struct { // | 平台 | 支持类型 | // | 华为云 |SSD, SAS, SATA| // | 阿里云 |local_ssd, cloud_essd, cloud_ssd| + // | Google |PD_SSD, PD_HDD| + // 翻译: + // PD_SSD: SSD + // PD_HDD: HDD // required: true StorageType string `json:"storage_type"` @@ -129,8 +136,14 @@ type DBInstanceCreateInput struct { // rds初始化密码 // 阿里云不需要此参数 // 华为云会默认创建一个用户,若不传此参数, 则为随机密码 + // 谷歌云会默认创建一个用户,若不传此参数, 则为随机密码 Password string `json:"password"` + // 是否不设置初始密码 + // 华为云不支持此参数 + // 谷歌云仅mysql支持此参数 + ResetPassword *bool `json:"reset_password"` + // rds实例cpu大小 // 若指定实例套餐,此参数将根据套餐设置 VcpuCount int `json:"vcpu_count"` diff --git a/pkg/apis/compute/dbinstance_const.go b/pkg/apis/compute/dbinstance_const.go index 47b9afa918..de8382137d 100644 --- a/pkg/apis/compute/dbinstance_const.go +++ b/pkg/apis/compute/dbinstance_const.go @@ -27,6 +27,7 @@ const ( DBINSTANCE_CLONING = "cloning" //克隆中 DBINSTANCE_DELETING = "deleting" //删除中 DBINSTANCE_DELETE_FAILED = "delete_failed" //删除失败 + DBINSTANCE_MAINTENANCE = "maintenance" //维护中 DBINSTANCE_UNKNOWN = "unknown" DBINSTANCE_CHANGE_CONFIG = "change_config" //调整配置 @@ -36,6 +37,7 @@ const ( DBINSTANCE_RENEW_FAILED = "renew_failed" //续费失败 DBINSTANCE_SYNC_STATUS = "sync_status" //同步状态 + DBINSTANCE_SYNC_CONFIG = "sync_config" //同步配置 DBINSTANCE_REBOOT_FAILED = "reboot_failed" //重启失败 DBINSTANCE_CREATE_FAILED = "create_failed" //创建失败 diff --git a/pkg/compute/models/dbinstancenetworks.go b/pkg/compute/models/dbinstancenetworks.go index f930539a3a..649c4b8436 100644 --- a/pkg/compute/models/dbinstancenetworks.go +++ b/pkg/compute/models/dbinstancenetworks.go @@ -132,6 +132,9 @@ func (m *SDBInstanceNetworkManager) NewDBInstanceNetwork(ctx context.Context, us func (manager *SDBInstanceNetworkManager) SyncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, network *cloudprovider.SDBInstanceNetwork) compare.SyncResult { result := compare.SyncResult{} + if network == nil { + return result + } dbNetwork, err := dbinstance.GetDBNetwork() if err != nil && err != sql.ErrNoRows { diff --git a/pkg/compute/models/dbinstances.go b/pkg/compute/models/dbinstances.go index 762afae01f..3522fa5403 100644 --- a/pkg/compute/models/dbinstances.go +++ b/pkg/compute/models/dbinstances.go @@ -303,6 +303,15 @@ func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred return nil, errors.Wrapf(err, "Unmarshal input failed: %v", err) } + if len(input.Password) == 0 { + input.Password = seclib2.RandomPassword2(12) + } + + // reset_password == flase 则置密码为空 + if input.ResetPassword != nil && !*input.ResetPassword { + input.Password = "" + } + if len(input.Password) > 0 { if !seclib2.MeetComplxity(input.Password) { return nil, httperrors.NewWeakPasswordError() @@ -742,6 +751,14 @@ func (self *SDBInstance) PerformSyncstatus(ctx context.Context, userCred mcclien return nil, StartResourceSyncStatusTask(ctx, userCred, self, "DBInstanceSyncStatusTask", "") } +func (self *SDBInstance) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "sync") +} + +func (self *SDBInstance) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, self.StartDBInstanceSyncTask(ctx, userCred, jsonutils.NewDict(), "") +} + func (self *SDBInstance) AllowPerformSyncStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "sync-status") } @@ -939,6 +956,16 @@ func (self *SDBInstance) StartDBInstanceRebootTask(ctx context.Context, userCred return nil } +func (self *SDBInstance) StartDBInstanceSyncTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_SYNC_CONFIG, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceSyncTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (manager *SDBInstanceManager) getDBInstancesByProviderId(providerId string) ([]SDBInstance, error) { instances := []SDBInstance{} err := fetchByManagerId(manager, providerId, &instances) @@ -1395,6 +1422,16 @@ func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred m self.CreatedAt = createdAt } + if len(self.VpcId) == 0 { + if vpcId := extInstance.GetIVpcId(); len(vpcId) > 0 { + vpc, err := db.FetchByExternalId(VpcManager, vpcId) + if err != nil { + return errors.Wrapf(err, "SyncWithCloudDBInstance.FetchVpcId") + } + self.VpcId = vpc.GetId() + } + } + factory, err := provider.GetProviderFactory() if err != nil { return errors.Wrap(err, "SyncWithCloudDBInstance.GetProviderFactory") diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 76e09c8363..fa02f05ce7 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -154,6 +154,7 @@ type IDBInstanceDriver interface { IsSupportKeepDBInstanceManualBackup() bool InitDBInstanceUser(dbinstance *SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error + IsDBInstanceNeedSecgroup() bool } type IElasticcacheDriver interface { diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index 192f33bab9..81165dd243 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -338,3 +338,7 @@ func (self *SBaseRegionDriver) RequestSyncDBInstanceBackupStatus(ctx context.Con func (self *SBaseRegionDriver) RequestSyncElasticcacheStatus(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask) error { return fmt.Errorf("Not Implement RequestSyncElasticcacheStatus") } + +func (self *SBaseRegionDriver) IsDBInstanceNeedSecgroup() bool { + return false +} diff --git a/pkg/compute/regiondrivers/google.go b/pkg/compute/regiondrivers/google.go index f84b54b9b2..da8a7c4838 100644 --- a/pkg/compute/regiondrivers/google.go +++ b/pkg/compute/regiondrivers/google.go @@ -19,13 +19,16 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" + billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -150,5 +153,90 @@ func (self *SGoogleRegionDriver) RequestDeleteVpc(ctx context.Context, userCred return nil, nil }) return nil +} +func (self *SGoogleRegionDriver) IsSupportedDBInstance() bool { + return true +} + +func (self *SGoogleRegionDriver) ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input api.DBInstanceCreateInput, skus []models.SDBInstanceSku, network *models.SNetwork) (api.DBInstanceCreateInput, error) { + if input.BillingType == billing_api.BILLING_TYPE_PREPAID { + return input, httperrors.NewInputParameterError("Google dbinstance not support prepaid billing type") + } + + if input.DiskSizeGB < 10 || input.DiskSizeGB > 30720 { + return input, httperrors.NewInputParameterError("disk size gb must in range 10 ~ 30720 Gb") + } + + if input.Engine != api.DBINSTANCE_TYPE_MYSQL && len(input.Password) == 0 { + return input, httperrors.NewMissingParameterError("password") + } + + return input, nil +} + +func (self *SGoogleRegionDriver) InitDBInstanceUser(instance *models.SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error { + user := "root" + switch desc.Engine { + case api.DBINSTANCE_TYPE_POSTGRESQL: + user = "postgres" + case api.DBINSTANCE_TYPE_SQLSERVER: + user = "sqlserver" + default: + user = "root" + } + + account := models.SDBInstanceAccount{} + 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(&account) + if err != nil { + return err + } + + return account.SetPassword(desc.Password) +} + +func (self *SGoogleRegionDriver) ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceDatabaseCreateInput) (api.DBInstanceDatabaseCreateInput, error) { + return input, nil +} + +func (self *SGoogleRegionDriver) ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceBackupCreateInput) (api.DBInstanceBackupCreateInput, error) { + return input, nil +} + +func (self *SGoogleRegionDriver) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input api.DBInstanceAccountCreateInput) (api.DBInstanceAccountCreateInput, error) { + return input, nil +} + +func (self *SGoogleRegionDriver) RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, backup *models.SDBInstanceBackup, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRds, err := instance.GetIDBInstance() + if err != nil { + return nil, errors.Wrap(err, "instance.GetIDBInstance") + } + + desc := &cloudprovider.SDBInstanceBackupCreateConfig{ + Name: backup.Name, + Description: backup.Description, + } + + _, err = iRds.CreateIBackup(desc) + if err != nil { + return nil, errors.Wrap(err, "iRds.CreateBackup") + } + + backups, err := iRds.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrap(err, "iRds.GetIDBInstanceBackups") + } + + result := models.DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, backup.GetCloudprovider(), instance, backup.GetRegion(), backups) + log.Infof("SyncDBInstanceBackups for dbinstance %s(%s) result: %s", instance.Name, instance.Id, result.Result()) + return nil, nil + }) + return nil } diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go index 4724710071..5d0f7943c7 100644 --- a/pkg/compute/regiondrivers/huawei.go +++ b/pkg/compute/regiondrivers/huawei.go @@ -2141,14 +2141,14 @@ func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceData(ctx context.Contex return input, httperrors.NewInputParameterError("The disk_size_gb must be an integer multiple of 10") } + if len(input.Password) == 0 { + return input, httperrors.NewMissingParameterError("password") + } + return input, nil } func (self *SHuaWeiRegionDriver) InitDBInstanceUser(instance *models.SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error { - if len(desc.Password) == 0 { - desc.Password = seclib2.RandomPassword2(12) - } - user := "root" if desc.Engine == api.DBINSTANCE_TYPE_SQLSERVER { user = "rdsuser" @@ -2586,3 +2586,7 @@ func (self *SHuaWeiRegionDriver) IsSupportedElasticcache() bool { func (self *SHuaWeiRegionDriver) GetBackendStatusForAdd() []string { return []string{api.VM_RUNNING, api.VM_READY} } + +func (self *SHuaWeiRegionDriver) IsDBInstanceNeedSecgroup() bool { + return true +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 95cff41a96..249f50a93e 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -1621,15 +1621,17 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont return nil, err } - secgroup, _ := dbinstance.GetSecgroup() - if secgroup != nil { - vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) - if err != nil { - return nil, errors.Wrap(err, "GetSecurityGroupVpcId") - } - desc.SecgroupId, err = region.GetDriver().RequestSyncSecurityGroup(ctx, userCred, vpcId, vpc, secgroup) - if err != nil { - return nil, errors.Wrap(err, "SyncSecurityGroup") + if region.GetDriver().IsDBInstanceNeedSecgroup() { + secgroup, _ := dbinstance.GetSecgroup() + if secgroup != nil { + vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) + if err != nil { + return nil, errors.Wrap(err, "GetSecurityGroupVpcId") + } + desc.SecgroupId, err = region.GetDriver().RequestSyncSecurityGroup(ctx, userCred, vpcId, vpc, secgroup) + if err != nil { + return nil, errors.Wrap(err, "SyncSecurityGroup") + } } } diff --git a/pkg/compute/tasks/dbinstance_delete_task.go b/pkg/compute/tasks/dbinstance_delete_task.go index 1859f76224..4ba591ea24 100644 --- a/pkg/compute/tasks/dbinstance_delete_task.go +++ b/pkg/compute/tasks/dbinstance_delete_task.go @@ -51,7 +51,7 @@ func (self *DBInstanceDeleteTask) OnInit(ctx context.Context, obj db.IStandalone func (self *DBInstanceDeleteTask) DeleteDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { idbinstance, err := dbinstance.GetIDBInstance() if err != nil { - if err == cloudprovider.ErrNotFound { + if errors.Cause(err) == cloudprovider.ErrNotFound { self.DeleteDBInstanceComplete(ctx, dbinstance) return } diff --git a/pkg/compute/tasks/dbinstance_sync_task.go b/pkg/compute/tasks/dbinstance_sync_task.go new file mode 100644 index 0000000000..3cb7f40998 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_sync_task.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceSyncTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceSyncTask{}) +} + +func (self *DBInstanceSyncTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(dbinstance, db.ACT_SYNC_CONF, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_SYNC_CONF, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceSyncTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.SyncDBInstance(ctx, dbinstance) +} + +func (self *DBInstanceSyncTask) SyncDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { + idbinstance, err := dbinstance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrapf(err, "dbinstance.GetIDBInstance")) + return + } + err = dbinstance.SyncWithCloudDBInstance(ctx, self.UserCred, dbinstance.GetCloudprovider(), idbinstance) + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrapf(err, "dbinstance.GetIDBInstance")) + return + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/multicloud/google/dbinstance.go b/pkg/multicloud/google/dbinstance.go new file mode 100644 index 0000000000..ae4fb224a9 --- /dev/null +++ b/pkg/multicloud/google/dbinstance.go @@ -0,0 +1,622 @@ +// 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 google + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + billing_api "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" +) + +var ( + EngineVersions = map[string]GoogleSQLVersion{ + "MYSQL_5_5": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_MYSQL, Version: "5.5"}, + "MYSQL_5_6": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_MYSQL, Version: "5.6"}, + "MYSQL_5_7": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_MYSQL, Version: "5.7"}, + "POSTGRES_9_6": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_POSTGRESQL, Version: "9.6"}, + "POSTGRES_10": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_POSTGRESQL, Version: "10"}, + "POSTGRES_11": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_POSTGRESQL, Version: "11"}, + "POSTGRES_12": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_POSTGRESQL, Version: "12"}, + "SQLSERVER_2017_STANDARD": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_SQLSERVER, Version: "2017 Standard"}, + "SQLSERVER_2017_ENTERPRISE": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_SQLSERVER, Version: "2017 Enterprise"}, + "SQLSERVER_2017_EXPRESS": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_SQLSERVER, Version: "2017 Express"}, + "SQLSERVER_2017_WEB": GoogleSQLVersion{Engine: api.DBINSTANCE_TYPE_SQLSERVER, Version: "2017 Web"}, + } + InstanceTypes = map[string]GoogleSQLType{ + "db-f1-micro": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 614}, + "db-g1-small": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 1740}, + "D0": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 512}, + "D1": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 1024}, + "D2": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 2048}, + "D4": GoogleSQLType{VcpuCount: 1, VmemSizeMb: 5120}, + "D8": GoogleSQLType{VcpuCount: 2, VmemSizeMb: 10240}, + "D16": GoogleSQLType{VcpuCount: 4, VmemSizeMb: 10240}, + "D32": GoogleSQLType{VcpuCount: 8, VmemSizeMb: 10240}, + } +) + +type GoogleSQLType struct { + VcpuCount int + VmemSizeMb int +} + +type GoogleSQLVersion struct { + Engine string + Version string +} + +type SDBInstanceLocationPreference struct { + Zone string + Kind string +} + +type SDBInstanceMaintenanceWindow struct { + Kind string + Hour int + Day int +} + +type SDBInstanceBackupConfiguration struct { + StartTime string + Kind string + Enabled bool + BinaryLogEnabled bool +} + +type SAuthorizedNetwork struct { + Value string + Kind string + Name string +} + +type SDBInstanceSettingIpConfiguration struct { + PrivateNetwork string + AuthorizedNetworks []SAuthorizedNetwork + Ipv4Enabled bool +} + +type SDBInstanceSetting struct { + AuthorizedGaeApplications []string + Tier string + Kind string + AvailabilityType string + PricingPlan string + ReplicationType string + ActivationPolicy string + IpConfiguration SDBInstanceSettingIpConfiguration + LocationPreference SDBInstanceLocationPreference + DataDiskType string + MaintenanceWindow SDBInstanceMaintenanceWindow + BackupConfiguration SDBInstanceBackupConfiguration + SettingsVersion string + StorageAutoResizeLimit string + StorageAutoResize bool + DataDiskSizeGb int + DatabaseFlags []SDBInstanceParameter +} + +type SDBInstanceIpAddress struct { + Type string + IpAddress string +} + +type SDBInstanceCaCert struct { + Kind string + CertSerialNumber string + Cert string + CommonName string + Sha1Fingerprint string + Instance string + CreateTime time.Time + ExpirationTime time.Time +} + +type SDBInstance struct { + multicloud.SDBInstanceBase + region *SRegion + + Kind string + State string + DatabaseVersion string + Settings SDBInstanceSetting + Etag string + MasterInstanceName string + IpAddresses []SDBInstanceIpAddress + ServerCaCert SDBInstanceCaCert + InstanceType string + Project string + ServiceAccountEmailAddress string + BackendType string + SelfLink string + ConnectionName string + Name string + Region string + GceZone string +} + +func (region *SRegion) GetDBInstances(maxResults int, pageToken string) ([]SDBInstance, error) { + instances := []SDBInstance{} + params := map[string]string{"filter": "region=" + region.Name} + err := region.RdsList("instances", params, maxResults, pageToken, &instances) + if err != nil { + return nil, errors.Wrap(err, "RdsList") + } + return instances, nil +} + +func (region *SRegion) GetDBInstance(instanceId string) (*SDBInstance, error) { + instance := SDBInstance{region: region} + err := region.rdsGet(instanceId, &instance) + if err != nil { + return nil, errors.Wrap(err, "RdsGet") + } + return &instance, nil +} + +func (self *SDBInstance) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (rds *SDBInstance) GetName() string { + return rds.Name +} + +func (rds *SDBInstance) GetId() string { + return rds.SelfLink +} + +func (rds *SDBInstance) GetGlobalId() string { + return strings.TrimPrefix(rds.SelfLink, fmt.Sprintf("%s/%s/", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION)) +} + +func (rds *SDBInstance) GetProjectId() string { + return rds.region.GetProjectId() +} + +func (rds *SDBInstance) IsEmulated() bool { + return false +} + +func (rds *SDBInstance) GetStatus() string { + switch rds.State { + case "RUNNABLE": + return api.DBINSTANCE_RUNNING + case "PENDING_CREATE": + return api.DBINSTANCE_DEPLOYING + case "MAINTENANCE": + return api.DBINSTANCE_MAINTENANCE + case "FAILED": + return api.DBINSTANCE_CREATE_FAILED + case "UNKNOWN_STATE", "SUSPENDED": + return api.DBINSTANCE_UNKNOWN + } + return rds.State +} + +func (rds *SDBInstance) GetBillingType() string { + return billing_api.BILLING_TYPE_POSTPAID +} + +func (rds *SDBInstance) GetCreatedAt() time.Time { + return rds.ServerCaCert.CreateTime +} + +func (rds *SDBInstance) GetExpiredAt() time.Time { + return time.Time{} +} + +func (rds *SDBInstance) GetMasterInstanceId() string { + if len(rds.MasterInstanceName) > 0 { + if master := strings.Split(rds.MasterInstanceName, ":"); len(master) == 2 { + return fmt.Sprintf("projects/%s/instances/%s", master[0], master[1]) + } + } + return "" +} + +func (rds *SDBInstance) GetSecurityGroupId() string { + return "" +} + +func (rds *SDBInstance) Refresh() error { + instance, err := rds.region.GetDBInstance(rds.SelfLink) + if err != nil { + return errors.Wrapf(err, "GetDBInstance(%s)", rds.SelfLink) + } + return jsonutils.Update(rds, instance) +} + +func (rds *SDBInstance) GetPort() int { + switch rds.GetEngine() { + case api.DBINSTANCE_TYPE_MYSQL: + return 3306 + case api.DBINSTANCE_TYPE_POSTGRESQL: + return 5432 + case api.DBINSTANCE_TYPE_SQLSERVER: + return 1433 + default: + return 0 + } +} + +func (rds *SDBInstance) GetEngine() string { + if e, ok := EngineVersions[rds.DatabaseVersion]; ok { + return e.Engine + } + return rds.DatabaseVersion +} + +func (rds *SDBInstance) GetEngineVersion() string { + if e, ok := EngineVersions[rds.DatabaseVersion]; ok { + return e.Version + } + return rds.DatabaseVersion +} + +func (rds *SDBInstance) GetInstanceType() string { + return rds.Settings.Tier +} + +func (rds *SDBInstance) GetVcpuCount() int { + if t, ok := InstanceTypes[rds.Settings.Tier]; ok { + return t.VcpuCount + } + numStr := "" + if strings.HasPrefix(rds.Settings.Tier, "db-n1-standard-") { + numStr = strings.TrimPrefix(rds.Settings.Tier, "db-n1-standard-") + } else if strings.HasPrefix(rds.Settings.Tier, "db-n1-highmem-") { + numStr = strings.TrimPrefix(rds.Settings.Tier, "db-n1-highmem-") + } else { + numStr = strings.TrimPrefix(rds.Settings.Tier, "db-custom-") + numStr = strings.Split(numStr, "-")[0] + } + cpu, _ := strconv.ParseInt(numStr, 10, 32) + return int(cpu) +} + +func (rds *SDBInstance) GetVmemSizeMB() int { + if t, ok := InstanceTypes[rds.Settings.Tier]; ok { + return t.VmemSizeMb + } + if strings.HasPrefix(rds.Settings.Tier, "db-custom-") { + info := strings.Split(rds.Settings.Tier, "-") + numStr := info[len(info)-1] + mem, _ := strconv.ParseInt(numStr, 10, 32) + return int(mem) + } else if strings.HasPrefix(rds.Settings.Tier, "db-n1-standard-") { + return rds.GetVcpuCount() * 3840 + } else if strings.HasPrefix(rds.Settings.Tier, "db-n1-highmem-") { + return rds.GetVcpuCount() * 3840 * 2 + } + return 0 +} + +func (rds *SDBInstance) GetDiskSizeGB() int { + return rds.Settings.DataDiskSizeGb +} + +func (rds *SDBInstance) GetCategory() string { + return rds.BackendType +} + +func (rds *SDBInstance) GetStorageType() string { + return rds.Settings.DataDiskType +} + +func (rds *SDBInstance) GetMaintainTime() string { + startTime := (rds.Settings.MaintenanceWindow.Hour + 8) % 24 + startDay := (rds.Settings.MaintenanceWindow.Day + 1) % 7 + return fmt.Sprintf("%s %02d:00 - %02d:00", time.Weekday(startDay).String(), startTime, startTime+1) +} + +func (rds *SDBInstance) GetConnectionStr() string { + for _, ip := range rds.IpAddresses { + if ip.Type == "PRIMARY" { + return ip.IpAddress + } + } + return "" +} + +func (rds *SDBInstance) GetInternalConnectionStr() string { + ret := []string{rds.ConnectionName} + for _, ip := range rds.IpAddresses { + if ip.Type == "PRIVATE" { + ret = append(ret, ip.IpAddress) + } + } + return strings.Join(ret, ",") +} + +func (rds *SDBInstance) GetIZoneId() string { + return rds.GceZone +} + +func (rds *SDBInstance) GetIVpcId() string { + if len(rds.Settings.IpConfiguration.PrivateNetwork) > 0 { + globalnetwork, err := rds.region.client.GetGlobalNetwork(rds.Settings.IpConfiguration.PrivateNetwork) + if err != nil { + log.Errorf("failed to get global network %s error: %v", rds.Settings.IpConfiguration.PrivateNetwork, err) + return "" + } + vpc := &SVpc{ + region: rds.region, + globalnetwork: globalnetwork, + } + return vpc.GetGlobalId() + } + return "" +} + +func (rds *SDBInstance) GetDBNetwork() (*cloudprovider.SDBInstanceNetwork, error) { + return nil, nil +} + +func (rds *SDBInstance) GetIDBInstanceParameters() ([]cloudprovider.ICloudDBInstanceParameter, error) { + ret := []cloudprovider.ICloudDBInstanceParameter{} + for i := range rds.Settings.DatabaseFlags { + rds.Settings.DatabaseFlags[i].rds = rds + ret = append(ret, &rds.Settings.DatabaseFlags[i]) + } + return ret, nil +} + +func (rds *SDBInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { + databases, err := rds.region.GetDBInstanceDatabases(rds.Name) + if err != nil { + return nil, errors.Wrap(err, "GetDBInstanceDatabases") + } + ret := []cloudprovider.ICloudDBInstanceDatabase{} + for i := range databases { + databases[i].rds = rds + ret = append(ret, &databases[i]) + } + return ret, nil +} + +func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) { + accounts, err := rds.region.GetDBInstanceAccounts(rds.Name) + if err != nil { + return nil, errors.Wrap(err, "GetDBInstanceAccounts") + } + ret := []cloudprovider.ICloudDBInstanceAccount{} + for i := range accounts { + accounts[i].rds = rds + ret = append(ret, &accounts[i]) + } + return ret, nil +} + +func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := rds.region.GetDBInstanceBackups(rds.Name) + if err != nil { + return nil, errors.Wrap(err, "GetDBInstanceBackups") + } + ret := []cloudprovider.ICloudDBInstanceBackup{} + for i := range backups { + backups[i].rds = rds + ret = append(ret, &backups[i]) + } + return ret, nil +} + +func (region *SRegion) ChangeDBInstanceConfig(instanceId string, diskSizeGb int, instanceType string) error { + rds, err := region.GetDBInstance(instanceId) + if err != nil { + return errors.Wrapf(err, "GetDBInstance(%s)", instanceId) + } + body := map[string]interface{}{} + settings := map[string]interface{}{} + if len(instanceType) > 0 && instanceType != rds.GetInstanceType() { + settings["tier"] = instanceType + } + if diskSizeGb > 0 && diskSizeGb != rds.GetDiskSizeGB() { + settings["dataDiskSizeGb"] = diskSizeGb + } + if len(settings) == 0 { + return nil + } + body["settings"] = settings + return region.rdsPatch(rds.SelfLink, jsonutils.Marshal(body)) +} + +func (rds *SDBInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedDBInstanceChangeConfig) error { + return rds.region.ChangeDBInstanceConfig(rds.SelfLink, config.DiskSizeGB, config.InstanceType) +} + +func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { + return cloudprovider.ErrNotSupported +} + +func (region *SRegion) DBInstancePublicConnectionOperation(instanceId string, open bool) error { + ipConfiguration := map[string]interface{}{ + "ipv4Enabled": open, + } + if open { + ipConfiguration["authorizedNetworks"] = []map[string]string{ + map[string]string{ + "name": "White list", + "value": "0.0.0.0/0", + }, + } + } + body := map[string]interface{}{ + "settings": map[string]interface{}{ + "ipConfiguration": ipConfiguration, + }, + } + return region.rdsPatch(instanceId, jsonutils.Marshal(body)) +} + +func (rds *SDBInstance) OpenPublicConnection() error { + return rds.region.DBInstancePublicConnectionOperation(rds.SelfLink, true) +} + +func (rds *SDBInstance) ClosePublicConnection() error { + return rds.region.DBInstancePublicConnectionOperation(rds.SelfLink, false) +} + +func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return rds.region.CreateDatabase(rds.SelfLink, conf.Name, conf.CharacterSet) +} + +func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return rds.region.CreateDBInstanceAccount(rds.SelfLink, conf.Name, conf.Password, "") +} + +func (rds *SDBInstance) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + err := rds.region.CreateDBInstanceBackup(rds.SelfLink, conf.Name, conf.Description) + if err != nil { + return "", errors.Wrap(err, "CreateIBackup") + } + return "", nil +} + +func (region *SRegion) RecoverFromBackup(instanceId string, backupId string) error { + backup, err := region.GetDBInstanceBackup(backupId) + if err != nil { + return errors.Wrap(err, "GetDBInstanceBackup") + } + body := map[string]interface{}{ + "restoreBackupContext": map[string]string{ + "backupRunId": backup.Id, + }, + } + return region.rdsDo(instanceId, "restoreBackup", nil, jsonutils.Marshal(body)) +} + +func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + return rds.region.RecoverFromBackup(rds.SelfLink, conf.BackupId) +} + +func (rds *SDBInstance) Reboot() error { + return rds.region.rdsDo(rds.SelfLink, "restart", nil, nil) +} + +func (rds *SDBInstance) Delete() error { + return rds.region.DeleteDBInstance(rds.SelfLink) +} + +func (region *SRegion) DeleteDBInstance(id string) error { + return region.rdsDelete(id) +} + +func (region *SRegion) CreateRds(name, databaseVersion, category, instanceType, storageType string, diskSizeGb int, vpcId, zoneId, password string) (*SDBInstance, error) { + settings := map[string]interface{}{ + "tier": instanceType, + "storageAutoResize": true, + "dataDiskType": storageType, + "dataDiskSizeGb": diskSizeGb, + } + ipConfiguration := map[string]interface{}{ + "ipv4Enabled": true, + } + ipConfiguration["authorizedNetworks"] = []map[string]string{ + map[string]string{ + "name": "White list", + "value": "0.0.0.0/0", + }, + } + if len(vpcId) > 0 { + vpcId = strings.TrimPrefix(vpcId, region.GetGlobalId()+"/") + ipConfiguration["privateNetwork"] = vpcId + } + settings["ipConfiguration"] = ipConfiguration + body := map[string]interface{}{ + "databaseVersion": databaseVersion, + "name": name, + "region": region.Name, + "settings": settings, + "backendType": category, + } + if len(zoneId) > 0 { + body["gceZone"] = zoneId + } + if len(password) > 0 { + body["rootPassword"] = password + } + rds := SDBInstance{region: region} + err := region.rdsInsert("instances", jsonutils.Marshal(body), &rds) + if err != nil { + return nil, errors.Wrap(err, "rdsInsert") + } + return &rds, nil +} + +func (region *SRegion) CreateDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (*SDBInstance, error) { + desc.EngineVersion = strings.ToUpper(desc.EngineVersion) + desc.EngineVersion = strings.Replace(desc.EngineVersion, ".", "_", -1) + desc.EngineVersion = strings.Replace(desc.EngineVersion, " ", "_", -1) + if desc.Engine == api.DBINSTANCE_TYPE_POSTGRESQL { + desc.Engine = "POSTGRES" + } + databaseVersion := fmt.Sprintf("%s_%s", strings.ToUpper(desc.Engine), desc.EngineVersion) + 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, 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, 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") + } + return rds, nil +} diff --git a/pkg/multicloud/google/dbinstance_account.go b/pkg/multicloud/google/dbinstance_account.go new file mode 100644 index 0000000000..c899297455 --- /dev/null +++ b/pkg/multicloud/google/dbinstance_account.go @@ -0,0 +1,139 @@ +// 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 google + +import ( + "fmt" + "net/url" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SSqlserverUserDetails struct { + ServerRoles []string +} + +type SDBInstanceAccount struct { + rds *SDBInstance + + Kind string + Etag string + Name string + Host string + Instance string + SelfLink string + Project string + SqlserverUserDetails SSqlserverUserDetails +} + +func (region *SRegion) GetDBInstanceAccounts(instance string) ([]SDBInstanceAccount, error) { + accounts := []SDBInstanceAccount{} + params := map[string]string{} + resource := fmt.Sprintf("instances/%s/users", instance) + err := region.RdsListAll(resource, params, &accounts) + if err != nil { + return nil, errors.Wrap(err, "RdsListAll") + } + return accounts, nil +} + +func (region *SRegion) GetDBInstanceAccount(id string) (*SDBInstanceAccount, error) { + account := &SDBInstanceAccount{} + err := region.rdsGet(id, account) + if err != nil { + return nil, errors.Wrap(err, "rdsGet") + } + return account, nil +} + +func (region *SRegion) DeleteDBInstanceAccount(instanceName, user, host string) error { + resource := fmt.Sprintf("projects/%s/instances/%s/users?name=%s&host=%s", region.GetProjectId(), instanceName, url.PathEscape(user), url.PathEscape(host)) + return region.rdsDelete(resource) +} + +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) 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 { + return errors.Wrap(err, "GetDBInstanceAccount") + } + return jsonutils.Update(account, _account) +} + +func (account *SDBInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { + return []cloudprovider.ICloudDBInstanceAccountPrivilege{}, nil +} + +func (account *SDBInstanceAccount) ResetPassword(password string) error { + params := map[string]string{ + "host": account.Host, + "name": account.Name, + } + resource := fmt.Sprintf("instances/%s/users", account.rds.Name) + body := map[string]string{ + "password": password, + } + return account.rds.region.rdsUpdate(resource, params, jsonutils.Marshal(body)) +} + +func (account *SDBInstanceAccount) GrantPrivilege(database, privilege string) error { + return cloudprovider.ErrNotSupported +} + +func (account *SDBInstanceAccount) RevokePrivilege(database string) error { + return cloudprovider.ErrNotSupported +} + +func (region *SRegion) CreateDBInstanceAccount(instanceId string, name, password, host string) error { + body := map[string]interface{}{ + "name": name, + "password": password, + } + if len(host) > 0 { + body["host"] = host + } + return region.rdsDo(instanceId, "users", nil, jsonutils.Marshal(body)) +} diff --git a/pkg/multicloud/google/dbinstance_backup.go b/pkg/multicloud/google/dbinstance_backup.go new file mode 100644 index 0000000000..e24b68fbb9 --- /dev/null +++ b/pkg/multicloud/google/dbinstance_backup.go @@ -0,0 +1,172 @@ +// 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 google + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type OperationError struct { + Kind string + Code string + Message string +} + +type SDBInstanceBackup struct { + rds *SDBInstance + + Kind string + Status string + EnqueuedTime string + Id string + StartTime time.Time + EndTime time.Time + Type string + Description string + WindowStartTime time.Time + Instance string + SelfLink string + Location string + Error OperationError +} + +func (region *SRegion) GetDBInstanceBackups(instanceId string) ([]SDBInstanceBackup, error) { + backups := []SDBInstanceBackup{} + params := map[string]string{} + resource := fmt.Sprintf("instances/%s/backupRuns", instanceId) + err := region.RdsListAll(resource, params, &backups) + if err != nil { + return nil, errors.Wrap(err, "RdsListAll") + } + return backups, nil +} + +func (region *SRegion) GetDBInstanceBackup(backupId string) (*SDBInstanceBackup, error) { + backup := SDBInstanceBackup{} + err := region.rdsGet(backupId, &backup) + if err != nil { + return nil, errors.Wrap(err, "RdsGet") + } + rds, err := region.GetDBInstance(strings.TrimSuffix(backup.SelfLink, fmt.Sprintf("/%s", backup.Id))) + if err != nil { + return nil, errors.Wrap(err, "GetDBInstance") + } + backup.rds = rds + return &backup, nil +} + +func (backup *SDBInstanceBackup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (backup *SDBInstanceBackup) GetName() string { + return backup.Id +} + +func (backup *SDBInstanceBackup) GetId() string { + return backup.SelfLink +} + +func (backup *SDBInstanceBackup) GetGlobalId() string { + return strings.TrimPrefix(backup.SelfLink, fmt.Sprintf("%s/%s/", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION)) +} + +func (backup *SDBInstanceBackup) GetProjectId() string { + return backup.rds.GetProjectId() +} + +func (backup *SDBInstanceBackup) Refresh() error { + _backup, err := backup.rds.region.GetDBInstanceBackup(backup.SelfLink) + if err != nil { + return errors.Wrap(err, "GetDBInstanceBackup") + } + return jsonutils.Update(backup, _backup) +} + +func (backup *SDBInstanceBackup) GetStatus() string { + switch backup.Status { + case "SQL_BACKUP_RUN_STATUS_UNSPECIFIED": + return api.DBINSTANCE_BACKUP_UNKNOWN + case "ENQUEUED": + return api.DBINSTANCE_BACKUP_CREATING + case "FAILED": + return api.DBINSTANCE_BACKUP_FAILED + case "SUCCESSFUL", "OVERDUE", "RUNNING", "SKIPPED", "DELETION_PENDING": + return api.DBINSTANCE_BACKUP_READY + case "DELETION_FAILED", "DELETED": + return api.DBINSTANCE_BACKUP_DELETING + } + return backup.Status +} + +func (backup *SDBInstanceBackup) IsEmulated() bool { + return false +} + +func (backup *SDBInstanceBackup) GetEngine() string { + return backup.rds.GetEngine() +} + +func (backup *SDBInstanceBackup) GetEngineVersion() string { + return backup.rds.GetEngineVersion() +} + +func (backup *SDBInstanceBackup) GetDBInstanceId() string { + return backup.rds.GetGlobalId() +} + +func (backup *SDBInstanceBackup) GetStartTime() time.Time { + return backup.StartTime +} + +func (backup *SDBInstanceBackup) GetEndTime() time.Time { + return backup.EndTime +} + +func (backup *SDBInstanceBackup) GetBackupSizeMb() int { + return 0 +} + +func (backup *SDBInstanceBackup) GetDBNames() string { + return "" +} + +func (backup *SDBInstanceBackup) GetBackupMode() string { + switch backup.Type { + case "AUTOMATED": + return api.BACKUP_MODE_AUTOMATED + default: + return api.BACKUP_MODE_MANUAL + } +} + +func (backup *SDBInstanceBackup) Delete() error { + return backup.rds.region.rdsDelete(backup.SelfLink) +} + +func (region *SRegion) CreateDBInstanceBackup(instanceId string, name, desc string) error { + body := map[string]interface{}{ + "name": name, + "description": desc, + } + return region.rdsDo(instanceId, "backupRuns", nil, jsonutils.Marshal(body)) +} diff --git a/pkg/multicloud/google/dbinstance_database.go b/pkg/multicloud/google/dbinstance_database.go new file mode 100644 index 0000000000..e927cf8a15 --- /dev/null +++ b/pkg/multicloud/google/dbinstance_database.go @@ -0,0 +1,106 @@ +// 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 google + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SSqlserverDatabaseDetails struct { + CompatibilityLevel int + RecoveryModel string +} + +type SDBInstanceDatabase struct { + rds *SDBInstance + Kind string + Collation string + Etag string + Name string + Instance string + SelfLink string + Charset string + Project string + SqlserverDatabaseDetails SSqlserverDatabaseDetails +} + +func (region *SRegion) GetDBInstanceDatabases(instance string) ([]SDBInstanceDatabase, error) { + databases := []SDBInstanceDatabase{} + params := map[string]string{} + resource := fmt.Sprintf("instances/%s/databases", instance) + err := region.RdsListAll(resource, params, &databases) + if err != nil { + return nil, errors.Wrap(err, "RdsListAll") + } + return databases, nil +} + +func (region *SRegion) DeleteDBInstanceDatabase(id string) error { + return region.rdsDelete(id) +} + +func (database *SDBInstanceDatabase) Delete() error { + return database.rds.region.DeleteDBInstanceDatabase(database.SelfLink) +} + +func (database *SDBInstanceDatabase) GetCharacterSet() string { + return database.Charset +} + +func (database *SDBInstanceDatabase) GetGlobalId() string { + return database.Name +} + +func (database *SDBInstanceDatabase) GetId() string { + return database.SelfLink +} + +func (database *SDBInstanceDatabase) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (database *SDBInstanceDatabase) GetName() string { + return database.Name +} + +func (database *SDBInstanceDatabase) GetStatus() string { + return api.DBINSTANCE_DATABASE_RUNNING +} + +func (database *SDBInstanceDatabase) IsEmulated() bool { + return false +} + +func (database *SDBInstanceDatabase) Refresh() error { + _database := SDBInstanceDatabase{} + err := database.rds.region.rdsGet(database.SelfLink, &_database) + if err != nil { + return errors.Wrap(err, "rdsGet") + } + return jsonutils.Update(database, _database) +} + +func (region *SRegion) CreateDatabase(instanceId string, name, charset string) error { + body := map[string]interface{}{ + "charset": charset, + "name": name, + } + return region.rdsDo(instanceId, "databases", nil, jsonutils.Marshal(body)) +} diff --git a/pkg/multicloud/google/dbinstance_parameter.go b/pkg/multicloud/google/dbinstance_parameter.go new file mode 100644 index 0000000000..143d957153 --- /dev/null +++ b/pkg/multicloud/google/dbinstance_parameter.go @@ -0,0 +1,40 @@ +// 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 google + +import "fmt" + +type SDBInstanceParameter struct { + rds *SDBInstance + + Name string + Value string +} + +func (parameter *SDBInstanceParameter) GetGlobalId() string { + return fmt.Sprintf("%s/%s", parameter.rds.GetGlobalId(), parameter.Name) +} + +func (parameter *SDBInstanceParameter) GetKey() string { + return parameter.Name +} + +func (parameter *SDBInstanceParameter) GetValue() string { + return parameter.Value +} + +func (parameter *SDBInstanceParameter) GetDescription() string { + return "" +} diff --git a/pkg/multicloud/google/google.go b/pkg/multicloud/google/google.go index 3e6069d429..578a0b5919 100644 --- a/pkg/multicloud/google/google.go +++ b/pkg/multicloud/google/google.go @@ -50,6 +50,7 @@ const ( GOOGLE_CLOUDBUILD_API_VERSION = "v1" GOOGLE_BILLING_API_VERSION = "v1" GOOGLE_MONITOR_API_VERSION = "v3" + GOOGLE_DBINSTANCE_API_VERSION = "v1beta4" GOOGLE_MANAGER_DOMAIN = "https://cloudresourcemanager.googleapis.com" GOOGLE_COMPUTE_DOMAIN = "https://www.googleapis.com/compute" @@ -58,6 +59,7 @@ const ( GOOGLE_STORAGE_UPLOAD_DOMAIN = "https://www.googleapis.com/upload/storage" GOOGLE_BILLING_DOMAIN = "https://cloudbilling.googleapis.com" GOOGLE_MONITOR_DOMAIN = "https://monitoring.googleapis.com" + GOOGLE_DBINSTANCE_DOMAIN = "https://www.googleapis.com/sql" MAX_RETRY = 3 ) @@ -292,6 +294,46 @@ func (self *SGoogleClient) ecsDo(resource string, action string, params map[stri return selfLink, nil } +func (self *SGoogleClient) rdsDelete(id string, retval interface{}) error { + resp, err := jsonRequest(self.client, "DELETE", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, id, nil, nil, self.Debug) + if err != nil { + return err + } + if retval != nil { + return resp.Unmarshal(retval) + } + return nil +} + +func (self *SGoogleClient) rdsDo(resource string, action string, params map[string]string, body jsonutils.JSONObject) (string, error) { + resource = fmt.Sprintf("%s/%s", resource, action) + resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.Debug) + if err != nil { + return "", err + } + selfLink, _ := resp.GetString("selfLink") + return selfLink, nil +} + +func (self *SGoogleClient) rdsPatch(resource string, body jsonutils.JSONObject) (string, error) { + resp, err := jsonRequest(self.client, "PATCH", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.Debug) + if err != nil { + return "", err + } + selfLink, _ := resp.GetString("selfLink") + return selfLink, nil +} + +func (self *SGoogleClient) rdsUpdate(resource string, params map[string]string, body jsonutils.JSONObject) (string, error) { + resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource) + resp, err := jsonRequest(self.client, "PUT", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, body, self.Debug) + if err != nil { + return "", err + } + selfLink, _ := resp.GetString("selfLink") + return selfLink, nil +} + func (self *SGoogleClient) ecsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error { resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource) if name, _ := body.GetString("name"); len(name) > 0 { @@ -489,6 +531,65 @@ func (self *SGoogleClient) cloudbuildInsert(resource string, body jsonutils.JSON return nil } +func (self *SGoogleClient) rdsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error { + resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource) + resp, err := jsonRequest(self.client, "POST", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, body, self.Debug) + if err != nil { + return err + } + if retval != nil { + return resp.Unmarshal(retval) + } + return nil +} + +func (self *SGoogleClient) rdsGet(resource string, retval interface{}) error { + resp, err := jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, nil, nil, self.Debug) + if err != nil { + return err + } + if retval != nil { + err = resp.Unmarshal(retval) + if err != nil { + return errors.Wrap(err, "resp.Unmarshal") + } + } + return nil +} + +func (self *SGoogleClient) rdsList(resource string, params map[string]string) (jsonutils.JSONObject, error) { + resource = fmt.Sprintf("projects/%s/%s", self.projectId, resource) + return jsonRequest(self.client, "GET", GOOGLE_DBINSTANCE_DOMAIN, GOOGLE_DBINSTANCE_API_VERSION, resource, params, nil, self.Debug) +} + +func (self *SGoogleClient) rdsListAll(resource string, params map[string]string, retval interface{}) error { + if params == nil { + params = map[string]string{} + } + items := jsonutils.NewArray() + nextPageToken := "" + params["maxResults"] = "500" + for { + params["pageToken"] = nextPageToken + resp, err := self.rdsList(resource, params) + if err != nil { + return errors.Wrap(err, "rdsList") + } + if resp.Contains("items") { + _items, err := resp.GetArray("items") + if err != nil { + return errors.Wrap(err, "resp.GetArray") + } + items.Add(_items...) + } + nextPageToken, _ = resp.GetString("nextPageToken") + if len(nextPageToken) == 0 { + break + } + } + return items.Unmarshal(retval) +} + func (self *SGoogleClient) billingList(resource string, params map[string]string) (jsonutils.JSONObject, error) { return jsonRequest(self.client, "GET", GOOGLE_BILLING_DOMAIN, GOOGLE_BILLING_API_VERSION, resource, params, nil, self.Debug) } @@ -592,7 +693,9 @@ func _jsonRequest(client *http.Client, method httputils.THttpMethod, url string, } } if err != nil { - if strings.Index(strings.ToLower(err.Error()), "not found") > 0 { + errMsg := strings.ToLower(err.Error()) + if strings.Index(errMsg, "not found") > 0 || strings.Index(errMsg, "not exist") > 0 { + // The Cloud SQL instance does not exist. return nil, cloudprovider.ErrNotFound } return nil, errors.Wrap(err, "JSONRequest") @@ -681,12 +784,12 @@ func (self *SGoogleClient) GetIProjects() ([]cloudprovider.ICloudProject, error) func (self *SGoogleClient) GetCapabilities() []string { caps := []string{ - // cloudprovider.CLOUD_CAPABILITY_PROJECT, + cloudprovider.CLOUD_CAPABILITY_PROJECT, cloudprovider.CLOUD_CAPABILITY_COMPUTE, 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, } diff --git a/pkg/multicloud/google/operation.go b/pkg/multicloud/google/operation.go index 544209c03e..532c322c6b 100644 --- a/pkg/multicloud/google/operation.go +++ b/pkg/multicloud/google/operation.go @@ -69,3 +69,29 @@ func (region *SRegion) WaitOperation(id string, resource, action string) (string }) return targetLink, err } + +func (region *SRegion) GetRdsOperation(id string) (*SOperation, error) { + operation := &SOperation{} + err := region.rdsGet(id, &operation) + if err != nil { + return nil, err + } + return operation, nil +} + +func (region *SRegion) WaitRdsOperation(id string, resource, action string) (string, error) { + targetLink := "" + err := cloudprovider.Wait(time.Second*5, time.Minute*20, func() (bool, error) { + operation, err := region.GetRdsOperation(id) + if err != nil { + return false, err + } + log.Debugf("%s %s operation status: %s expect %s", action, resource, operation.Status, OPERATION_STATUS_DONE) + if operation.Status == OPERATION_STATUS_DONE { + targetLink = operation.TargetLink + return true, nil + } + return false, nil + }) + return targetLink, err +} diff --git a/pkg/multicloud/google/region.go b/pkg/multicloud/google/region.go index ca1124050f..f14ab4251c 100644 --- a/pkg/multicloud/google/region.go +++ b/pkg/multicloud/google/region.go @@ -377,6 +377,98 @@ func (region *SRegion) GetISnapshotById(id string) (cloudprovider.ICloudSnapshot return snapshot, nil } +func (region *SRegion) rdsDelete(id string) error { + operation := &SOperation{} + err := region.client.rdsDelete(id, operation) + if err != nil { + return errors.Wrap(err, "client.rdsDelete") + } + _, err = region.WaitRdsOperation(operation.SelfLink, id, "delete") + if err != nil { + return errors.Wrapf(err, "region.WaitRdsOperation(%s)", operation.SelfLink) + } + return nil +} + +func (region *SRegion) rdsDo(id string, action string, params map[string]string, body jsonutils.JSONObject) error { + opId, err := region.client.rdsDo(id, action, params, body) + if err != nil { + return err + } + if strings.Index(opId, "/operations/") > 0 { + _, err = region.WaitRdsOperation(opId, id, action) + return err + } + return nil +} + +func (region *SRegion) rdsPatch(id string, body jsonutils.JSONObject) error { + opId, err := region.client.rdsPatch(id, body) + if err != nil { + return err + } + if strings.Index(opId, "/operations/") > 0 { + _, err = region.WaitRdsOperation(opId, id, "update") + return err + } + return nil +} + +func (region *SRegion) rdsUpdate(id string, params map[string]string, body jsonutils.JSONObject) error { + opId, err := region.client.rdsUpdate(id, params, body) + if err != nil { + return err + } + if strings.Index(opId, "/operations/") > 0 { + _, err = region.WaitRdsOperation(opId, id, "update") + return err + } + return nil +} + +func (region *SRegion) rdsGet(resource string, retval interface{}) error { + return region.client.rdsGet(resource, retval) +} + +func (region *SRegion) rdsInsert(resource string, body jsonutils.JSONObject, retval interface{}) error { + operation := SOperation{} + err := region.client.rdsInsert(resource, body, &operation) + if err != nil { + return errors.Wrap(err, "rdsInsert") + } + resourceId, err := region.WaitRdsOperation(operation.SelfLink, resource, "insert") + if err != nil { + return errors.Wrapf(err, "region.WaitRdsOperation(%s)", operation.SelfLink) + } + return region.rdsGet(resourceId, retval) +} + +func (region *SRegion) RdsListAll(resource string, params map[string]string, retval interface{}) error { + return region.client.rdsListAll(resource, params, retval) +} + +func (region *SRegion) RdsList(resource string, params map[string]string, maxResults int, pageToken string, retval interface{}) error { + if maxResults == 0 && len(pageToken) == 0 { + return region.RdsListAll(resource, params, retval) + } + if params == nil { + params = map[string]string{} + } + params["maxResults"] = fmt.Sprintf("%d", maxResults) + params["pageToken"] = pageToken + resp, err := region.client.rdsList(resource, params) + if err != nil { + return errors.Wrap(err, "billingList") + } + if resp.Contains("items") && retval != nil { + err = resp.Unmarshal(retval, "items") + if err != nil { + return errors.Wrap(err, "resp.Unmarshal") + } + } + return nil +} + func (region *SRegion) BillingList(resource string, params map[string]string, pageSize int, pageToken string, retval interface{}) error { if pageSize == 0 && len(pageToken) == 0 { return region.BillingListAll(resource, params, retval) @@ -635,3 +727,60 @@ func (region *SRegion) GetCapabilities() []string { } return region.capabilities } + +func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { + instances, err := region.GetDBInstances(0, "") + if err != nil { + return nil, errors.Wrap(err, "GetDBInstances") + } + ret := []cloudprovider.ICloudDBInstance{} + for i := range instances { + instances[i].region = region + ret = append(ret, &instances[i]) + } + return ret, nil +} + +func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + instance, err := region.GetDBInstance(instanceId) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstance(%s)", instanceId) + } + return instance, nil +} + +func (region *SRegion) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + instances, err := region.GetDBInstances(0, "") + if err != nil { + return nil, errors.Wrap(err, "GetDBInstances") + } + ret := []cloudprovider.ICloudDBInstanceBackup{} + for i := range instances { + instances[i].region = region + backups, err := region.GetDBInstanceBackups(instances[i].Name) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceBackups(%s)", instances[i].Name) + } + for j := range backups { + backups[j].rds = &instances[i] + ret = append(ret, &backups[j]) + } + } + return ret, nil +} + +func (region *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + backup, err := region.GetDBInstanceBackup(backupId) + if err != nil { + return nil, errors.Wrapf(err, "GetDBInstanceBackup(%s)", backupId) + } + return backup, nil +} + +func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + rds, err := region.CreateDBInstance(desc) + if err != nil { + return nil, errors.Wrap(err, "CreateDBInstance") + } + return rds, nil +} diff --git a/pkg/multicloud/google/shell/dbinstance.go b/pkg/multicloud/google/shell/dbinstance.go new file mode 100644 index 0000000000..277f1f49d3 --- /dev/null +++ b/pkg/multicloud/google/shell/dbinstance.go @@ -0,0 +1,101 @@ +// 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/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceListOptions struct { + MaxResults int + PageToken string + } + shellutils.R(&DBInstanceListOptions{}, "dbinstance-list", "List dbinstances", func(cli *google.SRegion, args *DBInstanceListOptions) error { + instances, err := cli.GetDBInstances(args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(instances, 0, 0, 0, nil) + return nil + }) + + type DBInstanceIdOptions struct { + INSTANCE string + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-show", "Show dbinstance", func(cli *google.SRegion, args *DBInstanceIdOptions) error { + instance, err := cli.GetDBInstance(args.INSTANCE) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close dbinstance public connection", func(cli *google.SRegion, args *DBInstanceIdOptions) error { + return cli.DBInstancePublicConnectionOperation(args.INSTANCE, false) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open dbinstance public connection", func(cli *google.SRegion, args *DBInstanceIdOptions) error { + return cli.DBInstancePublicConnectionOperation(args.INSTANCE, true) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-delete", "Delete dbinstance", func(cli *google.SRegion, args *DBInstanceIdOptions) error { + return cli.DeleteDBInstance(args.INSTANCE) + }) + + type DBInstanceCreateOptions struct { + NAME string + DATABASEVERSION string + BackendType string `default:"SECOND_GEN" choices:"FIRST_GEN|SECOND_GEN|EXTERNAL"` + INSTANCE_TYPE string + STORAGE_TYPE string + DISK_SIZE_GB int + VpcId string + ZoneId string + Password string + } + + shellutils.R(&DBInstanceCreateOptions{}, "dbinstance-create", "Create dbinstance", func(cli *google.SRegion, args *DBInstanceCreateOptions) error { + instance, err := cli.CreateRds(args.NAME, args.DATABASEVERSION, args.BackendType, args.INSTANCE_TYPE, args.STORAGE_TYPE, args.DISK_SIZE_GB, args.VpcId, args.ZoneId, args.Password) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + type DBInstanceChangeConfigOptions struct { + INSTANCE string + DiskSizeGb int + InstnaceType string + } + + shellutils.R(&DBInstanceChangeConfigOptions{}, "dbinstance-change-config", "Change dbinstance config", func(cli *google.SRegion, args *DBInstanceChangeConfigOptions) error { + return cli.ChangeDBInstanceConfig(args.INSTANCE, args.DiskSizeGb, args.InstnaceType) + }) + + type DBInstanceRecoveryOptions struct { + RDS string + BACKUP string + } + + shellutils.R(&DBInstanceRecoveryOptions{}, "dbinstance-restore", "restore dbinstance from backup", func(cli *google.SRegion, args *DBInstanceRecoveryOptions) error { + return cli.RecoverFromBackup(args.RDS, args.BACKUP) + }) + +} diff --git a/pkg/multicloud/google/shell/dbinstance_account.go b/pkg/multicloud/google/shell/dbinstance_account.go new file mode 100644 index 0000000000..9e9e5b166e --- /dev/null +++ b/pkg/multicloud/google/shell/dbinstance_account.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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceAccountListOptions struct { + RDS string + } + shellutils.R(&DBInstanceAccountListOptions{}, "dbinstance-account-list", "List dbinstance account", func(cli *google.SRegion, args *DBInstanceAccountListOptions) error { + accounts, err := cli.GetDBInstanceAccounts(args.RDS) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, nil) + return nil + }) + + type DBInstanceAccountCreateOptions struct { + RDS string + NAME string + PASSWORD string + Host string + } + shellutils.R(&DBInstanceAccountCreateOptions{}, "dbinstance-account-create", "Create dbinstance account", func(cli *google.SRegion, args *DBInstanceAccountCreateOptions) error { + return cli.CreateDBInstanceAccount(args.RDS, args.NAME, args.PASSWORD, args.Host) + }) +} diff --git a/pkg/multicloud/google/shell/dbinstance_backup.go b/pkg/multicloud/google/shell/dbinstance_backup.go new file mode 100644 index 0000000000..58ed4aef25 --- /dev/null +++ b/pkg/multicloud/google/shell/dbinstance_backup.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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceBackupListOptions struct { + RDS string + } + shellutils.R(&DBInstanceBackupListOptions{}, "dbinstance-backup-list", "List dbinstance backup", func(cli *google.SRegion, args *DBInstanceBackupListOptions) error { + backups, err := cli.GetDBInstanceBackups(args.RDS) + if err != nil { + return err + } + printList(backups, 0, 0, 0, nil) + return nil + }) + + type DBInstanceBackupCreateOptions struct { + RDS string + NAME string + Desc string + } + + shellutils.R(&DBInstanceBackupCreateOptions{}, "dbinstance-backup-create", "Create dbinstance backup", func(cli *google.SRegion, args *DBInstanceBackupCreateOptions) error { + return cli.CreateDBInstanceBackup(args.RDS, args.NAME, args.Desc) + }) +} diff --git a/pkg/multicloud/google/shell/dbinstance_database.go b/pkg/multicloud/google/shell/dbinstance_database.go new file mode 100644 index 0000000000..217b13f019 --- /dev/null +++ b/pkg/multicloud/google/shell/dbinstance_database.go @@ -0,0 +1,45 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceDatabaseListOptions struct { + RDS string + } + shellutils.R(&DBInstanceDatabaseListOptions{}, "dbinstance-database-list", "List dbinstance database", func(cli *google.SRegion, args *DBInstanceDatabaseListOptions) error { + databases, err := cli.GetDBInstanceDatabases(args.RDS) + if err != nil { + return err + } + printList(databases, 0, 0, 0, nil) + return nil + }) + + type DBInstanceDatabaseCreateOptions struct { + RDS string + NAME string + CHARSET string + } + + shellutils.R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create dbinstance database", func(cli *google.SRegion, args *DBInstanceDatabaseCreateOptions) error { + return cli.CreateDatabase(args.RDS, args.NAME, args.CHARSET) + }) + +} diff --git a/pkg/util/seclib2/aes_test.go b/pkg/util/seclib2/aes_test.go index ae57f7a3d2..912b31cd7f 100644 --- a/pkg/util/seclib2/aes_test.go +++ b/pkg/util/seclib2/aes_test.go @@ -14,7 +14,9 @@ package seclib2 -import "testing" +import ( + "testing" +) func TestAes(t *testing.T) { secret := "This is a secret for AES!!!"