From 21be27e36c48814fb621c888fdd67377b8d91654 Mon Sep 17 00:00:00 2001 From: ioito Date: Mon, 12 Aug 2019 11:26:31 +0800 Subject: [PATCH] add rds operation --- cmd/climc/shell/cloudregions.go | 5 +- cmd/climc/shell/dbinstance_backups.go | 90 ++ cmd/climc/shell/dbinstance_databases.go | 74 ++ cmd/climc/shell/dbinstance_skus.go | 58 ++ cmd/climc/shell/dbinstances.go | 254 ++++- docs/dbinstance/change-config.yaml | 16 + docs/dbinstance/dbinstanceaccount.yaml | 6 +- docs/dbinstance/dbinstanceaccounts.yaml | 20 +- docs/dbinstance/dbinstancebackup.yaml | 6 +- docs/dbinstance/dbinstancebackups.yaml | 4 +- docs/dbinstance/dbinstancedatabase.yaml | 6 +- docs/dbinstance/dbinstancedatabases.yaml | 4 +- docs/dbinstance/dbinstancenetwork.yaml | 4 +- docs/dbinstance/dbinstancenetworks.yaml | 4 +- docs/dbinstance/dbinstanceparameter.yaml | 6 +- docs/dbinstance/dbinstanceparameters.yaml | 4 +- docs/dbinstance/dbinstances.yaml | 18 +- docs/dbinstance/public-connection.yaml | 16 + docs/dbinstance/purge.yaml | 12 + docs/dbinstance/reboot.yaml | 12 + docs/dbinstance/recovery.yaml | 17 + docs/dbinstance/sync-status.yaml | 11 + docs/dbinstance_sku/dbinstance_sku.yaml | 12 + docs/dbinstance_sku/dbinstance_skus.yaml | 15 + docs/dbinstanceaccount/account.yaml | 23 + docs/dbinstanceaccount/accounts.yaml | 30 + docs/dbinstanceaccount/grant-privilege.yaml | 17 + docs/dbinstanceaccount/reset-password.yaml | 17 + docs/dbinstanceaccount/revoke-privilege.yaml | 17 + docs/dbinstanceaccount/set-privileges.yaml | 17 + docs/dbinstancebackup/backup.yaml | 23 + docs/dbinstancebackup/backups.yaml | 32 + docs/dbinstancedatabase/database.yaml | 23 + docs/dbinstancedatabase/databases.yaml | 30 + docs/index.yaml | 43 +- docs/parameters/dbinstance_sku.yaml | 6 + docs/schemas/dbinstance.yaml | 363 ++++---- docs/schemas/dbinstance_account.yaml | 134 +++ docs/schemas/dbinstance_backup.yaml | 100 ++ docs/schemas/dbinstance_database.yaml | 103 +++ docs/schemas/dbinstance_network.yaml | 42 + docs/schemas/dbinstance_parameter.yaml | 44 + docs/schemas/dbinstance_sku.yaml | 105 +++ pkg/apis/compute/dbinstance.go | 56 ++ pkg/apis/compute/dbinstance_account.go | 23 + pkg/apis/compute/dbinstance_backup.go | 18 + pkg/apis/compute/dbinstance_const.go | 121 ++- pkg/apis/compute/dbinstance_database.go | 20 + pkg/apis/compute/dbinstance_sku_const.go | 5 + pkg/cloudcommon/db/opslog.go | 8 + pkg/cloudcommon/policy/defaults.go | 12 + pkg/cloudprovider/dbinstance.go | 67 ++ pkg/cloudprovider/resources.go | 39 +- pkg/compute/models/capabilities.go | 43 + pkg/compute/models/cloudregions.go | 14 +- pkg/compute/models/cloudsync.go | 3 +- pkg/compute/models/dbinstance_accounts.go | 333 ++++++- pkg/compute/models/dbinstance_backups.go | 167 +++- pkg/compute/models/dbinstance_databases.go | 143 ++- pkg/compute/models/dbinstance_privileges.go | 5 + pkg/compute/models/dbinstance_skus.go | 487 ++++++++++ pkg/compute/models/dbinstancenetworks.go | 51 + pkg/compute/models/dbinstances.go | 869 +++++++++++++++++- pkg/compute/models/purge.go | 93 +- pkg/compute/models/regiondrivers.go | 24 +- pkg/compute/regiondrivers/aliyun.go | 209 +++++ pkg/compute/regiondrivers/base.go | 66 ++ pkg/compute/regiondrivers/huawei.go | 108 +++ pkg/compute/regiondrivers/managedvirtual.go | 295 ++++++ pkg/compute/service/handlers.go | 1 + pkg/compute/service/service.go | 1 + .../tasks/dbinstance_account_create_task.go | 84 ++ .../tasks/dbinstance_account_delete_task.go | 84 ++ ...dbinstance_account_grant_privilege_task.go | 101 ++ .../dbinstance_account_reset_password_task.go | 78 ++ ...binstance_account_revoke_privilege_task.go | 92 ++ .../dbinstance_account_set_privileges_task.go | 129 +++ .../tasks/dbinstance_backup_create_task.go | 73 ++ pkg/compute/tasks/dbinstance_backup_delete.go | 79 ++ pkg/compute/tasks/dbinstance_change_config.go | 74 ++ pkg/compute/tasks/dbinstance_create_task.go | 79 ++ .../tasks/dbinstance_database_create_task.go | 87 ++ .../tasks/dbinstance_database_delete_task.go | 85 ++ pkg/compute/tasks/dbinstance_delete_task.go | 111 +++ .../dbinstance_public_connection_task.go | 99 ++ pkg/compute/tasks/dbinstance_reboot_task.go | 75 ++ pkg/compute/tasks/dbinstance_recovery_task.go | 88 ++ pkg/compute/tasks/dbinstance_renew_task.go | 69 ++ .../tasks/dbinstance_sync_status_task.go | 59 ++ pkg/mcclient/modules/managers.go | 10 +- pkg/mcclient/modules/mod_dbinstance_skus.go | 33 + .../modules/mod_dbinstanceaccounts.go | 56 +- pkg/mcclient/modules/mod_dbinstancebackups.go | 2 +- .../modules/mod_dbinstancedatabases.go | 2 +- pkg/multicloud/aliyun/aliyun.go | 2 +- pkg/multicloud/aliyun/dbinstance.go | 302 +++++- pkg/multicloud/aliyun/dbinstance_account.go | 89 ++ pkg/multicloud/aliyun/dbinstance_backup.go | 110 +++ pkg/multicloud/aliyun/dbinstance_database.go | 27 + pkg/multicloud/aliyun/shell/dbinstance.go | 60 +- .../aliyun/shell/dbinstance_account.go | 70 ++ .../aliyun/shell/dbinstance_backup.go | 64 ++ .../aliyun/shell/dbinstance_database.go | 59 ++ pkg/multicloud/aws/dbinstance.go | 4 + pkg/multicloud/aws/dbinstance_snapshot.go | 8 + pkg/multicloud/dbinstance_account_base.go | 21 +- pkg/multicloud/dbinstance_backup_base.go | 10 + pkg/multicloud/dbinstance_base.go | 54 ++ pkg/multicloud/dbinstance_database_base.go | 6 + pkg/multicloud/huawei/client/client.go | 4 + .../huawei/client/modules/manager_base.go | 2 +- .../huawei/client/modules/mod_dbinstance.go | 24 +- .../client/modules/mod_dbinstance_backup.go | 2 +- .../client/modules/mod_dbinstance_flavor.go | 37 + .../client/modules/mod_dbinstance_job.go | 42 + pkg/multicloud/huawei/dbinstance.go | 449 ++++++++- pkg/multicloud/huawei/dbinstance_account.go | 61 ++ pkg/multicloud/huawei/dbinstance_backup.go | 86 +- pkg/multicloud/huawei/dbinstance_database.go | 11 + pkg/multicloud/huawei/shell/dbinstance.go | 52 +- .../huawei/shell/dbinstance_account.go | 76 ++ .../huawei/shell/dbinstance_database.go | 55 ++ pkg/multicloud/huawei/shell/order.go | 36 + pkg/multicloud/huawei/utils.go | 12 +- pkg/multicloud/region_base.go | 12 + pkg/util/logclient/consts.go | 12 + pkg/util/seclib2/seclib.go | 2 +- 127 files changed, 8143 insertions(+), 491 deletions(-) create mode 100644 cmd/climc/shell/dbinstance_backups.go create mode 100644 cmd/climc/shell/dbinstance_databases.go create mode 100644 cmd/climc/shell/dbinstance_skus.go create mode 100644 docs/dbinstance/change-config.yaml create mode 100644 docs/dbinstance/public-connection.yaml create mode 100644 docs/dbinstance/purge.yaml create mode 100644 docs/dbinstance/reboot.yaml create mode 100644 docs/dbinstance/recovery.yaml create mode 100644 docs/dbinstance/sync-status.yaml create mode 100644 docs/dbinstance_sku/dbinstance_sku.yaml create mode 100644 docs/dbinstance_sku/dbinstance_skus.yaml create mode 100644 docs/dbinstanceaccount/account.yaml create mode 100644 docs/dbinstanceaccount/accounts.yaml create mode 100644 docs/dbinstanceaccount/grant-privilege.yaml create mode 100644 docs/dbinstanceaccount/reset-password.yaml create mode 100644 docs/dbinstanceaccount/revoke-privilege.yaml create mode 100644 docs/dbinstanceaccount/set-privileges.yaml create mode 100644 docs/dbinstancebackup/backup.yaml create mode 100644 docs/dbinstancebackup/backups.yaml create mode 100644 docs/dbinstancedatabase/database.yaml create mode 100644 docs/dbinstancedatabase/databases.yaml create mode 100644 docs/parameters/dbinstance_sku.yaml create mode 100644 docs/schemas/dbinstance_account.yaml create mode 100644 docs/schemas/dbinstance_backup.yaml create mode 100644 docs/schemas/dbinstance_database.yaml create mode 100644 docs/schemas/dbinstance_network.yaml create mode 100644 docs/schemas/dbinstance_parameter.yaml create mode 100644 docs/schemas/dbinstance_sku.yaml create mode 100644 pkg/apis/compute/dbinstance.go create mode 100644 pkg/apis/compute/dbinstance_account.go create mode 100644 pkg/apis/compute/dbinstance_backup.go create mode 100644 pkg/apis/compute/dbinstance_database.go create mode 100644 pkg/apis/compute/dbinstance_sku_const.go create mode 100644 pkg/compute/models/dbinstance_skus.go create mode 100644 pkg/compute/tasks/dbinstance_account_create_task.go create mode 100644 pkg/compute/tasks/dbinstance_account_delete_task.go create mode 100644 pkg/compute/tasks/dbinstance_account_grant_privilege_task.go create mode 100644 pkg/compute/tasks/dbinstance_account_reset_password_task.go create mode 100644 pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go create mode 100644 pkg/compute/tasks/dbinstance_account_set_privileges_task.go create mode 100644 pkg/compute/tasks/dbinstance_backup_create_task.go create mode 100644 pkg/compute/tasks/dbinstance_backup_delete.go create mode 100644 pkg/compute/tasks/dbinstance_change_config.go create mode 100644 pkg/compute/tasks/dbinstance_create_task.go create mode 100644 pkg/compute/tasks/dbinstance_database_create_task.go create mode 100644 pkg/compute/tasks/dbinstance_database_delete_task.go create mode 100644 pkg/compute/tasks/dbinstance_delete_task.go create mode 100644 pkg/compute/tasks/dbinstance_public_connection_task.go create mode 100644 pkg/compute/tasks/dbinstance_reboot_task.go create mode 100644 pkg/compute/tasks/dbinstance_recovery_task.go create mode 100644 pkg/compute/tasks/dbinstance_renew_task.go create mode 100644 pkg/compute/tasks/dbinstance_sync_status_task.go create mode 100644 pkg/mcclient/modules/mod_dbinstance_skus.go create mode 100644 pkg/multicloud/aliyun/shell/dbinstance_account.go create mode 100644 pkg/multicloud/aliyun/shell/dbinstance_backup.go create mode 100644 pkg/multicloud/aliyun/shell/dbinstance_database.go create mode 100644 pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go create mode 100644 pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go create mode 100644 pkg/multicloud/huawei/shell/dbinstance_account.go create mode 100644 pkg/multicloud/huawei/shell/dbinstance_database.go create mode 100644 pkg/multicloud/huawei/shell/order.go diff --git a/cmd/climc/shell/cloudregions.go b/cmd/climc/shell/cloudregions.go index 3441ee76df..a24f88191a 100644 --- a/cmd/climc/shell/cloudregions.go +++ b/cmd/climc/shell/cloudregions.go @@ -27,8 +27,9 @@ func init() { type CloudregionListOptions struct { options.BaseListOptions - Usable *bool `help:"List regions where networks are usable"` - UsableVpc *bool `help:"List regions where VPC are usable"` + Usable *bool `help:"List regions where networks are usable"` + UsableVpc *bool `help:"List regions where VPC are usable"` + Service string `help:"List regions which service has available skus" choices:"dbinstances|servers"` City string `help:"List regions in the specified city"` } diff --git a/cmd/climc/shell/dbinstance_backups.go b/cmd/climc/shell/dbinstance_backups.go new file mode 100644 index 0000000000..ba2f068381 --- /dev/null +++ b/cmd/climc/shell/dbinstance_backups.go @@ -0,0 +1,90 @@ +// 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/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type DBInstanceBackupListOptions struct { + options.BaseListOptions + DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` + Cloudregion string `help:"ID or Name of cloudregion"` + } + R(&DBInstanceBackupListOptions{}, "dbinstance-backup-list", "List DB instance backups", func(s *mcclient.ClientSession, opts *DBInstanceBackupListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + + result, err := modules.DBInstanceBackups.List(s, params) + if err != nil { + return err + } + printList(result, modules.DBInstanceBackups.GetColumns(s)) + return nil + }) + + type DBInstanceBackupCreateOptions struct { + INSTANCE string `help:"ID or Name of DBInstance" json:"dbinstance"` + NAME string + Databases []string + Description string + } + + R(&DBInstanceBackupCreateOptions{}, "dbinstance-backup-create", "Create DB instance backup", func(s *mcclient.ClientSession, opts *DBInstanceBackupCreateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(opts.INSTANCE), "dbinstance") + params.Add(jsonutils.NewString(opts.NAME), "name") + params.Add(jsonutils.NewString(opts.Description), "description") + databases := jsonutils.NewArray() + for _, database := range opts.Databases { + databases.Add(jsonutils.NewString(database)) + } + params.Add(databases, "databases") + result, err := modules.DBInstanceBackups.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceBackupIdOptions struct { + ID string + } + + R(&DBInstanceBackupIdOptions{}, "dbinstance-backup-show", "Show DB instance backup", func(s *mcclient.ClientSession, opts *DBInstanceBackupIdOptions) error { + result, err := modules.DBInstanceBackups.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceBackupIdOptions{}, "dbinstance-backup-delete", "Delete DB instance backup", func(s *mcclient.ClientSession, opts *DBInstanceBackupIdOptions) error { + result, err := modules.DBInstanceBackups.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/cmd/climc/shell/dbinstance_databases.go b/cmd/climc/shell/dbinstance_databases.go new file mode 100644 index 0000000000..ec4f8c83b6 --- /dev/null +++ b/cmd/climc/shell/dbinstance_databases.go @@ -0,0 +1,74 @@ +// 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/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type DBInstanceDatabaseListOptions struct { + options.BaseListOptions + DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` + } + R(&DBInstanceDatabaseListOptions{}, "dbinstance-database-list", "List DB instance databases", func(s *mcclient.ClientSession, opts *DBInstanceDatabaseListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + + result, err := modules.DBInstanceDatabases.List(s, params) + if err != nil { + return err + } + printList(result, modules.DBInstanceDatabases.GetColumns(s)) + return nil + }) + + type DBInstanceDatabaseCreateOptions struct { + NAME string + DBINSTANCE string `help:"ID or Name of DBInstance" json:"dbinstance"` + CharacterSet string `help:"CharacterSet for database"` + } + + R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create DB instance databases", func(s *mcclient.ClientSession, opts *DBInstanceDatabaseCreateOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.DBInstanceDatabases.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceDatabaseIdOptions struct { + ID string + } + + R(&DBInstanceDatabaseIdOptions{}, "dbinstance-database-delete", "Delete DB instance databases", func(s *mcclient.ClientSession, opts *DBInstanceDatabaseIdOptions) error { + result, err := modules.DBInstanceDatabases.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/dbinstance_skus.go b/cmd/climc/shell/dbinstance_skus.go new file mode 100644 index 0000000000..e06ab4fe76 --- /dev/null +++ b/cmd/climc/shell/dbinstance_skus.go @@ -0,0 +1,58 @@ +// 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/mcclient" + "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 + }) + +} diff --git a/cmd/climc/shell/dbinstances.go b/cmd/climc/shell/dbinstances.go index 3c68fd0014..47fa11061a 100644 --- a/cmd/climc/shell/dbinstances.go +++ b/cmd/climc/shell/dbinstances.go @@ -15,6 +15,10 @@ package shell import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -40,6 +44,219 @@ func init() { return nil }) + type DBInstanceCreateOptions struct { + NAME string `help:"DBInstance Name"` + InstanceType string `help:"InstanceType for DBInstance"` + VcpuCount int `help:"Core of cpu for DBInstance"` + VmemSizeMb int `help:"Memory size of DBInstance"` + Port int `help:"Port of DBInstance"` + Category string `help:"Category of DBInstance"` + Network string `help:"Network of DBInstance"` + Address string `help:"Address of DBInstance"` + Engine string `help:"Engine of DBInstance"` + EngineVersion string `help:"EngineVersion of DBInstance Engine"` + StorageType string `help:"StorageTyep of DBInstance"` + Secgroup string `help:"Secgroup name or Id for DBInstance"` + Zone string `help:"ZoneId or name for DBInstance"` + DiskSizeGB int `help:"Storage size for DBInstance"` + Duration string `help:"Duration for DBInstance"` + AllowDelete *bool `help:"not lock dbinstance" ` + } + + R(&DBInstanceCreateOptions{}, "dbinstance-create", "Create DB instance", func(s *mcclient.ClientSession, opts *DBInstanceCreateOptions) error { + params, err := options.StructToParams(opts) + if err != nil { + return err + } + if opts.AllowDelete != nil && *opts.AllowDelete { + params.Add(jsonutils.JSONFalse, "disable_delete") + } + result, err := modules.DBInstance.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceRenewOptions struct { + ID string `help:"ID or name of server to renew"` + DURATION string `help:"Duration of renew, ADMIN only command"` + } + R(&DBInstanceRenewOptions{}, "dbinstance-renew", "Renew a dbinstance", func(s *mcclient.ClientSession, args *DBInstanceRenewOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.DURATION), "duration") + result, err := modules.DBInstance.PerformAction(s, args.ID, "renew", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceUpdateOptions struct { + ID string `help:"ID or name of server to renew"` + Name string + Description string + Delete string `help:"Lock or not lock dbinstance" choices:"enable|disable"` + } + R(&DBInstanceUpdateOptions{}, "dbinstance-update", "Update a dbinstance", func(s *mcclient.ClientSession, args *DBInstanceUpdateOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + if len(args.Delete) > 0 { + if args.Delete == "disable" { + params.Add(jsonutils.JSONTrue, "disable_delete") + } else { + params.Add(jsonutils.JSONFalse, "disable_delete") + } + } + result, err := modules.DBInstance.Update(s, args.ID, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceChangeConfigOptions struct { + ID string `help:"ID or name of server to renew"` + DiskSizeGb int64 `help:"Change DBInstance storage size"` + VcpuCount int64 `help:"Change DBInstance vcpu count"` + VmemSizeMb int64 `help:"Change DBInstance vmem size mb"` + InstanceType string `help:"Change DBInstance instanceType"` + Category string `help:"Change DBInstance category"` + } + R(&DBInstanceChangeConfigOptions{}, "dbinstance-change-config", "ChangeConfig a dbinstance", func(s *mcclient.ClientSession, args *DBInstanceChangeConfigOptions) error { + params := jsonutils.NewDict() + if len(args.Category) > 0 { + params.Add(jsonutils.NewString(args.Category), "category") + } + if len(args.InstanceType) > 0 { + params.Add(jsonutils.NewString(args.InstanceType), "instance_type") + } + if args.DiskSizeGb > 0 { + params.Add(jsonutils.NewInt(args.DiskSizeGb), "disk_size_gb") + } + if args.VcpuCount > 0 { + params.Add(jsonutils.NewInt(args.VcpuCount), "vcpu_count") + } + if args.VmemSizeMb > 0 { + params.Add(jsonutils.NewInt(args.VmemSizeMb), "vmeme_size_mb") + } + result, err := modules.DBInstance.PerformAction(s, args.ID, "change-config", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceIdOptions struct { + ID string `help:"ID of dbinstance"` + } + + R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open DB instance public connection", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.JSONTrue, "open") + result, err := modules.DBInstance.PerformAction(s, opts.ID, "public-connection", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close DB instance public connection", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.JSONFalse, "open") + result, err := modules.DBInstance.PerformAction(s, opts.ID, "public-connection", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type DBInstanceRecoveryOptions struct { + ID string + BACKUP string + Databases []string + } + + R(&DBInstanceRecoveryOptions{}, "dbinstance-recovery", "Recovery DB instance database from backup", func(s *mcclient.ClientSession, opts *DBInstanceRecoveryOptions) error { + params := jsonutils.NewDict() + params.Set("dbinstancebackup", jsonutils.NewString(opts.BACKUP)) + dbs := jsonutils.NewDict() + for _, database := range opts.Databases { + if len(database) > 0 { + dbInfo := strings.Split(database, ":") + if len(dbInfo) == 1 { + dbs.Add(jsonutils.NewString(dbInfo[0]), dbInfo[0]) + } else if len(dbInfo) == 2 { + dbs.Add(jsonutils.NewString(dbInfo[1]), dbInfo[0]) + } else { + return fmt.Errorf("Invalid dbinfo: %s", database) + } + } + } + if dbs.Length() > 0 { + params.Add(dbs, "databases") + } + result, err := modules.DBInstance.PerformAction(s, opts.ID, "recovery", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceIdOptions{}, "dbinstance-show", "Show DB instance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + result, err := modules.DBInstance.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceIdOptions{}, "dbinstance-reboot", "Reboot DB instance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + result, err := modules.DBInstance.PerformAction(s, opts.ID, "reboot", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceIdOptions{}, "dbinstance-delete", "Delete DB instance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + result, err := modules.DBInstance.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&DBInstanceIdOptions{}, "dbinstance-purge", "Purge DB instance", func(s *mcclient.ClientSession, opts *DBInstanceIdOptions) error { + result, err := modules.DBInstance.PerformAction(s, opts.ID, "purge", 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 { + return err + } + printObject(result) + return nil + }) + type DBInstanceNetworkListOptions struct { options.BaseListOptions DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` @@ -85,43 +302,6 @@ func init() { return nil }) - type DBInstanceDatabaseListOptions struct { - options.BaseListOptions - DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` - } - R(&DBInstanceDatabaseListOptions{}, "dbinstance-database-list", "List DB instance databases", func(s *mcclient.ClientSession, opts *DBInstanceDatabaseListOptions) error { - params, err := options.ListStructToParams(opts) - if err != nil { - return err - } - - result, err := modules.DBInstanceDatabases.List(s, params) - if err != nil { - return err - } - printList(result, modules.DBInstanceDatabases.GetColumns(s)) - return nil - }) - - type DBInstanceBackupListOptions struct { - options.BaseListOptions - DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` - Cloudregion string `help:"ID or Name of cloudregion"` - } - R(&DBInstanceBackupListOptions{}, "dbinstance-backup-list", "List DB instance backups", func(s *mcclient.ClientSession, opts *DBInstanceBackupListOptions) error { - params, err := options.ListStructToParams(opts) - if err != nil { - return err - } - - result, err := modules.DBInstanceBackups.List(s, params) - if err != nil { - return err - } - printList(result, modules.DBInstanceBackups.GetColumns(s)) - return nil - }) - type DBInstanceAccountListOptions struct { options.BaseListOptions DBInstance string `help:"ID or Name of DBInstance" json:"dbinstance"` diff --git a/docs/dbinstance/change-config.yaml b/docs/dbinstance/change-config.yaml new file mode 100644 index 0000000000..758a9e9fd5 --- /dev/null +++ b/docs/dbinstance/change-config.yaml @@ -0,0 +1,16 @@ +post: + summary: 调整RDS实例配置 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + - in: body + name: dbinstance + required: true + schema: + $ref: '../schemas/dbinstance.yaml#DBInstanceChangeConfig' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/dbinstanceaccount.yaml b/docs/dbinstance/dbinstanceaccount.yaml index a773368b78..9636a8caeb 100644 --- a/docs/dbinstance/dbinstanceaccount.yaml +++ b/docs/dbinstance/dbinstanceaccount.yaml @@ -6,7 +6,7 @@ get: 200: description: RDS实例用户信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceAccountResponse' + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' tags: - dbinstanceaccount @@ -18,6 +18,6 @@ delete: 200: description: 被删除RDS实例的用户信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceAccountResponse' + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' tags: - - dbinstanceaccount \ No newline at end of file + - dbinstanceaccount diff --git a/docs/dbinstance/dbinstanceaccounts.yaml b/docs/dbinstance/dbinstanceaccounts.yaml index df9cc1465e..a0d17bfe51 100644 --- a/docs/dbinstance/dbinstanceaccounts.yaml +++ b/docs/dbinstance/dbinstanceaccounts.yaml @@ -9,6 +9,22 @@ get: 200: description: RDS实例用户列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceAccountListResponse' + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountListResponse' tags: - - dbinstanceaccount \ No newline at end of file + - dbinstanceaccount + +post: + summary: 创建RDS实例账号 + parameters: + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountCreate' + responses: + 200: + description: 新建RDS实例账号信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstance/dbinstancebackup.yaml b/docs/dbinstance/dbinstancebackup.yaml index f1ff0bd9f7..3130795951 100644 --- a/docs/dbinstance/dbinstancebackup.yaml +++ b/docs/dbinstance/dbinstancebackup.yaml @@ -6,7 +6,7 @@ get: 200: description: RDS实例备份信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceBackupResponse' + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupResponse' tags: - dbinstancebackup @@ -18,6 +18,6 @@ delete: 200: description: 被删除RDS实例的备份信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceBackupResponse' + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupResponse' tags: - - dbinstancebackup \ No newline at end of file + - dbinstancebackup diff --git a/docs/dbinstance/dbinstancebackups.yaml b/docs/dbinstance/dbinstancebackups.yaml index fe6f4fa2e4..5b2f01d14d 100644 --- a/docs/dbinstance/dbinstancebackups.yaml +++ b/docs/dbinstance/dbinstancebackups.yaml @@ -11,6 +11,6 @@ get: 200: description: RDS实例备份列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceBackupListResponse' + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupListResponse' tags: - - dbinstancebackup \ No newline at end of file + - dbinstancebackup diff --git a/docs/dbinstance/dbinstancedatabase.yaml b/docs/dbinstance/dbinstancedatabase.yaml index 98b53d970d..c65804119e 100644 --- a/docs/dbinstance/dbinstancedatabase.yaml +++ b/docs/dbinstance/dbinstancedatabase.yaml @@ -6,7 +6,7 @@ get: 200: description: RDS实例数据库信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceDatabaseResponse' + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseResponse' tags: - dbinstancedatabase @@ -18,6 +18,6 @@ delete: 200: description: 被删除RDS实例的数据库信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceDatabaseResponse' + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseResponse' tags: - - dbinstancedatabase \ No newline at end of file + - dbinstancedatabase diff --git a/docs/dbinstance/dbinstancedatabases.yaml b/docs/dbinstance/dbinstancedatabases.yaml index aa231d5eaa..c1b0e3558e 100644 --- a/docs/dbinstance/dbinstancedatabases.yaml +++ b/docs/dbinstance/dbinstancedatabases.yaml @@ -9,6 +9,6 @@ get: 200: description: RDS实例数据库列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceDatabaseListResponse' + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseListResponse' tags: - - dbinstancedatabase \ No newline at end of file + - dbinstancedatabase diff --git a/docs/dbinstance/dbinstancenetwork.yaml b/docs/dbinstance/dbinstancenetwork.yaml index 1bf21d81ad..c5711cf9a7 100644 --- a/docs/dbinstance/dbinstancenetwork.yaml +++ b/docs/dbinstance/dbinstancenetwork.yaml @@ -7,6 +7,6 @@ get: 200: description: RDS实例网络列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceNetworkListResponse' + $ref: '../schemas/dbinstance_network.yaml#/DBInstanceNetworkListResponse' tags: - - dbinstance \ No newline at end of file + - dbinstance diff --git a/docs/dbinstance/dbinstancenetworks.yaml b/docs/dbinstance/dbinstancenetworks.yaml index 11a7b7d17f..dfa4712078 100644 --- a/docs/dbinstance/dbinstancenetworks.yaml +++ b/docs/dbinstance/dbinstancenetworks.yaml @@ -8,6 +8,6 @@ get: 200: description: RDS实例网络列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceNetworkListResponse' + $ref: '../schemas/dbinstance_network.yaml#/DBInstanceNetworkListResponse' tags: - - dbinstancenetwork \ No newline at end of file + - dbinstancenetwork diff --git a/docs/dbinstance/dbinstanceparameter.yaml b/docs/dbinstance/dbinstanceparameter.yaml index de3e0869ec..747ac5578d 100644 --- a/docs/dbinstance/dbinstanceparameter.yaml +++ b/docs/dbinstance/dbinstanceparameter.yaml @@ -6,7 +6,7 @@ get: 200: description: RDS实例信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceParameterResponse' + $ref: '../schemas/dbinstance_parameter.yaml#/DBInstanceParameterResponse' tags: - dbinstanceparameter @@ -18,6 +18,6 @@ delete: 200: description: 被删除RDS实例的参数信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceParameterResponse' + $ref: '../schemas/dbinstance_parameter.yaml#/DBInstanceParameterResponse' tags: - - dbinstanceparameter \ No newline at end of file + - dbinstanceparameter diff --git a/docs/dbinstance/dbinstanceparameters.yaml b/docs/dbinstance/dbinstanceparameters.yaml index c3ab79d009..a9240f52ab 100644 --- a/docs/dbinstance/dbinstanceparameters.yaml +++ b/docs/dbinstance/dbinstanceparameters.yaml @@ -9,6 +9,6 @@ get: 200: description: RDS实例参数列表信息 schema: - $ref: '../schemas/dbinstance.yaml#/DBInstanceParameterListResponse' + $ref: '../schemas/dbinstance_parameter.yaml#/DBInstanceParameterListResponse' tags: - - dbinstanceparameter \ No newline at end of file + - dbinstanceparameter diff --git a/docs/dbinstance/dbinstances.yaml b/docs/dbinstance/dbinstances.yaml index ec264a73b9..eaad4213cd 100644 --- a/docs/dbinstance/dbinstances.yaml +++ b/docs/dbinstance/dbinstances.yaml @@ -17,4 +17,20 @@ get: schema: $ref: '../schemas/dbinstance.yaml#/DBInstanceListResponse' tags: - - dbinstance \ No newline at end of file + - dbinstance + +post: + summary: 创建RDS实例 + parameters: + - in: body + name: dbinstance + required: true + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceCreate' + responses: + 200: + description: 新建RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/public-connection.yaml b/docs/dbinstance/public-connection.yaml new file mode 100644 index 0000000000..56ca8c79cc --- /dev/null +++ b/docs/dbinstance/public-connection.yaml @@ -0,0 +1,16 @@ +post: + summary: 开放关闭RDS实例外网地址 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + - in: body + name: dbinstance + required: true + schema: + $ref: '../schemas/dbinstance.yaml#DBInstancePublicConnection' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/purge.yaml b/docs/dbinstance/purge.yaml new file mode 100644 index 0000000000..91f7e2b61d --- /dev/null +++ b/docs/dbinstance/purge.yaml @@ -0,0 +1,12 @@ +post: + summary: 抹除RDS实例 + description: 仅删除本地数据库记录,不删除公有云RDS实例 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/reboot.yaml b/docs/dbinstance/reboot.yaml new file mode 100644 index 0000000000..52f6a13dd4 --- /dev/null +++ b/docs/dbinstance/reboot.yaml @@ -0,0 +1,12 @@ +post: + summary: 重启RDS实例 + description: 要求RDS实例状态正常或重启失败 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/recovery.yaml b/docs/dbinstance/recovery.yaml new file mode 100644 index 0000000000..01728226b3 --- /dev/null +++ b/docs/dbinstance/recovery.yaml @@ -0,0 +1,17 @@ +post: + summary: 从备份恢复实例数据库 + description: 要求RDS实例状态正常 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + - in: body + name: dbinstance + required: true + schema: + $ref: '../schemas/dbinstance.yaml#DBInstanceRecovery' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance/sync-status.yaml b/docs/dbinstance/sync-status.yaml new file mode 100644 index 0000000000..b1f4ca28ed --- /dev/null +++ b/docs/dbinstance/sync-status.yaml @@ -0,0 +1,11 @@ +post: + summary: 同步RDS实例状态 + parameters: + - $ref: '../parameters/dbinstance.yaml#/dbinstanceId' + responses: + 200: + description: RDS实例信息 + schema: + $ref: '../schemas/dbinstance.yaml#/DBInstanceResponse' + tags: + - dbinstance diff --git a/docs/dbinstance_sku/dbinstance_sku.yaml b/docs/dbinstance_sku/dbinstance_sku.yaml new file mode 100644 index 0000000000..795cdae502 --- /dev/null +++ b/docs/dbinstance_sku/dbinstance_sku.yaml @@ -0,0 +1,12 @@ +get: + summary: 获取指定RDS套餐详情信息 + parameters: + - $ref: '../parameters/dbinstance_sku.yaml#/skuId' + responses: + 200: + description: RDS套餐信息 + schema: + $ref: '../schemas/dbinstance_sku.yaml#/DBInstanceSkuResponse' + tags: + - dbinstance-sku + diff --git a/docs/dbinstance_sku/dbinstance_skus.yaml b/docs/dbinstance_sku/dbinstance_skus.yaml new file mode 100644 index 0000000000..ca873d36cc --- /dev/null +++ b/docs/dbinstance_sku/dbinstance_skus.yaml @@ -0,0 +1,15 @@ +get: + summary: 按指定条件列出RDS套餐 + parameters: + - $ref: '../parameters/common.yaml#/offset' + - $ref: '../parameters/common.yaml#/limit' + - $ref: '../parameters/common.yaml#/provider' + - $ref: '../parameters/common.yaml#/cloudregion' + responses: + 200: + description: RDS套餐列表信息 + schema: + $ref: "../schemas/dbinstance_sku.yaml#/DBInstanceSkuListResponse" + tags: + - dbinstance-sku + diff --git a/docs/dbinstanceaccount/account.yaml b/docs/dbinstanceaccount/account.yaml new file mode 100644 index 0000000000..9636a8caeb --- /dev/null +++ b/docs/dbinstanceaccount/account.yaml @@ -0,0 +1,23 @@ +get: + summary: 获取指定RDS实例用户详情信息 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + responses: + 200: + description: RDS实例用户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount + +delete: + summary: 删除指定RDS实例用户 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + responses: + 200: + description: 被删除RDS实例的用户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstanceaccount/accounts.yaml b/docs/dbinstanceaccount/accounts.yaml new file mode 100644 index 0000000000..a0d17bfe51 --- /dev/null +++ b/docs/dbinstanceaccount/accounts.yaml @@ -0,0 +1,30 @@ +get: + summary: 按指定条件列出RDS实例用户列表 + parameters: + - $ref: '../parameters/common.yaml#/limit' + - $ref: '../parameters/common.yaml#/offset' + - $ref: '../parameters/dbinstance.yaml#/dbinstance' + + responses: + 200: + description: RDS实例用户列表信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountListResponse' + tags: + - dbinstanceaccount + +post: + summary: 创建RDS实例账号 + parameters: + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountCreate' + responses: + 200: + description: 新建RDS实例账号信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstanceaccount/grant-privilege.yaml b/docs/dbinstanceaccount/grant-privilege.yaml new file mode 100644 index 0000000000..6a31a6b94e --- /dev/null +++ b/docs/dbinstanceaccount/grant-privilege.yaml @@ -0,0 +1,17 @@ +post: + summary: 赋予RDS实例的数据库权限 + description: 要求RDS实例状态正常 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#DBInstanceAccountGrantPrivilege' + responses: + 200: + description: RDS实例账户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstanceaccount/reset-password.yaml b/docs/dbinstanceaccount/reset-password.yaml new file mode 100644 index 0000000000..7d644b29ad --- /dev/null +++ b/docs/dbinstanceaccount/reset-password.yaml @@ -0,0 +1,17 @@ +post: + summary: 重置RDS实例用户密码 + description: 要求RDS实例状态正常 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#DBInstanceAccountResetPassword' + responses: + 200: + description: RDS实例账户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstanceaccount/revoke-privilege.yaml b/docs/dbinstanceaccount/revoke-privilege.yaml new file mode 100644 index 0000000000..6ee97b9ad6 --- /dev/null +++ b/docs/dbinstanceaccount/revoke-privilege.yaml @@ -0,0 +1,17 @@ +post: + summary: 解除RDS实例的数据库权限 + description: 要求RDS实例状态正常 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#DBInstanceAccountRevokePrivilege' + responses: + 200: + description: RDS实例账户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstanceaccount/set-privileges.yaml b/docs/dbinstanceaccount/set-privileges.yaml new file mode 100644 index 0000000000..00a8222fe6 --- /dev/null +++ b/docs/dbinstanceaccount/set-privileges.yaml @@ -0,0 +1,17 @@ +post: + summary: 设置RDS实例的数据库权限 + description: 要求RDS实例及账号状态正常 + parameters: + - $ref: '../parameters/dbinstance.yaml#/accountId' + - in: body + name: dbinstanceaccount + required: true + schema: + $ref: '../schemas/dbinstance_account.yaml#DBInstanceAccountSetPrivileges' + responses: + 200: + description: RDS实例账户信息 + schema: + $ref: '../schemas/dbinstance_account.yaml#/DBInstanceAccountResponse' + tags: + - dbinstanceaccount diff --git a/docs/dbinstancebackup/backup.yaml b/docs/dbinstancebackup/backup.yaml new file mode 100644 index 0000000000..3130795951 --- /dev/null +++ b/docs/dbinstancebackup/backup.yaml @@ -0,0 +1,23 @@ +get: + summary: 获取指定RDS实例备份详情信息 + parameters: + - $ref: '../parameters/dbinstance.yaml#/backupId' + responses: + 200: + description: RDS实例备份信息 + schema: + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupResponse' + tags: + - dbinstancebackup + +delete: + summary: 删除指定RDS实例备份 + parameters: + - $ref: '../parameters/dbinstance.yaml#/backupId' + responses: + 200: + description: 被删除RDS实例的备份信息 + schema: + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupResponse' + tags: + - dbinstancebackup diff --git a/docs/dbinstancebackup/backups.yaml b/docs/dbinstancebackup/backups.yaml new file mode 100644 index 0000000000..56ba7ee67d --- /dev/null +++ b/docs/dbinstancebackup/backups.yaml @@ -0,0 +1,32 @@ +get: + summary: 按指定条件列出RDS实例备份列表 + parameters: + - $ref: '../parameters/common.yaml#/limit' + - $ref: '../parameters/common.yaml#/offset' + - $ref: '../parameters/common.yaml#/cloudregion' + - $ref: '../parameters/dbinstance.yaml#/dbinstance' + + + responses: + 200: + description: RDS实例备份列表信息 + schema: + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupListResponse' + tags: + - dbinstancebackup + +post: + summary: 创建RDS实例备份 + parameters: + - in: body + name: dbinstancebackup + required: true + schema: + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupCreate' + responses: + 200: + description: 新建RDS实例账号信息 + schema: + $ref: '../schemas/dbinstance_backup.yaml#/DBInstanceBackupResponse' + tags: + - dbinstancebackup diff --git a/docs/dbinstancedatabase/database.yaml b/docs/dbinstancedatabase/database.yaml new file mode 100644 index 0000000000..c65804119e --- /dev/null +++ b/docs/dbinstancedatabase/database.yaml @@ -0,0 +1,23 @@ +get: + summary: 获取指定RDS实例数据库详情信息 + parameters: + - $ref: '../parameters/dbinstance.yaml#/databaseId' + responses: + 200: + description: RDS实例数据库信息 + schema: + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseResponse' + tags: + - dbinstancedatabase + +delete: + summary: 删除指定RDS实例数据库 + parameters: + - $ref: '../parameters/dbinstance.yaml#/databaseId' + responses: + 200: + description: 被删除RDS实例的数据库信息 + schema: + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseResponse' + tags: + - dbinstancedatabase diff --git a/docs/dbinstancedatabase/databases.yaml b/docs/dbinstancedatabase/databases.yaml new file mode 100644 index 0000000000..034d6aeb89 --- /dev/null +++ b/docs/dbinstancedatabase/databases.yaml @@ -0,0 +1,30 @@ +get: + summary: 按指定条件列出RDS实例数据库列表 + parameters: + - $ref: '../parameters/common.yaml#/limit' + - $ref: '../parameters/common.yaml#/offset' + - $ref: '../parameters/dbinstance.yaml#/dbinstance' + + responses: + 200: + description: RDS实例数据库列表信息 + schema: + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseListResponse' + tags: + - dbinstancedatabase + +post: + summary: 创建RDS实例数据库 + parameters: + - in: body + name: dbinstancedatabase + required: true + schema: + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseCreate' + responses: + 200: + description: 新建RDS实例数据库信息 + schema: + $ref: '../schemas/dbinstance_database.yaml#/DBInstanceDatabaseResponse' + tags: + - dbinstancedatabase diff --git a/docs/index.yaml b/docs/index.yaml index 263b0bcc3c..e16c5d316f 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -525,6 +525,27 @@ paths: $ref: "./dbinstance/dbinstance.yaml" /dbinstances/{dbinstanceId}/networks: $ref: "./dbinstance/dbinstancenetwork.yaml" + /dbinstances/{dbinstanceId}/reboot: + $ref: "./dbinstance/reboot.yaml" + /dbinstances/{dbinstanceId}/sync-status: + $ref: "./dbinstance/sync-status.yaml" + /dbinstances/{dbinstanceId}/purge: + $ref: "./dbinstance/purge.yaml" + /dbinstances/{dbinstanceId}/change-config: + $ref: "./dbinstance/change-config.yaml" + /dbinstances/{dbinstanceId}/public-connection: + $ref: "./dbinstance/public-connection.yaml" + /dbinstances/{dbinstanceId}/recovery: + $ref: "./dbinstance/recovery.yaml" + + + + /dbinstance_skus: + $ref: "./dbinstance_sku/dbinstance_skus.yaml" + /dbinstance_skus/{skuId}: + $ref: "./dbinstance_sku/dbinstance_sku.yaml" + + /dbinstancenetworks: $ref: "./dbinstance/dbinstancenetworks.yaml" @@ -535,19 +556,29 @@ paths: $ref: "./dbinstance/dbinstanceparameter.yaml" /dbinstancedatabases: - $ref: "./dbinstance/dbinstancedatabases.yaml" + $ref: "./dbinstancedatabase/databases.yaml" /dbinstancedatabases/{databaseId}: - $ref: "./dbinstance/dbinstancedatabase.yaml" + $ref: "./dbinstancedatabase/database.yaml" /dbinstancebackups: - $ref: "./dbinstance/dbinstancebackups.yaml" + $ref: "./dbinstancebackup/backups.yaml" /dbinstancebackups/{backupId}: - $ref: "./dbinstance/dbinstancebackup.yaml" + $ref: "./dbinstancebackup/backup.yaml" /dbinstanceaccounts: - $ref: "./dbinstance/dbinstanceaccounts.yaml" + $ref: "./dbinstanceaccount/accounts.yaml" /dbinstanceaccounts/{accountId}: - $ref: "./dbinstance/dbinstanceaccount.yaml" + $ref: "./dbinstanceaccount/account.yaml" + /dbinstanceaccounts/{accountId}/reset-password: + $ref: "./dbinstanceaccount/reset-password.yaml" + /dbinstanceaccounts/{accountId}/grant-privilege: + $ref: "./dbinstanceaccount/grant-privilege.yaml" + /dbinstanceaccounts/{accountId}/revoke-privilege: + $ref: "./dbinstanceaccount/revoke-privilege.yaml" + /dbinstanceaccounts/{accountId}/set-privileges: + $ref: "./dbinstanceaccount/set-privileges.yaml" + + /cloudaccounts: $ref: "./cloudaccount/cloudaccounts.yaml" diff --git a/docs/parameters/dbinstance_sku.yaml b/docs/parameters/dbinstance_sku.yaml new file mode 100644 index 0000000000..89596f5673 --- /dev/null +++ b/docs/parameters/dbinstance_sku.yaml @@ -0,0 +1,6 @@ +skuId: + name: skuId + required: true + in: path + type: string + description: RDS套餐ID diff --git a/docs/schemas/dbinstance.yaml b/docs/schemas/dbinstance.yaml index 5ad2e60e17..6bef9abd6d 100644 --- a/docs/schemas/dbinstance.yaml +++ b/docs/schemas/dbinstance.yaml @@ -13,6 +13,10 @@ DBInstance: type: integer example: 20 description: RDS实例存储大小 + storage_type: + type: string + example: cloud_essd + description: RDS实例存储类型 engine: type: string example: MySQL @@ -50,7 +54,30 @@ DBInstance: type: string example: vpc-2zecuo9v4idebme295ofy description: RDS实例VPC ID - + disable_delete: + type: boolean + example: true + description: 是否锁定(删除保护) + connection_str: + type: string + example: rm-2zeyj104t2b0c2270mo.mysql.rds.aliyuncs.com + description: 外网连接地址 + internal_connection_str: + type: string + example: fsdghello.mysql.rds.aliyuncs.com + description: 内网连接地址 + secgroup_id: + type: string + example: 318e8b0e-c392-4ac2-8562-40303f79a5c9 + description: 安全组id + secgroup: + type: string + example: Default + description: 安全组名称 + iops: + type: integer + example: 500 + description: IOPS大小 DBInstanceListResponse: type: object @@ -73,209 +100,151 @@ DBInstanceResponse: type: object $ref: '#/DBInstance' -DBInstanceNetwork: - allOf: - - $ref: "./common.yaml#/ResourceBaseResponse" - - type: object - description: RDS实例网络 - properties: - dbinstance_id: - type: string - example: "20f8b552-1b86-4595-89e3-49d123b25215" - description: RDS实例ID - ip_addr: - type: string - example: 10.10.140.141 - description: 实例内网IP地址 - network_id: - type: string - example: "38897a74-17b4-4c21-86b3-4f8528ced003" - description: 实例子网ID - -DBInstanceNetworkListResponse: +DBInstanceCreate: type: object properties: - limit: - type: integer - example: 20 - dbinstancenetworks: - type: array - items: - $ref: '#/DBInstanceNetwork' - total: - type: integer - example: 124 + zone1: + type: string + example: test-zone + description: 可用区1名称或ID + zone2: + type: string + example: test-zone + description: 可用区2名称或ID + zone3: + type: string + example: test-zone + description: 可用区3名称或ID + network: + type: string + example: test-network + required: true + description: IP子网ID或名称 + address: + type: string + example: 10.12.2.12 + description: 内网IP,需要在network网段内 + vcpu_count: + type: integer + example: 1 + description: RDS实例CPU核数 + vmem_size_mb: + type: integer + example: 2 + description: RDS实例内存大小 + storage_type: + type: string + required: true + example: cloud_ssd + enum: [local_ssd, cloud_essd, cloud_ssd] + description: RDS实例存储类型 + disk_size_gb: + type: integer + required: true + example: 30 + description: RDS实例存储大小 + port: + type: integer + example: 3306 + description: RDS实例连接端口 + category: + type: string + example: basic + enum: [basic, high_availability, always_on, finance, single, replica] + description: RDS实例类别(单机、高可用、只读) + engine: + type: string + example: MySQL + description: RDS实例引擎 + engine_version: + type: string + example: 8.0 + description: RDS实例引擎版本 + instance_type: + type: string + example: mysql.n4.large.1 + description: RDS实例规格,若不指定CPU及内存,此参数必填 + duration: + type: string + example: 1m + description: 包年包月时长 + master_instance: + type: string + example: test-master-instance + description: 主RDS实例名称或ID,创建只读实例时需要指定 + secgroup: + type: string + example: test-secgroup + description: 安全组名称或ID + password: + type: string + example: bsnt{Hz{Z3p6 + description: 密码信息 + description: + type: string + example: test-description + description: 描述信息 + disable_delete: + type: boolean + example: true + default: true + description: 是否锁定(删除保护) -DBInstanceNetworkResponse: + +DBInstanceChangeConfig: type: object properties: - dbinstancenetwork: + instance_type: + type: string + example: mssql.x4.8xlarge.e2 + description: 更换RDS实例规格 + vcpu_count: + type: integer + example: 6 + description: 更改CPU大小 + vmem_size_mb: + type: int + example: 1024 + description: 更改内存大小 + storage_type: + type: string + example: local_ssd + description: 存储类型 + disk_size_gb: + type: integer + example: 40 + description: 存储磁盘大小 + category: + type: string + example: high_availability + description: 更改实例类型 + +DBInstancePublicConnection: + type: object + properties: + open: + type: boolean + example: true + default: true + description: 关闭或打开外网地址 + +DBInstanceRecovery: + type: object + properties: + databases: type: object - $ref: '#/DBInstanceNetwork' - - - -DBInstanceParameter: - allOf: - - $ref: "./common.yaml#/StandaloneResponse" - - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" - - type: object - description: RDS实例参数 - properties: - key: - type: string - example: read_buffer_size - description: RDS实例参数 - value: - type: string - example: "131072" - description: RDS实例参数值 - readOnly: true - dbinstance_id: - type: string - example: d0fe1519-8de5-4e13-844a-7367f4210f83 - description: RDS实例ID - readOnly: true - -DBInstanceParameterListResponse: - type: object - properties: - limit: - type: integer - example: 20 - dbinstanceparameters: - type: array - items: - $ref: '#/DBInstanceParameter' - total: - type: integer - example: 124 - -DBInstanceParameterResponse: - type: object - properties: - dbinstanceparameter: - type: object - $ref: '#/DBInstanceParameter' - - -DBInstanceDatabase: - allOf: - - $ref: "./common.yaml#/StatusStandaloneResponse" - - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" - - type: object - description: RDS实例参数 - properties: - character_set: - type: string - example: utf8 - description: RDS实例参数 - dbinstance_id: - type: string - example: d0fe1519-8de5-4e13-844a-7367f4210f83 - description: RDS实例ID - readOnly: true - -DBInstanceDatabaseListResponse: - type: object - properties: - limit: - type: integer - example: 20 - dbinstancedatabases: - type: array - items: - $ref: '#/DBInstanceDatabase' - total: - type: integer - example: 124 - -DBInstanceDatabaseResponse: - type: object - properties: - dbinstancedatabase: - type: object - $ref: '#/DBInstanceDatabase' - -DBInstanceBackup: - allOf: - - $ref: "./common.yaml#/StatusStandaloneResponse" - - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" - - type: object - description: RDS实例参数 - properties: - start_time: - type: string - example: "2019-06-22T02:31:08.000000Z" - description: RDS实例备份开始时间 - end_time: - type: string - example: "2019-06-22T02:33:24.000000Z" - description: RDS实例备份结束时间 - backup_mode: - type: string - example: full_backup - description: RDS实例备份类型,手动或自动 - backup_size_mb: - type: integer - example: 21 - description: 备份大小 - dbinstance_id: - type: string - example: d0fe1519-8de5-4e13-844a-7367f4210f83 - description: RDS实例ID - readOnly: true - cloudregion_id: - type: string - example: c8b87bc0-fd97-4b28-83ad-2a8f9b755bab - description: RDS备份所属的region id - readOnly: true - -DBInstanceBackupListResponse: - type: object - properties: - limit: - type: integer - example: 20 - dbinstancebackups: - type: array - items: - $ref: '#/DBInstanceBackup' - total: - type: integer - example: 124 - -DBInstanceBackupResponse: - type: object - properties: + additionalProperties: + type: string + example: + source-database1: dest-database1 + source-database2: dest-database2 + description: 源数据库名称->目的数据库名称 + description: 数据库列表,为空代表恢复整个备份内容,databases源数据库内容可以从backup的dbnames中获取,目的数据库不可与目标rds实例数据库重复 dbinstancebackup: - type: object - $ref: '#/DBInstanceBackup' + type: string + example: test-backup + required: true + description: 实例备份名称或ID -DBInstanceAccount: - allOf: - - $ref: "./common.yaml#/StandaloneResponse" - - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" - -DBInstanceAccountListResponse: - type: object - properties: - limit: - type: integer - example: 20 - dbinstanceaccounts: - type: array - items: - $ref: '#/DBInstanceAccount' - total: - type: integer - example: 124 - -DBInstanceAccountResponse: - type: object - properties: - dbinstanceaccount: - type: object - $ref: '#/DBInstanceAccount' \ No newline at end of file diff --git a/docs/schemas/dbinstance_account.yaml b/docs/schemas/dbinstance_account.yaml new file mode 100644 index 0000000000..2193ae4098 --- /dev/null +++ b/docs/schemas/dbinstance_account.yaml @@ -0,0 +1,134 @@ +DBInstanceAccountListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstanceaccounts: + type: array + items: + $ref: '#/DBInstanceAccount' + total: + type: integer + example: 124 + +DBInstanceAccountResponse: + type: object + properties: + dbinstanceaccount: + type: object + $ref: '#/DBInstanceAccount' + +DBInstanceAccount: + allOf: + - $ref: "./common.yaml#/StandaloneResponse" + - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" + - type: object + properties: + status: + type: string + example: available + description: RDS实例账号状态 + dbinstanceprivileges: + type: array + items: + $ref: '#/DBInstanceAccountPrivilege' + +DBInstanceAccountPrivilege: + type: object + properties: + database: + type: string + example: hello + description: 数据库名称 + account: + type: string + example: root + description: 账号名称 + dbinstancedatabase_id: + type: string + example: aad86533-a588-4342-8402-a1b11a1a7bf5 + description: 数据库ID + privileges: + type: string + example: ddl + description: 用户对数据库所拥有的权限 + + +DBInstanceAccountCreate: + type: object + properties: + dbinstance: + type: string + example: test-rds + required: true + description: RDS实例名称或ID + name: + type: string + example: test-user + required: true + description: 用户名称 + description: + type: string + example: string + description: 描述信息 + password: + type: string + example: fjs9@33f8 + description: 密码 + privileges: + type: array + items: + $ref: '#/DBInstanceAccountPrivilegeCreate' + +DBInstanceAccountPrivilegeCreate: + type: object + properties: + database: + type: string + example: test-database + description: 数据库名称或ID + privilege: + type: string + example: rw + description: 数据库权限 + +DBInstanceAccountResetPassword: + type: object + properties: + password: + type: string + example: 123@qweASD + description: 重置的账号密码,为空则自动生成 + +DBInstanceAccountGrantPrivilege: + type: object + properties: + database: + type: string + example: test-database + required: true + description: 实例的数据库名称或ID + privilege: + type: string + example: rw + required: true + description: 权限 + +DBInstanceAccountRevokePrivilege: + type: object + properties: + database: + type: string + example: test-database + required: true + description: 实例的数据库名称或ID + +DBInstanceAccountSetPrivileges: + type: object + properties: + privileges: + type: array + items: + $ref: '#/DBInstanceAccountPrivilegeCreate' + diff --git a/docs/schemas/dbinstance_backup.yaml b/docs/schemas/dbinstance_backup.yaml new file mode 100644 index 0000000000..af614f1a22 --- /dev/null +++ b/docs/schemas/dbinstance_backup.yaml @@ -0,0 +1,100 @@ +DBInstanceBackup: + allOf: + - $ref: "./common.yaml#/StatusStandaloneResponse" + - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" + - type: object + description: RDS实例参数 + properties: + start_time: + type: string + example: "2019-06-22T02:31:08.000000Z" + description: RDS实例备份开始时间 + end_time: + type: string + example: "2019-06-22T02:33:24.000000Z" + description: RDS实例备份结束时间 + backup_mode: + type: string + example: full_backup + description: RDS实例备份类型,手动或自动 + backup_size_mb: + type: integer + example: 21 + description: 备份大小 + dbinstance_id: + type: string + example: d0fe1519-8de5-4e13-844a-7367f4210f83 + description: RDS实例ID + readOnly: true + dbinstance: + type: string + example: test-rds + description: RDS实例名称 + engine: + type: string + example: MySQL + description: 备份的数据库类型 + engine_version: + type: string + example: 5.6 + description: 北京的数据库版本 + cloudregion_id: + type: string + example: c8b87bc0-fd97-4b28-83ad-2a8f9b755bab + description: RDS备份所属的region id + readOnly: true + region: + type: string + example: 阿里云 北京 + description: RDS备份所在的区域名称 + dbnames: + type: string + example: test-database1,test-database2 + description: 备份包含的数据库列表 + +DBInstanceBackupListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstancebackups: + type: array + items: + $ref: '#/DBInstanceBackup' + total: + type: integer + example: 124 + +DBInstanceBackupResponse: + type: object + properties: + dbinstancebackup: + type: object + $ref: '#/DBInstanceBackup' + +DBInstanceBackupCreate: + type: object + properties: + dbinstance: + type: string + example: test-rds + required: true + description: RDS实例名称或ID + name: + type: string + example: test-backup + required: true + description: 备份名称 + description: + type: string + example: test-description + description: 描述信息 + databases: + type: array + items: + type: string + example: test-database + description: 需要备份的数据库 + + diff --git a/docs/schemas/dbinstance_database.yaml b/docs/schemas/dbinstance_database.yaml new file mode 100644 index 0000000000..c45e4d171d --- /dev/null +++ b/docs/schemas/dbinstance_database.yaml @@ -0,0 +1,103 @@ +DBInstanceDatabase: + allOf: + - $ref: "./common.yaml#/StatusStandaloneResponse" + - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" + - type: object + description: RDS实例数据库 + properties: + character_set: + type: string + example: utf8 + description: RDS数据库字符集 + dbinstance_id: + type: string + example: d0fe1519-8de5-4e13-844a-7367f4210f83 + description: RDS实例ID + readOnly: true + dbinstanceprivileges: + type: array + items: + $ref: '#/DBInstanceAccountPrivilege' + +DBInstanceAccountPrivilege: + type: object + properties: + database: + type: string + example: hello + description: 数据库名称 + account: + type: string + example: root + description: 账号名称 + dbinstancedatabase_id: + type: string + example: aad86533-a588-4342-8402-a1b11a1a7bf5 + description: 数据库ID + privileges: + type: string + example: ddl + description: 用户对数据库所拥有的权限 + + + +DBInstanceDatabaseListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstancedatabases: + type: array + items: + $ref: '#/DBInstanceDatabase' + total: + type: integer + example: 124 + +DBInstanceDatabaseResponse: + type: object + properties: + dbinstancedatabase: + type: object + $ref: '#/DBInstanceDatabase' + +DBInstanceDatabaseCreate: + type: object + properties: + dbinstance: + type: string + example: test-rds + required: true + description: RDS实例名称或ID + name: + type: string + example: test-database + required: true + description: 数据库名称 + description: + type: string + example: test-description + description: 描述信息 + character_set: + type: string + example: utf8 + description: 数据库字符集 + accounts: + type: array + items: + $ref: '#/DBInstanceDatabaseAccountCreate' + +DBInstanceDatabaseAccountCreate: + type: object + properties: + account: + type: string + example: test-user + description: 需要授予权限的账号名称 + privilege: + type: string + example: rw + description: 账号权限 + + diff --git a/docs/schemas/dbinstance_network.yaml b/docs/schemas/dbinstance_network.yaml new file mode 100644 index 0000000000..8374e44b37 --- /dev/null +++ b/docs/schemas/dbinstance_network.yaml @@ -0,0 +1,42 @@ +DBInstanceNetwork: + allOf: + - $ref: "./common.yaml#/ResourceBaseResponse" + - type: object + description: RDS实例网络 + properties: + dbinstance_id: + type: string + example: "20f8b552-1b86-4595-89e3-49d123b25215" + description: RDS实例ID + ip_addr: + type: string + example: 10.10.140.141 + description: 实例内网IP地址 + network_id: + type: string + example: "38897a74-17b4-4c21-86b3-4f8528ced003" + description: 实例子网ID + + +DBInstanceNetworkListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstancenetworks: + type: array + items: + $ref: '#/DBInstanceNetwork' + total: + type: integer + example: 124 + +DBInstanceNetworkResponse: + type: object + properties: + dbinstancenetwork: + type: object + $ref: '#/DBInstanceNetwork' + + diff --git a/docs/schemas/dbinstance_parameter.yaml b/docs/schemas/dbinstance_parameter.yaml new file mode 100644 index 0000000000..127bd19503 --- /dev/null +++ b/docs/schemas/dbinstance_parameter.yaml @@ -0,0 +1,44 @@ +DBInstanceParameterListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstanceparameters: + type: array + items: + $ref: '#/DBInstanceParameter' + total: + type: integer + example: 124 + +DBInstanceParameterResponse: + type: object + properties: + dbinstanceparameter: + type: object + $ref: '#/DBInstanceParameter' + +DBInstanceParameter: + allOf: + - $ref: "./common.yaml#/StandaloneResponse" + - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" + - type: object + description: RDS实例参数 + properties: + key: + type: string + example: read_buffer_size + description: RDS实例参数 + value: + type: string + example: "131072" + description: RDS实例参数值 + readOnly: true + dbinstance_id: + type: string + example: d0fe1519-8de5-4e13-844a-7367f4210f83 + description: RDS实例ID + readOnly: true + + diff --git a/docs/schemas/dbinstance_sku.yaml b/docs/schemas/dbinstance_sku.yaml new file mode 100644 index 0000000000..aa496a4223 --- /dev/null +++ b/docs/schemas/dbinstance_sku.yaml @@ -0,0 +1,105 @@ +DBInstanceSku: + allOf: + - $ref: "./common.yaml#/CloudregionResourceBaseResponse" + - $ref: "./common.yaml#/ExternalizedResourceBaseResponse" + - type: object + description: RDS套餐信息 + properties: + disk_size_gb: + type: integer + example: 20 + description: RDS实例存储大小 + storage_type: + type: string + example: cloud_essd + description: RDS实例存储类型 + engine: + type: string + example: MySQL + description: RDS实例引擎 + readOnly: true + engine_version: + type: string + example: "5.7" + description: RDS实例引擎版本 + instance_type: + type: string + example: mysql.n1.micro.1 + description: RDS实例规格 + category: + type: string + example: basic + description: RDS实例高可用类型 + vcpu_count: + type: integer + example: 1 + description: RDS实例CPU核数 + vmem_size_mb: + type: integer + example: 1024 + description: RDS实例内存大小 + iops: + type: integer + example: 9000 + description: iops + max_disk_size_gb: + type: integer + example: 4000 + description: 最大存储大小 + min_disk_size_gb: + type: integer + example: 20 + description: 最小存储大小 + disk_size_step: + type: integer + example: 5 + description: 存储步长,单位GB + provider: + type: string + example: Aliyun + description: 平台 + status: + type: string + example: available + description: 套餐状态 + zone1: + type: string + example: fa39a6af-eafc-43bb-8683-bfdc04d6e18d + description: zone1的ID + zone2: + type: string + example: fa39a6af-eafc-43bb-8683-bfdc04d6e18d + description: zone2的ID + zone3: + type: string + example: fa39a6af-eafc-43bb-8683-bfdc04d6e18d + description: zone3的ID + zone_id: + type: string + example: us-east-1b + description: 公有云套餐zone代码 + + +DBInstanceSkuListResponse: + type: object + properties: + limit: + type: integer + example: 20 + dbinstance_skus: + type: array + items: + $ref: '#/DBInstanceSku' + total: + type: integer + example: 124 + +DBInstanceSkuResponse: + type: object + properties: + dbinstance_sku: + type: object + $ref: '#/DBInstanceSku' + + + diff --git a/pkg/apis/compute/dbinstance.go b/pkg/apis/compute/dbinstance.go new file mode 100644 index 0000000000..8d533be414 --- /dev/null +++ b/pkg/apis/compute/dbinstance.go @@ -0,0 +1,56 @@ +package compute + +import "yunion.io/x/onecloud/pkg/apis" + +type SDBInstanceCreateInput struct { + apis.Meta + + Name string + Description string + DisableDelete *bool + NetworkId string + Address string + MasterInstanceId string + SecgroupId string + Zone1 string + Zone2 string + Zone3 string + ZoneId string + CloudregionId string + Cloudregion string + VpcId string + ManagerId string + NetworkExternalId string + BillingType string + BillingCycle string + InstanceType string + Engine string + EngineVersion string + Category string + StorageType string + DiskSizeGB int + Password string + + VcpuCount int + VmemSizeMb int + Provider string +} + +type SDBInstanceChangeConfigInput struct { + apis.Meta + + InstanceType string + VCpuCount int + VmemSizeMb int + StorageType string + DiskSizeGB int + Category string +} + +type SDBInstanceRecoveryConfigInput struct { + apis.Meta + + DBInstancebackup string + DBInstancebackupId string `json:"dbinstancebackup_id"` + Databases map[string]string `json:"allowempty"` +} diff --git a/pkg/apis/compute/dbinstance_account.go b/pkg/apis/compute/dbinstance_account.go new file mode 100644 index 0000000000..608b7c3bd3 --- /dev/null +++ b/pkg/apis/compute/dbinstance_account.go @@ -0,0 +1,23 @@ +package compute + +import "yunion.io/x/onecloud/pkg/apis" + +type SDBInstanceAccountPrivilege struct { + Database string + DBInstancedatabaseId string + Privilege string +} + +type SDBInstanceAccountCreateInput struct { + apis.Meta + + DBInstanceId string `json:"dbinstance_id"` + Name string + Description string + Password string + Privileges []SDBInstanceAccountPrivilege +} + +type SDBInstanceSetPrivilegesInput struct { + Privileges []SDBInstanceAccountPrivilege +} diff --git a/pkg/apis/compute/dbinstance_backup.go b/pkg/apis/compute/dbinstance_backup.go new file mode 100644 index 0000000000..9d481fb0ac --- /dev/null +++ b/pkg/apis/compute/dbinstance_backup.go @@ -0,0 +1,18 @@ +package compute + +import "yunion.io/x/onecloud/pkg/apis" + +type SDBInstanceBackupCreateInput struct { + apis.Meta + + DBInstanceId string `json:"dbinstance_id"` + Name string + Description string + DBNames string + Databases []string + Engine string + EngineVersion string + CloudregionId string + BackupMode string + ManagerId string +} diff --git a/pkg/apis/compute/dbinstance_const.go b/pkg/apis/compute/dbinstance_const.go index 0b4498be91..a9b31ef33a 100644 --- a/pkg/apis/compute/dbinstance_const.go +++ b/pkg/apis/compute/dbinstance_const.go @@ -15,39 +15,73 @@ package compute const ( - DBINSTANCE_DEPLOYING = "deploying" //部署中 - DBINSTANCE_RUNNING = "running" //使用中 - DBINSTANCE_REBOOTING = "rebooting" //重启中 - DBINSTANCE_MIGRATING = "migrating" //迁移中 - DBINSTANCE_BACKING_UP = "backing_up" //备份中 - DBINSTANCE_RESTORING = "restoring" //备份恢复中 - DBINSTANCE_IMPORTING = "importing" //数据导入中 - DBINSTANCE_CLONING = "cloning" //克隆中 - DBINSTANCE_DELETING = "deleting" //删除中 - DBINSTANCE_UNKNOWN = "unknown" + //实例状态 + DBINSTANCE_DEPLOYING = "deploying" //部署中 + DBINSTANCE_RUNNING = "running" //运行中 + DBINSTANCE_REBOOTING = "rebooting" //重启中 + DBINSTANCE_MIGRATING = "migrating" //迁移中 + DBINSTANCE_BACKING_UP = "backing_up" //备份中 + DBINSTANCE_RESTORING = "restoring" //备份恢复中 + DBINSTANCE_RESTORE_FAILED = "restore_failed" + DBINSTANCE_IMPORTING = "importing" //数据导入中 + DBINSTANCE_CLONING = "cloning" //克隆中 + DBINSTANCE_DELETING = "deleting" //删除中 + DBINSTANCE_DELETE_FAILED = "delete_failed" //删除失败 + DBINSTANCE_UNKNOWN = "unknown" - DBINSTANCE_BACKUP_READY = "ready" - DBINSTANCE_BACKUP_CREATING = "creating" - DBINSTANCE_BACKUP_DELETING = "deleting" - DBINSTANCE_BACKUP_FAILED = "failed" - DBINSTANCE_BACKUP_UNKNOWN = "unknown" + DBINSTANCE_CHANGE_CONFIG = "change_config" //调整配置 + DBINSTANCE_CHANGE_CONFIG_FAILED = "change_config_failed" //调整配置失败 - BACKUP_MODE_AUTOMATED = "automated" - BACKUP_MODE_MANUAL = "manual" + DBINSTANCE_RENEWING = "renewing" //续费中 + DBINSTANCE_RENEW_FAILED = "renew_failed" //续费失败 - DBINSTANCE_DATABASE_CREATING = "creating" - DBINSTANCE_DATABASE_RUNNING = "running" - DBINSTANCE_DATABASE_DELETING = "deleting" + DBINSTANCE_SYNC_STATUS = "sync_status" //同步状态 - DBINSTANCE_USER_UNAVAILABLE = "unavailable" - DBINSTANCE_USER_AVAILABLE = "available" + DBINSTANCE_REBOOT_FAILED = "reboot_failed" //重启失败 + DBINSTANCE_CREATE_FAILED = "create_failed" //创建失败 - DATABASE_PRIVILEGE_RW = "rw" - DATABASE_PRIVILEGE_R = "r" + DBINSTANCE_FAILE = "failed" //操作失败 + + //备份状态 + DBINSTANCE_BACKUP_READY = "ready" //正常 + DBINSTANCE_BACKUP_CREATING = "creating" //创建中 + DBINSTANCE_BACKUP_CREATE_FAILED = "create_failed" //创建失败 + DBINSTANCE_BACKUP_DELETING = "deleting" //删除中 + DBINSTANCE_BACKUP_DELETE_FAILED = "delete_failed" //删除失败 + DBINSTANCE_BACKUP_FAILED = "failed" //异常 + DBINSTANCE_BACKUP_UNKNOWN = "unknown" //未知 + + //备份模式 + BACKUP_MODE_AUTOMATED = "automated" //自动 + BACKUP_MODE_MANUAL = "manual" //手动 + + //实例数据库状态 + DBINSTANCE_DATABASE_CREATING = "creating" //创建中 + DBINSTANCE_DATABASE_CREATE_FAILE = "create_failed" //创建失败 + DBINSTANCE_DATABASE_RUNNING = "running" //正常 + DBINSTANCE_DATABASE_DELETING = "deleting" //删除中 + DBINSTANCE_DATABASE_DELETE_FAILED = "delete_failed" //删除失败 + + //实例用户状态 + DBINSTANCE_USER_UNAVAILABLE = "unavailable" //不可用 + DBINSTANCE_USER_AVAILABLE = "available" //正常 + DBINSTANCE_USER_CREATING = "creating" //创建中 + DBINSTANCE_USER_CREATE_FAILED = "create_failed" //创建失败 + DBINSTANCE_USER_DELETING = "deleting" //删除中 + DBINSTANCE_USER_DELETE_FAILED = "delete_failed" //删除失败 + DBINSTANCE_USER_RESET_PASSWD = "reset_passwd" //重置密码中 + DBINSTANCE_USER_GRANT_PRIVILEGE = "grant_privilege" //赋予权限中 + DBINSTANCE_USER_SET_PRIVILEGE = "set_privilege" //设置权限中 + DBINSTANCE_USER_REVOKE_PRIVILEGE = "revoke_privilege" //解除权限中 + DBINSTANCE_USER_RESET_PASSWD_FAILED = "reset_passwd_failed" //重置密码失败 + + //数据库权限 + DATABASE_PRIVILEGE_RW = "rw" //读写 + DATABASE_PRIVILEGE_R = "r" //只读 DATABASE_PRIVILEGE_DDL = "ddl" DATABASE_PRIVILEGE_DML = "dml" DATABASE_PRIVILEGE_OWNER = "owner" - DATABASE_PRIVILEGE_CUSTOM = "custom" + DATABASE_PRIVILEGE_CUSTOM = "custom" //自定义 DBINSTANCE_TYPE_MYSQL = "MySQL" DBINSTANCE_TYPE_SQLSERVER = "SQLServer" @@ -56,10 +90,35 @@ const ( DBINSTANCE_TYPE_ORACLE = "Oracle" DBINSTANCE_TYPE_PPAS = "PPAS" - DBINSTANCE_CATEGORY_BASIC = "basic" //基础版 - DBINSTANCE_CATEGORY_HA = "high_availability" //高可用或主备 - DBINSTANCE_CATEGORY_ALWAYSON = "always_on" //集群版 - DBINSTANCE_CATEGORY_FINANCE = "finance" - DBINSTANCE_CATEGORY_SINGLE = "single" //单机 - DBINSTANCE_CATEGORY_Replica = "replica" //只读 + //阿里云实例类型 + ALIYUN_DBINSTANCE_CATEGORY_BASIC = "basic" //基础版 + ALIYUN_DBINSTANCE_CATEGORY_HA = "high_availability" //高可用 + ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON = "always_on" //集群版 + ALIYUN_DBINSTANCE_CATEGORY_FINANCE = "finance" //金融版 + + //华为云实例类型 + HUAWEI_DBINSTANCE_CATEGORY_HA = "ha" //主备 + HUAWEI_DBINSTANCE_CATEGORY_SINGLE = "single" //单机 + HUAWEI_DBINSTANCE_CATEGORY_REPLICA = "replica" //只读 + + //阿里云存储类型 + ALIYUN_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD = "local_ssd" //本地盘SSD盘 + ALIYUN_DBINSTANCE_STORAGE_TYPE_CLOUD_ESSD = "cloud_essd" //ESSD云盘 + ALIYUN_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD = "cloud_ssd" //SSD云盘 + + //华为云存储类型 + HUAWEI_DBINSTANCE_STORAGE_TYPE_SSD = "SSD" //超高IO云硬盘 + HUAWEI_DBINSTANCE_STORAGE_TYPE_SAS = "SAS" //高IO云硬盘 + HUAWEI_DBINSTANCE_STORAGE_TYPE_SATA = "SATA" //普通IO云硬盘 + +) + +var ( + ALIYUN_MYSQL_DENY_KEYWORKD []string = []string{ + "accessible", "account", "action", "actual", "add", "after", "against", "aggregate", "all", "algorithm", "alter", "always", "analyse", "analyze", "and", "any", "as", "asc", "ascii", "asensitive", "at", "auto_increment", "autoextend_size", "avg", "avg_row_length", "backup", "before", "begin", "between", "bigint", "binary", "binlog", "bit", "blob", "block", "bool", "boolean", "both", "btree", "by", "byte", "cache", "call", "cascade", "cascaded", "case", "catalog_name", "chain", "change", "changed", "channel", "char", "character", "charset", "check", "checksum", "cipher", "class_origin", "client", "close", "coalesce", "code", "collate", "collation", "column", "column_format", "column_name", "columns", "comment", "commit", "committed", "compact", "completion", "compression", "compressed", "encryption", "concurrent", "condition", "connection", "consistent", "constraint", "constraint_catalog", "constraint_name", "constraint_schema", "contains", "context", "continue", "convert", "cpu", "create", "cross", "cube", "current", "current_date", "current_time", "current_timestamp", "current_user", "cursor", "cursor_name", "data", "database", "databases", "datafile", "date", "datetime", "day", "day_hour", "day_microsecond", "day_minute", "day_second", "deallocate", "dec", "decimal", "declare", "default", "default_auth", "definer", "delayed", "delay_key_write", "desc", "describe", "des_key_file", "deterministic", "diagnostics", "directory", "disable", "discard", "disk", "distinct", "distinctrow", "div", "do", "double", "drop", "dual", "dumpfile", "duplicate", "dynamic", "each", "else", "elseif", "enable", "enclosed", "end", "ends", "engine", "engines", "enum", "error", "errors", "escape", "escaped", "event", "events", "every", "exchange", "execute", "exists", "exit", "expansion", "export", "expire", "explain", "extended", "extent_size", "false", "fast", "faults", "fetch", "fields", "file", "file_block_size", "filter", "first", "fixed", "float", "float4", "float8", "flush", "follows", "for", "force", "foreign", "format", "found", "from", "full", "fulltext", "function", "general", "group_replication", "geometry", "geometrycollection", "get_format", "get", "generated", "global", "grant", "grants", "group", "handler", "hash", "having", "help", "high_priority", "host", "hosts", "hour", "hour_microsecond", "hour_minute", "hour_second", "identified", "if", "ignore", "ignore_server_ids", "import", "in", "index", "indexes", "infile", "initial_size", "inner", "inout", "insensitive", "insert_method", "install", "instance", "int", "int1", "int2", "int3", "int4", "int8", "integer", "interval", "into", "io", "io_after_gtids", "io_before_gtids", "io_thread", "ipc", "is", "isolation", "issuer", "iterate", "invoker", "join", "json", "key", "keys", "key_block_size", "kill", "language", "last", "leading", "leave", "leaves", "left", "less", "level", "like", "limit", "linear", "lines", "linestring", "list", "load", "local", "localtime", "localtimestamp", "lock", "locks", "logfile", "logs", "long", "longblob", "longtext", "loop", "low_priority", "master", "master_auto_position", "master_bind", "master_connect_retry", "master_delay", "master_host", "master_log_file", "master_log_pos", "master_password", "master_port", "master_retry_count", "master_server_id", "master_ssl", "master_ssl_ca", "master_ssl_capath", "master_tls_version", "master_ssl_cert", "master_ssl_cipher", "master_ssl_crl", "master_ssl_crlpath", "master_ssl_key", "master_ssl_verify_server_cert", "master_user", "master_heartbeat_period", "match", "max_connections_per_hour", "max_queries_per_hour", "max_rows", "max_size", "max_updates_per_hour", "max_user_connections", "maxvalue", "medium", "mediumblob", "mediumint", "mediumtext", "memory", "merge", "message_text", "microsecond", "middleint", "migrate", "minute", "minute_microsecond", "minute_second", "min_rows", "mod", "mode", "modifies", "modify", "month", "multilinestring", "multipoint", "multipolygon", "mutex", "mysql_errno", "name", "names", "national", "natural", "ndb", "ndbcluster", "nchar", "never", "new", "next", "no", "no_wait", "nodegroup", "none", "not", "no_write_to_binlog", "null", "number", "numeric", "nvarchar", "offset", "on", "one", "only", "open", "optimize", "optimizer_costs", "options", "option", "optionally", "or", "order", "out", "outer", "outfile", "owner", "pack_keys", "parser", "page", "parse_gcol_expr", "partial", "partition", "partitioning", "partitions", "password", "phase", "plugin", "plugins", "plugin_dir", "point", "polygon", "port", "precedes", "precision", "prepare", "preserve", "prev", "primary", "privileges", "procedure", "process", "processlist", "profile", "profiles", "proxy", "purge", "quarter", "query", "quick", "range", "rds_charsetnum", "rds_connection_id", "rds_prepare_begin_id", "rds_current_connection", "rds_db", "rds_rw_mode", "rds_host_show", "rds_resetconnection", "read", "read_only", "read_write", "reads", "real", "rebuild", "recover", "redo_buffer_size", "redofile", "redundant", "references", "regexp", "relay", "relaylog", "relay_log_file", "relay_log_pos", "relay_thread", "release", "reload", "remove", "rename", "reorganize", "repair", "repeatable", "replication", "replicate_do_db", "replicate_ignore_db", "replicate_do_table", "replicate_ignore_table", "replicate_wild_do_table", "replicate_wild_ignore_table", "replicate_rewrite_db", "repeat", "require", "reset", "resignal", "restore", "restrict", "resume", "returned_sqlstate", "return", "returns", "reverse", "revoke", "right", "rlike", "rollback", "rollup", "routine", "rotate", "row", "row_count", "rows", "row_format", "rtree", "savepoint", "schedule", "schema", "schema_name", "schemas", "second", "second_microsecond", "security", "sensitive", "separator", "serial", "serializable", "session", "server", "set", "share", "show", "shutdown", "signal", "signed", "simple", "slave", "slow", "snapshot", "smallint", "socket", "some", "soname", "sounds", "source", "spatial", "specific", "sql", "sqlexception", "sqlstate", "sqlwarning", "sql_after_gtids", "sql_after_mts_gaps", "sql_before_gtids", "sql_big_result", "sql_buffer_result", "sql_cache", "sql_calc_found_rows", "sql_filters", "sql_no_cache", "sql_small_result", "sql_thread", "sql_tsi_second", "sql_tsi_minute", "sql_tsi_hour", "sql_tsi_day", "sql_tsi_week", "sql_tsi_month", "sql_tsi_quarter", "sql_tsi_year", "ssl", "stacked", "start", "starting", "starts", "stats_auto_recalc", "stats_persistent", "stats_sample_pages", "status", "stop", "storage", "stored", "straight_join", "string", "subclass_origin", "subject", "subpartition", "subpartitions", "super", "suspend", "swaps", "switches", "table", "table_name", "tables", "tablespace", "table_checksum", "temporary", "temptable", "terminated", "text", "than", "then", "time", "timestamp", "timestampadd", "timestampdiff", "tinyblob", "tinyint", "tinytext", "to", "trailing", "transaction", "trigger", "triggers", "true", "truncate", "type", "types", "uncommitted", "undefined", "undo_buffer_size", "undofile", "undo", "unicode", "union", "unique", "unknown", "unlock", "uninstall", "unsigned", "until", "upgrade", "usage", "use", "user", "user_resources", "use_frm", "using", "utc_date", "utc_time", "utc_timestamp", "validation", "value", "values", "varbinary", "varchar", "varcharacter", "variables", "varying", "wait", "warnings", "week", "weight_string", "when", "where", "while", "view", "virtual", "with", "without", "work", "wrapper", "write", "x509", "xor", "xa", "xid", "xml", "year", "year_month", "zerofill", "lag", "rds_audit", "rds_inner_backup", "rds_change_user", "rds_user", "rds_ip", "rds_add_proxy_protocol_networks", "rds_show_proxy_protocol_ips", "sync", "delete", "insert", "replace", "select", "update", "adddate", "bit_and", "bit_or", "bit_xor", "cast", "count", "curdate", "curtime", "date_add", "date_sub", "extract", "group_concat", "json_objectagg", "json_arrayagg", "max", "mid", "min", "now", "position", "session_user", "std", "stddev", "stddev_pop", "stddev_samp", "subdate", "substr", "substring", "sum", "sysdate", "system_user", "trim", "variance", "var_pop", "var_samp", "bka", "bnl", "dupsweedout", "firstmatch", "intoexists", "loosescan", "materialization", "max_execution_time", "no_bka", "no_bnl", "no_icp", "no_mrr", "no_range_optimization", "no_semijoin", "mrr", "qb_name", "semijoin", "subquery", + } + + 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", + } ) diff --git a/pkg/apis/compute/dbinstance_database.go b/pkg/apis/compute/dbinstance_database.go new file mode 100644 index 0000000000..74ee30dab1 --- /dev/null +++ b/pkg/apis/compute/dbinstance_database.go @@ -0,0 +1,20 @@ +package compute + +import "yunion.io/x/onecloud/pkg/apis" + +type SDBInstanceDatabasePrivilege struct { + Account string + DBInstancedccountId string + Privilege string +} + +type SDBInstanceDatabaseCreateInput struct { + apis.Meta + + DBInstanceId string `json:"dbinstance_id"` + CharacterSet string + Name string + Description string + Accounts []SDBInstanceDatabasePrivilege + DBInstanceaccountId string +} diff --git a/pkg/apis/compute/dbinstance_sku_const.go b/pkg/apis/compute/dbinstance_sku_const.go new file mode 100644 index 0000000000..0435ea6745 --- /dev/null +++ b/pkg/apis/compute/dbinstance_sku_const.go @@ -0,0 +1,5 @@ +package compute + +const ( + DBINSTANCE_SKU_AVAILABLE = "available" +) diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index 7c4a9e7681..17e3f02267 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -239,6 +239,14 @@ const ( ACT_UPLOAD_OBJECT = "upload_obj" ACT_DELETE_OBJECT = "delete_obj" ACT_MKDIR = "mkdir" + + ACT_GRANT_PRIVILEGE = "grant_privilege" + ACT_REVOKE_PRIVILEGE = "revoke_privilege" + ACT_SET_PRIVILEGES = "set_privileges" + ACT_REBOOT = "reboot" + ACT_RESTORE = "restore" + ACT_CHANGE_CONFIG = "change_config" + ACT_RESET_PASSWORD = "reset_password" ) type SOpsLogManager struct { diff --git a/pkg/cloudcommon/policy/defaults.go b/pkg/cloudcommon/policy/defaults.go index 24857930e9..8924d67d1c 100644 --- a/pkg/cloudcommon/policy/defaults.go +++ b/pkg/cloudcommon/policy/defaults.go @@ -126,6 +126,18 @@ var ( Action: PolicyActionGet, Result: rbacutils.Allow, }, + { + Service: "compute", + Resource: "dbinstance_skus", + Action: PolicyActionList, + Result: rbacutils.Allow, + }, + { + Service: "compute", + Resource: "dbinstance_skus", + Action: PolicyActionGet, + Result: rbacutils.Allow, + }, { Service: "compute", Resource: "serverskus", diff --git a/pkg/cloudprovider/dbinstance.go b/pkg/cloudprovider/dbinstance.go index d517c6b961..4e2f7d5f3c 100644 --- a/pkg/cloudprovider/dbinstance.go +++ b/pkg/cloudprovider/dbinstance.go @@ -14,6 +14,8 @@ package cloudprovider +import "yunion.io/x/onecloud/pkg/util/billing" + type SDBInstanceNetwork struct { IP string NetworkId string @@ -23,3 +25,68 @@ type SExtraIp struct { IP string URL string } + +type SInstanceType struct { + InstanceType string + ZoneIds []string +} + +type SManagedDBInstanceCreateConfig struct { + SInstanceType + Name string + Description string + StorageType string + DiskSizeGB int + InstanceType string + InstanceTypes []SInstanceType + VcpuCount int + VmemSizeMb int + VpcId string + SecgroupId string + NetworkId string + Address string + Engine string + EngineVersion string + Category string + Port int + MasterInstanceId string + Password string + Username string + + BillingCycle *billing.SBillingCycle +} + +type SManagedDBInstanceChangeConfig struct { + DiskSizeGB int + StorageType string + InstanceType string +} + +type SDBInstanceDatabaseCreateConfig struct { + Name string + CharacterSet string + Description string +} + +type SDBInstancePrivilege struct { + Account string + Database string + Privilege string +} + +type SDBInstanceAccountCreateConfig struct { + Name string + Description string + Password string +} + +type SDBInstanceBackupCreateConfig struct { + Name string + Description string + Databases []string +} + +type SDBInstanceRecoveryConfig struct { + BackupId string + Databases map[string]string +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 4867f6b63f..3984b34382 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -121,7 +121,11 @@ type ICloudRegion interface { GetIBucketByName(name string) (ICloudBucket, error) GetIDBInstances() ([]ICloudDBInstance, error) + GetIDBInstanceById(instanceId string) (ICloudDBInstance, error) GetIDBInstanceBackups() ([]ICloudDBInstanceBackup, error) + GetIDBInstanceBackupById(backupId string) (ICloudDBInstanceBackup, error) + + CreateIDBInstance(desc *SManagedDBInstanceCreateConfig) (ICloudDBInstance, error) GetIElasticcaches() ([]ICloudElasticcache, error) @@ -714,6 +718,10 @@ type ICloudDBInstance interface { IVirtualResource IBillingResource + Reboot() error + + GetMasterInstanceId() string + GetSecurityGroupId() string GetPort() int GetEngine() string GetEngineVersion() string @@ -725,6 +733,7 @@ type ICloudDBInstance interface { GetDiskSizeGB() int //基础版、高可用? GetCategory() string + GetStorageType() string GetMaintainTime() string @@ -737,6 +746,22 @@ type ICloudDBInstance interface { GetIDBInstanceParameters() ([]ICloudDBInstanceParameter, error) GetIDBInstanceDatabases() ([]ICloudDBInstanceDatabase, error) GetIDBInstanceAccounts() ([]ICloudDBInstanceAccount, error) + GetIDBInstanceBackups() ([]ICloudDBInstanceBackup, error) + + ChangeConfig(ctx context.Context, config *SManagedDBInstanceChangeConfig) error + Renew(bc billing.SBillingCycle) error + + OpenPublicConnection() error + ClosePublicConnection() error + + CreateDatabase(conf *SDBInstanceDatabaseCreateConfig) error + CreateAccount(conf *SDBInstanceAccountCreateConfig) error + + CreateIBackup(conf *SDBInstanceBackupCreateConfig) (string, error) + + RecoveryFromBackup(conf *SDBInstanceRecoveryConfig) error + + Delete() error } type ICloudDBInstanceParameter interface { @@ -747,26 +772,38 @@ type ICloudDBInstanceParameter interface { } type ICloudDBInstanceBackup interface { - ICloudResource + IVirtualResource + GetEngine() string + GetEngineVersion() string GetDBInstanceId() string GetStartTime() time.Time GetEndTime() time.Time GetBackupSizeMb() int GetDBNames() string GetBackupMode() string + + Delete() error } type ICloudDBInstanceDatabase interface { ICloudResource GetCharacterSet() string + + Delete() error } type ICloudDBInstanceAccount interface { ICloudResource GetIDBInstanceAccountPrivileges() ([]ICloudDBInstanceAccountPrivilege, error) + + ResetPassword(password string) error + GrantPrivilege(database, privilege string) error + RevokePrivilege(database string) error + + Delete() error } type ICloudDBInstanceAccountPrivilege interface { diff --git a/pkg/compute/models/capabilities.go b/pkg/compute/models/capabilities.go index d9f55a3825..d6d4deac62 100644 --- a/pkg/compute/models/capabilities.go +++ b/pkg/compute/models/capabilities.go @@ -47,6 +47,7 @@ type SCapabilities struct { SchedPolicySupport bool Usable bool PublicNetworkCount int + DBInstance map[string]map[string]map[string][]string //map[engine][engineVersion][category][]{storage_type} Specs jsonutils.JSONObject } @@ -86,6 +87,7 @@ func GetCapabilities(ctx context.Context, userCred mcclient.TokenCredential, que capa.MaxNicCount = getMaxNicCount(region, zone) capa.MinDataDiskCount = getMinDataDiskCount(region, zone) capa.MaxDataDiskCount = getMaxDataDiskCount(region, zone) + capa.DBInstance = getDBInstanceInfo(region, zone) capa.Usable = isUsable(region, zone, domainId) if query == nil { query = jsonutils.NewDict() @@ -128,6 +130,47 @@ func getDomainManagerSubq(domainId string) *sqlchemy.SSubQuery { return q.SubQuery() } +func getDBInstanceInfo(region *SCloudregion, zone *SZone) map[string]map[string]map[string][]string { + if zone != nil { + region = zone.GetRegion() + } + if region == nil { + return nil + } + + q := DBInstanceSkuManager.Query("engine", "engine_version", "category", "storage_type", "zone1", "zone2", "zone3").Equals("cloudregion_id", region.Id).IsTrue("enabled").Equals("status", api.DBINSTANCE_SKU_AVAILABLE).Distinct() + if zone != nil { + q = q.Filter(sqlchemy.OR( + sqlchemy.Equals(q.Field("zone1"), zone.Id), + sqlchemy.Equals(q.Field("zone2"), zone.Id), + sqlchemy.Equals(q.Field("zone3"), zone.Id), + )) + } + rows, err := q.Rows() + if err != nil { + return nil + } + defer rows.Close() + result := map[string]map[string]map[string][]string{} + for rows.Next() { + var engine, engineVersion, category, storageType, zone1, zone2, zone3 string + rows.Scan(&engine, &engineVersion, &category, &storageType, &zone1, &zone2, &zone3) + if _, ok := result[engine]; !ok { + result[engine] = map[string]map[string][]string{} + } + if _, ok := result[engine][engineVersion]; !ok { + result[engine][engineVersion] = map[string][]string{} + } + if _, ok := result[engine][engineVersion][category]; !ok { + result[engine][engineVersion][category] = []string{} + } + if !utils.IsInStringArray(storageType, result[engine][engineVersion][category]) { + result[engine][engineVersion][category] = append(result[engine][engineVersion][category], storageType) + } + } + return result +} + func getBrands(region *SCloudregion, zone *SZone, domainId string, hypervisors []string) []string { q := CloudaccountManager.Query("brand").IsTrue("enabled") if zone != nil { diff --git a/pkg/compute/models/cloudregions.go b/pkg/compute/models/cloudregions.go index d7fde5e9f8..0383f7ba71 100644 --- a/pkg/compute/models/cloudregions.go +++ b/pkg/compute/models/cloudregions.go @@ -165,12 +165,15 @@ func (self *SCloudregion) GetDBInstances(provider *SCloudprovider) ([]SDBInstanc return instances, nil } -func (self *SCloudregion) GetDBInstanceBackups(provider *SCloudprovider) ([]SDBInstanceBackup, error) { +func (self *SCloudregion) GetDBInstanceBackups(provider *SCloudprovider, instance *SDBInstance) ([]SDBInstanceBackup, error) { backups := []SDBInstanceBackup{} q := DBInstanceBackupManager.Query().Equals("cloudregion_id", self.Id) if provider != nil { q = q.Equals("manager_id", provider.Id) } + if instance != nil { + q = q.Equals("dbinstance_id", instance.Id) + } err := db.FetchModelObjects(DBInstanceBackupManager, q, &backups) if err != nil { return nil, errors.Wrapf(err, "FetchModelObjects for region %s", self.Id) @@ -666,6 +669,15 @@ func (manager *SCloudregionManager) ListItemFilter(ctx context.Context, q *sqlch sqlchemy.In(q.Field("id"), sq.SubQuery()), sqlchemy.In(q.Field("id"), sq2.SubQuery()), )) + + service, _ := query.GetString("service") + switch service { + case DBInstanceManager.KeywordPlural(): + skusSQ := DBInstanceSkuManager.Query("cloudregion_id").Equals("status", api.DBINSTANCE_SKU_AVAILABLE).IsTrue("enabled").SubQuery() + q = q.In("id", skusSQ) + default: + break + } } return q, nil } diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 6def6fba98..9bce73c65a 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -738,6 +738,7 @@ func syncRegionDBInstances(ctx context.Context, userCred mcclient.TokenCredentia localInstances, remoteInstances, result := DBInstanceManager.SyncDBInstances(ctx, userCred, provider.GetOwnerId(), provider, localRegion, instances) syncResults.Add(DBInstanceManager, result) + DBInstanceManager.SyncDBInstanceMasterId(ctx, userCred, provider, instances) msg := result.Result() log.Infof("SyncDBInstances for region %s result: %s", localRegion.Name, msg) @@ -806,7 +807,7 @@ func syncRegionDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCre return } - result := DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, provider, localRegion, backups) + result := DBInstanceBackupManager.SyncDBInstanceBackups(ctx, userCred, provider, nil, localRegion, backups) syncResults.Add(DBInstanceBackupManager, result) msg := result.Result() diff --git a/pkg/compute/models/dbinstance_accounts.go b/pkg/compute/models/dbinstance_accounts.go index 43be23a342..3742636baa 100644 --- a/pkg/compute/models/dbinstance_accounts.go +++ b/pkg/compute/models/dbinstance_accounts.go @@ -19,14 +19,19 @@ import ( "database/sql" "yunion.io/x/jsonutils" + "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/seclib2" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" ) @@ -52,6 +57,7 @@ type SDBInstanceAccount struct { db.SStatusStandaloneResourceBase db.SExternalizedResourceBase + Secret string `width:"256" charset:"ascii" nullable:"false" list:"domain" create:"optional"` DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` } @@ -97,17 +103,27 @@ func (self *SDBInstanceAccount) getPrivilegesDetails() (*jsonutils.JSONArray, er return result, nil } +func (self *SDBInstanceAccount) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) + extra, _ = self.getMoreDetails(ctx, userCred, extra) + return extra +} + +func (self *SDBInstanceAccount) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, extra *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + privileges, err := self.getPrivilegesDetails() + if err != nil { + return extra, err + } + extra.Add(privileges, "dbinstanceprivileges") + return extra, nil +} + func (self *SDBInstanceAccount) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query) if err != nil { return nil, err } - privileges, err := self.getPrivilegesDetails() - if err != nil { - return nil, err - } - extra.Add(privileges, "dbinstanceprivileges") - return extra, nil + return self.getMoreDetails(ctx, userCred, extra) } func (manager *SDBInstanceAccountManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { @@ -122,7 +138,285 @@ func (manager *SDBInstanceAccountManager) ListItemFilter(ctx context.Context, q } func (manager *SDBInstanceAccountManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("Not Implemented") + input := &api.SDBInstanceAccountCreateInput{} + instanceV := validators.NewModelIdOrNameValidator("dbinstance", "dbinstance", userCred) + err := instanceV.Validate(data) + if err != nil { + return nil, err + } + err = data.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Unmarshal input params error: %v", err) + } + instance := instanceV.Model.(*SDBInstance) + if instance.Status != api.DBINSTANCE_RUNNING { + return nil, httperrors.NewInputParameterError("DBInstance %s(%s) status is %s require status is %s", instance.Name, instance.Id, instance.Status, api.DBINSTANCE_RUNNING) + } + region := instance.GetRegion() + if region == nil { + return nil, httperrors.NewInputParameterError("failed to found region for dbinstance %s(%s)", instance.Name, instance.Id) + } + for i, privilege := range input.Privileges { + database, err := instance.GetDBInstanceDatabase(privilege.Database) + if err != nil { + return nil, httperrors.NewInputParameterError("failed to found dbinstance %s(%s) database %s: %v", instance.Name, instance.Id, privilege.Database, err) + } + input.Privileges[i].DBInstancedatabaseId = database.Id + } + input, err = region.GetDriver().ValidateCreateDBInstanceAccountData(ctx, userCred, ownerId, instance, input) + if err != nil { + return nil, err + } + return input.JSON(input), nil +} + +func (self *SDBInstanceAccount) SetPassword(passwd string) error { + return self.savePassword(passwd) +} + +func (self *SDBInstanceAccount) savePassword(secret string) error { + sec, err := utils.EncryptAESBase64(self.Id, secret) + if err != nil { + return err + } + + _, err = db.Update(self, func() error { + self.Secret = sec + return nil + }) + return err +} + +func (self *SDBInstanceAccount) GetPassword() (string, error) { + return utils.DescryptAESBase64(self.Id, self.Secret) +} + +func (self *SDBInstanceAccount) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + input := &api.SDBInstanceAccountCreateInput{} + data.Unmarshal(input) + self.savePassword(input.Password) + self.StartDBInstanceAccountCreateTask(ctx, userCred, data.(*jsonutils.JSONDict), "") +} + +func (self *SDBInstanceAccount) StartDBInstanceAccountCreateTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_USER_CREATING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountCreateTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstanceAccount) GetDBInstance() (*SDBInstance, error) { + instance, err := DBInstanceManager.FetchById(self.DBInstanceId) + if err != nil { + return nil, err + } + return instance.(*SDBInstance), nil +} + +func (self *SDBInstanceAccount) AllowPerformGrantPrivilege(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "grant-privilege") +} + +func (self *SDBInstanceAccount) PerformGrantPrivilege(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + instance, err := self.GetDBInstance() + if err != nil { + return nil, errors.Wrap(err, "failed to found dbinstance") + } + databaseStr, _ := data.GetString("database") + if len(databaseStr) == 0 { + return nil, httperrors.NewMissingParameterError("database") + } + database, err := instance.GetDBInstanceDatabase(databaseStr) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to found database %s for dbinstance %s(%s): %v", databaseStr, instance.Name, instance.Id, err) + } + privilegeStr, _ := data.GetString("privilege") + if len(privilegeStr) == 0 { + return nil, httperrors.NewMissingParameterError("privilege") + } + privilege, _ := instance.GetDBInstancePrivilege(self.Id, database.Id) + if privilege != nil { + return nil, httperrors.NewInputParameterError("The account %s(%s) has permission %s to the database %s(%s)", self.Name, self.Id, privilege.Privilege, database.Name, database.Id) + } + + err = instance.GetRegion().GetDriver().ValidateDBInstanceAccountPrivilege(ctx, userCred, instance, privilegeStr) + if err != nil { + return nil, err + } + + return nil, self.StartGrantPrivilegeTask(ctx, userCred, databaseStr, privilegeStr, "") +} + +func (self *SDBInstanceAccount) AllowPerformSetPrivileges(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "set-privileges") +} + +func (self *SDBInstanceAccount) PerformSetPrivileges(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + instance, err := self.GetDBInstance() + if err != nil { + return nil, errors.Wrap(err, "failed to found dbinstance") + } + + input := api.SDBInstanceSetPrivilegesInput{} + err = data.Unmarshal(&input) + if err != nil { + return nil, httperrors.NewInputParameterError("failed to unmarshal input params: %v", err) + } + + setPrivilege := map[string]map[string]string{ + "grant": map[string]string{}, + "revoke": map[string]string{}, + "input": map[string]string{}, + } + + for i, privilege := range input.Privileges { + database, err := instance.GetDBInstanceDatabase(privilege.Database) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to found database %s for dbinstance %s(%s): %v", privilege.Database, instance.Name, instance.Id, err) + } + input.Privileges[i].DBInstancedatabaseId = database.Id + err = instance.GetRegion().GetDriver().ValidateDBInstanceAccountPrivilege(ctx, userCred, instance, privilege.Privilege) + if err != nil { + return nil, err + } + dbPrivilege, _ := instance.GetDBInstancePrivilege(self.Id, database.Id) + if dbPrivilege == nil { + setPrivilege["grant"][database.Id] = privilege.Privilege + } else if dbPrivilege.Privilege != privilege.Privilege { + setPrivilege["grant"][database.Id] = privilege.Privilege + setPrivilege["revoke"][database.Id] = dbPrivilege.Privilege + } + setPrivilege["input"][database.Id] = privilege.Privilege + } + + dbPrivileges, err := self.GetDBInstancePrivileges() + if err != nil { + return nil, err + } + for _, privilege := range dbPrivileges { + if _, ok := setPrivilege["input"][privilege.DBInstancedatabaseId]; !ok { + setPrivilege["revoke"][privilege.DBInstancedatabaseId] = privilege.Privilege + } + } + + return nil, self.StartSetPrivilegesTask(ctx, userCred, jsonutils.Marshal(setPrivilege)) +} + +func (self *SDBInstanceAccount) StartSetPrivilegesTask(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) error { + self.SetStatus(userCred, api.DBINSTANCE_USER_SET_PRIVILEGE, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountSetPrivilegesTask", self, userCred, data.(*jsonutils.JSONDict), "", "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil + +} + +func (self *SDBInstanceAccount) StartGrantPrivilegeTask(ctx context.Context, userCred mcclient.TokenCredential, database string, privilege string, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_USER_GRANT_PRIVILEGE, "") + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(database), "database") + params.Add(jsonutils.NewString(privilege), "privilege") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountGrantPrivilegeTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstanceAccount) AllowPerformRevokePrivilege(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "revoke-privilege") +} + +func (self *SDBInstanceAccount) PerformRevokePrivilege(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != api.DBINSTANCE_USER_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Account status is not %s current status is %s", api.DBINSTANCE_USER_AVAILABLE, self.Status) + } + instance, err := self.GetDBInstance() + if err != nil { + return nil, errors.Wrap(err, "failed to found dbinstance") + } + if instance.Status != api.DBINSTANCE_RUNNING { + return nil, httperrors.NewInvalidStatusError("Instance status is not %s current status is %s", api.DBINSTANCE_RUNNING, instance.Status) + } + databaseStr, _ := data.GetString("database") + if len(databaseStr) == 0 { + return nil, httperrors.NewMissingParameterError("database") + } + database, err := instance.GetDBInstanceDatabase(databaseStr) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to found database %s for dbinstance %s(%s): %v", databaseStr, instance.Name, instance.Id, err) + } + + if database.Status != api.DBINSTANCE_DATABASE_RUNNING { + return nil, httperrors.NewInvalidStatusError("Database status is not %s current is %s", api.DBINSTANCE_DATABASE_RUNNING, database.Status) + } + + privilege, err := instance.GetDBInstancePrivilege(self.Id, database.Id) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewInputParameterError("Account %s(%s) does not have database %s(%s) permissions", self.Name, self.Id, database.Name, database.Id) + } + return nil, httperrors.NewGeneralError(err) + } + return nil, self.StartRevokePrivilegeTask(ctx, userCred, databaseStr, privilege.Privilege, "") +} + +func (self *SDBInstanceAccount) StartRevokePrivilegeTask(ctx context.Context, userCred mcclient.TokenCredential, database string, privilege string, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_USER_REVOKE_PRIVILEGE, "") + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(database), "database") + params.Add(jsonutils.NewString(privilege), "privilege") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountRevokePrivilegeTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstanceAccount) AllowPerformResetPassword(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowPerform(userCred, self, "reset-password") +} + +func (self *SDBInstanceAccount) PerformResetPassword(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + instance, err := self.GetDBInstance() + if err != nil { + return nil, err + } + passwdStr, _ := query.GetString("password") + if len(passwdStr) > 0 { + if !seclib2.MeetComplxity(passwdStr) { + return nil, httperrors.NewWeakPasswordError() + } + } + err = instance.GetRegion().GetDriver().ValidateResetDBInstancePassword(ctx, userCred, instance, self.Name) + if err != nil { + return nil, err + } + return nil, self.StartDBInstanceAccountResetPasswordTask(ctx, userCred, passwdStr) +} + +func (self *SDBInstanceAccount) StartDBInstanceAccountResetPasswordTask(ctx context.Context, userCred mcclient.TokenCredential, password string) error { + params := jsonutils.NewDict() + if len(password) > 0 { + params.Add(jsonutils.NewString(password), "password") + } else { + params.Add(jsonutils.NewString(seclib2.RandomPassword2(20)), "password") + } + self.SetStatus(userCred, api.DBINSTANCE_USER_RESET_PASSWD, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountResetPasswordTask", self, userCred, params, "", "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + task.ScheduleRun(nil) + return nil } func (self *SDBInstanceAccount) GetDBInstancePrivileges() ([]SDBInstancePrivilege, error) { @@ -180,7 +474,7 @@ func (manager *SDBInstanceAccountManager) SyncDBInstanceAccounts(ctx context.Con } for i := 0; i < len(removed); i++ { - err := removed[i].Delete(ctx, userCred) + err := removed[i].Purge(ctx, userCred) if err != nil { result.DeleteError(err) } else { @@ -241,3 +535,26 @@ func (manager *SDBInstanceAccountManager) newFromCloudDBInstanceAccount(ctx cont } return &account, nil } + +func (self *SDBInstanceAccount) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("dbinstance account delete do nothing") + return nil +} + +func (self *SDBInstanceAccount) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SDBInstanceAccount) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartDBInstanceAccountDeleteTask(ctx, userCred, "") +} + +func (self *SDBInstanceAccount) StartDBInstanceAccountDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_USER_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceAccountDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/dbinstance_backups.go b/pkg/compute/models/dbinstance_backups.go index 1f2db36224..5226b2ad5b 100644 --- a/pkg/compute/models/dbinstance_backups.go +++ b/pkg/compute/models/dbinstance_backups.go @@ -17,12 +17,16 @@ package models import ( "context" "database/sql" + "fmt" + "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -33,14 +37,14 @@ import ( ) type SDBInstanceBackupManager struct { - db.SStatusStandaloneResourceBaseManager + db.SVirtualResourceBaseManager } var DBInstanceBackupManager *SDBInstanceBackupManager func init() { DBInstanceBackupManager = &SDBInstanceBackupManager{ - SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( SDBInstanceBackup{}, "dbinstancebackups_tbl", "dbinstancebackup", @@ -51,17 +55,19 @@ func init() { } type SDBInstanceBackup struct { - db.SStatusStandaloneResourceBase + db.SVirtualResourceBase SCloudregionResourceBase SManagedResourceBase db.SExternalizedResourceBase - StartTime time.Time `list:"user"` - EndTime time.Time `list:"user"` - BackupMode string `width:"32" charset:"ascii" nullable:"true" list:"user"` - DBNames string `width:"512" charset:"ascii" nullable:"true" list:"user"` - BackupSizeMb int `nullable:"false" list:"user"` - DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` + Engine string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + StartTime time.Time `list:"user"` + EndTime time.Time `list:"user"` + BackupMode string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional"` + DBNames string `width:"512" charset:"ascii" nullable:"true" list:"user" create:"optional"` + BackupSizeMb int `nullable:"false" list:"user"` + DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` } func (manager *SDBInstanceBackupManager) GetContextManagers() [][]db.IModelManager { @@ -91,10 +97,23 @@ func (self *SDBInstanceBackup) AllowDeleteItem(ctx context.Context, userCred mcc } func (manager *SDBInstanceBackupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + q, err := manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query) if err != nil { return nil, err } + + q, err = managedResourceFilterByAccount(q, query, "", nil) + if err != nil { + return nil, err + } + + q = managedResourceFilterByCloudType(q, query, "", nil) + + q, err = managedResourceFilterByDomain(q, query, "", nil) + if err != nil { + return nil, err + } + data := query.(*jsonutils.JSONDict) return validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{ {Key: "dbinstance", ModelKeyword: "dbinstance", OwnerId: userCred}, @@ -103,7 +122,63 @@ func (manager *SDBInstanceBackupManager) ListItemFilter(ctx context.Context, q * } func (manager *SDBInstanceBackupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("Not Implemented") + instanceV := validators.NewModelIdOrNameValidator("dbinstance", "dbinstance", userCred) + err := instanceV.Validate(data) + if err != nil { + return nil, err + } + input := &api.SDBInstanceBackupCreateInput{} + err = data.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to unmarshal input params: %v", err) + } + input.BackupMode = api.BACKUP_MODE_MANUAL + input.DBNames = strings.Join(input.Databases, ",") + instance := instanceV.Model.(*SDBInstance) + if instance.Status != api.DBINSTANCE_RUNNING { + return nil, httperrors.NewInputParameterError("DBInstance %s(%s) status is %s require status is %s", instance.Name, instance.Id, instance.Status, api.DBINSTANCE_RUNNING) + } + input.Engine = instance.Engine + input.EngineVersion = instance.EngineVersion + input.ManagerId = instance.ManagerId + region := instance.GetRegion() + if region == nil { + return nil, httperrors.NewInputParameterError("failed to found region for dbinstance %s(%s)", instance.Name, instance.Id) + } + input.CloudregionId = region.Id + input, err = region.GetDriver().ValidateCreateDBInstanceBackupData(ctx, userCred, ownerId, instance, input) + if err != nil { + return nil, err + } + + return input.JSON(input), nil +} + +func (self *SDBInstanceBackup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data) + self.StartDBInstanceBackupCreateTask(ctx, userCred, nil, "") +} + +func (self *SDBInstanceBackup) StartDBInstanceBackupCreateTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_BACKUP_CREATING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceBackupCreateTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstanceBackup) GetIRegion() (cloudprovider.ICloudRegion, error) { + driver, err := self.GetDriver() + if err != nil { + return nil, err + } + region := self.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to found region for rds backup %s(%s)", self.Name, self.Id) + } + return driver.GetIRegionById(region.ExternalId) } func (manager *SDBInstanceBackupManager) getDBInstanceBackupsByInstance(instance *SDBInstance) ([]SDBInstanceBackup, error) { @@ -125,16 +200,42 @@ func (manager *SDBInstanceBackupManager) getDBInstanceBackupsByProviderId(provid return backups, nil } -func (self *SDBInstanceBackup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { - return self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) +func (self *SDBInstanceBackup) GetDBInstance() (*SDBInstance, error) { + if len(self.DBInstanceId) > 0 { + instance, err := DBInstanceManager.FetchById(self.DBInstanceId) + if err != nil { + return nil, err + } + return instance.(*SDBInstance), nil + } + return nil, fmt.Errorf("empty dbinstance id") } -func (manager *SDBInstanceBackupManager) SyncDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, region *SCloudregion, cloudBackups []cloudprovider.ICloudDBInstanceBackup) compare.SyncResult { +func (self *SDBInstanceBackup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + dbinstance, err := self.GetDBInstance() + if err == nil { + extra.Add(jsonutils.NewString(dbinstance.Name), "dbinstance") + } + cloudregionInfo := self.SCloudregionResourceBase.GetCustomizeColumns(ctx, userCred, query) + if cloudregionInfo != nil { + extra.Update(cloudregionInfo) + } + + accountInfo := self.SManagedResourceBase.GetCustomizeColumns(ctx, userCred, query) + if accountInfo != nil { + extra.Update(accountInfo) + } + + return extra +} + +func (manager *SDBInstanceBackupManager) SyncDBInstanceBackups(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, instance *SDBInstance, region *SCloudregion, cloudBackups []cloudprovider.ICloudDBInstanceBackup) compare.SyncResult { lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) result := compare.SyncResult{} - dbBackups, err := region.GetDBInstanceBackups(provider) + dbBackups, err := region.GetDBInstanceBackups(provider, instance) if err != nil { result.Error(err) return result @@ -150,7 +251,7 @@ func (manager *SDBInstanceBackupManager) SyncDBInstanceBackups(ctx context.Conte } for i := 0; i < len(removed); i++ { - err := removed[i].Delete(ctx, userCred) + err := removed[i].RealDelete(ctx, userCred) if err != nil { result.DeleteError(err) } else { @@ -184,6 +285,8 @@ func (self *SDBInstanceBackup) SyncWithCloudDBInstanceBackup(ctx context.Context self.StartTime = extBackup.GetStartTime() self.EndTime = extBackup.GetEndTime() self.BackupSizeMb = extBackup.GetBackupSizeMb() + self.Engine = extBackup.GetEngine() + self.EngineVersion = extBackup.GetEngineVersion() self.DBNames = extBackup.GetDBNames() if dbinstanceId := extBackup.GetDBInstanceId(); len(dbinstanceId) > 0 { @@ -199,6 +302,10 @@ func (self *SDBInstanceBackup) SyncWithCloudDBInstanceBackup(ctx context.Context if err != nil { return errors.Wrapf(err, "SyncWithCloudDBInstancebackup.UpdateWithLock") } + + provider := self.GetCloudprovider() + SyncCloudProject(userCred, self, provider.GetOwnerId(), extBackup, self.ManagerId) + return nil } @@ -219,6 +326,8 @@ func (manager *SDBInstanceBackupManager) newFromCloudDBInstanceBackup(ctx contex backup.ManagerId = provider.Id backup.Status = extBackup.GetStatus() backup.StartTime = extBackup.GetStartTime() + backup.Engine = extBackup.GetEngine() + backup.EngineVersion = extBackup.GetEngineVersion() backup.EndTime = extBackup.GetEndTime() backup.BackupSizeMb = extBackup.GetBackupSizeMb() backup.DBNames = extBackup.GetDBNames() @@ -238,5 +347,31 @@ func (manager *SDBInstanceBackupManager) newFromCloudDBInstanceBackup(ctx contex if err != nil { return errors.Wrapf(err, "newFromCloudDBInstanceBackup.Insert") } + + SyncCloudProject(userCred, &backup, provider.GetOwnerId(), extBackup, backup.ManagerId) + + return nil +} + +func (self *SDBInstanceBackup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("dbinstance backup delete do nothing") + return nil +} + +func (self *SDBInstanceBackup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SVirtualResourceBase.Delete(ctx, userCred) +} + +func (self *SDBInstanceBackup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartDBInstanceBackupDeleteTask(ctx, userCred, "") +} + +func (self *SDBInstanceBackup) StartDBInstanceBackupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_BACKUP_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceBackupDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) return nil } diff --git a/pkg/compute/models/dbinstance_databases.go b/pkg/compute/models/dbinstance_databases.go index 8896b31b6d..2c9136ccaa 100644 --- a/pkg/compute/models/dbinstance_databases.go +++ b/pkg/compute/models/dbinstance_databases.go @@ -18,8 +18,11 @@ import ( "context" "yunion.io/x/jsonutils" + "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" @@ -51,7 +54,7 @@ type SDBInstanceDatabase struct { db.SStatusStandaloneResourceBase db.SExternalizedResourceBase - CharacterSet string `width:"32" charset:"ascii" nullable:"true" list:"user"` + CharacterSet string `width:"32" charset:"ascii" nullable:"true" list:"user" create:"optional"` DBInstanceId string `width:"36" charset:"ascii" name:"dbinstance_id" nullable:"false" list:"user" create:"required" index:"true"` } @@ -74,7 +77,8 @@ func (self *SDBInstanceDatabase) AllowGetDetails(ctx context.Context, userCred m } func (self *SDBInstanceDatabase) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { - return db.IsAdminAllowUpdate(userCred, self) + //只能创建或删除,避免update name后造成登录数据库名称异常 + return false } func (self *SDBInstanceDatabase) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { @@ -92,8 +96,116 @@ func (manager *SDBInstanceDatabaseManager) ListItemFilter(ctx context.Context, q }) } +func (self *SDBInstanceDatabase) GetParentId() string { + return self.DBInstanceId +} + +func (manager *SDBInstanceDatabaseManager) FetchParentId(ctx context.Context, data jsonutils.JSONObject) string { + parentId, _ := data.GetString("dbinstance_id") + return parentId +} + +func (manager *SDBInstanceDatabaseManager) FilterByParentId(q *sqlchemy.SQuery, parentId string) *sqlchemy.SQuery { + if len(parentId) > 0 { + q = q.Equals("dbinstance_id", parentId) + } + return q +} + func (manager *SDBInstanceDatabaseManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("Not Implemented") + instanceV := validators.NewModelIdOrNameValidator("dbinstance", "dbinstance", userCred) + err := instanceV.Validate(data) + if err != nil { + return nil, err + } + + input := &api.SDBInstanceDatabaseCreateInput{} + err = data.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to unmarshal input params: %v", err) + } + instance := instanceV.Model.(*SDBInstance) + if instance.Status != api.DBINSTANCE_RUNNING { + return nil, httperrors.NewInputParameterError("DBInstance %s(%s) status is %s require status is %s", instance.Name, instance.Id, instance.Status, api.DBINSTANCE_RUNNING) + } + region := instance.GetRegion() + if region == nil { + return nil, httperrors.NewInputParameterError("failed to found region for dbinstance %s(%s)", instance.Name, instance.Id) + } + for i, _account := range input.Accounts { + account, err := instance.GetDBInstanceAccount(_account.Account) + if err != nil { + return nil, httperrors.NewInputParameterError("failed to found dbinstance %s(%s) account %s: %v", instance.Name, instance.Id, _account.Account, err) + } + input.Accounts[i].DBInstancedccountId = account.Id + } + + input, err = region.GetDriver().ValidateCreateDBInstanceDatabaseData(ctx, userCred, ownerId, instance, input) + if err != nil { + return nil, err + } + + return input.JSON(input), nil +} + +func (self *SDBInstanceDatabase) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SetStatus(userCred, api.DBINSTANCE_DATABASE_CREATING, "") + params := data.(*jsonutils.JSONDict) + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceDatabaseCreateTask", self, userCred, params, "", "", nil) + if err != nil { + log.Errorf("DBInstanceDatabaseCreateTask newTask error %s", err) + return + } + task.ScheduleRun(nil) +} + +func (self *SDBInstanceDatabase) GetDBInstancePrivileges() ([]SDBInstancePrivilege, error) { + privileges := []SDBInstancePrivilege{} + q := DBInstancePrivilegeManager.Query().Equals("dbinstancedatabase_id", self.Id) + err := db.FetchModelObjects(DBInstancePrivilegeManager, q, &privileges) + if err != nil { + return nil, err + } + return privileges, nil +} + +func (self *SDBInstanceDatabase) GetDBInstance() (*SDBInstance, error) { + instance, err := DBInstanceManager.FetchById(self.DBInstanceId) + if err != nil { + return nil, err + } + return instance.(*SDBInstance), nil +} + +func (self *SDBInstanceDatabase) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) + extra, _ = self.getMoreDetails(ctx, userCred, extra) + return extra +} + +func (self *SDBInstanceDatabase) getPrivilegesDetails() (*jsonutils.JSONArray, error) { + result := jsonutils.NewArray() + privileges, err := self.GetDBInstancePrivileges() + if err != nil { + return nil, errors.Wrap(err, "GetDBInstancePrivileges") + } + for _, privilege := range privileges { + detail, err := privilege.GetDetailedJson() + if err != nil { + return nil, errors.Wrap(err, "GetDetailedJson") + } + result.Add(detail) + } + return result, nil +} + +func (self *SDBInstanceDatabase) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, extra *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + privileges, err := self.getPrivilegesDetails() + if err != nil { + return extra, err + } + extra.Add(privileges, "dbinstanceprivileges") + return extra, nil } func (manager *SDBInstanceDatabaseManager) SyncDBInstanceDatabases(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, cloudDatabases []cloudprovider.ICloudDBInstanceDatabase) compare.SyncResult { @@ -117,7 +229,7 @@ func (manager *SDBInstanceDatabaseManager) SyncDBInstanceDatabases(ctx context.C } for i := 0; i < len(removed); i++ { - err := removed[i].Delete(ctx, userCred) + err := removed[i].Purge(ctx, userCred) if err != nil { result.DeleteError(err) } else { @@ -178,3 +290,26 @@ func (manager *SDBInstanceDatabaseManager) newFromCloudDBInstanceDatabase(ctx co } return nil } + +func (self *SDBInstanceDatabase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("dbinstance database delete do nothing") + return nil +} + +func (self *SDBInstanceDatabase) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SDBInstanceDatabase) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartDBInstanceDatabaseDeleteTask(ctx, userCred, "") +} + +func (self *SDBInstanceDatabase) StartDBInstanceDatabaseDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_DATABASE_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceDatabaseDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/dbinstance_privileges.go b/pkg/compute/models/dbinstance_privileges.go index 129685dce9..3c749de166 100644 --- a/pkg/compute/models/dbinstance_privileges.go +++ b/pkg/compute/models/dbinstance_privileges.go @@ -111,7 +111,12 @@ func (self *SDBInstancePrivilege) GetDetailedJson() (*jsonutils.JSONDict, error) if err != nil { return nil, err } + account, err := self.GetDBInstanceAccount() + if err != nil { + return nil, err + } result.Add(jsonutils.NewString(database.Name), "database") + result.Add(jsonutils.NewString(account.Name), "account") result.Add(jsonutils.NewString(database.Id), "dbinstancedatabase_id") result.Add(jsonutils.NewString(self.Privilege), "privileges") return result, nil diff --git a/pkg/compute/models/dbinstance_skus.go b/pkg/compute/models/dbinstance_skus.go new file mode 100644 index 0000000000..e96d7ca5ad --- /dev/null +++ b/pkg/compute/models/dbinstance_skus.go @@ -0,0 +1,487 @@ +package models + +import ( + "context" + "fmt" + "net/url" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" +) + +type SDBInstanceSkuManager struct { + db.SEnabledStatusStandaloneResourceBaseManager +} + +var DBInstanceSkuManager *SDBInstanceSkuManager + +func init() { + DBInstanceSkuManager = &SDBInstanceSkuManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SDBInstanceSku{}, + "dbinstance_skus_tbl", + "dbinstance_sku", + "dbinstance_skus", + ), + } + + DBInstanceSkuManager.NameRequireAscii = true + db.RegisterModelManager(DBInstanceSkuManager) + DBInstanceSkuManager.SetVirtualObject(DBInstanceSkuManager) + DBInstanceSkuManager.NameRequireAscii = false +} + +type SDBInstanceSku struct { + db.SEnabledStatusStandaloneResourceBase + db.SExternalizedResourceBase + SCloudregionResourceBase + Provider string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_required" update:"admin"` + + StorageType string `list:"user" create:"optional"` + DiskSizeStep int `list:"user" default:"1" create:"optional"` //步长 + MaxDiskSizeGb int `list:"user" create:"optional"` + MinDiskSizeGb int `list:"user" create:"optional"` + + IOPS int `list:"user" create:"optional"` + + VcpuCount int `nullable:"false" default:"1" list:"user" create:"optional"` + VmemSizeMb int `nullable:"false" list:"user" create:"required"` + + Category string `nullable:"false" list:"user" create:"optional"` + Engine string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + + Zone1 string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + Zone2 string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + Zone3 string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + ZoneId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` +} + +func (self *SDBInstanceSkuManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (self *SDBInstanceSkuManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (self *SDBInstanceSku) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (self *SDBInstanceSku) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return false +} + +func (manager *SDBInstanceSkuManager) fetchDBInstanceSkus(provider string, region *SCloudregion) ([]SDBInstanceSku, error) { + skus := []SDBInstanceSku{} + q := manager.Query().Equals("provider", provider).Equals("cloudregion_id", region.Id) + err := db.FetchModelObjects(manager, q, &skus) + if err != nil { + return nil, err + } + return skus, nil +} + +func (manager *SDBInstanceSkuManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + data := query.(*jsonutils.JSONDict) + return validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{ + {Key: "cloudregion", ModelKeyword: "cloudregion", OwnerId: userCred}, + }) +} + +func (manager *SDBInstanceSkuManager) GetDBStringArray(q *sqlchemy.SQuery) ([]string, error) { + array := []string{} + rows, err := q.Rows() + if err != nil { + return nil, errors.Wrap(err, "q.Rows") + } + defer rows.Close() + for rows.Next() { + data := "" + err = rows.Scan(&data) + if err != nil { + return nil, errors.Wrap(err, "rows.Scan") + } + array = append(array, data) + } + return array, err + +} + +func (manager *SDBInstanceSkuManager) AllowGetPropertyInstanceSpecs(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (manager *SDBInstanceSkuManager) GetPropertyInstanceSpecs(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + q, err := manager.ListItemFilter(ctx, manager.Query(), userCred, query) + if err != nil { + return nil, err + } + + input := &SDBInstanceSku{} + query.Unmarshal(input) + for k, v := range map[string]interface{}{ + "provider": input.Provider, + "storage_type": input.StorageType, + "category": input.Category, + "engine": input.Engine, + "engine_version": input.EngineVersion, + "iops": input.IOPS, + "vcpu_count": input.VcpuCount, + "vmem_size_mb": input.VmemSizeMb, + } { + switch v.(type) { + case string: + value := v.(string) + if len(value) > 0 { + q = q.Equals(k, v) + } + case int, int64: + value := fmt.Sprintf("%d", v) + if value != "0" { + q = q.Equals(k, v) + } + } + } + + skus := []SDBInstanceSku{} + q = q.GroupBy(q.Field("vcpu_count"), q.Field("vmem_size_mb")) + q = q.Asc(q.Field("vcpu_count"), q.Field("vmem_size_mb")) + err = db.FetchModelObjects(manager, q, &skus) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "db.FetchModelObjects")) + } + result := struct { + CpuMemsMb map[string][]int + cpuMemsMb map[int]map[int]bool + Cpus []int + MemsMb []int + memsMb map[int]bool + Zones struct { + Zones map[string]string + Zone1 map[string]string + Zone2 map[string]string + Zone3 map[string]string + } + zones []string + zoneNames map[string]string + }{ + CpuMemsMb: map[string][]int{}, + cpuMemsMb: map[int]map[int]bool{}, + Cpus: []int{}, + MemsMb: []int{}, + memsMb: map[int]bool{}, + zones: []string{}, + zoneNames: map[string]string{}, + } + result.Zones.Zones = map[string]string{} + result.Zones.Zone1 = map[string]string{} + result.Zones.Zone2 = map[string]string{} + result.Zones.Zone3 = map[string]string{} + for _, sku := range skus { + if _, ok := result.cpuMemsMb[sku.VcpuCount]; !ok { + result.cpuMemsMb[sku.VcpuCount] = map[int]bool{} + result.CpuMemsMb[fmt.Sprintf("%d", sku.VcpuCount)] = []int{} + result.Cpus = append(result.Cpus, sku.VcpuCount) + } + + if _, ok := result.cpuMemsMb[sku.VcpuCount][sku.VmemSizeMb]; !ok { + result.cpuMemsMb[sku.VcpuCount][sku.VmemSizeMb] = true + result.CpuMemsMb[fmt.Sprintf("%d", sku.VcpuCount)] = append(result.CpuMemsMb[fmt.Sprintf("%d", sku.VcpuCount)], sku.VmemSizeMb) + } + + if _, ok := result.memsMb[sku.VmemSizeMb]; !ok { + result.memsMb[sku.VmemSizeMb] = true + result.MemsMb = append(result.MemsMb, sku.VmemSizeMb) + } + + if _, ok := result.Zones.Zone1[sku.Zone1]; !ok && len(sku.Zone1) > 0 { + result.Zones.Zone1[sku.Zone1] = sku.Zone1 + } + + if _, ok := result.Zones.Zone2[sku.Zone2]; !ok && len(sku.Zone2) > 0 { + result.Zones.Zone2[sku.Zone2] = sku.Zone2 + } + + if _, ok := result.Zones.Zone3[sku.Zone3]; !ok && len(sku.Zone3) > 0 { + result.Zones.Zone3[sku.Zone3] = sku.Zone3 + } + + zoneIds := []string{} + for _, zone := range []string{sku.Zone1, sku.Zone2, sku.Zone3} { + if len(zone) > 0 { + zoneIds = append(zoneIds, zone) + if !utils.IsInStringArray(zone, result.zones) { + result.zones = append(result.zones, zone) + } + } + } + zoneId := strings.Join(zoneIds, "+") + if _, ok := result.Zones.Zones[zoneId]; !ok && len(zoneId) > 0 { + result.Zones.Zones[zoneId] = zoneId + } + } + + zq := ZoneManager.Query("id", "name").In("id", result.zones) + rows, err := zq.Rows() + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + id, name := "", "" + err = rows.Scan(&id, &name) + if err != nil { + return nil, errors.Wrap(err, "rows.Scan") + } + result.zoneNames[id] = name + } + + for _, zoneId := range result.Zones.Zone1 { + result.Zones.Zone1[zoneId] = result.zoneNames[zoneId] + } + + for _, zoneId := range result.Zones.Zone2 { + result.Zones.Zone2[zoneId] = result.zoneNames[zoneId] + } + + for _, zoneId := range result.Zones.Zone3 { + result.Zones.Zone3[zoneId] = result.zoneNames[zoneId] + } + + for _, zoneId := range result.Zones.Zones { + zoneIds := strings.Split(zoneId, "+") + zoneNames := []string{} + for _, id := range zoneIds { + zoneNames = append(zoneNames, result.zoneNames[id]) + } + result.Zones.Zones[zoneId] = strings.Join(zoneNames, "+") + } + + return jsonutils.Marshal(result), nil +} + +func (manager *SDBInstanceSkuManager) GetEngines(provider, cloudregionId string) ([]string, error) { + q := manager.Query("engine").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Distinct() + return manager.GetDBStringArray(q) +} + +func (manager *SDBInstanceSkuManager) GetEngineVersions(provider, cloudregionId, engine string) ([]string, error) { + q := manager.Query("engine_version").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Equals("engine", engine).Distinct() + return manager.GetDBStringArray(q) +} + +func (manager *SDBInstanceSkuManager) GetCategories(provider, cloudregionId, engine, version string) ([]string, error) { + q := manager.Query("category").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Equals("engine", engine).Equals("engine_version", version).Distinct() + return manager.GetDBStringArray(q) +} + +func (manager *SDBInstanceSkuManager) GetStorageTypes(provider, cloudregionId, engine, version, category string) ([]string, error) { + q := manager.Query("storage_type").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Equals("engine", engine).Equals("engine_version", version).Distinct() + q = q.Equals("category", category) + return manager.GetDBStringArray(q) +} + +func (manager *SDBInstanceSkuManager) GetInstanceTypes(provider, cloudregionId, engine, version, category, storageType string) ([]string, error) { + q := manager.Query("name").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Equals("engine", engine).Equals("engine_version", version).Distinct() + q = q.Equals("category", category).Equals("storage_type", storageType) + return manager.GetDBStringArray(q) +} + +func (manager *SDBInstanceSkuManager) GetDBInstanceSkus(provider, cloudregionId, engine, version, category, storageType string) ([]SDBInstanceSku, error) { + skus := []SDBInstanceSku{} + q := manager.Query("name").Equals("provider", provider).Equals("cloudregion_id", cloudregionId).Equals("engine", engine).Equals("engine_version", version).Distinct() + q = q.Equals("category", category).Equals("storage_type", storageType) + err := db.FetchModelObjects(manager, q, &skus) + if err != nil { + return nil, err + } + return skus, nil +} + +func (manager *SDBInstanceSkuManager) syncDBInstanceSkus(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion) compare.SyncResult { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + syncResult := compare.SyncResult{} + + regionInfo := strings.Split(region.ExternalId, "/") + if len(regionInfo) == 0 { + syncResult.Error(fmt.Errorf("region %s is not belong public cloud???", region.Name)) + return syncResult + } + regionId := regionInfo[len(regionInfo)-1] + + s := auth.GetAdminSession(ctx, options.Options.Region, "") + uri, err := s.GetServiceURL("yunionmeta", "") + if err != nil { + syncResult.Error(err) + return syncResult + } + iskus := []SDBInstanceSku{} + params := url.Values{} + params.Add("region_id", regionId) + params.Add("provider", region.Provider) + params.Set("limit", "2048") + for { + params.Set("offset", fmt.Sprintf("%d", len(iskus))) + _, resp, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, httputils.THttpMethod("GET"), fmt.Sprintf("%s/dbinstance_skus?%s", uri, params.Encode()), nil, nil, false) + if err != nil { + syncResult.Error(errors.Wrap(err, "request yunionmeta dbinstance_skus")) + return syncResult + } + + parts := []SDBInstanceSku{} + err = resp.Unmarshal(&parts, "dbinstance_skus") + if err != nil { + syncResult.Error(errors.Wrapf(err, "skus.Unmarshal")) + return syncResult + } + iskus = append(iskus, parts...) + + total, _ := resp.Int("total") + if len(iskus) >= int(total) { + break + } + } + + dbSkus, err := manager.fetchDBInstanceSkus(region.Provider, region) + if err != nil { + syncResult.Error(err) + return syncResult + } + + removed := make([]SDBInstanceSku, 0) + commondb := make([]SDBInstanceSku, 0) + commonext := make([]SDBInstanceSku, 0) + added := make([]SDBInstanceSku, 0) + + err = compare.CompareSets(dbSkus, iskus, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].Delete(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].syncWithCloudSku(ctx, userCred, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + } else { + syncResult.Update() + } + } + for i := 0; i < len(added); i += 1 { + err = manager.newFromCloudSku(ctx, userCred, added[i], region) + if err != nil { + syncResult.AddError(err) + } else { + syncResult.Add() + } + } + return syncResult +} + +func (sku SDBInstanceSku) GetGlobalId() string { + return sku.ExternalId +} + +func (sku *SDBInstanceSku) syncWithCloudSku(ctx context.Context, userCred mcclient.TokenCredential, isku SDBInstanceSku) error { + _, err := db.Update(sku, func() error { + sku.Status = isku.Status + return nil + }) + return err +} + +func (manager *SDBInstanceSkuManager) getZoneBySuffix(region *SCloudregion, suffix string) (*SZone, error) { + q := ZoneManager.Query().Equals("cloudregion_id", region.Id).Endswith("external_id", suffix) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count > 1 { + return nil, fmt.Errorf("duplicate zone with suffix %s in region %s", suffix, region.Name) + } + if count == 0 { + return nil, fmt.Errorf("failed to found zone with suffix %s in region %s", suffix, region.Name) + } + zone := &SZone{} + return zone, q.First(zone) +} + +func (manager *SDBInstanceSkuManager) newFromCloudSku(ctx context.Context, userCred mcclient.TokenCredential, isku SDBInstanceSku, region *SCloudregion) error { + sku := &isku + sku.SetModelManager(manager, sku) + sku.Id = "" //避免使用yunion meta的id,导致出现duplicate entry问题 + sku.CloudregionId = region.Id + + if len(isku.Zone1) > 0 { + zone, err := manager.getZoneBySuffix(region, isku.Zone1) + if err != nil { + return errors.Wrapf(err, "failed to get zone1 info by %s", isku.Zone1) + } + sku.Zone1 = zone.Id + } + + if len(isku.Zone2) > 0 { + zone, err := manager.getZoneBySuffix(region, isku.Zone2) + if err != nil { + return errors.Wrapf(err, "failed to get zone1 info by %s", isku.Zone2) + } + sku.Zone2 = zone.Id + } + + if len(isku.Zone3) > 0 { + zone, err := manager.getZoneBySuffix(region, isku.Zone3) + if err != nil { + return errors.Wrapf(err, "failed to get zone1 info by %s", isku.Zone3) + } + sku.Zone3 = zone.Id + } + + return manager.TableSpec().Insert(sku) +} + +func SyncDBInstanceSkus(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + q := CloudregionManager.Query() + q = q.In("provider", CloudproviderManager.GetPublicProviderProvidersQuery()) + cloudregions := []SCloudregion{} + err := db.FetchModelObjects(CloudregionManager, q, &cloudregions) + if err != nil { + log.Errorf("failed to fetch cloudregions: %v", err) + return + } + + for _, region := range cloudregions { + result := DBInstanceSkuManager.syncDBInstanceSkus(ctx, userCred, ®ion) + msg := result.Result() + notes := fmt.Sprintf("SyncDBInstanceSkus for region %s result: %s", region.Name, msg) + log.Infof(notes) + } +} diff --git a/pkg/compute/models/dbinstancenetworks.go b/pkg/compute/models/dbinstancenetworks.go index 68a8c7717c..31e40f2973 100644 --- a/pkg/compute/models/dbinstancenetworks.go +++ b/pkg/compute/models/dbinstancenetworks.go @@ -19,6 +19,7 @@ import ( "database/sql" "fmt" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/compare" @@ -79,6 +80,56 @@ func (self *SDBInstanceNetwork) AllowDeleteItem(ctx context.Context, userCred mc return false } +func (self *SDBInstanceNetwork) GetNetwork() (*SNetwork, error) { + network, err := NetworkManager.FetchById(self.NetworkId) + if err != nil { + return nil, err + } + return network.(*SNetwork), nil +} + +type SDBInstanceNetworkRequestData struct { + DBInstance *SDBInstance + NetworkId string + reserved bool // allocate from reserved + Address string // the address user intends to use + strategy api.IPAllocationDirection // allocate bottom up, top down, randomly +} + +func (m *SDBInstanceNetworkManager) NewDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, req *SDBInstanceNetworkRequestData) (*SDBInstanceNetwork, error) { + networkMan := db.GetModelManager("network").(*SNetworkManager) + if networkMan == nil { + return nil, fmt.Errorf("failed getting network manager") + } + im, err := networkMan.FetchById(req.NetworkId) + if err != nil { + return nil, err + } + network := im.(*SNetwork) + in := &SDBInstanceNetwork{ + NetworkId: network.Id, + } + in.DBInstanceId = req.DBInstance.Id + in.SetModelManager(m, in) + + lockman.LockObject(ctx, network) + defer lockman.ReleaseObject(ctx, network) + usedMap := network.GetUsedAddresses() + recentReclaimed := map[string]bool{} + ipAddr, err := network.GetFreeIP(ctx, userCred, + usedMap, recentReclaimed, req.Address, req.strategy, req.reserved) + if err != nil { + return nil, err + } + in.IpAddr = ipAddr + err = m.TableSpec().Insert(in) + if err != nil { + // NOTE no need to free ipAddr as GetFreeIP has no side effect + return nil, err + } + return in, nil +} + func (manager *SDBInstanceNetworkManager) SyncDBInstanceNetwork(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, network *cloudprovider.SDBInstanceNetwork) compare.SyncResult { result := compare.SyncResult{} diff --git a/pkg/compute/models/dbinstances.go b/pkg/compute/models/dbinstances.go index f31db47e3d..a92c430719 100644 --- a/pkg/compute/models/dbinstances.go +++ b/pkg/compute/models/dbinstances.go @@ -17,17 +17,27 @@ package models import ( "context" "database/sql" + "fmt" + "strings" + "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" + 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/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" ) @@ -56,13 +66,15 @@ type SDBInstance struct { SBillingResourceBase SCloudregionResourceBase - SZoneResourceBase + DisableDelete tristate.TriState `nullable:"false" default:"true" list:"user" update:"user" create:"optional"` - VcpuCount int `nullable:"false" default:"1" list:"user" create:"optional"` // Column(TINYINT, nullable=False, default=1) - VmemSizeMb int `nullable:"false" list:"user" create:"required"` // Column(Integer, nullable=False) - DiskSizeGB int `nullable:"false" list:"user" create:"required"` - Port int `nullable:"false" list:"user" create:"required"` - Category string `nullable:"true" list:"user" create:"optional"` //实例类别,单机,高可用,只读 + MasterInstanceId string `width:"128" charset:"ascii" list:"user" create:"optional"` + VcpuCount int `nullable:"false" default:"1" list:"user" create:"optional"` + VmemSizeMb int `nullable:"false" list:"user" create:"required"` + StorageType string `nullable:"false" list:"user" create:"required"` //存储类型 + DiskSizeGB int `nullable:"false" list:"user" create:"required"` + Port int `nullable:"false" list:"user" create:"optional"` + Category string `nullable:"false" list:"user" create:"optional"` //实例类别,单机,高可用,只读 Engine string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` @@ -70,9 +82,16 @@ type SDBInstance struct { MaintainTime string `width:"64" charset:"ascii" nullable:"true" list:"user" create:"optional"` - VpcId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` - ConnectionStr string `width:"256" charset:"ascii" nullable:"true" list:"user" create:"optional"` - InternalConnectionStr string `width:"256" charset:"ascii" nullable:"true" list:"user" create:"optional"` + SecgroupId string `width:"128" charset:"ascii" list:"user" default:"default" create:"optional"` + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` + ConnectionStr string `width:"256" charset:"ascii" nullable:"false" list:"user" create:"optional"` + InternalConnectionStr string `width:"256" charset:"ascii" nullable:"false" list:"user" create:"optional"` + + Zone1 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` + Zone2 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` + Zone3 string `width:"36" charset:"ascii" nullable:"false" create:"optional" list:"user"` + + ZoneId string `width:"36" charset:"ascii" nullable:"false" create:"optional"` } func (manager *SDBInstanceManager) GetContextManagers() [][]db.IModelManager { @@ -119,11 +138,180 @@ func (man *SDBInstanceManager) ListItemFilter(ctx context.Context, q *sqlchemy.S if err != nil { return nil, err } + + q = managedResourceFilterByCloudType(q, query, "", nil) + + q, err = managedResourceFilterByDomain(q, query, "", nil) + if err != nil { + return nil, err + } + return q, nil } func (man *SDBInstanceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("Not Implemented") + networkV := validators.NewModelIdOrNameValidator("network", "network", ownerId) + addressV := validators.NewIPv4AddrValidator("address") + secgroupV := validators.NewModelIdOrNameValidator("secgroup", "secgroup", ownerId) + masterV := validators.NewModelIdOrNameValidator("master_instance", "dbinstance", ownerId) + zone1V := validators.NewModelIdOrNameValidator("zone1", "zone", ownerId) + zone2V := validators.NewModelIdOrNameValidator("zone2", "zone", ownerId) + zone3V := validators.NewModelIdOrNameValidator("zone3", "zone", ownerId) + keyV := map[string]validators.IValidator{ + "network": networkV, + "address": addressV.Optional(true), + "master": masterV.ModelIdKey("master_instance_id").Optional(true), + "secgroup": secgroupV.Optional(true), + "zone1": zone1V.ModelIdKey("zone1").Optional(true), + "zone2": zone2V.ModelIdKey("zone2").Optional(true), + "zone3": zone3V.ModelIdKey("zone3").Optional(true), + } + for _, v := range keyV { + err := v.Validate(data) + if err != nil { + return nil, err + } + } + + input := &api.SDBInstanceCreateInput{} + err := data.Unmarshal(input) + if err != nil { + return nil, errors.Wrapf(err, "Unmarshal input failed: %v", err) + } + + network := networkV.Model.(*SNetwork) + input.NetworkExternalId = network.ExternalId + + vpc := network.GetVpc() + input.VpcId = vpc.Id + input.ManagerId = vpc.ManagerId + cloudprovider := vpc.GetCloudprovider() + if cloudprovider == nil { + return nil, httperrors.NewGeneralError(fmt.Errorf("failed to get vpc %s(%s) cloudprovider", vpc.Name, vpc.Id)) + } + if !cloudprovider.Enabled { + return nil, httperrors.NewInputParameterError("cloudprovider %s(%s) disabled", cloudprovider.Name, cloudprovider.Id) + } + + region, err := vpc.GetRegion() + if err != nil { + return nil, err + } + input.CloudregionId = region.Id + input.Cloudregion = region.Name + input.Provider = region.Provider + + if addressV.IP != nil { + ip, err := netutils.NewIPV4Addr(addressV.IP.String()) + if err != nil { + return nil, err + } + if !network.IsAddressInRange(ip) { + return nil, httperrors.NewInputParameterError("Ip %s not in network %s(%s) range", addressV.IP.String(), network.Name, network.Id) + } + } + + if duration, _ := data.GetString("duration"); len(duration) > 0 { + billingCycle, err := billing.ParseBillingCycle(duration) + if err != nil { + return nil, httperrors.NewInputParameterError("invalid duration %s", duration) + } + if !region.GetDriver().IsSupportedBillingCycle(billingCycle, man.KeywordPlural()) { + return nil, httperrors.NewInputParameterError("unsupported duration %s", duration) + } + input.BillingType = billing_api.BILLING_TYPE_PREPAID + input.BillingCycle = billingCycle.String() + } + + if len(input.InstanceType) == 0 && (input.VcpuCount == 0 || input.VmemSizeMb == 0) { + return nil, httperrors.NewMissingParameterError("Missing instance_type or vcpu_count, vmem_size_mb parameters") + } + + engines, err := DBInstanceSkuManager.GetEngines(input.Provider, input.CloudregionId) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if len(input.Engine) == 0 { + return nil, httperrors.NewMissingParameterError("engine") + } + + if !utils.IsInStringArray(input.Engine, engines) { + return nil, httperrors.NewInputParameterError("%s(%s) not support engine %s, only support %s", input.Provider, input.Cloudregion, input.Engine, engines) + } + + if len(input.EngineVersion) == 0 { + return nil, httperrors.NewMissingParameterError("engine_version") + } + + versions, err := DBInstanceSkuManager.GetEngineVersions(input.Provider, input.CloudregionId, input.Engine) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if !utils.IsInStringArray(input.EngineVersion, versions) { + return nil, httperrors.NewInputParameterError("%s(%s) engine %s not support version %s, only support %s", input.Provider, input.Cloudregion, input.Engine, input.EngineVersion, versions) + } + + if len(input.Category) == 0 { + return nil, httperrors.NewMissingParameterError("category") + } + + categories, err := DBInstanceSkuManager.GetCategories(input.Provider, input.CloudregionId, input.Engine, input.EngineVersion) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if !utils.IsInStringArray(input.Category, categories) { + return nil, httperrors.NewInputParameterError("%s(%s) engine %s(%s) not support category %s, only support %s", input.Provider, input.Cloudregion, input.Engine, input.EngineVersion, input.Category, categories) + } + + if len(input.StorageType) == 0 { + return nil, httperrors.NewMissingParameterError("storage_type") + } + + storageTypes, err := DBInstanceSkuManager.GetStorageTypes(input.Provider, input.CloudregionId, input.Engine, input.EngineVersion, input.Category) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if !utils.IsInStringArray(input.StorageType, storageTypes) { + return nil, httperrors.NewInputParameterError("%s(%s) engine %s(%s) %s not support storage %s, only support %s", input.Provider, input.Cloudregion, input.Engine, input.EngineVersion, input.Category, input.StorageType, storageTypes) + } + + instance := SDBInstance{} + jsonutils.Update(&instance, input) + skus, err := instance.GetDBInstanceSkus() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if len(skus) == 0 { + return nil, httperrors.NewInputParameterError("not match any dbinstance sku") + } + + if len(input.InstanceType) > 0 { //设置下cpu和内存的大小 + input.VcpuCount = skus[0].VcpuCount + input.VmemSizeMb = skus[0].VmemSizeMb + } + + input, err = region.GetDriver().ValidateCreateDBInstanceData(ctx, userCred, ownerId, input, skus, network) + if err != nil { + return nil, err + } + + return input.JSON(input), nil +} + +func (self *SDBInstance) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SetStatus(userCred, api.DBINSTANCE_DEPLOYING, "") + params := data.(*jsonutils.JSONDict) + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceCreateTask", self, userCred, params, "", "", nil) + if err != nil { + log.Errorf("DBInstanceCreateTask newTask error %s", err) + return + } + task.ScheduleRun(nil) } func (self *SDBInstance) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { @@ -142,6 +330,38 @@ func (self *SDBInstance) GetVpc() (*SVpc, error) { return vpc.(*SVpc), nil } +func (self *SDBInstance) GetZone() (*SZone, error) { + zone, err := ZoneManager.FetchById(self.ZoneId) + if err != nil { + return nil, err + } + return zone.(*SZone), nil +} + +func (self *SDBInstance) GetNetwork() (*SNetwork, error) { + dbnet := DBInstanceNetworkManager.Query().SubQuery() + q := NetworkManager.Query() + q = q.Join(dbnet, sqlchemy.Equals(q.Field("id"), dbnet.Field("network_id"))).Filter(sqlchemy.Equals(dbnet.Field("dbinstance_id"), self.Id)) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count == 1 { + network := &SNetwork{} + network.SetModelManager(NetworkManager, network) + err = q.First(network) + if err != nil { + return nil, err + } + return network, nil + } + if count > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + return nil, sql.ErrNoRows + +} + func (self *SDBInstance) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, extra *jsonutils.JSONDict) *jsonutils.JSONDict { accountInfo := self.SManagedResourceBase.GetCustomizeColumns(ctx, userCred, query) if accountInfo != nil { @@ -151,17 +371,349 @@ func (self *SDBInstance) getMoreDetails(ctx context.Context, userCred mcclient.T if regionInfo != nil { extra.Update(regionInfo) } - zoneInfo := self.SZoneResourceBase.GetCustomizeColumns(ctx, userCred, query) - if zoneInfo != nil { - extra.Update(zoneInfo) - } vpc, _ := self.GetVpc() if vpc != nil { extra.Add(jsonutils.NewString(vpc.Name), "vpc") } + if len(self.SecgroupId) > 0 { + if secgroup, _ := self.GetSecgroup(); secgroup != nil { + extra.Add(jsonutils.NewString(secgroup.Name), "secgroup") + } + } + + if skus, _ := self.GetDBInstanceSkus(); len(skus) > 0 { + extra.Add(jsonutils.NewInt(int64(skus[0].IOPS)), "iops") + } + + network, _ := self.GetNetwork() + if network != nil { + extra.Add(jsonutils.NewString(network.Name), "network") + } + + if metaData, err := self.GetAllMetadata(userCred); err == nil { + extra.Add(jsonutils.Marshal(metaData), "metadata") + } + return extra } +func (self *SDBInstance) GetSecgroup() (*SSecurityGroup, error) { + secgroup, err := SecurityGroupManager.FetchById(self.SecgroupId) + if err != nil { + return nil, err + } + return secgroup.(*SSecurityGroup), nil +} + +func (self *SDBInstance) GetMasterInstance() (*SDBInstance, error) { + instance, err := DBInstanceManager.FetchById(self.MasterInstanceId) + if err != nil { + return nil, err + } + return instance.(*SDBInstance), nil +} + +func (self *SDBInstance) GetIRegion() (cloudprovider.ICloudRegion, error) { + driver, err := self.GetDriver() + if err != nil { + return nil, err + } + region := self.GetRegion() + if region == nil { + return nil, fmt.Errorf("failed to found region for rds %s(%s)", self.Name, self.Id) + } + return driver.GetIRegionById(region.ExternalId) +} + +func (self *SDBInstance) GetIDBInstance() (cloudprovider.ICloudDBInstance, error) { + iregion, err := self.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "self.GetIRegion") + } + return iregion.GetIDBInstanceById(self.ExternalId) +} + +func (self *SDBInstance) AllowPerformRecovery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "recovery") +} + +func (self *SDBInstance) PerformRecovery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := data.(*jsonutils.JSONDict) + backupV := validators.NewModelIdOrNameValidator("dbinstancebackup", "dbinstancebackup", userCred) + err := backupV.Validate(params) + if err != nil { + return nil, err + } + input := &api.SDBInstanceRecoveryConfigInput{} + err = params.Unmarshal(input) + if err != nil { + return nil, httperrors.NewInputParameterError("Failed to unmarshal input config: %v", err) + } + + databases, err := self.GetDBInstanceDatabases() + if err != nil { + return nil, err + } + + dbDatabases := []string{} + for _, database := range databases { + dbDatabases = append(dbDatabases, database.Name) + } + + backup := backupV.Model.(*SDBInstanceBackup) + for src, dest := range input.Databases { + if len(dest) == 0 { + dest = src + } + if strings.Index(backup.DBNames, src) < 0 { + return nil, httperrors.NewInputParameterError("backup %s(%s) not contain database %s", backup.Name, backup.Id, src) + } + + if utils.IsInStringArray(dest, dbDatabases) { + return nil, httperrors.NewConflictError("conflict database %s for instance %s(%s)", dest, self.Name, self.Id) + } + input.Databases[src] = dest + } + + if backup.ManagerId != self.ManagerId { + return nil, httperrors.NewInputParameterError("back and instance not in same cloudaccount") + } + + if backup.CloudregionId != self.CloudregionId { + return nil, httperrors.NewInputParameterError("backup and instance not in same cloudregion") + } + + return nil, self.StartDBInstanceRecoveryTask(ctx, userCred, input.JSON(input), "") +} + +func (self *SDBInstance) StartDBInstanceRecoveryTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_RESTORING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceRecoveryTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "purge") +} + +func (self *SDBInstance) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Set("purge", jsonutils.JSONTrue) + return nil, self.StartDBInstanceDeleteTask(ctx, userCred, params, "") +} + +func (self *SDBInstance) AllowPerformReboot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "reboot") +} + +func (self *SDBInstance) PerformReboot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{api.DBINSTANCE_RUNNING, api.DBINSTANCE_REBOOT_FAILED}) { + return nil, self.StartDBInstanceRebootTask(ctx, userCred, jsonutils.NewDict(), "") + } + return nil, httperrors.NewInvalidStatusError("Cannot do reboot dbinstance in status %s", self.Status) +} + +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") +} + +func (self *SDBInstance) PerformSyncStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, self.StartDBInstanceSyncStatusTask(ctx, userCred, jsonutils.NewDict(), "") +} + +func (self *SDBInstance) AllowPerformRenew(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "renew") +} + +func (self *SDBInstance) PerformRenew(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + durationStr := jsonutils.GetAnyString(data, []string{"duration"}) + if len(durationStr) == 0 { + return nil, httperrors.NewInputParameterError("missong duration") + } + + bc, err := billing.ParseBillingCycle(durationStr) + if err != nil { + return nil, httperrors.NewInputParameterError("invalid duration %s: %s", durationStr, err) + } + + if !self.GetRegion().GetDriver().IsSupportedBillingCycle(bc, DBInstanceManager.KeywordPlural()) { + return nil, httperrors.NewInputParameterError("unsupported duration %s", durationStr) + } + + return nil, self.StartDBInstanceRenewTask(ctx, userCred, durationStr, "") +} + +func (self *SDBInstance) AllowPerformPublicConnection(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "public-connection") +} + +func (self *SDBInstance) PerformPublicConnection(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + open := jsonutils.QueryBoolean(data, "open", true) + if open && len(self.ConnectionStr) > 0 { + return nil, httperrors.NewInputParameterError("DBInstance has opened the outer network connection") + } + if !open && len(self.ConnectionStr) == 0 { + return nil, httperrors.NewInputParameterError("The extranet connection is not open") + } + + region := self.GetRegion() + if region == nil { + return nil, httperrors.NewGeneralError(fmt.Errorf("failed to found region for dbinstance %s(%s)", self.Name, self.Id)) + } + + if !region.GetDriver().IsSupportDBInstancePublicConnection() { + return nil, httperrors.NewInputParameterError("%s not support this operation", region.Provider) + } + + return nil, self.StartDBInstancePublicConnectionTask(ctx, userCred, "", open) +} + +func (self *SDBInstance) StartDBInstancePublicConnectionTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, open bool) error { + self.SetStatus(userCred, api.DBINSTANCE_DEPLOYING, "") + params := jsonutils.NewDict() + params.Add(jsonutils.NewBool(open), "open") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstancePublicConnectionTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) AllowPerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "change-config") +} + +func (self *SDBInstance) PerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{api.DBINSTANCE_RUNNING}) { + return nil, httperrors.NewInputParameterError("Cannot change config in status %s", self.Status) + } + input := api.SDBInstanceChangeConfigInput{} + err := data.Unmarshal(&input) + if err != nil { + return nil, httperrors.NewInputParameterError("Unmarshal input error: %v", err) + } + + tmp := &SDBInstance{} + jsonutils.Update(tmp, self) + + if len(input.StorageType) > 0 { + self.StorageType = input.StorageType + } + + changed := false + if len(input.InstanceType) > 0 { + tmp.InstanceType = input.InstanceType + changed = true + } else if input.VCpuCount > 0 { + tmp.VcpuCount = input.VCpuCount + self.InstanceType = "" + changed = true + } else if input.VmemSizeMb > 0 { + tmp.VmemSizeMb = input.VmemSizeMb + tmp.InstanceType = "" + changed = true + } else if len(input.Category) > 0 { + tmp.Category = input.Category + tmp.InstanceType = "" + changed = true + } + + if changed { + skus, err := tmp.GetDBInstanceSkus() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "self.GetDBInstanceSkus")) + } + if len(skus) == 0 { + return nil, httperrors.NewInputParameterError("failed to match any skus for change config") + } + } + + err = self.GetRegion().GetDriver().ValidateChangeDBInstanceConfigData(ctx, userCred, self, &input) + if err != nil { + return nil, err + } + + return nil, self.StartDBInstanceChangeConfig(ctx, userCred, data.(*jsonutils.JSONDict), "") +} + +func (self *SDBInstance) StartDBInstanceChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_CHANGE_CONFIG, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceChangeConfigTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) StartDBInstanceRenewTask(ctx context.Context, userCred mcclient.TokenCredential, duration string, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_RENEWING, "") + params := jsonutils.NewDict() + params.Set("duration", jsonutils.NewString(duration)) + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceRenewTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) SaveRenewInfo(ctx context.Context, userCred mcclient.TokenCredential, bc *billing.SBillingCycle, expireAt *time.Time) error { + _, err := db.Update(self, func() error { + if self.BillingType != billing_api.BILLING_TYPE_PREPAID { + self.BillingType = billing_api.BILLING_TYPE_PREPAID + } + if expireAt != nil && !expireAt.IsZero() { + self.ExpiredAt = *expireAt + } else { + self.BillingCycle = bc.String() + self.ExpiredAt = bc.EndAt(self.ExpiredAt) + } + return nil + }) + if err != nil { + log.Errorf("Update error %s", err) + return err + } + db.OpsLog.LogEvent(self, db.ACT_RENEW, self.GetShortDesc(ctx), userCred) + return nil +} + +func (self *SDBInstance) StartDBInstanceDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_DELETING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceDeleteTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) StartDBInstanceRebootTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_REBOOTING, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceRebootTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SDBInstance) StartDBInstanceSyncStatusTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.DBINSTANCE_SYNC_STATUS, "") + task, err := taskman.TaskManager.NewTask(ctx, "DBInstanceSyncStatusTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (self *SDBInstance) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := self.SStatusStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) return self.getMoreDetails(ctx, userCred, query, extra) @@ -176,6 +728,19 @@ func (manager *SDBInstanceManager) getDBInstancesByProviderId(providerId string) return instances, nil } +func (self *SDBInstance) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartDBInstanceDeleteTask(ctx, userCred, nil, "") +} + +func (self *SDBInstance) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("dbinstance delete do nothing") + return nil +} + +func (self *SDBInstance) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SVirtualResourceBase.Delete(ctx, userCred) +} + func (self *SDBInstance) GetDBInstanceParameters() ([]SDBInstanceParameter, error) { params := []SDBInstanceParameter{} q := DBInstanceParameterManager.Query().Equals("dbinstance_id", self.Id) @@ -186,6 +751,52 @@ func (self *SDBInstance) GetDBInstanceParameters() ([]SDBInstanceParameter, erro return params, nil } +func (self *SDBInstance) GetDBInstanceBackup(name string) (*SDBInstanceBackup, error) { + q := DBInstanceBackupManager.Query().Equals("dbinstance_id", self.Id) + q = q.Filter( + sqlchemy.OR( + sqlchemy.Equals(q.Field("name"), name), + sqlchemy.Equals(q.Field("id"), name), + ), + ) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count > 1 { + return nil, fmt.Errorf("Duplicate %d backup %s for dbinstance %s(%s)", count, name, self.Name, self.Id) + } + if count == 0 { + return nil, fmt.Errorf("Failed to found backup %s for dbinstance %s(%s)", name, self.Name, self.Id) + } + backup := &SDBInstanceBackup{} + backup.SetModelManager(DBInstanceBackupManager, backup) + return backup, q.First(backup) +} + +func (self *SDBInstance) GetDBInstanceDatabase(name string) (*SDBInstanceDatabase, error) { + q := DBInstanceDatabaseManager.Query().Equals("dbinstance_id", self.Id) + q = q.Filter( + sqlchemy.OR( + sqlchemy.Equals(q.Field("name"), name), + sqlchemy.Equals(q.Field("id"), name), + ), + ) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count > 1 { + return nil, fmt.Errorf("Duplicate %d database %s for dbinstance %s(%s)", count, name, self.Name, self.Id) + } + if count == 0 { + return nil, fmt.Errorf("Failed to found database %s for dbinstance %s(%s)", name, self.Name, self.Id) + } + database := &SDBInstanceDatabase{} + database.SetModelManager(DBInstanceDatabaseManager, database) + return database, q.First(database) +} + func (self *SDBInstance) GetDBInstanceDatabases() ([]SDBInstanceDatabase, error) { databases := []SDBInstanceDatabase{} q := DBInstanceDatabaseManager.Query().Equals("dbinstance_id", self.Id) @@ -196,6 +807,29 @@ func (self *SDBInstance) GetDBInstanceDatabases() ([]SDBInstanceDatabase, error) return databases, nil } +func (self *SDBInstance) GetDBInstanceAccount(name string) (*SDBInstanceAccount, error) { + q := DBInstanceAccountManager.Query().Equals("dbinstance_id", self.Id) + q = q.Filter( + sqlchemy.OR( + sqlchemy.Equals(q.Field("name"), name), + sqlchemy.Equals(q.Field("id"), name), + ), + ) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count > 1 { + return nil, fmt.Errorf("Duplicate %d account %s for dbinstance %s(%s)", count, name, self.Name, self.Id) + } + if count == 0 { + return nil, fmt.Errorf("Failed to found account %s for dbinstance %s(%s)", name, self.Name, self.Id) + } + account := &SDBInstanceAccount{} + account.SetModelManager(DBInstanceAccountManager, account) + return account, q.First(account) +} + func (self *SDBInstance) GetDBInstanceAccounts() ([]SDBInstanceAccount, error) { accounts := []SDBInstanceAccount{} q := DBInstanceAccountManager.Query().Equals("dbinstance_id", self.Id) @@ -206,6 +840,44 @@ func (self *SDBInstance) GetDBInstanceAccounts() ([]SDBInstanceAccount, error) { return accounts, nil } +func (self *SDBInstance) GetDBInstancePrivilege(account, database string) (*SDBInstancePrivilege, error) { + instances := DBInstanceManager.Query().SubQuery() + accounts := DBInstanceAccountManager.Query().SubQuery() + databases := DBInstanceDatabaseManager.Query().SubQuery() + q := DBInstancePrivilegeManager.Query() + q = q.Join(accounts, sqlchemy.Equals(accounts.Field("id"), q.Field("dbinstanceaccount_id"))). + Join(databases, sqlchemy.Equals(databases.Field("id"), q.Field("dbinstancedatabase_id"))). + Join(instances, sqlchemy.AND(sqlchemy.Equals(instances.Field("id"), accounts.Field("dbinstance_id")), sqlchemy.Equals(instances.Field("id"), databases.Field("dbinstance_id")))) + q = q.Filter( + sqlchemy.AND( + sqlchemy.OR( + sqlchemy.Equals(accounts.Field("id"), account), + sqlchemy.Equals(accounts.Field("name"), account), + ), + sqlchemy.OR( + sqlchemy.Equals(databases.Field("id"), database), + sqlchemy.Equals(databases.Field("name"), database), + ), + ), + ) + count, err := q.CountWithError() + if err != nil { + return nil, err + } + if count > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + if count == 0 { + return nil, sql.ErrNoRows + } + privilege := &SDBInstancePrivilege{} + err = q.First(privilege) + if err != nil { + return nil, errors.Wrap(err, "q.First()") + } + return privilege, nil +} + func (self *SDBInstance) GetDBInstanceBackups() ([]SDBInstanceBackup, error) { backups := []SDBInstanceBackup{} q := DBInstanceBackupManager.Query().Equals("dbinstance_id", self.Id) @@ -257,6 +929,32 @@ func (self *SDBInstance) GetDBNetwork() (*SDBInstanceNetwork, error) { return nil, sql.ErrNoRows } +func (manager *SDBInstanceManager) SyncDBInstanceMasterId(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, cloudDBInstances []cloudprovider.ICloudDBInstance) { + for _, instance := range cloudDBInstances { + masterId := instance.GetMasterInstanceId() + if len(masterId) > 0 { + master, err := db.FetchByExternalId(manager, masterId) + if err != nil { + log.Errorf("failed to found master dbinstance by externalId: %s error: %v", masterId, err) + continue + } + slave, err := db.FetchByExternalId(manager, instance.GetGlobalId()) + if err != nil { + log.Errorf("failed to found local dbinstance by externalId %s error: %v", instance.GetGlobalId(), err) + continue + } + localInstance := slave.(*SDBInstance) + _, err = db.Update(localInstance, func() error { + localInstance.MasterInstanceId = master.GetId() + return nil + }) + if err != nil { + log.Errorf("failed to update dbinstance %s(%s) master instanceId error: %v", localInstance.Name, localInstance.Id, err) + } + } + } +} + func (manager *SDBInstanceManager) SyncDBInstances(ctx context.Context, userCred mcclient.TokenCredential, syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider, region *SCloudregion, cloudDBInstances []cloudprovider.ICloudDBInstance) ([]SDBInstance, []cloudprovider.ICloudDBInstance, compare.SyncResult) { lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) @@ -323,7 +1021,109 @@ func (self *SDBInstance) syncRemoveCloudDBInstance(ctx context.Context, userCred if err != nil { // cannot delete return self.SetStatus(userCred, api.VPC_STATUS_UNKNOWN, "sync to delete") } - return self.Delete(ctx, userCred) + return self.RealDelete(ctx, userCred) +} + +func (self *SDBInstance) ValidateDeleteCondition(ctx context.Context) error { + if self.DisableDelete.IsTrue() { + return httperrors.NewInvalidStatusError("DBInstance is locked, cannot delete") + } + return self.SStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SDBInstance) GetDBInstanceSkuQuery() *sqlchemy.SQuery { + q := DBInstanceSkuManager.Query().Equals("storage_type", self.StorageType).Equals("category", self.Category). + Equals("cloudregion_id", self.CloudregionId).Equals("engine", self.Engine).Equals("engine_version", self.EngineVersion) + for k, v := range map[string]string{"zone1": self.Zone1, "zone2": self.Zone2, "zone3": self.Zone3, "zone_id": self.ZoneId} { + if len(v) > 0 { + q = q.Equals(k, v) + } + } + if len(self.InstanceType) > 0 { + q = q.Equals("name", self.InstanceType) + } else { + q = q.Equals("vcpu_count", self.VcpuCount).Equals("vmem_size_mb", self.VmemSizeMb) + } + return q +} + +func (self *SDBInstance) GetDBInstanceSkus() ([]SDBInstanceSku, error) { + skus := []SDBInstanceSku{} + q := self.GetDBInstanceSkuQuery() + err := db.FetchModelObjects(DBInstanceSkuManager, q, &skus) + if err != nil { + return nil, err + } + return skus, nil +} + +func (self *SDBInstance) GetAvailableZoneIds() ([]string, error) { + zoneIds := []string{} + skus, err := self.GetDBInstanceSkus() + if err != nil { + return nil, errors.Wrap(err, "self.GetDBInstanceSkus") + } + for _, sku := range skus { + if !utils.IsInStringArray(sku.ZoneId, zoneIds) { + zoneIds = append(zoneIds, sku.ZoneId) + } + } + return zoneIds, nil +} + +func (self *SDBInstance) GetAvailableInstanceTypes() ([]cloudprovider.SInstanceType, error) { + instanceTypes := map[string]cloudprovider.SInstanceType{} + skus, err := self.GetDBInstanceSkus() + if err != nil { + return nil, errors.Wrap(err, "self.GetDBInstanceSkus") + } + + 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) + } + } + + result := []cloudprovider.SInstanceType{} + for _, instanceType := range instanceTypes { + result = append(result, instanceType) + } + + return result, nil +} + +func (self *SDBInstance) setZoneInfo() error { + sku := SDBInstanceSku{} + q := self.GetDBInstanceSkuQuery() + count, err := q.CountWithError() + if err != nil { + return errors.Wrapf(err, "q.CountWithError") + } + if count == 0 { + q.DebugQuery() + return fmt.Errorf("failed to fetch any sku for dbinstance %s(%s)", self.Name, self.Id) + } + if count > 1 { + q.DebugQuery() + return fmt.Errorf("fetch %d skus for dbinstance %s(%s)", count, self.Name, self.Id) + } + err = q.First(&sku) + if err != nil { + return errors.Wrap(err, "q.First()") + } + self.Zone1 = sku.Zone1 + self.Zone2 = sku.Zone2 + self.Zone3 = sku.Zone3 + return nil +} + +func (self *SDBInstance) SetZoneInfo(ctx context.Context, userCred mcclient.TokenCredential) error { + _, err := db.UpdateWithLock(ctx, self, func() error { + return self.setZoneInfo() + }) + return err } func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, extInstance cloudprovider.ICloudDBInstance) error { @@ -334,6 +1134,7 @@ func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred m self.VcpuCount = extInstance.GetVcpuCount() self.VmemSizeMb = extInstance.GetVmemSizeMB() self.DiskSizeGB = extInstance.GetDiskSizeGB() + self.StorageType = extInstance.GetStorageType() self.Status = extInstance.GetStatus() self.ConnectionStr = extInstance.GetConnectionStr() @@ -341,12 +1142,10 @@ func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred m self.MaintainTime = extInstance.GetMaintainTime() - if zoneId := extInstance.GetIZoneId(); len(zoneId) > 0 { - zone, err := db.FetchByExternalId(ZoneManager, zoneId) - if err != nil { - return errors.Wrapf(err, "SyncWithCloudDBInstance.FetchZoneId") - } - self.ZoneId = zone.GetId() + self.ZoneId = extInstance.GetIZoneId() + err := self.setZoneInfo() + if err != nil { + return errors.Wrap(err, "setZoneInfo") } if createdAt := extInstance.GetCreatedAt(); !createdAt.IsZero() { @@ -372,6 +1171,12 @@ func (self *SDBInstance) SyncWithCloudDBInstance(ctx context.Context, userCred m return nil } +func (self *SDBInstance) GetSlaveDBInstances() ([]SDBInstance, error) { + dbinstances := []SDBInstance{} + q := DBInstanceManager.Query().Equals("master_instance_id", self.Id) + return dbinstances, db.FetchModelObjects(DBInstanceManager, q, &dbinstances) +} + func (manager *SDBInstanceManager) newFromCloudDBInstance(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, provider *SCloudprovider, region *SCloudregion, extInstance cloudprovider.ICloudDBInstance) (*SDBInstance, error) { lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) @@ -400,16 +1205,30 @@ func (manager *SDBInstanceManager) newFromCloudDBInstance(ctx context.Context, u instance.VmemSizeMb = extInstance.GetVmemSizeMB() instance.DiskSizeGB = extInstance.GetDiskSizeGB() instance.ConnectionStr = extInstance.GetConnectionStr() + instance.StorageType = extInstance.GetStorageType() instance.InternalConnectionStr = extInstance.GetInternalConnectionStr() instance.MaintainTime = extInstance.GetMaintainTime() + instance.ZoneId = extInstance.GetIZoneId() + err = instance.setZoneInfo() + if err != nil { + return nil, errors.Wrap(err, "instance.setZoneInfo") + } - if zoneId := extInstance.GetIZoneId(); len(zoneId) > 0 { - zone, err := db.FetchByExternalId(ZoneManager, zoneId) + if secgroupId := extInstance.GetSecurityGroupId(); len(secgroupId) > 0 { + q := SecurityGroupCacheManager.Query().Equals("manager_id", provider.Id).Equals("external_id", secgroupId) + count, err := q.CountWithError() if err != nil { - return nil, errors.Wrapf(err, "newFromCloudDBInstance.FetchZoneId") + log.Errorf("failed get secgroup cache by externalId %s error: %v", secgroupId, err) + } else if count > 0 { + cache := SSecurityGroupCache{} + err = q.First(&cache) + if err != nil { + log.Errorf("failed get secgroup cache by externalId %s error: %v", secgroupId, err) + } else { + instance.SecgroupId = cache.SecgroupId + } } - instance.ZoneId = zone.GetId() } if vpcId := extInstance.GetIVpcId(); len(vpcId) > 0 { diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index dc5f7e7097..0ca87a6401 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -19,6 +19,7 @@ import ( "fmt" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/tristate" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -1232,46 +1233,27 @@ func (bucketManager *SBucketManager) purgeAll(ctx context.Context, userCred mccl return nil } -func (privilege *SDBInstancePrivilege) purge(ctx context.Context, userCred mcclient.TokenCredential) error { - lockman.LockObject(ctx, privilege) - defer lockman.ReleaseObject(ctx, privilege) - - err := privilege.ValidateDeleteCondition(ctx) - if err != nil { - return err - } - return privilege.Delete(ctx, userCred) -} - -func (account *SDBInstanceAccount) purgePrivileges(ctx context.Context, userCred mcclient.TokenCredential) error { - privileges, err := account.GetDBInstancePrivileges() - if err != nil { - return err - } - - for i := range privileges { - err = privileges[i].purge(ctx, userCred) - if err != nil { - return err - } - } - return nil -} - -func (account *SDBInstanceAccount) purge(ctx context.Context, userCred mcclient.TokenCredential) error { +func (account *SDBInstanceAccount) Purge(ctx context.Context, userCred mcclient.TokenCredential) error { lockman.LockObject(ctx, account) defer lockman.ReleaseObject(ctx, account) - err := account.purgePrivileges(ctx, userCred) + privileges, err := account.GetDBInstancePrivileges() if err != nil { - return err + return errors.Wrap(err, "account.GetDBInstancePrivileges") + } + + for _, privilege := range privileges { + err = privilege.Delete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "privilege.Delete() %s", privilege.Id) + } } err = account.ValidateDeleteCondition(ctx) if err != nil { return err } - return account.Delete(ctx, userCred) + return account.RealDelete(ctx, userCred) } func (instance *SDBInstance) purgeAccounts(ctx context.Context, userCred mcclient.TokenCredential) error { @@ -1281,7 +1263,7 @@ func (instance *SDBInstance) purgeAccounts(ctx context.Context, userCred mcclien } for i := range accounts { - err = accounts[i].purge(ctx, userCred) + err = accounts[i].Purge(ctx, userCred) if err != nil { return err } @@ -1289,15 +1271,27 @@ func (instance *SDBInstance) purgeAccounts(ctx context.Context, userCred mcclien return nil } -func (database *SDBInstanceDatabase) purge(ctx context.Context, userCred mcclient.TokenCredential) error { +func (database *SDBInstanceDatabase) Purge(ctx context.Context, userCred mcclient.TokenCredential) error { lockman.LockObject(ctx, database) defer lockman.ReleaseObject(ctx, database) - err := database.ValidateDeleteCondition(ctx) + privileges, err := database.GetDBInstancePrivileges() + if err != nil { + return errors.Wrap(err, "database.GetDBInstancePrivileges") + } + + for _, privilege := range privileges { + err = privilege.Delete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "privilege.Delete() %s", privilege.Id) + } + } + + err = database.ValidateDeleteCondition(ctx) if err != nil { return err } - return database.Delete(ctx, userCred) + return database.RealDelete(ctx, userCred) } func (instance *SDBInstance) purgeDatabases(ctx context.Context, userCred mcclient.TokenCredential) error { @@ -1307,7 +1301,7 @@ func (instance *SDBInstance) purgeDatabases(ctx context.Context, userCred mcclie } for i := range databases { - err = databases[i].purge(ctx, userCred) + err = databases[i].Purge(ctx, userCred) if err != nil { return err } @@ -1360,7 +1354,23 @@ func (instance *SDBInstance) purgeNetwork(ctx context.Context, userCred mcclient return nil } -func (instance *SDBInstance) purge(ctx context.Context, userCred mcclient.TokenCredential) error { +func (instance *SDBInstance) purgeBackups(ctx context.Context, userCred mcclient.TokenCredential) error { + backups, err := instance.GetDBInstanceBackups() + if err != nil { + return errors.Wrap(err, "instance.GetDBInstanceBackups") + } + for _, backup := range backups { + if backup.BackupMode == api.BACKUP_MODE_AUTOMATED { + err = backup.purge(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "backup.purge %s(%s)", backup.Name, backup.Id) + } + } + } + return nil +} + +func (instance *SDBInstance) Purge(ctx context.Context, userCred mcclient.TokenCredential) error { lockman.LockObject(ctx, instance) defer lockman.ReleaseObject(ctx, instance) @@ -1384,12 +1394,17 @@ func (instance *SDBInstance) purge(ctx context.Context, userCred mcclient.TokenC return err } + err = instance.purgeBackups(ctx, userCred) + if err != nil { + return errors.Wrap(err, "instance.purgeBackups") + } + err = instance.ValidateDeleteCondition(ctx) if err != nil { return err } - return instance.Delete(ctx, userCred) + return instance.RealDelete(ctx, userCred) } func (manager *SDBInstanceManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { @@ -1398,7 +1413,7 @@ func (manager *SDBInstanceManager) purgeAll(ctx context.Context, userCred mcclie return err } for i := range instances { - err = instances[i].purge(ctx, userCred) + err = instances[i].Purge(ctx, userCred) if err != nil { return err } @@ -1414,7 +1429,7 @@ func (backup *SDBInstanceBackup) purge(ctx context.Context, userCred mcclient.To if err != nil { return err } - return backup.Delete(ctx, userCred) + return backup.RealDelete(ctx, userCred) } func (manager *SDBInstanceBackupManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 8d51acc9ff..be5bf0290a 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -16,14 +16,17 @@ package models import ( "context" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" ) type IRegionDriver interface { @@ -99,7 +102,7 @@ type IRegionDriver interface { //Nat gateway DealNatGatewaySpec(spec string) string - RequestBindIPToNatgateway(ctx context.Context, task taskman.ITask, natgateway *SNatGateway, eipId string) error + RequestBindIPToNatgateway(ctx context.Context, task taskman.ITask, natgateway *SNatGateway, eipID string) error RequestUnBindIPFromNatgateway(ctx context.Context, task taskman.ITask, nat INatHelper, natgateway *SNatGateway) error BindIPToNatgatewayRollback(ctx context.Context, eipId string) error @@ -109,6 +112,25 @@ type IRegionDriver interface { IsSecurityGroupBelongVpc() bool GetDefaultSecurityGroupVpcId() string GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, host *SHost, vpc *SVpc, classic bool) string + + ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input *api.SDBInstanceCreateInput, skus []SDBInstanceSku, network *SNetwork) (*api.SDBInstanceCreateInput, error) + ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *SDBInstance, input *api.SDBInstanceAccountCreateInput) (*api.SDBInstanceAccountCreateInput, error) + ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *SDBInstance, input *api.SDBInstanceDatabaseCreateInput) (*api.SDBInstanceDatabaseCreateInput, error) + ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *SDBInstance, input *api.SDBInstanceBackupCreateInput) (*api.SDBInstanceBackupCreateInput, error) + ValidateChangeDBInstanceConfigData(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, input *api.SDBInstanceChangeConfigInput) error + ValidateDBInstanceAccountPrivilege(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, privilege string) error + ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, account string) error + + RequestCreateDBInstance(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *SDBInstance, task taskman.ITask) error + RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, backup *SDBInstanceBackup, task taskman.ITask) error + IsSupportDBInstancePublicConnection() bool + IsSupportKeepDBInstanceManualBackup() bool + + IsSupportedBillingCycle(bc billing.SBillingCycle, resource string) bool + GetSecgroupVpcid(vpcId string) string + InitDBInstanceUser(dbinstance *SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error + RequestRenewDBInstance(instance *SDBInstance, bc billing.SBillingCycle) (time.Time, error) + RequestChangeDBInstanceConfig(ctx context.Context, userCred mcclient.TokenCredential, instance *SDBInstance, task taskman.ITask) error } var regionDrivers map[string]IRegionDriver diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index 9cc6ece56c..08dd918dcf 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -20,11 +20,14 @@ import ( "regexp" "strings" "time" + "unicode" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" + 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/lockman" @@ -34,6 +37,7 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/choices" "yunion.io/x/onecloud/pkg/util/rand" ) @@ -963,3 +967,208 @@ func (self *SAliyunRegionDriver) BindIPToNatgatewayRollback(ctx context.Context, func (self *SAliyunRegionDriver) IsSecurityGroupBelongVpc() bool { return true } + +func (self *SAliyunRegionDriver) ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input *api.SDBInstanceCreateInput, skus []models.SDBInstanceSku, network *models.SNetwork) (*api.SDBInstanceCreateInput, error) { + if input.BillingType == billing_api.BILLING_TYPE_PREPAID && len(input.MasterInstanceId) > 0 { + return nil, httperrors.NewInputParameterError("slave dbinstance not support prepaid billing type") + } + + wire := network.GetWire() + if wire == nil { + return nil, httperrors.NewGeneralError(fmt.Errorf("failed to found wire for network %s(%s)", network.Name, network.Id)) + } + zone := wire.GetZone() + if zone == nil { + return nil, httperrors.NewGeneralError(fmt.Errorf("failed to found zone for wire %s(%s)", wire.Name, wire.Id)) + } + + match := false + for _, sku := range skus { + if utils.IsInStringArray(zone.Id, []string{sku.Zone1, sku.Zone2, sku.Zone3}) { + match = true + break + } + } + + if !match { + return nil, httperrors.NewInputParameterError("failed to match any skus in the network %s(%s) zone %s(%s)", network.Name, network.Id, zone.Name, zone.Id) + } + + var master *models.SDBInstance + var slaves []models.SDBInstance + var err error + if len(input.MasterInstanceId) > 0 { + _master, _ := models.DBInstanceManager.FetchById(input.MasterInstanceId) + master = _master.(*models.SDBInstance) + slaves, err = master.GetSlaveDBInstances() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + switch master.Engine { + case api.DBINSTANCE_TYPE_MYSQL: + switch master.EngineVersion { + case "5.6": + break + case "5.7", "8.0": + if master.Category != api.ALIYUN_DBINSTANCE_CATEGORY_HA { + return nil, httperrors.NewInputParameterError("Not support create readonly dbinstance for MySQL %s %s", master.EngineVersion, master.Category) + } + if master.StorageType != api.ALIYUN_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD { + return nil, httperrors.NewInputParameterError("Not support create readonly dbinstance for MySQL %s %s with storage type %s, only support %s", master.EngineVersion, master.Category, master.StorageType, api.ALIYUN_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD) + } + default: + return nil, httperrors.NewInputParameterError("Not support create readonly dbinstance for MySQL %s", master.EngineVersion) + } + case api.DBINSTANCE_TYPE_SQLSERVER: + if master.Category != api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON || master.EngineVersion != "2017_ent" { + return nil, httperrors.NewInputParameterError("SQL Server only support create readonly dbinstance for 2017_ent") + } + if len(slaves) >= 7 { + return nil, httperrors.NewInputParameterError("SQL Server cannot have more than seven read-only dbinstances") + } + default: + return nil, httperrors.NewInputParameterError("Not support create readonly dbinstance which master dbinstance engine is", master.Engine) + } + } + + switch input.Engine { + case api.DBINSTANCE_TYPE_MYSQL: + if input.VmemSizeMb/1024 >= 64 && len(slaves) >= 10 { + return nil, httperrors.NewInputParameterError("Master dbinstance memory ≥64GB, up to 10 read-only instances are allowed to be created") + } else if input.VmemSizeMb/1024 < 64 && len(slaves) >= 5 { + return nil, httperrors.NewInputParameterError("Master dbinstance memory <64GB, up to 5 read-only instances are allowed to be created") + } + case api.DBINSTANCE_TYPE_SQLSERVER: + if input.Category == api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON { + vpc := network.GetVpc() + count, err := vpc.GetNetworkCount() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if count < 2 { + return nil, httperrors.NewInputParameterError("At least two networks are required under vpc %s(%s) whith aliyun %s(%s)", vpc.Name, vpc.Id, input.Engine, input.Category) + } + } + } + + if len(input.Name) > 0 { + if strings.HasPrefix(input.Description, "http://") || strings.HasPrefix(input.Description, "https://") { + return nil, httperrors.NewInputParameterError("Description can not start with http:// or https://") + } + } + + return input, nil +} + +func (self *SAliyunRegionDriver) IsSupportedBillingCycle(bc billing.SBillingCycle, resource string) bool { + switch resource { + case models.DBInstanceManager.KeywordPlural(): + years := bc.GetYears() + months := bc.GetMonths() + if (years >= 1 && years <= 3) || (months >= 1 && months <= 9) { + return true + } + } + return false +} + +func (self *SAliyunRegionDriver) 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, + } + if len(backup.DBNames) > 0 { + desc.Databases = strings.Split(backup.DBNames, ",") + } + + _, 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 +} + +func (self *SAliyunRegionDriver) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceAccountCreateInput) (*api.SDBInstanceAccountCreateInput, error) { + if len(input.Name) < 2 || len(input.Name) > 16 { + return nil, httperrors.NewInputParameterError("Aliyun DBInstance account name length shoud be 2~16 characters") + } + + DENY_KEY := map[string][]string{ + api.DBINSTANCE_TYPE_MYSQL: api.ALIYUN_MYSQL_DENY_KEYWORKD, + api.DBINSTANCE_TYPE_SQLSERVER: api.ALIYUN_SQL_SERVER_DENY_KEYWORD, + } + + if keys, ok := DENY_KEY[instance.Engine]; ok && utils.IsInStringArray(input.Name, keys) { + return nil, httperrors.NewInputParameterError("%s is reserved for aliyun %s, please use another", input.Name, instance.Engine) + } + + for i, s := range input.Name { + if !unicode.IsLetter(s) && !unicode.IsDigit(s) && s != '_' { + return nil, httperrors.NewInputParameterError("invalid character %s for account name", s) + } + if s == '_' && (i == 0 || i == len(input.Name)) { + return nil, httperrors.NewInputParameterError("account name can not start or end with _") + } + } + + for _, privilege := range input.Privileges { + err := self.ValidateDBInstanceAccountPrivilege(ctx, userCred, instance, privilege.Privilege) + if err != nil { + return nil, err + } + } + + return input, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceDatabaseCreateInput) (*api.SDBInstanceDatabaseCreateInput, error) { + if len(input.CharacterSet) == 0 { + return nil, httperrors.NewMissingParameterError("character_set") + } + + for _, account := range input.Accounts { + err := self.ValidateDBInstanceAccountPrivilege(ctx, userCred, instance, account.Privilege) + if err != nil { + return nil, err + } + } + + return input, nil +} + +func (self *SAliyunRegionDriver) ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceBackupCreateInput) (*api.SDBInstanceBackupCreateInput, error) { + return input, nil +} + +func (self *SAliyunRegionDriver) ValidateDBInstanceAccountPrivilege(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, privilege string) error { + switch privilege { + case api.DATABASE_PRIVILEGE_RW: + case api.DATABASE_PRIVILEGE_R: + case api.DATABASE_PRIVILEGE_DDL, api.DATABASE_PRIVILEGE_DML: + if instance.Engine != api.DBINSTANCE_TYPE_MYSQL && instance.Engine != api.DBINSTANCE_TYPE_MARIADB { + return httperrors.NewInputParameterError("%s only support aliyun %s or %s", privilege, api.DBINSTANCE_TYPE_MARIADB, api.DBINSTANCE_TYPE_MYSQL) + } + case api.DATABASE_PRIVILEGE_OWNER: + if instance.Engine != api.DBINSTANCE_TYPE_SQLSERVER { + return httperrors.NewInputParameterError("%s only support aliyun %s", privilege, api.DBINSTANCE_TYPE_SQLSERVER) + } + default: + return httperrors.NewInputParameterError("Unknown privilege %s", privilege) + } + return nil +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index a0494de77c..b0821f1b68 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -17,6 +17,7 @@ package regiondrivers import ( "context" "fmt" + "time" "yunion.io/x/jsonutils" @@ -26,6 +27,7 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" ) type SBaseRegionDriver struct { @@ -216,3 +218,67 @@ func (self *SBaseRegionDriver) GetSecurityGroupVpcId(ctx context.Context, userCr func (self *SBaseRegionDriver) RequestSyncSecurityGroup(ctx context.Context, userCred mcclient.TokenCredential, vpcId string, vpc *models.SVpc, secgroup *models.SSecurityGroup) (string, error) { return "", fmt.Errorf("Not Implemented RequestSyncSecurityGroup") } + +func (self *SBaseRegionDriver) ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input *api.SDBInstanceCreateInput, skus []models.SDBInstanceSku, network *models.SNetwork) (*api.SDBInstanceCreateInput, error) { + return input, nil +} + +func (self *SBaseRegionDriver) RequestCreateDBInstance(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateDBInstance") +} + +func (self *SBaseRegionDriver) RequestCreateDBInstanceBackup(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, backup *models.SDBInstanceBackup, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestCreateDBInstanceBackup") +} + +func (self *SBaseRegionDriver) IsSupportedBillingCycle(bc billing.SBillingCycle, resource string) bool { + return false +} + +func (self *SBaseRegionDriver) GetSecgroupVpcid(vpcId string) string { + return vpcId +} + +func (self *SBaseRegionDriver) InitDBInstanceUser(dbinstance *models.SDBInstance, task taskman.ITask, desc *cloudprovider.SManagedDBInstanceCreateConfig) error { + return nil +} + +func (self *SBaseRegionDriver) RequestRenewDBInstance(instance *models.SDBInstance, bc billing.SBillingCycle) (time.Time, error) { + return time.Time{}, fmt.Errorf("Not Implement RequestRenewDBInstance") +} + +func (self *SBaseRegionDriver) RequestChangeDBInstanceConfig(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestChangeDBInstanceConfig") +} + +func (self *SBaseRegionDriver) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceAccountCreateInput) (*api.SDBInstanceAccountCreateInput, error) { + return nil, fmt.Errorf("Not Implement ValidateCreateDBInstanceAccountData") +} + +func (self *SBaseRegionDriver) ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceDatabaseCreateInput) (*api.SDBInstanceDatabaseCreateInput, error) { + return nil, fmt.Errorf("Not Implement ValidateCreateDBInstanceDatabaseData") +} + +func (self *SBaseRegionDriver) ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceBackupCreateInput) (*api.SDBInstanceBackupCreateInput, error) { + return nil, fmt.Errorf("Not Implement ValidateCreateDBInstanceBackupData") +} + +func (self *SBaseRegionDriver) ValidateChangeDBInstanceConfigData(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, input *api.SDBInstanceChangeConfigInput) error { + return fmt.Errorf("Not Implement ValidateChangeDBInstanceConfigData") +} + +func (self *SBaseRegionDriver) ValidateDBInstanceAccountPrivilege(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, privilege string) error { + return fmt.Errorf("Not Implement ValidateDBInstanceAccountPrivilege") +} + +func (self *SBaseRegionDriver) IsSupportDBInstancePublicConnection() bool { + return true +} + +func (self *SBaseRegionDriver) ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, account string) error { + return nil +} + +func (self *SBaseRegionDriver) IsSupportKeepDBInstanceManualBackup() bool { + return false +} diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go index 5db39cb1ab..14a526df70 100644 --- a/pkg/compute/regiondrivers/huawei.go +++ b/pkg/compute/regiondrivers/huawei.go @@ -28,8 +28,11 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/rand" + "yunion.io/x/onecloud/pkg/util/seclib2" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" ) type SHuaWeiRegionDriver struct { @@ -1530,3 +1533,108 @@ func (self *SHuaWeiRegionDriver) DealNatGatewaySpec(spec string) string { func (self *SHuaWeiRegionDriver) IsSecurityGroupBelongVpc() bool { return true } + +func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input *api.SDBInstanceCreateInput, skus []models.SDBInstanceSku, network *models.SNetwork) (*api.SDBInstanceCreateInput, error) { + if len(input.MasterInstanceId) > 0 && input.Engine == api.DBINSTANCE_TYPE_SQLSERVER { + return nil, httperrors.NewInputParameterError("Not support create read-only dbinstance for %s", input.Engine) + } + + if input.DiskSizeGB < 40 || input.DiskSizeGB > 4000 { + return nil, httperrors.NewInputParameterError("%s require disk size must in 40 ~ 4000 GB", self.GetProvider()) + } + + if input.DiskSizeGB%10 > 0 { + return nil, httperrors.NewInputParameterError("The disk_size_gb must be an integer multiple of 10") + } + + 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" + } + + account := models.SDBInstanceAccount{ + 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 *SHuaWeiRegionDriver) IsSupportedBillingCycle(bc billing.SBillingCycle, resource string) bool { + switch resource { + case models.DBInstanceManager.KeywordPlural(): + years := bc.GetYears() + months := bc.GetMonths() + if (years >= 1 && years <= 3) || (months >= 1 && months <= 9) { + return true + } + } + return false +} + +func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceAccountCreateInput) (*api.SDBInstanceAccountCreateInput, error) { + if utils.IsInStringArray(instance.Engine, []string{api.DBINSTANCE_TYPE_POSTGRESQL, api.DBINSTANCE_TYPE_SQLSERVER}) { + return nil, httperrors.NewInputParameterError("Not support create account for huawei cloud %s instance", instance.Engine) + } + return input, nil +} + +func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceDatabaseData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceDatabaseCreateInput) (*api.SDBInstanceDatabaseCreateInput, error) { + if utils.IsInStringArray(instance.Engine, []string{api.DBINSTANCE_TYPE_POSTGRESQL, api.DBINSTANCE_TYPE_SQLSERVER}) { + return nil, httperrors.NewInputParameterError("Not support create database for huawei cloud %s instance", instance.Engine) + } + return input, nil +} + +func (self *SHuaWeiRegionDriver) ValidateCreateDBInstanceBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *models.SDBInstance, input *api.SDBInstanceBackupCreateInput) (*api.SDBInstanceBackupCreateInput, error) { + if len(input.Name) < 4 || len(input.Name) > 64 { + return nil, httperrors.NewInputParameterError("Huawei DBInstance backup name length shoud be 4~64 characters") + } + + if len(input.Databases) > 0 && instance.Engine != api.DBINSTANCE_TYPE_SQLSERVER { + return nil, httperrors.NewInputParameterError("Huawei only supports specified databases with %s", api.DBINSTANCE_TYPE_SQLSERVER) + } + + return input, nil +} + +func (self *SHuaWeiRegionDriver) ValidateChangeDBInstanceConfigData(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, input *api.SDBInstanceChangeConfigInput) error { + if input.DiskSizeGB != 0 && input.DiskSizeGB < instance.DiskSizeGB { + return httperrors.NewUnsupportOperationError("Huawei DBInstance Disk cannot be thrink") + } + if len(input.Category) > 0 && input.Category != instance.Category { + return httperrors.NewUnsupportOperationError("Huawei DBInstance category cannot change") + } + if len(input.StorageType) > 0 && input.StorageType != instance.StorageType { + return httperrors.NewUnsupportOperationError("Huawei DBInstance storage type cannot change") + } + return nil +} + +func (self *SHuaWeiRegionDriver) IsSupportDBInstancePublicConnection() bool { + //目前华为云未对外开放打开远程连接的API接口 + return false +} + +func (self *SHuaWeiRegionDriver) ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, account string) error { + return httperrors.NewUnsupportOperationError("Huawei current not support reset dbinstance account password") +} + +func (self *SHuaWeiRegionDriver) IsSupportKeepDBInstanceManualBackup() bool { + return true +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 9e6b83ecad..ec8c4a4132 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" + 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/lockman" @@ -34,6 +35,7 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" "yunion.io/x/onecloud/pkg/util/rand" ) @@ -1462,3 +1464,296 @@ func (self *SManagedVirtualizationRegionDriver) RequestCacheSecurityGroup(ctx co }) return nil } + +func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx context.Context, userCred mcclient.TokenCredential, dbinstance *models.SDBInstance, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iregion, err := dbinstance.GetIRegion() + if err != nil { + return nil, err + } + + vpc, err := dbinstance.GetVpc() + if err != nil { + return nil, errors.Wrap(err, "dbinstance.GetVpc()") + } + + params := task.GetParams() + networkId, _ := params.GetString("network_external_id") + if len(networkId) == 0 { + return nil, fmt.Errorf("failed to get network externalId") + } + address, _ := params.GetString("address") + passwd, _ := params.GetString("password") + desc := cloudprovider.SManagedDBInstanceCreateConfig{ + Name: dbinstance.Name, + Description: dbinstance.Description, + StorageType: dbinstance.StorageType, + DiskSizeGB: dbinstance.DiskSizeGB, + InstanceType: dbinstance.InstanceType, + VcpuCount: dbinstance.VcpuCount, + VmemSizeMb: dbinstance.VmemSizeMb, + VpcId: vpc.ExternalId, + NetworkId: networkId, + Address: address, + Engine: dbinstance.Engine, + EngineVersion: dbinstance.EngineVersion, + Category: dbinstance.Category, + Port: dbinstance.Port, + Password: passwd, + } + + if len(dbinstance.InstanceType) > 0 { + desc.ZoneIds, _ = dbinstance.GetAvailableZoneIds() + } else { + desc.InstanceTypes, _ = dbinstance.GetAvailableInstanceTypes() + } + + region := dbinstance.GetRegion() + + err = region.GetDriver().InitDBInstanceUser(dbinstance, task, &desc) + if err != nil { + return nil, err + } + + secgroup, _ := dbinstance.GetSecgroup() + if secgroup != nil { + vpcId := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) + _, err = region.GetDriver().RequestSyncSecurityGroup(ctx, userCred, vpcId, vpc, secgroup) + if err != nil { + return nil, errors.Wrap(err, "SyncSecurityGroup") + } + } + + if dbinstance.BillingType == billing_api.BILLING_TYPE_PREPAID { + bc, err := billing.ParseBillingCycle(dbinstance.BillingCycle) + if err != nil { + log.Errorf("failed to parse billing cycle %s: %v", dbinstance.BillingCycle, err) + } else if bc.IsValid() { + desc.BillingCycle = &bc + } + } + + if len(dbinstance.MasterInstanceId) > 0 { + master, err := dbinstance.GetMasterInstance() + if err != nil { + return nil, errors.Wrap(err, "dbinstnace.GetMasterInstance()") + } + desc.MasterInstanceId = master.ExternalId + } + + log.Debugf("create dbinstance params: %s", jsonutils.Marshal(desc).String()) + + idbinstance, err := iregion.CreateIDBInstance(&desc) + if err != nil { + return nil, err + } + + db.SetExternalId(dbinstance, userCred, idbinstance.GetGlobalId()) + + err = cloudprovider.WaitStatus(idbinstance, api.DBINSTANCE_RUNNING, time.Second*5, time.Hour*1) + if err != nil { + log.Errorf("timeout for waiting dbinstance running error: %v", err) + } + + dbinstance.ZoneId = idbinstance.GetIZoneId() + err = dbinstance.SetZoneInfo(ctx, userCred) + if err != nil { + log.Errorf("failed to set dbinstance %s(%s) zoneInfo from cloud dbinstance: %v", dbinstance.Name, dbinstance.Id, err) + } + + _, err = db.Update(dbinstance, func() error { + dbinstance.Engine = idbinstance.GetEngine() + dbinstance.EngineVersion = idbinstance.GetEngineVersion() + dbinstance.StorageType = idbinstance.GetStorageType() + dbinstance.DiskSizeGB = idbinstance.GetDiskSizeGB() + dbinstance.Category = idbinstance.GetCategory() + dbinstance.VcpuCount = idbinstance.GetVcpuCount() + dbinstance.VmemSizeMb = idbinstance.GetVmemSizeMB() + dbinstance.InstanceType = idbinstance.GetInstanceType() + dbinstance.ConnectionStr = idbinstance.GetConnectionStr() + dbinstance.InternalConnectionStr = idbinstance.GetInternalConnectionStr() + dbinstance.MaintainTime = idbinstance.GetMaintainTime() + dbinstance.Port = idbinstance.GetPort() + + dbinstance.CreatedAt = idbinstance.GetCreatedAt() + dbinstance.ExpiredAt = idbinstance.GetExpiredAt() + return nil + }) + if err != nil { + log.Errorf("failed to update dbinstance conf: %v", err) + } + + network, err := idbinstance.GetDBNetwork() + 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) + } + + return nil, nil + }) + + return nil +} + +func (self *SManagedVirtualizationRegionDriver) RequestChangeDBInstanceConfig(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + input := &api.SDBInstanceChangeConfigInput{} + err := task.GetParams().Unmarshal(input) + if err != nil { + return nil, errors.Wrap(err, "task.GetParams().Unmarshal") + } + if len(input.StorageType) > 0 { + instance.StorageType = input.StorageType + } + + conf := &cloudprovider.SManagedDBInstanceChangeConfig{ + DiskSizeGB: input.DiskSizeGB, + StorageType: instance.StorageType, + } + + instanceTypes := []string{} + + 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 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) + } + } + + if len(conf.InstanceType) == 0 && len(instanceTypes) == 0 { + return nil, fmt.Errorf("No available dbinstance sku for change config") + } + + iRds, err := instance.GetIDBInstance() + if err != nil { + 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) + if err != nil { + log.Warningf("change failed: %v try another", err) + } + } + return nil, fmt.Errorf("no available dbinstance sku to change") + } + + 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) + } + _, err = db.Update(instance, func() error { + instance.InstanceType = iRds.GetInstanceType() + instance.Category = iRds.GetCategory() + instance.VcpuCount = iRds.GetVcpuCount() + instance.VmemSizeMb = iRds.GetVmemSizeMB() + instance.StorageType = iRds.GetStorageType() + instance.DiskSizeGB = iRds.GetDiskSizeGB() + return nil + }) + if err != nil { + return nil, errors.Wrapf(err, "db.Update(instance)") + } + return nil, nil + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) 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") + } + + iRegion, err := backup.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "backup.GetIRegion") + } + + desc := &cloudprovider.SDBInstanceBackupCreateConfig{ + Name: backup.Name, + } + + if len(backup.DBNames) > 0 { + desc.Databases = strings.Split(backup.DBNames, ",") + } + + backupId, err := iRds.CreateIBackup(desc) + if err != nil { + return nil, errors.Wrap(err, "iRds.CreateBackup") + } + + db.SetExternalId(backup, userCred, backupId) + + iBackup, err := iRegion.GetIDBInstanceBackupById(backupId) + if err != nil { + return nil, errors.Wrapf(err, "iRegion.GetIDBInstanceBackupById(%s)", backupId) + } + + _, err = db.Update(backup, func() error { + backup.StartTime = iBackup.GetStartTime() + backup.EndTime = iBackup.GetEndTime() + backup.BackupSizeMb = iBackup.GetBackupSizeMb() + return nil + }) + + return nil, err + }) + return nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateChangeDBInstanceConfigData(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, input *api.SDBInstanceChangeConfigInput) error { + return nil +} + +func (self *SManagedVirtualizationRegionDriver) ValidateResetDBInstancePassword(ctx context.Context, userCred mcclient.TokenCredential, instance *models.SDBInstance, account string) error { + return nil +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index dbaff2e43a..9d7b6f85b3 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -127,6 +127,7 @@ func InitHandlers(app *appsrv.Application) { models.DBInstanceDatabaseManager, models.DBInstanceAccountManager, models.DBInstancePrivilegeManager, + models.DBInstanceSkuManager, models.ElasticcacheManager, models.ElasticcacheAclManager, diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index 74d1ec76c1..7bd5bc5eeb 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -89,6 +89,7 @@ func StartService() { cron.AddJobEveryFewHour("SnapshotsCleanup", 1, 35, 0, models.SnapshotManager.CleanupSnapshots, false) cron.AddJobEveryFewHour("AutoSyncExtDiskSnapshot", 1, 10, 0, models.DiskManager.AutoSyncExtDiskSnapshot, false) cron.AddJobEveryFewDays("SyncSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncSkus, true) + cron.AddJobEveryFewDays("SyncDBInstanceSkus", opts.SyncSkusDay, opts.SyncSkusHour, 0, 0, models.SyncDBInstanceSkus, true) cron.AddJobEveryFewDays("StorageSnapshotsRecycle", 1, 2, 0, 0, models.StorageManager.StorageSnapshotsRecycle, false) cron.Start() diff --git a/pkg/compute/tasks/dbinstance_account_create_task.go b/pkg/compute/tasks/dbinstance_account_create_task.go new file mode 100644 index 0000000000..73c697afcc --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_create_task.go @@ -0,0 +1,84 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceAccountCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountCreateTask{}) +} + +func (self *DBInstanceAccountCreateTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_CREATE_FAILED, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_CREATE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_CREATE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + self.CreateDBInstanceAccount(ctx, account) +} + +func (self *DBInstanceAccountCreateTask) CreateDBInstanceAccount(ctx context.Context, account *models.SDBInstanceAccount) { + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + desc := &cloudprovider.SDBInstanceAccountCreateConfig{ + Name: account.Name, + } + desc.Password, _ = account.GetPassword() + + err = iRds.CreateAccount(desc) + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.CreateAccount")) + return + } + + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") + + input := api.SDBInstanceAccountCreateInput{} + self.GetParams().Unmarshal(&input) + for _, _privilege := range input.Privileges { + account.StartGrantPrivilegeTask(ctx, self.UserCred, _privilege.Database, _privilege.Privilege, "") + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_account_delete_task.go b/pkg/compute/tasks/dbinstance_account_delete_task.go new file mode 100644 index 0000000000..5076527687 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_delete_task.go @@ -0,0 +1,84 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package 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 DBInstanceAccountDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountDeleteTask{}) +} + +func (self *DBInstanceAccountDeleteTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_DELETE_FAILED, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_DELETE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_DELETE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + self.DeleteDBInstanceAccount(ctx, account) +} + +func (self *DBInstanceAccountDeleteTask) DeleteDBInstanceAccount(ctx context.Context, account *models.SDBInstanceAccount) { + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + accounts, err := iRds.GetIDBInstanceAccounts() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.GetIDBInstanceAccounts")) + return + } + for _, ac := range accounts { + if ac.GetName() == account.Name { + err = ac.Delete() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "ac.Delete()")) + return + } + } + } + + err = account.Purge(ctx, self.UserCred) + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.Purge")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_account_grant_privilege_task.go b/pkg/compute/tasks/dbinstance_account_grant_privilege_task.go new file mode 100644 index 0000000000..ad3d0ef623 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_grant_privilege_task.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 tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/pkg/errors" +) + +type DBInstanceAccountGrantPrivilegeTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountGrantPrivilegeTask{}) +} + +func (self *DBInstanceAccountGrantPrivilegeTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_GRANT_PRIVILEGE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountGrantPrivilegeTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + accounts, err := iRds.GetIDBInstanceAccounts() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.GetIDBInstanceAccounts")) + return + } + + databaseStr, _ := self.GetParams().GetString("database") + privilegeStr, _ := self.GetParams().GetString("privilege") + database, err := instance.GetDBInstanceDatabase(databaseStr) + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "instance.GetDBInstanceDatabase(%s)", databaseStr)) + return + } + + var iAccount cloudprovider.ICloudDBInstanceAccount = nil + for _, ac := range accounts { + if ac.GetName() == account.Name { + iAccount = ac + } + } + if iAccount == nil { + self.taskFailed(ctx, account, fmt.Errorf("failed to found iAccount by %s", account.Name)) + return + } + + err = iAccount.GrantPrivilege(databaseStr, privilegeStr) + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "ac.GrantPrivilege")) + return + } + + privilege := models.SDBInstancePrivilege{ + Privilege: privilegeStr, + DBInstanceaccountId: account.Id, + DBInstancedatabaseId: database.Id, + } + + models.DBInstancePrivilegeManager.TableSpec().Insert(&privilege) + + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_account_reset_password_task.go b/pkg/compute/tasks/dbinstance_account_reset_password_task.go new file mode 100644 index 0000000000..ef548c832f --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_reset_password_task.go @@ -0,0 +1,78 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/pkg/errors" +) + +type DBInstanceAccountResetPasswordTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountResetPasswordTask{}) +} + +func (self *DBInstanceAccountResetPasswordTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_RESET_PASSWD_FAILED, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_RESET_PASSWORD, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_RESET_PASSWORD, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountResetPasswordTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + accounts, err := iRds.GetIDBInstanceAccounts() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.GetIDBInstanceAccounts")) + return + } + + password, _ := self.GetParams().GetString("password") + for _, ac := range accounts { + if ac.GetName() == account.Name { + err = ac.ResetPassword(password) + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "ac.ResetPassword")) + return + } + account.SetPassword(password) + } + } + + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, account, logclient.ACT_RESET_PASSWORD, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go b/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go new file mode 100644 index 0000000000..b9e9128fc9 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_revoke_privilege_task.go @@ -0,0 +1,92 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + 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/util/logclient" + "yunion.io/x/pkg/errors" +) + +type DBInstanceAccountRevokePrivilegeTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountRevokePrivilegeTask{}) +} + +func (self *DBInstanceAccountRevokePrivilegeTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_REVOKE_PRIVILEGE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_REVOKE_PRIVILEGE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountRevokePrivilegeTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + databaseStr, _ := self.GetParams().GetString("database") + accounts, err := iRds.GetIDBInstanceAccounts() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.GetIDBInstanceAccounts")) + return + } + + var iAccount cloudprovider.ICloudDBInstanceAccount = nil + for _, ac := range accounts { + if ac.GetName() == account.Name { + iAccount = ac + break + } + } + if iAccount == nil { + self.taskFailed(ctx, account, fmt.Errorf("failed to found iAccount by %s", account.Name)) + return + } + + err = iAccount.RevokePrivilege(databaseStr) + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iAccount.RevokePrivilege")) + return + } + + dbPrivilege, _ := instance.GetDBInstancePrivilege(account.Id, databaseStr) + if dbPrivilege != nil { + dbPrivilege.Delete(ctx, self.UserCred) + } + + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, account, logclient.ACT_REVOKE_PRIVILEGE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_account_set_privileges_task.go b/pkg/compute/tasks/dbinstance_account_set_privileges_task.go new file mode 100644 index 0000000000..5b067b2a7a --- /dev/null +++ b/pkg/compute/tasks/dbinstance_account_set_privileges_task.go @@ -0,0 +1,129 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + 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/util/logclient" + "yunion.io/x/pkg/errors" +) + +type DBInstanceAccountSetPrivilegesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceAccountSetPrivilegesTask{}) +} + +func (self *DBInstanceAccountSetPrivilegesTask) taskFailed(ctx context.Context, account *models.SDBInstanceAccount, err error) { + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, err.Error()) + db.OpsLog.LogEvent(account, db.ACT_SET_PRIVILEGES, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_SET_PRIVILEGES, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceAccountSetPrivilegesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + account := obj.(*models.SDBInstanceAccount) + instance, err := account.GetDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "account.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + accounts, err := iRds.GetIDBInstanceAccounts() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "iRds.GetIDBInstanceAccounts")) + return + } + + var iAccount cloudprovider.ICloudDBInstanceAccount = nil + for _, ac := range accounts { + if ac.GetName() == account.Name { + iAccount = ac + break + } + } + if iAccount == nil { + self.taskFailed(ctx, account, fmt.Errorf("failed to found iAccount by %s", account.Name)) + return + } + + grant := map[string]string{} + revoke := map[string]string{} + params := self.GetParams() + params.Unmarshal(&grant, "grant") + params.Unmarshal(&revoke, "revoke") + + for db, privilege := range revoke { + database, err := instance.GetDBInstanceDatabase(db) + if err != nil { + log.Errorf("failed to found database %s for instance %s(%s): %v", db, instance.Name, instance.Id, err) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, err.Error(), self.UserCred, false) + continue + } + err = iAccount.RevokePrivilege(database.Name) + if err != nil { + log.Errorf("failed to revoke privilege %s for account %s(%s) error: %v", privilege, account.Name, account.Id, err) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_REVOKE_PRIVILEGE, err.Error(), self.UserCred, false) + continue + } + + dbPrivilege, _ := instance.GetDBInstancePrivilege(account.Id, database.Id) + if dbPrivilege != nil { + dbPrivilege.Delete(ctx, self.UserCred) + } + } + + for db, privilege := range grant { + database, err := instance.GetDBInstanceDatabase(db) + if err != nil { + log.Errorf("failed to found database %s for instance %s(%s): %v", db, instance.Name, instance.Id, err) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, err.Error(), self.UserCred, false) + continue + } + err = iAccount.GrantPrivilege(database.Name, privilege) + if err != nil { + log.Errorf("failed to grant privilege %s for account %s(%s) error: %v", privilege, account.Name, account.Id, err) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, err.Error(), self.UserCred, false) + continue + } + pri := models.SDBInstancePrivilege{ + Privilege: privilege, + DBInstanceaccountId: account.Id, + DBInstancedatabaseId: database.Id, + } + models.DBInstancePrivilegeManager.TableSpec().Insert(&pri) + logclient.AddActionLogWithStartable(self, account, logclient.ACT_GRANT_PRIVILEGE, nil, self.UserCred, true) + } + + account.SetStatus(self.UserCred, api.DBINSTANCE_USER_AVAILABLE, "") + logclient.AddActionLogWithStartable(self, account, logclient.ACT_REVOKE_PRIVILEGE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_backup_create_task.go b/pkg/compute/tasks/dbinstance_backup_create_task.go new file mode 100644 index 0000000000..e97d671f3e --- /dev/null +++ b/pkg/compute/tasks/dbinstance_backup_create_task.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/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 DBInstanceBackupCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceBackupCreateTask{}) +} + +func (self *DBInstanceBackupCreateTask) taskFailed(ctx context.Context, backup *models.SDBInstanceBackup, err error) { + backup.SetStatus(self.UserCred, api.DBINSTANCE_BACKUP_CREATE_FAILED, err.Error()) + db.OpsLog.LogEvent(backup, db.ACT_CREATE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, backup, logclient.ACT_CREATE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceBackupCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + backup := obj.(*models.SDBInstanceBackup) + self.CreateDBInstanceBackup(ctx, backup) +} + +func (self *DBInstanceBackupCreateTask) CreateDBInstanceBackup(ctx context.Context, backup *models.SDBInstanceBackup) { + instance, err := backup.GetDBInstance() + if err != nil { + self.taskFailed(ctx, backup, errors.Wrap(err, "backup.GetDBInstance")) + return + } + + self.SetStage("OnCreateDBInstanceBackupComplete", nil) + instance.GetRegion().GetDriver().RequestCreateDBInstanceBackup(ctx, self.UserCred, instance, backup, self) +} + +func (self *DBInstanceBackupCreateTask) OnCreateDBInstanceBackupComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + backup := obj.(*models.SDBInstanceBackup) + logclient.AddActionLogWithStartable(self, backup, logclient.ACT_CREATE, nil, self.UserCred, true) + + backup.SetStatus(self.UserCred, api.DBINSTANCE_BACKUP_READY, "") + self.SetStageComplete(ctx, nil) +} + +func (self *DBInstanceBackupCreateTask) OnCreateDBInstanceBackupCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + backup := obj.(*models.SDBInstanceBackup) + self.taskFailed(ctx, backup, fmt.Errorf("%s", data.String())) +} diff --git a/pkg/compute/tasks/dbinstance_backup_delete.go b/pkg/compute/tasks/dbinstance_backup_delete.go new file mode 100644 index 0000000000..307dac9f0c --- /dev/null +++ b/pkg/compute/tasks/dbinstance_backup_delete.go @@ -0,0 +1,79 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceBackupDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceBackupDeleteTask{}) +} + +func (self *DBInstanceBackupDeleteTask) taskFailed(ctx context.Context, backup *models.SDBInstanceBackup, err error) { + backup.SetStatus(self.UserCred, api.DBINSTANCE_BACKUP_DELETE_FAILED, err.Error()) + db.OpsLog.LogEvent(backup, db.ACT_DELETE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, backup, logclient.ACT_DELETE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceBackupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + backup := obj.(*models.SDBInstanceBackup) + self.DeleteDBInstanceBackup(ctx, backup) +} + +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")) + return + } + + if iBackup != nil { + err = iBackup.Delete() + if err != nil { + self.taskFailed(ctx, backup, errors.Wrap(err, "iBackup.Delete()")) + return + } + } + + err = backup.RealDelete(ctx, self.UserCred) + if err != nil { + self.taskFailed(ctx, backup, errors.Wrap(err, "backup.Purge")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_change_config.go b/pkg/compute/tasks/dbinstance_change_config.go new file mode 100644 index 0000000000..3f1da80f8d --- /dev/null +++ b/pkg/compute/tasks/dbinstance_change_config.go @@ -0,0 +1,74 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + 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 DBInstanceChangeConfigTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceChangeConfigTask{}) +} + +func (self *DBInstanceChangeConfigTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_CHANGE_CONFIG_FAILED, err.Error()) + db.OpsLog.LogEvent(dbinstance, db.ACT_CHANGE_CONFIG, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CHANGE_CONFIG, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceChangeConfigTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + instance := obj.(*models.SDBInstance) + self.SetStage("OnDBInstanceChangeConfigComplete", nil) + err := instance.GetRegion().GetDriver().RequestChangeDBInstanceConfig(ctx, self.UserCred, instance, self) + if err != nil { + self.taskFailed(ctx, instance, err) + return + } +} + +func (self *DBInstanceChangeConfigTask) OnDBInstanceChangeConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CHANGE_CONFIG, nil, self.UserCred, true) + + self.SetStage("OnSyncDBInstanceStatusComplete", nil) + dbinstance.StartDBInstanceSyncStatusTask(ctx, self.UserCred, nil, self.GetTaskId()) +} + +func (self *DBInstanceChangeConfigTask) OnDBInstanceChangeConfigCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + instance := obj.(*models.SDBInstance) + self.taskFailed(ctx, instance, fmt.Errorf("%s", data.String())) +} + +func (self *DBInstanceChangeConfigTask) OnSyncDBInstanceStatusComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *DBInstanceChangeConfigTask) OnSyncDBInstanceStatusCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data.String()) +} diff --git a/pkg/compute/tasks/dbinstance_create_task.go b/pkg/compute/tasks/dbinstance_create_task.go new file mode 100644 index 0000000000..fcdbb6ead4 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_create_task.go @@ -0,0 +1,79 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + 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 DBInstanceCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceCreateTask{}) +} + +func (self *DBInstanceCreateTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_CREATE_FAILED, err.Error()) + db.OpsLog.LogEvent(dbinstance, db.ACT_CREATE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CREATE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.CreateDBInstance(ctx, dbinstance) +} + +func (self *DBInstanceCreateTask) CreateDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { + region := dbinstance.GetRegion() + self.SetStage("OnCreateDBInstanceComplete", nil) + err := region.GetDriver().RequestCreateDBInstance(ctx, self.UserCred, dbinstance, self) + if err != nil { + self.taskFailed(ctx, dbinstance, err) + return + } +} + +func (self *DBInstanceCreateTask) OnCreateDBInstanceComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_CREATE, nil, self.UserCred, true) + + self.SetStage("OnSyncDBInstanceStatusComplete", nil) + dbinstance.StartDBInstanceSyncStatusTask(ctx, self.UserCred, nil, self.GetTaskId()) +} + +func (self *DBInstanceCreateTask) OnCreateDBInstanceCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.taskFailed(ctx, dbinstance, fmt.Errorf("%s", data.String())) +} + +func (self *DBInstanceCreateTask) OnSyncDBInstanceStatusComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *DBInstanceCreateTask) OnSyncDBInstanceStatusCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data.String()) +} diff --git a/pkg/compute/tasks/dbinstance_database_create_task.go b/pkg/compute/tasks/dbinstance_database_create_task.go new file mode 100644 index 0000000000..c1c9468240 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_database_create_task.go @@ -0,0 +1,87 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceDatabaseCreateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceDatabaseCreateTask{}) +} + +func (self *DBInstanceDatabaseCreateTask) taskFailed(ctx context.Context, database *models.SDBInstanceDatabase, err error) { + database.SetStatus(self.UserCred, api.DBINSTANCE_DATABASE_CREATE_FAILE, err.Error()) + db.OpsLog.LogEvent(database, db.ACT_CREATE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, database, logclient.ACT_CREATE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceDatabaseCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + database := obj.(*models.SDBInstanceDatabase) + self.CreateDBInstanceDatabase(ctx, database) +} + +func (self *DBInstanceDatabaseCreateTask) CreateDBInstanceDatabase(ctx context.Context, database *models.SDBInstanceDatabase) { + instance, err := database.GetDBInstance() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "database.GetDBInstance")) + return + } + + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + desc := &cloudprovider.SDBInstanceDatabaseCreateConfig{ + CharacterSet: database.CharacterSet, + Name: database.Name, + Description: database.Description, + } + + err = iRds.CreateDatabase(desc) + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "iRds.CreateDatabase")) + return + } + + database.SetStatus(self.UserCred, api.DBINSTANCE_DATABASE_RUNNING, "") + + input := api.SDBInstanceDatabaseCreateInput{} + for _, _account := range input.Accounts { + account, _ := instance.GetDBInstanceAccount(_account.DBInstancedccountId) + if account != nil { + account.StartGrantPrivilegeTask(ctx, self.UserCred, database.Name, _account.Privilege, "") + } + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_database_delete_task.go b/pkg/compute/tasks/dbinstance_database_delete_task.go new file mode 100644 index 0000000000..06b70c9dc2 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_database_delete_task.go @@ -0,0 +1,85 @@ +// 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 DBInstanceDatabaseDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceDatabaseDeleteTask{}) +} + +func (self *DBInstanceDatabaseDeleteTask) taskFailed(ctx context.Context, database *models.SDBInstanceDatabase, err error) { + database.SetStatus(self.UserCred, api.DBINSTANCE_DATABASE_DELETE_FAILED, err.Error()) + db.OpsLog.LogEvent(database, db.ACT_DELETE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, database, logclient.ACT_DELETE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceDatabaseDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + database := obj.(*models.SDBInstanceDatabase) + self.DeleteDBInstanceDatabase(ctx, database) +} + +func (self *DBInstanceDatabaseDeleteTask) DeleteDBInstanceDatabase(ctx context.Context, database *models.SDBInstanceDatabase) { + instance, err := database.GetDBInstance() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "database.GetDBInstance")) + return + } + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + databases, err := iRds.GetIDBInstanceDatabases() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "iRds.GetIDBInstanceDatabases")) + return + } + + for _, db := range databases { + if db.GetName() == database.Name { + err = db.Delete() + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "db.Delete()")) + return + } + } + } + + err = database.Purge(ctx, self.UserCred) + if err != nil { + self.taskFailed(ctx, database, errors.Wrap(err, "database.Purge")) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_delete_task.go b/pkg/compute/tasks/dbinstance_delete_task.go new file mode 100644 index 0000000000..46d4fa0f46 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_delete_task.go @@ -0,0 +1,111 @@ +// 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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceDeleteTask{}) +} + +func (self *DBInstanceDeleteTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_DELETE_FAILED, err.Error()) + db.OpsLog.LogEvent(dbinstance, db.ACT_DELETE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_DELETE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + + keepBackup := false + if !dbinstance.GetRegion().GetDriver().IsSupportKeepDBInstanceManualBackup() { + keepBackup = jsonutils.QueryBoolean(self.Params, "keep_backup", false) + } + + if !keepBackup { + self.SetStage("OnBackupDeleteComplete", nil) + self.OnBackupDeleteComplete(ctx, dbinstance, nil) + } else { + self.DeleteDBInstance(ctx, dbinstance) + } +} + +func (self *DBInstanceDeleteTask) OnBackupDeleteComplete(ctx context.Context, instance *models.SDBInstance, data jsonutils.JSONObject) { + _backups, err := instance.GetDBInstanceBackups() + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "instance.GetDBInstanceBackups")) + return + } + backups := []models.SDBInstanceBackup{} + for _, backup := range _backups { + if backup.BackupMode == api.BACKUP_MODE_MANUAL { + backups = append(backups, backup) + } + } + if len(backups) == 0 { + self.DeleteDBInstance(ctx, instance) + return + } + + backups[0].StartDBInstanceBackupDeleteTask(ctx, self.UserCred, self.GetTaskId()) +} + +func (self *DBInstanceDeleteTask) DeleteDBInstanceComplete(ctx context.Context, dbinstance *models.SDBInstance) { + err := dbinstance.Purge(ctx, self.UserCred) + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrap(err, "dbinstance.Purge")) + return + } + self.SetStageComplete(ctx, nil) +} + +func (self *DBInstanceDeleteTask) DeleteDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { + idbinstance, err := dbinstance.GetIDBInstance() + if err != nil { + if err == cloudprovider.ErrNotFound { + self.DeleteDBInstanceComplete(ctx, dbinstance) + return + } + self.taskFailed(ctx, dbinstance, err) + return + } + + if !jsonutils.QueryBoolean(self.Params, "purge", false) { + err = idbinstance.Delete() + if err != nil { + self.taskFailed(ctx, dbinstance, err) + return + } + } + + self.DeleteDBInstanceComplete(ctx, dbinstance) +} diff --git a/pkg/compute/tasks/dbinstance_public_connection_task.go b/pkg/compute/tasks/dbinstance_public_connection_task.go new file mode 100644 index 0000000000..c6a6ff9216 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_public_connection_task.go @@ -0,0 +1,99 @@ +// 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" + "time" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/pkg/errors" +) + +type DBInstancePublicConnectionTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstancePublicConnectionTask{}) +} + +func (self *DBInstancePublicConnectionTask) getAction() string { + act := logclient.ACT_OPEN_PUBLIC_CONNECTION + if isOpen, _ := self.GetParams().Bool("open"); !isOpen { + act = logclient.ACT_CLOSE_PUBLIC_CONNECTION + } + return act +} + +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.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstancePublicConnectionTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.DBInstancePublicConnectionOperation(ctx, dbinstance) +} + +func (self *DBInstancePublicConnectionTask) DBInstancePublicConnectionOperation(ctx context.Context, instance *models.SDBInstance) { + idbinstance, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + if isOpen, _ := self.GetParams().Bool("open"); isOpen { + err = idbinstance.OpenPublicConnection() + } else { + err = idbinstance.ClosePublicConnection() + } + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "idbinstance.OpenPublicConnection || idbinstance.ClosePublicConnection")) + return + } + + err = cloudprovider.WaitStatus(idbinstance, api.DBINSTANCE_RUNNING, 10*time.Second, time.Minute*30) + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "cloudprovider.WaitStatus")) + return + } + + _, err = db.Update(instance, func() error { + instance.ConnectionStr = idbinstance.GetConnectionStr() + return nil + }) + + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "db.Update")) + return + } + + self.taskComplete(ctx, instance) +} + +func (self *DBInstancePublicConnectionTask) taskComplete(ctx context.Context, dbinstance *models.SDBInstance) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_RUNNING, "") + logclient.AddActionLogWithStartable(self, dbinstance, self.getAction(), nil, self.UserCred, true) + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_reboot_task.go b/pkg/compute/tasks/dbinstance_reboot_task.go new file mode 100644 index 0000000000..a7bcbd131c --- /dev/null +++ b/pkg/compute/tasks/dbinstance_reboot_task.go @@ -0,0 +1,75 @@ +// 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" + "time" + + "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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceRebootTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceRebootTask{}) +} + +func (self *DBInstanceRebootTask) taskFailed(ctx context.Context, dbinstance *models.SDBInstance, err error) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_REBOOT_FAILED, err.Error()) + db.OpsLog.LogEvent(dbinstance, db.ACT_REBOOT, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_REBOOT, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceRebootTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.RebootDBInstance(ctx, dbinstance) +} + +func (self *DBInstanceRebootTask) RebootDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { + idbinstance, err := dbinstance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrap(err, "dbinstance.GetIDBInstance")) + return + } + err = idbinstance.Reboot() + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrap(err, "idbinstance.Reboot")) + return + } + err = cloudprovider.WaitStatus(idbinstance, api.DBINSTANCE_RUNNING, 10*time.Second, time.Minute*30) + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrap(err, "cloudprovider.WaitStatus")) + return + } + self.taskComplete(ctx, dbinstance) +} + +func (self *DBInstanceRebootTask) taskComplete(ctx context.Context, dbinstance *models.SDBInstance) { + dbinstance.SetStatus(self.UserCred, api.DBINSTANCE_RUNNING, "") + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_REBOOT, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_recovery_task.go b/pkg/compute/tasks/dbinstance_recovery_task.go new file mode 100644 index 0000000000..8f1806d058 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_recovery_task.go @@ -0,0 +1,88 @@ +// 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" + "time" + + "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/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceRecoveryTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceRecoveryTask{}) +} + +func (self *DBInstanceRecoveryTask) taskFailed(ctx context.Context, instance *models.SDBInstance, err error) { + instance.SetStatus(self.UserCred, api.DBINSTANCE_RESTORE_FAILED, err.Error()) + db.OpsLog.LogEvent(instance, db.ACT_RESTORE, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, instance, logclient.ACT_RESTORE, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceRecoveryTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + instance := obj.(*models.SDBInstance) + + iRds, err := instance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "instance.GetIDBInstance")) + return + } + + input := &api.SDBInstanceRecoveryConfigInput{} + err = self.GetParams().Unmarshal(input) + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "self.GetParams().Unmarshal(input)")) + return + } + + backup, err := instance.GetDBInstanceBackup(input.DBInstancebackupId) + if err != nil { + self.taskFailed(ctx, instance, errors.Wrapf(err, "instance.GetDBInstanceBackup(%s)", input.DBInstancebackupId)) + return + } + + conf := &cloudprovider.SDBInstanceRecoveryConfig{ + BackupId: backup.ExternalId, + Databases: input.Databases, + } + + err = iRds.RecoveryFromBackup(conf) + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "iRds.RecoveryFromBackup")) + return + } + + err = cloudprovider.WaitStatus(iRds, api.DBINSTANCE_RUNNING, time.Second*10, time.Second*40) + if err != nil { + self.taskFailed(ctx, instance, errors.Wrap(err, "cloudprovider.WaitStatus(running)")) + return + } + + instance.SetStatus(self.UserCred, api.DBINSTANCE_RUNNING, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_renew_task.go b/pkg/compute/tasks/dbinstance_renew_task.go new file mode 100644 index 0000000000..7a830d9927 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_renew_task.go @@ -0,0 +1,69 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + 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/billing" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type DBInstanceRenewTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceRenewTask{}) +} + +func (self *DBInstanceRenewTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + instance := obj.(*models.SDBInstance) + + durationStr, _ := self.GetParams().GetString("duration") + bc, _ := billing.ParseBillingCycle(durationStr) + + exp, err := instance.GetRegion().GetDriver().RequestRenewDBInstance(instance, bc) + if err != nil { + msg := fmt.Sprintf("RequestRenewDBInstance failed %s", err) + log.Errorf(msg) + db.OpsLog.LogEvent(instance, db.ACT_REW_FAIL, msg, self.UserCred) + logclient.AddActionLogWithStartable(self, instance, logclient.ACT_RENEW, msg, self.UserCred, false) + instance.SetStatus(self.GetUserCred(), api.DBINSTANCE_RENEW_FAILED, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = instance.SaveRenewInfo(ctx, self.UserCred, &bc, &exp) + if err != nil { + msg := fmt.Sprintf("SaveRenewInfo fail %s", err) + log.Errorf(msg) + self.SetStageFailed(ctx, msg) + return + } + + logclient.AddActionLogWithStartable(self, instance, logclient.ACT_RENEW, nil, self.UserCred, true) + + instance.StartDBInstanceSyncStatusTask(ctx, self.UserCred, nil, self.GetTaskId()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/dbinstance_sync_status_task.go b/pkg/compute/tasks/dbinstance_sync_status_task.go new file mode 100644 index 0000000000..f727d1b012 --- /dev/null +++ b/pkg/compute/tasks/dbinstance_sync_status_task.go @@ -0,0 +1,59 @@ +// 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 DBInstanceSyncStatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(DBInstanceSyncStatusTask{}) +} + +func (self *DBInstanceSyncStatusTask) 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_STATUS, err.Error(), self.GetUserCred()) + logclient.AddActionLogWithStartable(self, dbinstance, logclient.ACT_SYNC_STATUS, err.Error(), self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *DBInstanceSyncStatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + dbinstance := obj.(*models.SDBInstance) + self.SyncStatusDBInstance(ctx, dbinstance) +} + +func (self *DBInstanceSyncStatusTask) SyncStatusDBInstance(ctx context.Context, dbinstance *models.SDBInstance) { + idbinstance, err := dbinstance.GetIDBInstance() + if err != nil { + self.taskFailed(ctx, dbinstance, errors.Wrapf(err, "dbinstance.GetIDBInstance")) + return + } + status := idbinstance.GetStatus() + dbinstance.SetStatus(self.UserCred, status, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/mcclient/modules/managers.go b/pkg/mcclient/modules/managers.go index 32eb424dec..6e5a1d0df0 100644 --- a/pkg/mcclient/modules/managers.go +++ b/pkg/mcclient/modules/managers.go @@ -14,7 +14,9 @@ package modules -import "yunion.io/x/onecloud/pkg/mcclient/modulebase" +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) /* 添加新manager注意事项: @@ -149,6 +151,12 @@ func NewCloudmetaManager(keyword, keywordPlural string, columns, adminColumns [] Keyword: keyword, KeywordPlural: keywordPlural} } +func NewYunionmetaManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { + return modulebase.ResourceManager{ + BaseManager: *modulebase.NewBaseManager("yunionmeta", "", "", columns, adminColumns), + Keyword: keyword, KeywordPlural: keywordPlural} +} + func NewAnsibleManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ BaseManager: *modulebase.NewBaseManager("ansible", "", "", columns, adminColumns), diff --git a/pkg/mcclient/modules/mod_dbinstance_skus.go b/pkg/mcclient/modules/mod_dbinstance_skus.go new file mode 100644 index 0000000000..10c319e106 --- /dev/null +++ b/pkg/mcclient/modules/mod_dbinstance_skus.go @@ -0,0 +1,33 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type DBInstanceSkusManager struct { + modulebase.ResourceManager +} + +var ( + DBInstanceSkus DBInstanceSkusManager +) + +func init() { + DBInstanceSkus = DBInstanceSkusManager{NewComputeManager("dbinstance_sku", "dbinstance_skus", + []string{"Id", "Name", "Engine", "Engine_Version", "Category", "Storage_Type", "Status", "Enabled"}, + []string{})} + + registerCompute(&DBInstanceSkus) +} diff --git a/pkg/mcclient/modules/mod_dbinstanceaccounts.go b/pkg/mcclient/modules/mod_dbinstanceaccounts.go index c73df73a6a..a82de7b756 100644 --- a/pkg/mcclient/modules/mod_dbinstanceaccounts.go +++ b/pkg/mcclient/modules/mod_dbinstanceaccounts.go @@ -14,16 +14,60 @@ package modules -import "yunion.io/x/onecloud/pkg/mcclient/modulebase" +import ( + "fmt" -var ( - DBInstanceAccounts modulebase.ResourceManager + "github.com/pkg/errors" + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/pkg/utils" ) +var ( + DBInstanceAccounts SDBInstanceAccountManager +) + +type SDBInstanceAccountManager struct { + modulebase.ResourceManager +} + +func (this *SDBInstanceAccountManager) GetLoginInfo(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + data, err := this.Get(s, id, nil) + if err != nil { + return nil, err + } + + account := struct { + Id string + Name string + Secret string + }{} + + err = data.Unmarshal(&account) + if err != nil { + return nil, errors.Wrap(err, "data.Unmarshal") + } + + if len(account.Secret) == 0 { + return nil, fmt.Errorf("No login secret found") + } + + password, err := utils.DescryptAESBase64(account.Id, account.Secret) + if err != nil { + return nil, errors.Wrap(err, "Descrypt") + } + + return jsonutils.Marshal(map[string]string{ + "account": account.Name, + "password": password, + }), nil +} + func init() { - DBInstanceAccounts = NewComputeManager("dbinstanceaccount", "dbinstanceaccounts", - []string{"ID", "Name", "DBInstance_id"}, - []string{}) + DBInstanceAccounts = SDBInstanceAccountManager{NewComputeManager("dbinstanceaccount", "dbinstanceaccounts", + []string{"ID", "Name", "DBInstance_id", "Status", "Dbinstanceprivileges"}, + []string{})} registerCompute(&DBInstanceAccounts) } diff --git a/pkg/mcclient/modules/mod_dbinstancebackups.go b/pkg/mcclient/modules/mod_dbinstancebackups.go index 32f5aeb80c..33582ce7e3 100644 --- a/pkg/mcclient/modules/mod_dbinstancebackups.go +++ b/pkg/mcclient/modules/mod_dbinstancebackups.go @@ -22,7 +22,7 @@ var ( func init() { DBInstanceBackups = NewComputeManager("dbinstancebackup", "dbinstancebackups", - []string{"ID", "Name", "Start_Time", "End_Time", "Backup_Type", "Intranet_Download_URL", "Download_URL", "DBNames", "Backup_Size_Mb", "DBInstance_id"}, + []string{"ID", "Name", "Start_Time", "End_Time", "Status", "Backup_Type", "DBNames", "Backup_Size_Mb", "DBInstance", "Engine", "EngineVersion"}, []string{}) registerCompute(&DBInstanceBackups) diff --git a/pkg/mcclient/modules/mod_dbinstancedatabases.go b/pkg/mcclient/modules/mod_dbinstancedatabases.go index 47231fe89d..f3ccd72273 100644 --- a/pkg/mcclient/modules/mod_dbinstancedatabases.go +++ b/pkg/mcclient/modules/mod_dbinstancedatabases.go @@ -22,7 +22,7 @@ var ( func init() { DBInstanceDatabases = NewComputeManager("dbinstancedatabase", "dbinstancedatabases", - []string{"ID", "Name", "Character_Set", "DBInstance_id"}, + []string{"ID", "Name", "Character_Set", "DBInstance_id", "DBInstance", "Status"}, []string{}) registerCompute(&DBInstanceDatabases) diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index 6569e1b09e..423ba3a63f 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -137,7 +137,7 @@ func _jsonRequest(client *sdk.Client, domain string, version string, apiName str resp, err := processCommonRequest(client, req) if err != nil { - log.Errorf("request error %s with params %s", err, params) + log.Errorf("request %s error %s with params %s", apiName, err, params) return nil, err } body, err := jsonutils.Parse(resp.GetHttpContentBytes()) diff --git a/pkg/multicloud/aliyun/dbinstance.go b/pkg/multicloud/aliyun/dbinstance.go index 2c8899ad8c..a7cd2e4165 100644 --- a/pkg/multicloud/aliyun/dbinstance.go +++ b/pkg/multicloud/aliyun/dbinstance.go @@ -15,7 +15,9 @@ package aliyun import ( + "context" "fmt" + "strings" "time" "yunion.io/x/jsonutils" @@ -24,8 +26,11 @@ import ( 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" + "yunion.io/x/onecloud/pkg/util/rand" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" ) type SReadOnlyDBInstanceIds struct { @@ -67,6 +72,7 @@ type SDBInstance struct { DBInstanceDiskUsed int64 DBInstanceStorage int DBInstanceStorageType string + MasterInstanceId string DBInstanceMemory int DBMaxQuantity int IPType string @@ -132,8 +138,10 @@ func (rds *SDBInstance) GetGlobalId() string { // INS_CLONING 实例克隆中 func (rds *SDBInstance) GetStatus() string { switch rds.DBInstanceStatus { - case "Creating", "DBInstanceClassChanging", "GuardDBInstanceCreating", "DBInstanceNetTypeChanging", "GuardSwitching": + case "Creating", "GuardDBInstanceCreating", "DBInstanceNetTypeChanging", "GuardSwitching", "NET_CREATING", "NET_DELETING": return api.DBINSTANCE_DEPLOYING + case "DBInstanceClassChanging": + return api.DBINSTANCE_CHANGE_CONFIG case "Running": return api.DBINSTANCE_RUNNING case "Deleting": @@ -149,6 +157,7 @@ func (rds *SDBInstance) GetStatus() string { case "INS_CLONING": return api.DBINSTANCE_CLONING default: + log.Errorf("Unknown dbinstance status %s", rds.DBInstanceStatus) return api.DBINSTANCE_UNKNOWN } } @@ -165,6 +174,10 @@ func (rds *SDBInstance) GetCreatedAt() time.Time { return rds.CreateTime } +func (rds *SDBInstance) GetStorageType() string { + return rds.DBInstanceStorageType +} + func (rds *SDBInstance) GetEngine() string { switch rds.Engine { case "MySQL": @@ -192,13 +205,13 @@ func (rds *SDBInstance) GetInstanceType() string { func (rds *SDBInstance) GetCategory() string { switch rds.Category { case "Basic": - return api.DBINSTANCE_CATEGORY_BASIC + return api.ALIYUN_DBINSTANCE_CATEGORY_BASIC case "HighAvailability": - return api.DBINSTANCE_CATEGORY_HA + return api.ALIYUN_DBINSTANCE_CATEGORY_HA case "AlwaysOn": - return api.DBINSTANCE_CATEGORY_ALWAYSON + return api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON case "Finance": - return api.DBINSTANCE_CATEGORY_FINANCE + return api.ALIYUN_DBINSTANCE_CATEGORY_FINANCE } return rds.Category } @@ -248,15 +261,7 @@ func (rds *SDBInstance) Refresh() error { } func (rds *SDBInstance) GetIZoneId() string { - if len(rds.ZoneId) > 0 { - zone, err := rds.region.getZoneById(rds.ZoneId) - if err != nil { - log.Errorf("SDBInstances.getZoneById %s error: %v", rds.ZoneId, err) - return "" - } - return zone.GetGlobalId() - } - return "" + return rds.ZoneId } func (rds *SDBInstance) GetDBNetwork() (*cloudprovider.SDBInstanceNetwork, error) { @@ -339,6 +344,15 @@ func (region *SRegion) GetDBInstances(ids []string, offset int, limit int) ([]SD return instances, int(total), nil } +func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + rds, err := region.GetDBInstanceDetail(instanceId) + if err != nil { + return nil, err + } + rds.region = region + return rds, nil +} + func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { instances := []SDBInstance{} for { @@ -360,6 +374,9 @@ func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, erro } func (region *SRegion) GetDBInstanceDetail(instanceId string) (*SDBInstance, error) { + if len(instanceId) == 0 { + return nil, cloudprovider.ErrNotFound + } params := map[string]string{} params["RegionId"] = region.RegionId params["DBInstanceId"] = instanceId @@ -457,6 +474,35 @@ func (rds *SDBInstance) GetIDBInstanceParameters() ([]cloudprovider.ICloudDBInst return iparameters, nil } +func (region *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := region.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrap(err, "region.GetIDBInstanceBackups") + } + for _, backup := range backups { + if backup.GetGlobalId() == backupId { + return backup, nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (rds *SDBInstance) Reboot() error { + return rds.region.RebootDBInstance(rds.DBInstanceId) +} + +func (rds *SDBInstance) Delete() error { + return rds.region.DeleteDBInstance(rds.DBInstanceId) +} + +func (region *SRegion) RebootDBInstance(instanceId string) error { + params := map[string]string{} + params["RegionId"] = region.RegionId + params["DBInstanceId"] = instanceId + _, err := region.rdsRequest("RestartDBInstance", params) + return err +} + func (rds *SDBInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { databases := []SDBInstanceDatabase{} for { @@ -498,3 +544,231 @@ func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstan } return iaccounts, nil } + +func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + return rds.region.ChangeDBInstanceConfig(rds.DBInstanceId, string(rds.PayType), desc) +} + +func (region *SRegion) ChangeDBInstanceConfig(instanceId, payType string, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "PayType": payType, + "DBInstanceClass": desc.InstanceType, + "DBInstanceStorage": fmt.Sprintf("%d", desc.DiskSizeGB), + } + + _, err := region.rdsRequest("ModifyDBInstanceSpec", params) + if err != nil { + return errors.Wrap(err, "region.rdsRequest.ModifyDBInstanceSpec") + } + return nil +} + +func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + params := map[string]string{ + "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, + } + switch desc.Category { + case api.ALIYUN_DBINSTANCE_CATEGORY_HA: + params["Category"] = "HighAvailability" + case api.ALIYUN_DBINSTANCE_CATEGORY_BASIC: + params["Category"] = "Basic" + case api.ALIYUN_DBINSTANCE_CATEGORY_ALWAYSON: + params["Category"] = "AlwaysOn" + case api.ALIYUN_DBINSTANCE_CATEGORY_FINANCE: + params["Category"] = "Finance" + } + + if len(desc.Address) > 0 { + params["PrivateIpAddress"] = desc.Address + } + if desc.BillingCycle != nil { + params["PayType"] = "Prepaid" + if desc.BillingCycle.GetMonths() > 0 { + params["Period"] = "Month" + params["UsedTime"] = fmt.Sprintf("%d", desc.BillingCycle.GetMonths()) + } else { + params["Period"] = "Year" + params["UsedTime"] = fmt.Sprintf("%d", desc.BillingCycle.GetYears()) + } + err := billingCycle2Params(desc.BillingCycle, params) + if err != nil { + return nil, err + } + params["AutoRenew"] = "False" + } + + action := "CreateDBInstance" + if len(desc.MasterInstanceId) > 0 { + action = "CreateReadOnlyDBInstance" + 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) + } + } + instanceId, err := resp.GetString("DBInstanceId") + if err != nil { + return nil, errors.Wrap(err, `resp.GetString("DBInstanceId")`) + } + return region.GetIDBInstanceById(instanceId) +} + +func (rds *SDBInstance) GetMasterInstanceId() string { + if len(rds.MasterInstanceId) > 0 { + return rds.MasterInstanceId + } + rds.Refresh() + return rds.MasterInstanceId +} + +func (region *SRegion) OpenPublicConnection(instanceId string) error { + rds, err := region.GetDBInstanceDetail(instanceId) + if err != nil { + return err + } + params := map[string]string{ + "RegionId": region.RegionId, + "ConnectionStringPrefix": rds.DBInstanceId + rand.String(3), + "DBInstanceId": rds.DBInstanceId, + "Port": fmt.Sprintf("%d", rds.Port), + } + _, err = rds.region.rdsRequest("AllocateInstancePublicConnection", params) + if err != nil { + return errors.Wrap(err, "rdsRequest(AllocateInstancePublicConnection)") + } + return nil +} + +func (rds *SDBInstance) OpenPublicConnection() error { + if url := rds.GetConnectionStr(); len(url) == 0 { + err := rds.region.OpenPublicConnection(rds.DBInstanceId) + if err != nil { + return err + } + rds.netInfo = []SDBInstanceNetwork{} + } + return nil +} + +func (region *SRegion) ClosePublicConnection(instanceId string) error { + netInfo, err := region.GetDBInstanceNetInfo(instanceId) + if err != nil { + return errors.Wrap(err, "GetDBInstanceNetInfo") + } + + for _, net := range netInfo { + if net.IPType == "Public" { + params := map[string]string{ + "RegionId": region.RegionId, + "CurrentConnectionString": net.ConnectionString, + "DBInstanceId": instanceId, + } + _, err = region.rdsRequest("ReleaseInstancePublicConnection", params) + if err != nil { + return errors.Wrap(err, "rdsRequest(ReleaseInstancePublicConnection)") + } + + } + } + return nil + +} + +func (rds *SDBInstance) ClosePublicConnection() error { + return rds.region.ClosePublicConnection(rds.DBInstanceId) +} + +func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + return rds.region.RecoveryDBInstanceFromBackup(rds.DBInstanceId, conf.BackupId, conf.Databases) +} + +func (region *SRegion) RecoveryDBInstanceFromBackup(instanceId string, backupId string, databases map[string]string) error { + dbs := []string{} + for k, v := range databases { + dbs = append(dbs, fmt.Sprintf(`"%s":"%s"`, k, v)) + } + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "BackupId": backupId, + "DbNames": strings.Join(dbs, ","), + } + _, err := region.rdsRequest("RecoveryDBInstance", params) + if err != nil { + return errors.Wrap(err, "rdsRequest.RecoveryDBInstance") + } + return nil + +} + +func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return rds.region.CreateDBInstanceDatabae(rds.DBInstanceId, conf.CharacterSet, conf.Name, conf.Description) +} + +func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return rds.region.CreateDBInstanceAccount(rds.DBInstanceId, conf.Name, conf.Password, conf.Description) +} + +func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { + return rds.region.RenewInstance(rds.DBInstanceId, bc) +} + +func (region *SRegion) RenewDBInstance(instanceId string, bc billing.SBillingCycle) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "Period": fmt.Sprintf("%d", bc.GetMonths()), + "ClientToken": utils.GenRequestId(20), + } + _, err := region.rdsRequest("RenewInstance", params) + return err +} diff --git a/pkg/multicloud/aliyun/dbinstance_account.go b/pkg/multicloud/aliyun/dbinstance_account.go index f025568c56..73608d4461 100644 --- a/pkg/multicloud/aliyun/dbinstance_account.go +++ b/pkg/multicloud/aliyun/dbinstance_account.go @@ -53,6 +53,70 @@ func (account *SDBInstanceAccount) GetName() string { return account.AccountName } +func (account *SDBInstanceAccount) Delete() error { + return account.instance.region.DeleteDBInstanceAccount(account.DBInstanceId, account.AccountName) +} + +func (account *SDBInstanceAccount) RevokePrivilege(database string) error { + return account.instance.region.RevokeDBInstancePrivilege(account.DBInstanceId, account.AccountName, database) +} + +func (region *SRegion) RevokeDBInstancePrivilege(instanceId, account, database string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "DBName": database, + } + _, err := region.rdsRequest("RevokeAccountPrivilege", params) + return err +} + +func (account *SDBInstanceAccount) GrantPrivilege(database, privilege string) error { + return account.instance.region.GrantDBInstancePrivilege(account.DBInstanceId, account.AccountName, database, privilege) +} + +func (region *SRegion) GrantDBInstancePrivilege(instanceId, account, database, privilege string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "DBName": database, + } + switch privilege { + case api.DATABASE_PRIVILEGE_R: + params["AccountPrivilege"] = "ReadOnly" + case api.DATABASE_PRIVILEGE_RW: + params["AccountPrivilege"] = "ReadWrite" + case api.DATABASE_PRIVILEGE_DDL: + params["AccountPrivilege"] = "DDLOnly" + case api.DATABASE_PRIVILEGE_DML: + params["AccountPrivilege"] = "DMLOnly" + case api.DATABASE_PRIVILEGE_OWNER: + params["AccountPrivilege"] = "DBOwner" + default: + return fmt.Errorf("Unknown privilege [%s]", privilege) + } + _, err := region.rdsRequest("GrantAccountPrivilege", params) + return err +} + +func (account *SDBInstanceAccount) ResetPassword(password string) error { + return account.instance.region.ResetDBInstanceAccountPassword(account.DBInstanceId, account.AccountName, password, account.AccountType) +} + +func (region *SRegion) ResetDBInstanceAccountPassword(instanceId, account, password, accountType string) error { + action := "ResetAccountPassword" + if accountType == "Super" { + action = "ResetAccount" + } + params := map[string]string{ + "DBInstanceId": instanceId, + "AccountName": account, + "AccountPassword": password, + } + _, err := region.rdsRequest(action, params) + return err +} + func (account *SDBInstanceAccount) GetStatus() string { switch account.AccountStatus { case "Available": @@ -94,3 +158,28 @@ func (region *SRegion) GetDBInstanceAccounts(instanceId string, offset int, limi total, _ := body.Int("TotalRecordCount") return accounts, int(total), nil } + +func (region *SRegion) DeleteDBInstanceAccount(instanceId string, accountName string) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "AccountName": accountName, + } + + _, err := region.rdsRequest("DeleteAccount", params) + return err +} + +func (region *SRegion) CreateDBInstanceAccount(instanceId string, name string, password string, desc string) error { + params := map[string]string{ + "RegionId": region.RegionId, + "DBInstanceId": instanceId, + "AccountName": name, + "AccountPassword": password, + "AccountDescription": desc, + } + + _, err := region.rdsRequest("CreateAccount", params) + return err + +} diff --git a/pkg/multicloud/aliyun/dbinstance_backup.go b/pkg/multicloud/aliyun/dbinstance_backup.go index 1b82e3a194..ce388fb967 100644 --- a/pkg/multicloud/aliyun/dbinstance_backup.go +++ b/pkg/multicloud/aliyun/dbinstance_backup.go @@ -16,9 +16,12 @@ package aliyun import ( "fmt" + "strings" "time" + "github.com/coredns/coredns/plugin/pkg/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -96,6 +99,22 @@ func (backup *SDBInstanceBackup) GetDBNames() string { return backup.BackupDBNames } +func (backup *SDBInstanceBackup) GetEngine() string { + instance, _ := backup.region.GetDBInstanceDetail(backup.DBInstanceId) + if instance != nil { + return instance.Engine + } + return "" +} + +func (backup *SDBInstanceBackup) GetEngineVersion() string { + instance, _ := backup.region.GetDBInstanceDetail(backup.DBInstanceId) + if instance != nil { + return instance.EngineVersion + } + return "" +} + func (backup *SDBInstanceBackup) GetDBInstanceId() string { return backup.DBInstanceId } @@ -163,3 +182,94 @@ func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanc } return ibackups, nil } + +func (rds *SDBInstance) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + params := map[string]string{ + "DBInstanceId": rds.DBInstanceId, + "BackupMethod": "Snapshot", + } + if len(conf.Databases) > 0 { + params["BackupStrategy"] = "db" + params["DBName"] = strings.Join(conf.Databases, ",") + params["BackupMethod"] = "Logical" + } + body, err := rds.region.rdsRequest("CreateBackup", params) + if err != nil { + return "", errors.Wrap(err, "CreateBackup") + } + jobId, err := body.GetString("BackupJobId") + if err != nil { + return "", errors.Wrap(err, "body.BackupJobId") + } + return "", rds.region.waitBackupCreateComplete(rds.DBInstanceId, jobId) +} + +func (backup *SDBInstanceBackup) Delete() error { + return backup.region.DeleteDBInstanceBackup(backup.DBInstanceId, backup.BackupId) +} + +func (region *SRegion) DeleteDBInstanceBackup(instanceId string, backupId string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "BackupId": backupId, + } + _, err := region.rdsRequest("DeleteBackup", params) + return err +} + +type SDBInstanceBackupJob struct { + BackupProgressStatus string + Process string + JobMode string + TaskAction string + BackupStatus string + BackupJobId string +} + +type SDBInstanceBackupJobs struct { + BackupJob []SDBInstanceBackupJob +} + +func (region *SRegion) GetDBInstanceBackupJobs(instanceId, jobId string) (*SDBInstanceBackupJobs, error) { + params := map[string]string{ + "DBInstanceId": instanceId, + "ClientToken": utils.GenRequestId(20), + "BackupMode": "Manual", + } + if len(jobId) > 0 { + params["BackupJobId"] = jobId + } + body, err := region.rdsRequest("DescribeBackupTasks", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeBackupTasks") + } + + jobs := SDBInstanceBackupJobs{} + + err = body.Unmarshal(&jobs, "Items") + if err != nil { + return nil, errors.Wrapf(err, "body.Unmarshal(%s)", body) + } + + return &jobs, nil +} + +func (region *SRegion) waitBackupCreateComplete(instanceId, jobId string) error { + for i := 0; i < 20*40; i++ { + jobs, err := region.GetDBInstanceBackupJobs(instanceId, jobId) + if err != nil { + return errors.Wrapf(err, "region.GetDBInstanceBackupJobs(%s, %s)", instanceId, jobId) + } + if len(jobs.BackupJob) == 0 { + return nil + } + for _, job := range jobs.BackupJob { + log.Infof("instance %s backup job %s status: %s(%s)", instanceId, jobId, job.BackupStatus, job.Process) + if job.BackupStatus == "Finished" && job.BackupJobId == jobId { + return nil + } + } + time.Sleep(time.Second * 3) + } + return fmt.Errorf("timeout for waiting create job complete") +} diff --git a/pkg/multicloud/aliyun/dbinstance_database.go b/pkg/multicloud/aliyun/dbinstance_database.go index 54280b38bc..cf239be407 100644 --- a/pkg/multicloud/aliyun/dbinstance_database.go +++ b/pkg/multicloud/aliyun/dbinstance_database.go @@ -63,6 +63,33 @@ func (database *SDBInstanceDatabase) GetCharacterSet() string { return database.CharacterSetName } +func (database *SDBInstanceDatabase) Delete() error { + return database.instance.region.DeleteDBInstanceDatabase(database.DBInstanceId, database.DBName) +} + +func (region *SRegion) DeleteDBInstanceDatabase(instanceId string, dbName string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "DBName": dbName, + } + + _, err := region.rdsRequest("DeleteDatabase", params) + return err +} + +func (region *SRegion) CreateDBInstanceDatabae(instanceId, characterSet, dbName, desc string) error { + params := map[string]string{ + "DBInstanceId": instanceId, + "DBName": dbName, + "CharacterSetName": characterSet, + "DBDescription": desc, + } + + _, err := region.rdsRequest("CreateDatabase", params) + return err + +} + func (region *SRegion) GetDBInstanceDatabases(instanceId, dbName string, offset int, limit int) ([]SDBInstanceDatabase, int, error) { if limit > 500 || limit <= 0 { limit = 500 diff --git a/pkg/multicloud/aliyun/shell/dbinstance.go b/pkg/multicloud/aliyun/shell/dbinstance.go index 50a9dbc5f2..419502118f 100644 --- a/pkg/multicloud/aliyun/shell/dbinstance.go +++ b/pkg/multicloud/aliyun/shell/dbinstance.go @@ -15,6 +15,9 @@ package shell import ( + "fmt" + "strings" + "yunion.io/x/onecloud/pkg/multicloud/aliyun" "yunion.io/x/onecloud/pkg/util/shellutils" ) @@ -46,10 +49,22 @@ func init() { return nil }) + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open dbintance public connection", func(cli *aliyun.SRegion, args *DBInstanceIdOptions) error { + return cli.OpenPublicConnection(args.ID) + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close dbintance public connection", func(cli *aliyun.SRegion, args *DBInstanceIdOptions) error { + return cli.ClosePublicConnection(args.ID) + }) + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-delete", "Delete dbintance", func(cli *aliyun.SRegion, args *DBInstanceIdOptions) error { return cli.DeleteDBInstance(args.ID) }) + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-restart", "Restart dbintance", func(cli *aliyun.SRegion, args *DBInstanceIdOptions) error { + return cli.RebootDBInstance(args.ID) + }) + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-network-list", "Show dbintance network info", func(cli *aliyun.SRegion, args *DBInstanceIdOptions) error { networks, err := cli.GetDBInstanceNetInfo(args.ID) if err != nil { @@ -59,37 +74,28 @@ func init() { return nil }) - type DBInstanceIdExtraOptions struct { - ID string `help:"ID of instances to show"` - Limit int `help:"page size"` - Offset int `help:"page offset"` + type DBInstanceRecoveryOptions struct { + ID string + BACKUP string + Databases []string } - shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-backup-list", "List dbintance backups", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { - backups, _, err := cli.GetDBInstanceBackups(args.ID, "", args.Offset, args.Limit) - if err != nil { - return err - } - printList(backups, 0, 0, 0, []string{}) - return nil - }) + shellutils.R(&DBInstanceRecoveryOptions{}, "dbinstance-recovery", "Recovery dbintance from backup", func(cli *aliyun.SRegion, args *DBInstanceRecoveryOptions) error { + databases := map[string]string{} + for _, database := range args.Databases { + if len(database) > 0 { + dbInfo := strings.Split(database, ":") + if len(dbInfo) == 1 { + databases[dbInfo[0]] = dbInfo[0] - shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-database-list", "List dbintance databases", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { - databases, _, err := cli.GetDBInstanceDatabases(args.ID, "", args.Offset, args.Limit) - if err != nil { - return err + } else if len(dbInfo) == 2 { + databases[dbInfo[0]] = dbInfo[1] + } else { + return fmt.Errorf("Invalid dbinfo: %s", database) + } + } } - printList(databases, 0, 0, 0, []string{}) - return nil - }) - - shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-account-list", "List dbintance account", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { - accounts, _, err := cli.GetDBInstanceAccounts(args.ID, args.Offset, args.Limit) - if err != nil { - return err - } - printList(accounts, 0, 0, 0, []string{}) - return nil + return cli.RecoveryDBInstanceFromBackup(args.ID, args.BACKUP, databases) }) } diff --git a/pkg/multicloud/aliyun/shell/dbinstance_account.go b/pkg/multicloud/aliyun/shell/dbinstance_account.go new file mode 100644 index 0000000000..f511de62a7 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/dbinstance_account.go @@ -0,0 +1,70 @@ +// 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/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-account-list", "List dbintance accounts", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { + accounts, _, err := cli.GetDBInstanceAccounts(args.ID, args.Offset, args.Limit) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, []string{}) + return nil + }) + + type DBInstanceAccountCreateOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"account name"` + PASSWORD string `help:"account password"` + Desc string + } + + shellutils.R(&DBInstanceAccountCreateOptions{}, "dbinstance-account-create", "Create dbintance account", func(cli *aliyun.SRegion, args *DBInstanceAccountCreateOptions) error { + return cli.CreateDBInstanceAccount(args.INSTANCE, args.NAME, args.PASSWORD, args.Desc) + }) + + type DBInstanceAccountDeleteOptions struct { + INSTANCE string + NAME string + } + + shellutils.R(&DBInstanceAccountDeleteOptions{}, "dbinstance-account-delete", "Delete dbintance account", func(cli *aliyun.SRegion, args *DBInstanceAccountDeleteOptions) error { + return cli.DeleteDBInstanceAccount(args.INSTANCE, args.NAME) + }) + + type DBInstanceAccountResetOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"account name"` + PASSWORD string `help:"account password"` + AccountType string `help:"account type" choices:"Normal|Super" default:"Normal"` + } + + shellutils.R(&DBInstanceAccountResetOptions{}, "dbinstance-account-delete", "Delete dbintance account", func(cli *aliyun.SRegion, args *DBInstanceAccountResetOptions) error { + return cli.ResetDBInstanceAccountPassword(args.INSTANCE, args.NAME, args.PASSWORD, args.AccountType) + }) + +} diff --git a/pkg/multicloud/aliyun/shell/dbinstance_backup.go b/pkg/multicloud/aliyun/shell/dbinstance_backup.go new file mode 100644 index 0000000000..848e6f41ce --- /dev/null +++ b/pkg/multicloud/aliyun/shell/dbinstance_backup.go @@ -0,0 +1,64 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceBackupJobListOptions struct { + INSTANCE string + JobId string + } + shellutils.R(&DBInstanceBackupJobListOptions{}, "dbinstance-backup-job-list", "Get dbinstance backup jobs", func(cli *aliyun.SRegion, args *DBInstanceBackupJobListOptions) error { + jobs, err := cli.GetDBInstanceBackupJobs(args.INSTANCE, args.JobId) + if err != nil { + return err + } + printObject(jobs) + return nil + }) + + type DBInstanceBackupOptions struct { + INSTANCE string + BACKUP string + } + + shellutils.R(&DBInstanceBackupOptions{}, "dbinstance-backup-delete", "Delete dbinstance backup", func(cli *aliyun.SRegion, args *DBInstanceBackupOptions) error { + return cli.DeleteDBInstanceBackup(args.INSTANCE, args.BACKUP) + }) + + shellutils.R(&DBInstanceBackupOptions{}, "dbinstance-backup-job-list", "Get dbinstance backup jobs", func(cli *aliyun.SRegion, args *DBInstanceBackupOptions) error { + return cli.DeleteDBInstanceBackup(args.INSTANCE, args.BACKUP) + }) + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-backup-list", "List dbintance backups", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { + backups, _, err := cli.GetDBInstanceBackups(args.ID, "", args.Offset, args.Limit) + if err != nil { + return err + } + printList(backups, 0, 0, 0, []string{}) + return nil + }) + +} diff --git a/pkg/multicloud/aliyun/shell/dbinstance_database.go b/pkg/multicloud/aliyun/shell/dbinstance_database.go new file mode 100644 index 0000000000..ce1a425bd5 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/dbinstance_database.go @@ -0,0 +1,59 @@ +// 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/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + + type DBInstanceIdExtraOptions struct { + ID string `help:"ID of instances to show"` + Limit int `help:"page size"` + Offset int `help:"page offset"` + } + + shellutils.R(&DBInstanceIdExtraOptions{}, "dbinstance-database-list", "List dbintance databases", func(cli *aliyun.SRegion, args *DBInstanceIdExtraOptions) error { + databases, _, err := cli.GetDBInstanceDatabases(args.ID, "", args.Offset, args.Limit) + if err != nil { + return err + } + printList(databases, 0, 0, 0, []string{}) + return nil + }) + + type DBInstanceDatabaseCreateOptions struct { + INSTANCE string `help:"ID of instances"` + NAME string `help:"database name"` + CHARACTERSET string `help:"character set for database"` + Desc string + } + + shellutils.R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create dbintance database", func(cli *aliyun.SRegion, args *DBInstanceDatabaseCreateOptions) error { + return cli.CreateDBInstanceDatabae(args.INSTANCE, args.CHARACTERSET, args.NAME, args.Desc) + }) + + type DBInstanceDatabaseDeleteOptions struct { + INSTANCE string + NAME string + } + + shellutils.R(&DBInstanceDatabaseDeleteOptions{}, "dbinstance-database-delete", "Delete dbintance database", func(cli *aliyun.SRegion, args *DBInstanceDatabaseDeleteOptions) error { + return cli.DeleteDBInstanceDatabase(args.INSTANCE, args.NAME) + }) + +} diff --git a/pkg/multicloud/aws/dbinstance.go b/pkg/multicloud/aws/dbinstance.go index 995eec149a..60d348482e 100644 --- a/pkg/multicloud/aws/dbinstance.go +++ b/pkg/multicloud/aws/dbinstance.go @@ -156,6 +156,10 @@ func (rds *SDBInstance) GetCreatedAt() time.Time { return rds.InstanceCreateTime } +func (rds *SDBInstance) GetStorageType() string { + return rds.StorageType +} + func (rds *SDBInstance) GetEngine() string { return rds.Engine } diff --git a/pkg/multicloud/aws/dbinstance_snapshot.go b/pkg/multicloud/aws/dbinstance_snapshot.go index 0e4e185d98..4ec0c1b9ef 100644 --- a/pkg/multicloud/aws/dbinstance_snapshot.go +++ b/pkg/multicloud/aws/dbinstance_snapshot.go @@ -66,6 +66,14 @@ func (snapshot *SDBInstanceSnapshot) GetName() string { return snapshot.DBSnapshotIdentifier } +func (snapshot *SDBInstanceSnapshot) GetEngine() string { + return snapshot.Engine +} + +func (snapshot *SDBInstanceSnapshot) GetEngineVersion() string { + return snapshot.EngineVersion +} + func (snapshot *SDBInstanceSnapshot) GetStartTime() time.Time { return snapshot.SnapshotCreateTime } diff --git a/pkg/multicloud/dbinstance_account_base.go b/pkg/multicloud/dbinstance_account_base.go index b23890ad84..413489bfc9 100644 --- a/pkg/multicloud/dbinstance_account_base.go +++ b/pkg/multicloud/dbinstance_account_base.go @@ -5,8 +5,7 @@ // 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 +// // 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 @@ -15,6 +14,8 @@ package multicloud import ( + "fmt" + "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -25,3 +26,19 @@ type SDBInstanceAccountBase struct { func (account *SDBInstanceAccountBase) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { return []cloudprovider.ICloudDBInstanceAccountPrivilege{}, nil } + +func (account *SDBInstanceAccountBase) Delete() error { + return fmt.Errorf("Not Implemented Delete") +} + +func (account *SDBInstanceAccountBase) ResetPassword(password string) error { + return fmt.Errorf("Not Implemented ResetPassword") +} + +func (backup *SDBInstanceAccountBase) GrantPrivilege(database, privilege string) error { + return fmt.Errorf("Not Implement GrantPrivilege") +} + +func (backup *SDBInstanceAccountBase) RevokePrivilege(database string) error { + return fmt.Errorf("Not Implement RevokePrivilege") +} diff --git a/pkg/multicloud/dbinstance_backup_base.go b/pkg/multicloud/dbinstance_backup_base.go index c5b559b2c1..411ff8f6b8 100644 --- a/pkg/multicloud/dbinstance_backup_base.go +++ b/pkg/multicloud/dbinstance_backup_base.go @@ -15,6 +15,8 @@ package multicloud import ( + "fmt" + api "yunion.io/x/onecloud/pkg/apis/compute" ) @@ -25,3 +27,11 @@ type SDBInstanceBackupBase struct { func (backup *SDBInstanceBackupBase) GetBackMode() string { return api.BACKUP_MODE_AUTOMATED } + +func (backup *SDBInstanceBackupBase) Delete() error { + return fmt.Errorf("Not Implement Delete") +} + +func (backup *SDBInstanceBackupBase) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/dbinstance_base.go b/pkg/multicloud/dbinstance_base.go index 39324f288e..7959fc7e6b 100644 --- a/pkg/multicloud/dbinstance_base.go +++ b/pkg/multicloud/dbinstance_base.go @@ -15,9 +15,11 @@ package multicloud import ( + "context" "fmt" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/billing" ) type SDBInstanceBase struct { @@ -48,6 +50,58 @@ func (instance *SDBInstanceBase) GetIDBInstanceAccounts() ([]cloudprovider.IClou return nil, fmt.Errorf("Not Implemented GetIDBInstanceAccounts") } +func (instance *SDBInstanceBase) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + return nil, fmt.Errorf("Not Implemented GetIDBInstanceBackups") +} + func (instance *SDBInstanceBase) GetCategory() string { return "" } + +func (instance *SDBInstanceBase) Reboot() error { + return fmt.Errorf("Not Implemmented Reboot") +} + +func (instance *SDBInstanceBase) Delete() error { + return fmt.Errorf("Not Implemmented Delete") +} + +func (instance *SDBInstanceBase) GetMasterInstanceId() string { + return "" +} + +func (instance *SDBInstanceBase) GetSecurityGroupId() string { + return "" +} + +func (instance *SDBInstanceBase) Renew(bc billing.SBillingCycle) error { + return fmt.Errorf("Not Implemented Renew") +} + +func (instance *SDBInstanceBase) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedDBInstanceChangeConfig) error { + return fmt.Errorf("Not Implemented ChnageConfig") +} + +func (instance *SDBInstanceBase) OpenPublicConnection() error { + return fmt.Errorf("Not Implemented OpenPublicConnection") +} + +func (instance *SDBInstanceBase) ClosePublicConnection() error { + return fmt.Errorf("Not Implemented ClosePublicConnection") +} + +func (instance *SDBInstanceBase) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return fmt.Errorf("Not Implemented CreateDatabase") +} + +func (instance *SDBInstanceBase) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return fmt.Errorf("Not Implemented CreateAccount") +} + +func (instance *SDBInstanceBase) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + return "", fmt.Errorf("Not Implemented CreateIBackup") +} + +func (instance *SDBInstanceBase) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + return fmt.Errorf("Not Implemented RecoveryFromBackup") +} diff --git a/pkg/multicloud/dbinstance_database_base.go b/pkg/multicloud/dbinstance_database_base.go index 6ae9350d28..cc448ccdc2 100644 --- a/pkg/multicloud/dbinstance_database_base.go +++ b/pkg/multicloud/dbinstance_database_base.go @@ -14,6 +14,12 @@ package multicloud +import "fmt" + type SDBInstanceDatabaseBase struct { SResourceBase } + +func (db *SDBInstanceDatabaseBase) Delete() error { + return fmt.Errorf("Not Implemented Delete") +} diff --git a/pkg/multicloud/huawei/client/client.go b/pkg/multicloud/huawei/client/client.go index ff7310bae2..3af61d3069 100644 --- a/pkg/multicloud/huawei/client/client.go +++ b/pkg/multicloud/huawei/client/client.go @@ -74,6 +74,8 @@ type Client struct { NatGateways *modules.SNatGatewayManager DBInstance *modules.SDBInstanceManager DBInstanceBackup *modules.SDBInstanceBackupManager + DBInstanceFlavor *modules.SDBInstanceFlavorManager + DBInstanceJob *modules.SDBInstanceJobManager } func (self *Client) Init() error { @@ -152,6 +154,8 @@ func (self *Client) initManagers() { self.NatGateways = modules.NewNatGatewayManager(self.regionId, self.projectId, self.signer, self.debug) self.DBInstance = modules.NewDBInstanceManager(self.regionId, self.projectId, self.signer, self.debug) self.DBInstanceBackup = modules.NewDBInstanceBackupManager(self.regionId, self.projectId, self.signer, self.debug) + self.DBInstanceFlavor = modules.NewDBInstanceFlavorManager(self.regionId, self.projectId, self.signer, self.debug) + self.DBInstanceJob = modules.NewDBInstanceJobManager(self.regionId, self.projectId, self.signer, self.debug) } self.init = true diff --git a/pkg/multicloud/huawei/client/modules/manager_base.go b/pkg/multicloud/huawei/client/modules/manager_base.go index f549ac5716..4b1cd5bf17 100644 --- a/pkg/multicloud/huawei/client/modules/manager_base.go +++ b/pkg/multicloud/huawei/client/modules/manager_base.go @@ -169,7 +169,7 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" { retry -= 1 time.Sleep(time.Second * time.Duration(MAX_RETRY-retry)) - } else if err.Code == 404 || strings.Index(err.Details, "could not be found") > 0 { + } else if (err.Code == 404 || strings.Index(err.Details, "could not be found") > 0) && request.GetMethod() != "POST" { return h, b, cloudprovider.ErrNotFound } else { return h, b, e diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance.go index feaf2ceb6f..df2b89f125 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance.go @@ -17,6 +17,8 @@ package modules import ( "fmt" + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -32,13 +34,33 @@ func NewDBInstanceManager(regionId string, projectId string, signer auth.Signer, Region: regionId, ProjectId: projectId, version: "v3", - Keyword: "instance", + Keyword: "", KeywordPlural: "instances", ResourceKeyword: "instances", }} } +func (self *SDBInstanceManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + if len(id) == 0 { + return nil, cloudprovider.ErrNotFound + } + resp, err := self.GetInContextWithSpec(nil, "", "", map[string]string{"id": id}, "") + if err != nil { + return nil, err + } + instances, err := resp.GetArray("instances") + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, cloudprovider.ErrNotFound + } else if len(instances) == 1 { + return instances[0], nil + } + return nil, cloudprovider.ErrDuplicateId +} + func (self *SDBInstanceManager) ListParameters(queries map[string]string) (*responses.ListResult, error) { id, _ := queries["instance_id"] if len(id) == 0 { diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go index eeefa7804b..c22090d9a8 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go @@ -29,7 +29,7 @@ func NewDBInstanceBackupManager(regionId string, projectId string, signer auth.S Region: regionId, ProjectId: projectId, version: "v3", - Keyword: "backups", + Keyword: "backup", KeywordPlural: "backups", ResourceKeyword: "backups", diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go new file mode 100644 index 0000000000..192e596afb --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go @@ -0,0 +1,37 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SDBInstanceFlavorManager struct { + SResourceManager +} + +func NewDBInstanceFlavorManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceFlavorManager { + return &SDBInstanceFlavorManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameRDS, + Region: regionId, + ProjectId: projectId, + version: "v3", + Keyword: "flavor", + KeywordPlural: "flavor", + + ResourceKeyword: "flavors", + }} +} diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go new file mode 100644 index 0000000000..3e11a2027c --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go @@ -0,0 +1,42 @@ +// 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 modules + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SDBInstanceJobManager struct { + SResourceManager +} + +func NewDBInstanceJobManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceJobManager { + return &SDBInstanceJobManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameRDS, + Region: regionId, + ProjectId: projectId, + version: "v3", + Keyword: "", + KeywordPlural: "", + + ResourceKeyword: "jobs", + }} +} + +func (self *SDBInstanceJobManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + return self.GetInContextWithSpec(nil, "", "", map[string]string{"id": id}, "job") +} diff --git a/pkg/multicloud/huawei/dbinstance.go b/pkg/multicloud/huawei/dbinstance.go index acbc093e11..ece0619146 100644 --- a/pkg/multicloud/huawei/dbinstance.go +++ b/pkg/multicloud/huawei/dbinstance.go @@ -15,15 +15,20 @@ package huawei import ( + "context" "fmt" + "strings" "time" + "github.com/pkg/errors" + "yunion.io/x/jsonutils" - billing "yunion.io/x/onecloud/pkg/apis/billing" + "yunion.io/x/log" + 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/pkg/errors" + "yunion.io/x/onecloud/pkg/util/billing" ) type SBackupStrategy struct { @@ -62,6 +67,8 @@ type SDBInstance struct { multicloud.SDBInstanceBase region *SRegion + flavorCache []SDBInstanceFlavor + BackupStrategy SBackupStrategy Created string //time.Time Datastore SDatastore @@ -97,7 +104,10 @@ func (region *SRegion) GetDBInstances() ([]SDBInstance, error) { } func (region *SRegion) GetDBInstance(instanceId string) (*SDBInstance, error) { - instance := SDBInstance{} + if len(instanceId) == 0 { + return nil, cloudprovider.ErrNotFound + } + instance := SDBInstance{region: region} err := DoGet(region.ecsClient.DBInstance.Get, instanceId, nil, &instance) return &instance, err } @@ -149,11 +159,47 @@ func (rds *SDBInstance) GetStatus() string { } func (rds *SDBInstance) GetBillingType() string { - return billing.BILLING_TYPE_POSTPAID + _, err := rds.region.GetOrderResourceDetail(fmt.Sprintf("%s.vm", rds.Id)) + if err != nil { + return billing_api.BILLING_TYPE_POSTPAID + } + return billing_api.BILLING_TYPE_PREPAID +} + +func (rds *SDBInstance) GetSecurityGroupId() string { + return rds.SecurityGroupId +} + +func (rds *SDBInstance) fetchFlavor() error { + if len(rds.flavorCache) > 0 { + return nil + } + flavors, err := rds.region.GetDBInstanceFlavors(rds.Datastore.Type, rds.Datastore.Version) + if err != nil { + return err + } + rds.flavorCache = flavors + return nil } func (rds *SDBInstance) GetExpiredAt() time.Time { - return time.Time{} + order, err := rds.region.GetOrderResourceDetail(fmt.Sprintf("%s.vm", rds.Id)) + if err != nil { + return time.Time{} + } + return order.ExpireTime +} + +func (rds *SDBInstance) GetStorageType() string { + switch rds.Volume.Type { + case "COMMON": + return api.STORAGE_HUAWEI_SATA + case "HIGH": + return api.STORAGE_HUAWEI_SAS + case "ULTRAHIGH": + return api.STORAGE_HUAWEI_SSD + } + return rds.Volume.Type } func (rds *SDBInstance) GetCreatedAt() time.Time { @@ -179,20 +225,40 @@ func (rds *SDBInstance) GetInstanceType() string { func (rds *SDBInstance) GetCategory() string { switch rds.Type { case "Single": - return api.DBINSTANCE_CATEGORY_BASIC + return api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE case "Ha": - return api.DBINSTANCE_CATEGORY_HA + return api.HUAWEI_DBINSTANCE_CATEGORY_HA case "Replica": - return api.DBINSTANCE_CATEGORY_Replica + return api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA } return rds.Type } func (rds *SDBInstance) GetVcpuCount() int { + err := rds.fetchFlavor() + if err != nil { + log.Errorf("failed to fetch flavors: %v", err) + return 0 + } + for _, flavor := range rds.flavorCache { + if flavor.SpecCode == rds.FlavorRef { + return flavor.Vcpus + } + } return 0 } func (rds *SDBInstance) GetVmemSizeMB() int { + err := rds.fetchFlavor() + if err != nil { + log.Errorf("failed to fetch flavors: %v", err) + return 0 + } + for _, flavor := range rds.flavorCache { + if flavor.SpecCode == rds.FlavorRef { + return flavor.Ram * 1024 + } + } return 0 } @@ -221,13 +287,15 @@ func (rds *SDBInstance) Refresh() error { } func (rds *SDBInstance) GetIZoneId() string { - if len(rds.Nodes) == 1 { - zone, err := rds.region.getZoneById(rds.Nodes[0].AvailabilityZone) - if err == nil { - return zone.GetGlobalId() + zones := []string{} + for _, node := range rds.Nodes { + if node.Role == "master" { + zones = append([]string{node.AvailabilityZone}, zones...) + } else if node.Role == "slave" { + zones = append(zones, node.AvailabilityZone) } } - return "" + return strings.Join(zones, ",") } type SRdsNetwork struct { @@ -261,6 +329,14 @@ func (rds *SDBInstance) GetConnectionStr() string { return "" } +func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + dbinstance, err := region.GetDBInstance(instanceId) + if err != nil { + log.Errorf("failed to get dbinstance by id %s error: %v", instanceId, err) + } + return dbinstance, err +} + func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { instances, err := region.GetDBInstances() if err != nil { @@ -306,6 +382,16 @@ func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstan return nil, errors.Wrap(err, "rds.region.GetDBInstanceAccounts(rds.Id)") } + user := "root" + if rds.GetEngine() == api.DBINSTANCE_TYPE_SQLSERVER { + user = "rduser" + } + + accounts = append(accounts, SDBInstanceAccount{ + Name: user, + instance: rds, + }) + iaccounts := []cloudprovider.ICloudDBInstanceAccount{} for i := 0; i < len(accounts); i++ { accounts[i].instance = rds @@ -313,3 +399,340 @@ func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstan } return iaccounts, nil } + +func (rds *SDBInstance) Delete() error { + return rds.region.DeleteDBInstance(rds.Id) +} + +func (region *SRegion) DeleteDBInstance(instanceId string) error { + _, err := region.ecsClient.DBInstance.Delete(instanceId, nil) + return err +} + +func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + zoneIds := []string{} + zones, err := region.GetIZones() + if err != nil { + return nil, err + } + for _, zone := range zones { + zoneIds = append(zoneIds, zone.GetId()) + } + storageType := "" + switch desc.StorageType { + case api.STORAGE_HUAWEI_SAS: + storageType = "HIGH" + case api.STORAGE_HUAWEI_SSD: + storageType = "ULTRAHIGH" + case api.STORAGE_HUAWEI_SATA: + storageType = "COMMON" + default: + return nil, fmt.Errorf("Unknown storage type %s", desc.StorageType) + } + + params := map[string]interface{}{ + "region": region.ID, + "name": desc.Name, + "datastore": map[string]string{ + "type": desc.Engine, + "version": desc.EngineVersion, + }, + "password": desc.Password, + "volume": map[string]interface{}{ + "type": storageType, + "size": desc.DiskSizeGB, + }, + "vpc_id": desc.VpcId, + "subnet_id": desc.NetworkId, + "security_group_id": desc.SecgroupId, + } + + if len(desc.MasterInstanceId) > 0 { + params["replica_of_id"] = desc.MasterInstanceId + } + + switch desc.Category { + case api.HUAWEI_DBINSTANCE_CATEGORY_HA: + switch desc.Engine { + case api.DBINSTANCE_TYPE_MYSQL, api.DBINSTANCE_TYPE_POSTGRESQL: + params["ha"] = map[string]string{ + "mode": "Ha", + "replication_mode": "async", + } + case api.DBINSTANCE_TYPE_SQLSERVER: + params["ha"] = map[string]string{ + "mode": "Ha", + "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: + } + + if desc.BillingCycle != nil { + periodType := "month" + periodNum := desc.BillingCycle.GetMonths() + if desc.BillingCycle.GetYears() > 0 { + periodType = "year" + periodNum = desc.BillingCycle.GetYears() + } + params["charge_info"] = map[string]interface{}{ + "charge_mode": "prePaid", + "period_type": periodType, + "period_num": periodNum, + "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) + } + } + + instance := &SDBInstance{region: region} + err = resp.Unmarshal(instance, "instance") + if err != nil { + return nil, errors.Wrap(err, `resp.Unmarshal(&instance, "instance")`) + } + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + log.Errorf("failed to get job %s info error: %v", jobId, err) + return false + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + if status == "Completed" { + return true + } + log.Debugf("create dbinstance job %s status: %s process: %s", jobId, status, process) + return false + }) + } + return instance, nil +} + +func (rds *SDBInstance) Reboot() error { + return rds.region.RebootDBInstance(rds.Id) +} + +func (rds *SDBInstance) OpenPublicConnection() error { + return fmt.Errorf("Huawei current not support this operation") + //return rds.region.PublicConnectionAction(rds.Id, "openRC") +} + +func (rds *SDBInstance) ClosePublicConnection() error { + return fmt.Errorf("Huawei current not support this operation") + //return rds.region.PublicConnectionAction(rds.Id, "closeRC") +} + +func (region *SRegion) PublicConnectionAction(instanceId string, action string) error { + resp, err := region.ecsClient.DBInstance.PerformAction2(action, instanceId, nil, "") + if err != nil { + return errors.Wrapf(err, "rds.%s", action) + } + + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + log.Errorf("failed to get job %s info error: %v", jobId, err) + return false + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + if status == "Completed" { + return true + } + log.Debugf("%s dbinstance job %s status: %s process: %s", action, jobId, status, process) + return false + }) + } + + return nil + +} + +func (region *SRegion) RebootDBInstance(instanceId string) error { + params := jsonutils.Marshal(map[string]interface{}{ + "restart": map[string]string{}, + }) + resp, err := region.ecsClient.DBInstance.PerformAction2("action", instanceId, params, "") + if err != nil { + return err + } + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + log.Errorf("failed to get job %s info error: %v", jobId, err) + return false + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + if status == "Completed" { + return true + } + log.Debugf("reboot dbinstance job %s status: %s process: %s", jobId, status, process) + return false + }) + } + return err +} + +type SDBInstanceFlavor struct { + Vcpus int + Ram int //单位GB + SpecCode string + InstanceMode string //实例模型 +} + +func (region *SRegion) GetDBInstanceFlavors(engine string, version string) ([]SDBInstanceFlavor, error) { + flavors := []SDBInstanceFlavor{} + resp, err := region.ecsClient.DBInstanceFlavor.ListInContextWithSpec(nil, engine, map[string]string{"version_name": version}, "flavors") + if err != nil { + return nil, err + } + return flavors, jsonutils.Update(&flavors, resp.Data) +} + +func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return rds.region.CreateDBInstanceAccount(rds.Id, conf.Name, conf.Password) +} + +func (region *SRegion) CreateDBInstanceAccount(instanceId, account, password string) error { + params := map[string]string{ + "name": account, + "password": password, + } + _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/db_user", instanceId), jsonutils.Marshal(params), "") + return err +} + +func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return rds.region.CreateDBInstanceDatabase(rds.Id, conf.Name, conf.CharacterSet) +} + +func (region *SRegion) CreateDBInstanceDatabase(instanceId, database, characterSet string) error { + params := map[string]string{ + "name": database, + "character_set": characterSet, + } + _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/database", instanceId), jsonutils.Marshal(params), "") + return err +} + +func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + if rds.GetInstanceType() == desc.InstanceType { + desc.InstanceType = "" + } + + if rds.GetDiskSizeGB() >= desc.DiskSizeGB { + desc.DiskSizeGB = 0 + } + + return rds.region.ChangeDBInstanceConfig(rds.Id, desc.InstanceType, desc.DiskSizeGB) +} + +func (region *SRegion) ChangeDBInstanceConfig(instanceId string, instanceType string, diskSizeGb int) error { + instance, err := region.GetIDBInstanceById(instanceId) + if err != nil { + return errors.Wrapf(err, "region.GetIDBInstanceById(%s)", instanceId) + } + + if len(instanceType) > 0 { + params := map[string]map[string]string{ + "resize_flavor": map[string]string{ + "spec_code": instanceType, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "resize_flavor") + } + cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) + } + if diskSizeGb > 0 { + params := map[string]map[string]int{ + "enlarge_volume": map[string]int{ + "size": diskSizeGb, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "enlarge_volume") + } + cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) + } + return nil +} + +func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + return rds.region.RecoveryDBInstanceFromBackup(rds.Id, conf.BackupId, conf.Databases) +} + +func (region *SRegion) RecoveryDBInstanceFromBackup(instanceId string, backupId string, databases map[string]string) error { + params := map[string]interface{}{ + "source": map[string]interface{}{ + "instance_id": instanceId, + "type": "backup", + "backup_id": backupId, + "database_name": databases, + }, + "target": map[string]string{ + "instance_id": instanceId, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("", "recovery", jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "dbinstance.recovery") + } + return nil +} + +func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { + return rds.region.RenewInstance(rds.Id, bc) +} diff --git a/pkg/multicloud/huawei/dbinstance_account.go b/pkg/multicloud/huawei/dbinstance_account.go index b11db7aa93..2f8658e183 100644 --- a/pkg/multicloud/huawei/dbinstance_account.go +++ b/pkg/multicloud/huawei/dbinstance_account.go @@ -15,6 +15,9 @@ package huawei import ( + "fmt" + + "yunion.io/x/jsonutils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" @@ -39,6 +42,14 @@ func (account *SDBInstanceAccount) GetName() string { return account.Name } +func (account *SDBInstanceAccount) Delete() error { + return account.instance.region.DeleteDBInstanceAccount(account.instance.Id, account.Name) +} + +func (region *SRegion) DeleteDBInstanceAccount(instanceId string, account string) error { + 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 } @@ -80,3 +91,53 @@ func (region *SRegion) GetDBInstancePrivvileges(instanceId string, username stri } return privileges, nil } + +func (account *SDBInstanceAccount) RevokePrivilege(database string) error { + return account.instance.region.RevokeDBInstancePrivilege(account.instance.Id, account.Name, database) +} + +func (region *SRegion) RevokeDBInstancePrivilege(instanceId string, account, database string) error { + params := map[string]interface{}{ + "db_name": database, + "users": []map[string]interface{}{ + map[string]interface{}{ + "name": account, + }, + }, + } + return DoDeleteWithSpec(region.ecsClient.DBInstance.DeleteInContextWithSpec, nil, instanceId, "db_privilege", nil, jsonutils.Marshal(params)) +} + +func (account *SDBInstanceAccount) GrantPrivilege(database, privilege string) error { + return account.instance.region.GrantDBInstancePrivilege(account.instance.Id, account.Name, database, privilege) +} + +func (region *SRegion) GrantDBInstancePrivilege(instanceId string, account, database string, privilege string) error { + readonly := false + switch privilege { + case api.DATABASE_PRIVILEGE_R: + readonly = true + case api.DATABASE_PRIVILEGE_RW: + default: + return fmt.Errorf("Unknown privilege %s", privilege) + } + params := map[string]interface{}{ + "db_name": database, + "users": []map[string]interface{}{ + map[string]interface{}{ + "name": account, + "readonly": readonly, + }, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("db_privilege", instanceId, jsonutils.Marshal(params)) + return err +} + +func (account *SDBInstanceAccount) ResetPassword(password string) error { + return account.instance.region.ResetDBInstanceAccountPassword(account.instance.Id, account.Name, password) +} + +func (region *SRegion) ResetDBInstanceAccountPassword(instanceId, account, password string) error { + return fmt.Errorf("The API does not exist or has not been published in the environment") +} diff --git a/pkg/multicloud/huawei/dbinstance_backup.go b/pkg/multicloud/huawei/dbinstance_backup.go index 93844aa1ba..d70789808d 100644 --- a/pkg/multicloud/huawei/dbinstance_backup.go +++ b/pkg/multicloud/huawei/dbinstance_backup.go @@ -17,6 +17,7 @@ package huawei import ( "time" + "yunion.io/x/jsonutils" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" @@ -50,6 +51,14 @@ func (backup *SDBInstanceBackup) GetName() string { return backup.Name } +func (backup *SDBInstanceBackup) GetEngine() string { + return backup.Datastore.Type +} + +func (backup *SDBInstanceBackup) GetEngineVersion() string { + return backup.Datastore.Version +} + func (backup *SDBInstanceBackup) GetStartTime() time.Time { //2019-08-05T08:00:02+0000 t, err := time.Parse("2006-01-02T15:04:05Z0700", backup.BeginTime) @@ -99,14 +108,26 @@ func (backup *SDBInstanceBackup) GetDBNames() string { return "" } +func (backup *SDBInstanceBackup) Delete() error { + return backup.region.DeleteDBInstanceBackup(backup.Id) +} + +func (region *SRegion) DeleteDBInstanceBackup(backupId string) error { + _, err := region.ecsClient.DBInstanceBackup.Delete(backupId, nil) + return err +} + func (backup *SDBInstanceBackup) GetDBInstanceId() string { return backup.InstanceId } -func (region *SRegion) GetDBInstanceBackups(instanceId string) ([]SDBInstanceBackup, error) { +func (region *SRegion) GetDBInstanceBackups(instanceId, backupId string) ([]SDBInstanceBackup, error) { params := map[string]string{ "instance_id": instanceId, } + if len(backupId) > 0 { + params["backup_id"] = backupId + } backups := []SDBInstanceBackup{} err := doListAllWithOffset(region.ecsClient.DBInstanceBackup.List, params, &backups) if err != nil { @@ -132,8 +153,21 @@ func (region *SRegion) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstance return ibackups, nil } +func (region *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := region.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrap(err, "region.GetIDBInstanceBackups") + } + for _, backup := range backups { + if backup.GetGlobalId() == backupId { + return backup, nil + } + } + return nil, cloudprovider.ErrNotFound +} + func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { - backups, err := rds.region.GetDBInstanceBackups(rds.Id) + backups, err := rds.region.GetDBInstanceBackups(rds.Id, "") if err != nil { return nil, err } @@ -145,3 +179,51 @@ func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanc } return ibackups, nil } + +func (backup *SDBInstanceBackup) Refresh() error { + backups, err := backup.region.GetDBInstanceBackups(backup.InstanceId, backup.Id) + if err != nil { + return err + } + if len(backups) == 0 { + return cloudprovider.ErrNotFound + } + return jsonutils.Update(backup, backups[0]) +} + +func (rds *SDBInstance) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + backupId, err := rds.region.CreateDBInstanceBackup(rds.Id, conf.Name, conf.Description, conf.Databases) + if err != nil { + return "", err + } + backup, err := rds.region.GetIDBInstanceBackupById(backupId) + if err != nil { + return "", errors.Wrap(err, "region.GetIDBInstanceBackupById") + } + cloudprovider.WaitStatus(backup, api.DBINSTANCE_BACKUP_READY, time.Second*3, time.Minute*30) + return backupId, nil +} + +func (region *SRegion) CreateDBInstanceBackup(instanceId string, name string, descrition string, databases []string) (string, error) { + params := map[string]interface{}{ + "instance_id": instanceId, + "name": name, + "description": descrition, + } + if len(databases) > 0 { + dbs := []map[string]string{} + for _, database := range databases { + dbs = append(dbs, map[string]string{"name": database}) + } + params["databases"] = dbs + } + resp, err := region.ecsClient.DBInstanceBackup.Create(jsonutils.Marshal(params)) + if err != nil { + return "", errors.Wrap(err, "DBInstanceBackup.Create") + } + backupId, err := resp.GetString("id") + if err != nil { + return "", errors.Wrap(err, "resp.GetBackupId") + } + return backupId, nil +} diff --git a/pkg/multicloud/huawei/dbinstance_database.go b/pkg/multicloud/huawei/dbinstance_database.go index f092b3555d..60d264b6d1 100644 --- a/pkg/multicloud/huawei/dbinstance_database.go +++ b/pkg/multicloud/huawei/dbinstance_database.go @@ -15,6 +15,8 @@ package huawei import ( + "fmt" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/multicloud" ) @@ -47,6 +49,15 @@ func (database *SDBInstanceDatabase) GetCharacterSet() string { return database.CharacterSet } +func (database *SDBInstanceDatabase) Delete() error { + return database.instance.region.DeleteDBInstanceDatabase(database.instance.Id, database.Name) +} + +func (region *SRegion) DeleteDBInstanceDatabase(instanceId, database string) error { + _, err := region.ecsClient.DBInstance.DeleteInContextWithSpec(nil, instanceId, fmt.Sprintf("database/%s", database), nil, nil, "") + return err +} + func (region *SRegion) GetDBInstanceDatabases(instanceId string) ([]SDBInstanceDatabase, error) { params := map[string]string{ "instance_id": instanceId, diff --git a/pkg/multicloud/huawei/shell/dbinstance.go b/pkg/multicloud/huawei/shell/dbinstance.go index a5e1ba9744..82a3656daf 100644 --- a/pkg/multicloud/huawei/shell/dbinstance.go +++ b/pkg/multicloud/huawei/shell/dbinstance.go @@ -35,6 +35,14 @@ func init() { ID string `help:"DBInstance ID"` } + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open dbinstance public connection", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + return cli.PublicConnectionAction(args.ID, "openRC") + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close dbinstance public connection", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + return cli.PublicConnectionAction(args.ID, "closeRC") + }) + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-show", "Show dbinstance", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { dbinstance, err := cli.GetDBInstance(args.ID) if err != nil { @@ -53,26 +61,8 @@ func init() { return nil }) - shellutils.R(&DBInstanceIdOptions{}, "dbinstance-database-list", "Show dbinstance databases", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { - databases, err := cli.GetDBInstanceDatabases(args.ID) - if err != nil { - return err - } - printList(databases, 0, 0, 0, nil) - return nil - }) - - shellutils.R(&DBInstanceIdOptions{}, "dbinstance-account-list", "Show dbinstance accounts", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { - accounts, err := cli.GetDBInstanceAccounts(args.ID) - if err != nil { - return err - } - printList(accounts, 0, 0, 0, nil) - return nil - }) - shellutils.R(&DBInstanceIdOptions{}, "dbinstance-backup-list", "Show dbinstance backups", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { - backups, err := cli.GetDBInstanceBackups(args.ID) + backups, err := cli.GetDBInstanceBackups(args.ID, "") if err != nil { return err } @@ -80,4 +70,28 @@ func init() { return nil }) + type DBInstanceFlavorListOption struct { + ENGINE string `help:"DBInstance engine" choices:"MySQL|SQLServer|PostgreSQL"` + VERSION string `help:"DBInstance engine version" choices:"5.6|5.7|9.5|9.6|10.0|2014 SE|2014 EE|2016 SE|2016 EE|2008 R2 EE|2008 R2 WEB|2014 WEB|2016 WEB"` + } + + shellutils.R(&DBInstanceFlavorListOption{}, "dbinstance-flavor-list", "Show dbinstance backups", func(cli *huawei.SRegion, args *DBInstanceFlavorListOption) error { + flavors, err := cli.GetDBInstanceFlavors(args.ENGINE, args.VERSION) + if err != nil { + return err + } + printList(flavors, 0, 0, 0, nil) + return nil + }) + + type DBInstanceChangeConfigOptions struct { + INSTANCE string + InstanceType string + DiskSizeGB int + } + + shellutils.R(&DBInstanceChangeConfigOptions{}, "dbinstance-change-config", "Change dbinstance config", func(cli *huawei.SRegion, args *DBInstanceChangeConfigOptions) error { + return cli.ChangeDBInstanceConfig(args.INSTANCE, args.InstanceType, args.DiskSizeGB) + }) + } diff --git a/pkg/multicloud/huawei/shell/dbinstance_account.go b/pkg/multicloud/huawei/shell/dbinstance_account.go new file mode 100644 index 0000000000..283b8baef7 --- /dev/null +++ b/pkg/multicloud/huawei/shell/dbinstance_account.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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceIdOptions struct { + ID string `help:"DBInstance ID"` + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-account-list", "Show dbinstance accounts", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + accounts, err := cli.GetDBInstanceAccounts(args.ID) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, nil) + return nil + }) + + type DBInstanceAccountDeleteOptions struct { + INSTANCE string + ACCOUNT string + } + + shellutils.R(&DBInstanceAccountDeleteOptions{}, "dbinstance-account-delete", "Delete dbinstance account", func(cli *huawei.SRegion, args *DBInstanceAccountDeleteOptions) error { + return cli.DeleteDBInstanceAccount(args.INSTANCE, args.ACCOUNT) + }) + + type DBInstanceAccountCreateOptions struct { + INSTANCE string + ACCOUNT string + PASSWORD string + } + + shellutils.R(&DBInstanceAccountCreateOptions{}, "dbinstance-account-create", "Create dbinstance account", func(cli *huawei.SRegion, args *DBInstanceAccountCreateOptions) error { + return cli.CreateDBInstanceAccount(args.INSTANCE, args.ACCOUNT, args.PASSWORD) + }) + + type DBInstanceAccountRevokePrivilegeOptions struct { + INSTANCE string + ACCOUNT string + DATABASE string + } + + shellutils.R(&DBInstanceAccountRevokePrivilegeOptions{}, "dbinstance-account-revoke-provilege", "Revoke dbinstance account privilege", func(cli *huawei.SRegion, args *DBInstanceAccountRevokePrivilegeOptions) error { + return cli.RevokeDBInstancePrivilege(args.INSTANCE, args.ACCOUNT, args.DATABASE) + }) + + type DBInstanceAccountGrantPrivilegeOptions struct { + INSTANCE string + ACCOUNT string + DATABASE string + PRIVILEGE string `help:"database privilege" choices:"r|rw"` + } + + shellutils.R(&DBInstanceAccountGrantPrivilegeOptions{}, "dbinstance-account-grant-provilege", "Grant dbinstance account privilege", func(cli *huawei.SRegion, args *DBInstanceAccountGrantPrivilegeOptions) error { + return cli.GrantDBInstancePrivilege(args.INSTANCE, args.ACCOUNT, args.DATABASE, args.PRIVILEGE) + }) + +} diff --git a/pkg/multicloud/huawei/shell/dbinstance_database.go b/pkg/multicloud/huawei/shell/dbinstance_database.go new file mode 100644 index 0000000000..51b8a7230b --- /dev/null +++ b/pkg/multicloud/huawei/shell/dbinstance_database.go @@ -0,0 +1,55 @@ +// 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/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceIdOptions struct { + INSTANCE string `help:"DBInstance ID"` + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-database-list", "Show dbinstance databases", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + databases, err := cli.GetDBInstanceDatabases(args.INSTANCE) + if err != nil { + return err + } + printList(databases, 0, 0, 0, nil) + return nil + }) + + type DBInstanceDatabaseDeleteOptions struct { + INSTANCE string + DATABASE string + } + + shellutils.R(&DBInstanceDatabaseDeleteOptions{}, "dbinstance-database-delete", "Delete dbinstance database", func(cli *huawei.SRegion, args *DBInstanceDatabaseDeleteOptions) error { + return cli.DeleteDBInstanceDatabase(args.INSTANCE, args.DATABASE) + }) + + type DBInstanceDatabaseCreateOptions struct { + INSTANCE string + DATABASE string + CHARACTER_SET string + } + + shellutils.R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create dbinstance database", func(cli *huawei.SRegion, args *DBInstanceDatabaseCreateOptions) error { + return cli.CreateDBInstanceDatabase(args.INSTANCE, args.DATABASE, args.CHARACTER_SET) + }) + +} diff --git a/pkg/multicloud/huawei/shell/order.go b/pkg/multicloud/huawei/shell/order.go new file mode 100644 index 0000000000..810502ab78 --- /dev/null +++ b/pkg/multicloud/huawei/shell/order.go @@ -0,0 +1,36 @@ +// 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/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type OrderListOptions struct { + OrderId string `help:"Order Id"` + ResourceIds []string `help:"ResourceIds"` + MainResource bool `help:"Main resource"` + } + shellutils.R(&OrderListOptions{}, "order-list", "List order", func(cli *huawei.SRegion, args *OrderListOptions) error { + orders, err := cli.GetOrderResources(args.OrderId, args.ResourceIds, args.MainResource) + if err != nil { + return err + } + printList(orders, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huawei/utils.go b/pkg/multicloud/huawei/utils.go index fc47eba559..acb4d6d0b8 100644 --- a/pkg/multicloud/huawei/utils.go +++ b/pkg/multicloud/huawei/utils.go @@ -40,11 +40,11 @@ type deleteFunc2 func(ctx manager.IManagerContext, id string, spec string, queri type listInCtxFunc func(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error) type listInCtxWithSpecFunc func(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error) -func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}) error { +func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}, method string) error { if respErr != nil { switch e := respErr.(type) { case *httputils.JSONClientError: - if e.Code == 404 || utils.IsInStringArray(e.Class, NOT_FOUND_CODES) { + if (e.Code == 404 || utils.IsInStringArray(e.Class, NOT_FOUND_CODES)) && method != "POST" { return cloudprovider.ErrNotFound } return e @@ -163,7 +163,7 @@ func DoGet(doGet getFunc, id string, queries map[string]string, result interface } ret, err := doGet(id, queries) - return unmarshalResult(ret, err, result) + return unmarshalResult(ret, err, result, "GET") } func DoListInContext(listFunc listInCtxFunc, ctx manager.IManagerContext, querys map[string]string, result interface{}) error { @@ -184,12 +184,12 @@ func DoListInContext(listFunc listInCtxFunc, ctx manager.IManagerContext, querys func DoCreate(createFunc createFunc, params jsonutils.JSONObject, result interface{}) error { ret, err := createFunc(params) - return unmarshalResult(ret, err, result) + return unmarshalResult(ret, err, result, "POST") } func DoUpdate(updateFunc updateFunc, id string, params jsonutils.JSONObject, result interface{}) error { ret, err := updateFunc(id, params) - return unmarshalResult(ret, err, result) + return unmarshalResult(ret, err, result, "PUT") } func DoUpdateWithSpec(updateFunc updateFunc2, id string, spec string, params jsonutils.JSONObject) error { @@ -203,7 +203,7 @@ func DoDelete(deleteFunc deleteFunc, id string, params jsonutils.JSONObject, res } ret, err := deleteFunc(id, params) - return unmarshalResult(ret, err, result) + return unmarshalResult(ret, err, result, "DELETE") } func DoDeleteWithSpec(deleteFunc deleteFunc2, ctx manager.IManagerContext, id string, spec string, queries map[string]string, params jsonutils.JSONObject) error { diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index 3d4cbac4da..9257c26000 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -66,10 +66,22 @@ func (self *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) return nil, fmt.Errorf("GetIDBInstances not implement") } +func (self *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + return nil, fmt.Errorf("GetIDBInstanceById not implement") +} + func (self *SRegion) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { return nil, fmt.Errorf("Not Implemented GetIDBInstanceBackups") } +func (self *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + return nil, fmt.Errorf("Not Implemented GetIDBInstanceBackupById") +} + func (self *SRegion) GetIElasticcaches() ([]cloudprovider.ICloudElasticcache, error) { return nil, fmt.Errorf("Not Implemented GetIElasticcaches") } + +func (self *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + return nil, fmt.Errorf("Not Implemented CreateIDBInstance") +} diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index 92e2ddbfd7..19ebbb64a1 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -127,6 +127,12 @@ const ( ACT_VM_RESET = "虚拟机回滚快照" ACT_VM_SNAPSHOT_AND_CLONE = "虚拟机快照并克隆" + ACT_REBOOT = "重启" + ACT_CHANGE_CONFIG = "调整配置" + + ACT_OPEN_PUBLIC_CONNECTION = "打开外网地址" + ACT_CLOSE_PUBLIC_CONNECTION = "关闭外网地址" + ACT_IMAGE_SAVE = "上传镜像" ACT_IMAGE_PROBE = "镜像检测" @@ -152,4 +158,10 @@ const ( ACT_NAT_CREATE_DNAT = "创建DNAT规则" ACT_NAT_DELETE_SNAT = "删除SNAT规则" ACT_NAT_DELETE_DNAT = "删除DNAT规则" + + ACT_GRANT_PRIVILEGE = "赋予权限" + ACT_REVOKE_PRIVILEGE = "解除权限" + ACT_SET_PRIVILEGES = "设置权限" + ACT_RESTORE = "备份恢复" + ACT_RESET_PASSWORD = "重置密码" ) diff --git a/pkg/util/seclib2/seclib.go b/pkg/util/seclib2/seclib.go index 97348d532c..84ecdbd832 100644 --- a/pkg/util/seclib2/seclib.go +++ b/pkg/util/seclib2/seclib.go @@ -25,7 +25,7 @@ const ( DIGITS = "23456789" LETTERS = "abcdefghjkmnpqrstuvwxyz" UPPERS = "ABCDEFGHJKMNPRSTUVWXYZ" - PUNC = "@$%^-+={}[]:,.?/" + PUNC = "!@#%^*-_=+?," ALL_DIGITS = "0123456789" ALL_LETTERS = "abcdefghijklmnopqrstuvwxyz"