yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/rds_mysql.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package qcloud
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"yunion.io/x/jsonutils"
    26  	"yunion.io/x/log"
    27  	"yunion.io/x/pkg/errors"
    28  	"yunion.io/x/pkg/util/timeutils"
    29  	"yunion.io/x/pkg/utils"
    30  
    31  	billingapi "yunion.io/x/cloudmux/pkg/apis/billing"
    32  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  	"yunion.io/x/cloudmux/pkg/multicloud"
    35  	"yunion.io/x/onecloud/pkg/util/billing"
    36  )
    37  
    38  type SlaveInstanceInfo struct {
    39  	Region string
    40  	Vip    string
    41  	VpcId  int
    42  	Vport  int
    43  	Zone   string
    44  }
    45  
    46  type SlaveInfo struct {
    47  	First  SlaveInstanceInfo
    48  	Second SlaveInstanceInfo
    49  }
    50  
    51  type SDrInfo struct {
    52  	Status       int
    53  	Zone         string
    54  	InstanceId   string
    55  	Region       string
    56  	SyncStatus   string
    57  	InstanceName string
    58  	InstanceType string
    59  }
    60  
    61  type SMasterInfo struct {
    62  	Region        string
    63  	RegionId      int
    64  	ZoneId        int
    65  	Zone          string
    66  	InstanceId    string
    67  	ResourceId    string
    68  	Status        int
    69  	InstanceName  string
    70  	InstanceType  int
    71  	TaskStatus    int
    72  	Memory        int
    73  	Volume        int
    74  	DeviceType    string
    75  	Qps           int
    76  	VpcId         int
    77  	SubnetId      int
    78  	ExClusterId   string
    79  	ExClusterName string
    80  }
    81  
    82  type SRoGroup struct {
    83  	RoGroupMode    string
    84  	RoGroupId      string
    85  	RoGroupName    string
    86  	RoOfflineDelay int
    87  	RoMaxDelayTime int
    88  	MinRoInGroup   int
    89  	WeightMode     string
    90  	Weight         int
    91  	// RoInstances
    92  	Vip           string
    93  	Vport         int
    94  	UniqVpcId     string
    95  	UniqSubnetId  string
    96  	RoGroupRegion string
    97  	RoGroupZone   string
    98  }
    99  
   100  type SRoVipInfo struct {
   101  	RoVipStatus int
   102  	RoSubnetId  int
   103  	RoVpcId     int
   104  	RoVport     int
   105  	RoVip       string
   106  }
   107  
   108  type SMySQLInstance struct {
   109  	region *SRegion
   110  	multicloud.SDBInstanceBase
   111  	QcloudTags
   112  
   113  	AutoRenew        int
   114  	CdbError         int
   115  	Cpu              int
   116  	CreateTime       time.Time
   117  	DeadlineTime     string
   118  	DeployGroupId    string
   119  	DeployMode       int
   120  	DeviceClass      string
   121  	DeviceType       string
   122  	DrInfo           []SDrInfo
   123  	EngineVersion    string
   124  	ExClusterId      string
   125  	HourFeeStatus    int
   126  	InitFlag         int
   127  	InstanceId       string
   128  	InstanceName     string
   129  	InstanceType     int
   130  	IsolateTime      string
   131  	MasterInfo       SMasterInfo
   132  	Memory           int
   133  	OfflineTime      string
   134  	PayType          int
   135  	PhysicalId       string
   136  	ProjectId        int
   137  	ProtectMode      string
   138  	Qps              int
   139  	Region           string
   140  	RegionId         string
   141  	ResourceId       string
   142  	RoGroups         []SRoGroup
   143  	RoVipInfo        SRoVipInfo
   144  	SecurityGroupIds []string
   145  	SlaveInfo        SlaveInfo
   146  	Status           int
   147  	SubnetId         int
   148  	//TagList": null,
   149  	TaskStatus   int
   150  	UniqSubnetId string
   151  	UniqVpcId    string
   152  	Vip          string
   153  	Volume       int
   154  	VpcId        int
   155  	Vport        int
   156  	WanDomain    string
   157  	WanPort      int
   158  	WanStatus    int
   159  	Zone         string
   160  	ZoneId       int
   161  	ZoneName     string
   162  }
   163  
   164  func (self *SMySQLInstance) GetId() string {
   165  	return self.InstanceId
   166  }
   167  
   168  func (self *SMySQLInstance) GetGlobalId() string {
   169  	return self.InstanceId
   170  }
   171  
   172  func (self *SMySQLInstance) GetName() string {
   173  	if len(self.InstanceName) > 0 {
   174  		return self.InstanceName
   175  	}
   176  	return self.InstanceId
   177  }
   178  
   179  func (self *SMySQLInstance) GetDiskSizeGB() int {
   180  	return self.Volume
   181  }
   182  
   183  func (self *SMySQLInstance) GetEngine() string {
   184  	return api.DBINSTANCE_TYPE_MYSQL
   185  }
   186  
   187  func (self *SMySQLInstance) GetEngineVersion() string {
   188  	return self.EngineVersion
   189  }
   190  
   191  func (self *SMySQLInstance) GetIVpcId() string {
   192  	return self.UniqVpcId
   193  }
   194  
   195  func (self *SMySQLInstance) Refresh() error {
   196  	rds, err := self.region.GetMySQLInstanceById(self.InstanceId)
   197  	if err != nil {
   198  		return errors.Wrapf(err, "GetMySQLInstanceById(%s)", self.InstanceId)
   199  	}
   200  	return jsonutils.Update(self, rds)
   201  }
   202  
   203  func (self *SMySQLInstance) GetInstanceType() string {
   204  	return fmt.Sprintf("%d核%dMB", self.Cpu, self.Memory)
   205  }
   206  
   207  func (self *SMySQLInstance) GetMaintainTime() string {
   208  	timeWindow, err := self.region.DescribeMySQLTimeWindow(self.InstanceId)
   209  	if err != nil {
   210  		log.Errorf("DescribeMySQLTimeWindow %s error: %v", self.InstanceId, err)
   211  		return ""
   212  	}
   213  	return timeWindow.String()
   214  }
   215  
   216  func (self *SMySQLInstance) GetDBNetworks() ([]cloudprovider.SDBInstanceNetwork, error) {
   217  	return []cloudprovider.SDBInstanceNetwork{
   218  		cloudprovider.SDBInstanceNetwork{NetworkId: self.UniqSubnetId, IP: self.Vip},
   219  	}, nil
   220  }
   221  
   222  func (self *SMySQLInstance) GetConnectionStr() string {
   223  	if self.WanStatus == 1 {
   224  		return fmt.Sprintf("%s:%d", self.WanDomain, self.WanPort)
   225  	}
   226  	return ""
   227  }
   228  
   229  func (self *SMySQLInstance) GetInternalConnectionStr() string {
   230  	return fmt.Sprintf("%s:%d", self.Vip, self.Vport)
   231  }
   232  
   233  func (self *SMySQLInstance) Reboot() error {
   234  	return self.region.RebootMySQLInstance(self.InstanceId)
   235  }
   236  
   237  func (self *SMySQLInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedDBInstanceChangeConfig) error {
   238  	mb := self.GetVmemSizeMB()
   239  	if len(opts.InstanceType) > 0 {
   240  		re := regexp.MustCompile(`(\d{1,4})核(\d{1,20})MB$`)
   241  		params := re.FindStringSubmatch(opts.InstanceType)
   242  		if len(params) != 3 {
   243  			return fmt.Errorf("invalid rds instance type %s", opts.InstanceType)
   244  		}
   245  		_mb, _ := strconv.Atoi(params[2])
   246  		mb = int(_mb)
   247  	}
   248  	if opts.DiskSizeGB == 0 {
   249  		opts.DiskSizeGB = self.GetDiskSizeGB()
   250  	}
   251  	return self.region.UpgradeMySQLDBInstance(self.InstanceId, mb, opts.DiskSizeGB)
   252  }
   253  
   254  func (self *SMySQLInstance) GetMasterInstanceId() string {
   255  	return self.MasterInfo.InstanceId
   256  }
   257  
   258  func (self *SMySQLInstance) GetSecurityGroupIds() ([]string, error) {
   259  	if len(self.SecurityGroupIds) > 0 {
   260  		return self.SecurityGroupIds, nil
   261  	}
   262  	if self.DeviceType == "BASIC" {
   263  		return []string{}, nil
   264  	}
   265  	secgroups, err := self.region.DescribeMySQLDBSecurityGroups(self.InstanceId)
   266  	if err != nil {
   267  		return []string{}, errors.Wrapf(err, "DescribeMySQLDBSecurityGroups")
   268  	}
   269  	ids := []string{}
   270  	for i := range secgroups {
   271  		ids = append(ids, secgroups[i].SecurityGroupId)
   272  	}
   273  	return ids, nil
   274  }
   275  
   276  func (self *SMySQLInstance) SetSecurityGroups(ids []string) error {
   277  	return self.region.ModifyMySQLInstanceSecurityGroups(self.InstanceId, ids)
   278  }
   279  
   280  func (self *SRegion) ModifyMySQLInstanceSecurityGroups(rdsId string, secIds []string) error {
   281  	params := map[string]string{
   282  		"InstanceId": rdsId,
   283  	}
   284  	for idx, id := range secIds {
   285  		params[fmt.Sprintf("SecurityGroupIds.%d", idx)] = id
   286  	}
   287  	_, err := self.cdbRequest("ModifyDBInstanceSecurityGroups", params)
   288  	return err
   289  }
   290  
   291  func (self *SMySQLInstance) Renew(bc billing.SBillingCycle) error {
   292  	month := bc.GetMonths()
   293  	return self.region.RenewMySQLDBInstance(self.InstanceId, month)
   294  }
   295  
   296  func (self *SMySQLInstance) OpenPublicConnection() error {
   297  	if self.WanStatus == 0 {
   298  		return self.region.OpenMySQLWanService(self.InstanceId)
   299  	}
   300  	return nil
   301  }
   302  
   303  func (self *SMySQLInstance) ClosePublicConnection() error {
   304  	if self.WanStatus == 1 {
   305  		return self.region.CloseMySQLWanService(self.InstanceId)
   306  	}
   307  	return nil
   308  }
   309  
   310  func (self *SMySQLInstance) GetPort() int {
   311  	return self.Vport
   312  }
   313  
   314  func (self *SMySQLInstance) GetStatus() string {
   315  	if self.InitFlag == 0 {
   316  		return api.DBINSTANCE_INIT
   317  	}
   318  	switch self.Status {
   319  	case 4:
   320  		return api.DBINSTANCE_ISOLATING
   321  	case 5:
   322  		return api.DBINSTANCE_ISOLATE
   323  	}
   324  	switch self.TaskStatus {
   325  	case 0:
   326  		switch self.Status {
   327  		case 0:
   328  			return api.DBINSTANCE_DEPLOYING
   329  		case 1:
   330  			return api.DBINSTANCE_RUNNING
   331  		}
   332  	case 1:
   333  		return api.DBINSTANCE_UPGRADING
   334  	case 2: //数据导入中
   335  		return api.DBINSTANCE_IMPORTING
   336  	case 3, 4: //开放关闭外网地址
   337  		return api.DBINSTANCE_DEPLOYING
   338  	case 10:
   339  		return api.DBINSTANCE_REBOOTING
   340  	case 12:
   341  		return api.DBINSTANCE_MIGRATING
   342  	default:
   343  		return api.DBINSTANCE_DEPLOYING
   344  	}
   345  	return api.DBINSTANCE_UNKNOWN
   346  }
   347  
   348  func (self *SMySQLInstance) GetCategory() string {
   349  	category := strings.ToLower(self.DeviceType)
   350  	if category == "universal" {
   351  		category = "ha"
   352  	}
   353  	return category
   354  }
   355  
   356  func (self *SMySQLInstance) GetStorageType() string {
   357  	switch self.DeviceType {
   358  	case "BASIC":
   359  		return api.QCLOUD_DBINSTANCE_STORAGE_TYPE_CLOUD_SSD
   360  	default:
   361  		return api.QCLOUD_DBINSTANCE_STORAGE_TYPE_LOCAL_SSD
   362  	}
   363  }
   364  
   365  func (self *SMySQLInstance) GetCreatedAt() time.Time {
   366  	// 2019-12-25 09:00:43  #非UTC时间
   367  	return self.CreateTime.Add(time.Hour * -8)
   368  }
   369  
   370  func (self *SMySQLInstance) GetBillingType() string {
   371  	if self.PayType == 0 {
   372  		return billingapi.BILLING_TYPE_PREPAID
   373  	}
   374  	return billingapi.BILLING_TYPE_POSTPAID
   375  }
   376  
   377  func (self *SMySQLInstance) SetAutoRenew(bc billing.SBillingCycle) error {
   378  	return self.region.ModifyMySQLAutoRenewFlag([]string{self.InstanceId}, bc.AutoRenew)
   379  }
   380  
   381  func (self *SMySQLInstance) IsAutoRenew() bool {
   382  	return self.AutoRenew == 1
   383  }
   384  
   385  func (self *SMySQLInstance) GetExpiredAt() time.Time {
   386  	offline, _ := timeutils.ParseTimeStr(self.OfflineTime)
   387  	if !offline.IsZero() {
   388  		return offline.Add(time.Hour * -8)
   389  	}
   390  	deadline, _ := timeutils.ParseTimeStr(self.DeadlineTime)
   391  	if !deadline.IsZero() {
   392  		return deadline.Add(time.Hour * -8)
   393  	}
   394  	return time.Time{}
   395  }
   396  
   397  func (self *SMySQLInstance) GetVcpuCount() int {
   398  	return self.Cpu
   399  }
   400  
   401  func (self *SMySQLInstance) GetVmemSizeMB() int {
   402  	return self.Memory
   403  }
   404  
   405  func (self *SMySQLInstance) GetZone1Id() string {
   406  	return self.Zone
   407  }
   408  
   409  func (self *SMySQLInstance) GetZone2Id() string {
   410  	return self.SlaveInfo.First.Zone
   411  }
   412  
   413  func (self *SMySQLInstance) GetZone3Id() string {
   414  	return self.SlaveInfo.Second.Zone
   415  }
   416  
   417  func (self *SMySQLInstance) GetProjectId() string {
   418  	return fmt.Sprintf("%d", self.ProjectId)
   419  }
   420  
   421  func (self *SMySQLInstance) Delete() error {
   422  	err := self.region.IsolateMySQLDBInstance(self.InstanceId)
   423  	if err != nil {
   424  		return errors.Wrapf(err, "IsolateMySQLDBInstance")
   425  	}
   426  	return self.region.OfflineIsolatedMySQLInstances([]string{self.InstanceId})
   427  }
   428  
   429  func (self *SRegion) ListMySQLInstances(ids []string, offset, limit int) ([]SMySQLInstance, int, error) {
   430  	if limit < 1 || limit > 50 {
   431  		limit = 50
   432  	}
   433  	params := map[string]string{
   434  		"Offset": fmt.Sprintf("%d", offset),
   435  		"Limit":  fmt.Sprintf("%d", limit),
   436  	}
   437  	for idx, id := range ids {
   438  		params[fmt.Sprintf("InstanceIds.%d", idx)] = id
   439  	}
   440  	resp, err := self.cdbRequest("DescribeDBInstances", params)
   441  	if err != nil {
   442  		return nil, 0, errors.Wrapf(err, "DescribeDBInstances")
   443  	}
   444  	items := []SMySQLInstance{}
   445  	err = resp.Unmarshal(&items, "Items")
   446  	if err != nil {
   447  		return nil, 0, errors.Wrapf(err, "resp.Unmarshal")
   448  	}
   449  	total, _ := resp.Float("TotalCount")
   450  	return items, int(total), nil
   451  }
   452  
   453  type SAsyncRequestResult struct {
   454  	Info   string
   455  	Status string
   456  }
   457  
   458  func (self *SRegion) DescribeMySQLAsyncRequestInfo(id string) (*SAsyncRequestResult, error) {
   459  	resp, err := self.cdbRequest("DescribeAsyncRequestInfo", map[string]string{"AsyncRequestId": id})
   460  	if err != nil {
   461  		return nil, errors.Wrapf(err, "DescribeAsyncRequestInfo")
   462  	}
   463  	result := SAsyncRequestResult{}
   464  	err = resp.Unmarshal(&result)
   465  	if err != nil {
   466  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   467  	}
   468  	return &result, nil
   469  }
   470  
   471  func (self *SRegion) waitAsyncAction(action string, resId, asyncRequestId string) error {
   472  	if len(asyncRequestId) == 0 {
   473  		return errors.Error("Missing AsyncRequestId")
   474  	}
   475  	return cloudprovider.Wait(time.Second*10, time.Minute*20, func() (bool, error) {
   476  		result, err := self.DescribeMySQLAsyncRequestInfo(asyncRequestId)
   477  		if err != nil {
   478  			return false, errors.Wrapf(err, action)
   479  		}
   480  		log.Debugf("task %s(%s) for mysql instance %s status: %s", action, asyncRequestId, resId, result.Status)
   481  		switch result.Status {
   482  		case "FAILED", "KILLED", "REMOVED", "PAUSED":
   483  			return true, errors.Errorf(result.Info)
   484  		case "SUCCESS":
   485  			return true, nil
   486  		default:
   487  			return false, nil
   488  		}
   489  	})
   490  }
   491  
   492  func (self *SRegion) RebootMySQLInstance(id string) error {
   493  	resp, err := self.cdbRequest("RestartDBInstances", map[string]string{"InstanceIds.0": id})
   494  	if err != nil {
   495  		return errors.Wrapf(err, "RestartDBInstances")
   496  	}
   497  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   498  	return self.waitAsyncAction("RestartDBInstances", id, asyncRequestId)
   499  }
   500  
   501  func (self *SRegion) DescribeMySQLDBInstanceInfo(id string) (*SMySQLInstance, error) {
   502  	resp, err := self.cdbRequest("DescribeDBInstanceInfo", map[string]string{"InstanceId": id})
   503  	if err != nil {
   504  		return nil, errors.Wrapf(err, "DescribeDBInstanceInfo")
   505  	}
   506  	result := SMySQLInstance{region: self}
   507  	err = resp.Unmarshal(&result)
   508  	if err != nil {
   509  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   510  	}
   511  	return &result, nil
   512  }
   513  
   514  func (self *SRegion) RenewMySQLDBInstance(id string, month int) error {
   515  	params := map[string]string{
   516  		"InstanceId": id,
   517  		"TimeSpan":   fmt.Sprintf("%d", month),
   518  	}
   519  	_, err := self.cdbRequest("RenewDBInstance", params)
   520  	if err != nil {
   521  		return errors.Wrapf(err, "RenewDBInstance")
   522  	}
   523  	return nil
   524  }
   525  
   526  func (self *SRegion) OfflineIsolatedMySQLInstances(ids []string) error {
   527  	params := map[string]string{}
   528  	for idx, id := range ids {
   529  		params[fmt.Sprintf("InstanceIds.%d", idx)] = id
   530  	}
   531  	_, err := self.cdbRequest("OfflineIsolatedInstances", params)
   532  	if err != nil {
   533  		return errors.Wrapf(err, "OfflineIsolatedInstances")
   534  	}
   535  	return nil
   536  }
   537  
   538  func (self *SRegion) ReleaseIsolatedMySQLDBInstances(ids []string) error {
   539  	params := map[string]string{}
   540  	for idx, id := range ids {
   541  		params[fmt.Sprintf("InstanceIds.%d", idx)] = id
   542  	}
   543  	resp, err := self.cdbRequest("ReleaseIsolatedDBInstances", params)
   544  	if err != nil {
   545  		return errors.Wrapf(err, "ReleaseIsolatedDBInstances")
   546  	}
   547  	result := []struct {
   548  		InstanceId string
   549  		Code       int
   550  		Message    string
   551  	}{}
   552  	err = resp.Unmarshal(&result, "Items")
   553  	if err != nil {
   554  		return errors.Wrapf(err, "resp.Unmarshal")
   555  	}
   556  	msg := []string{}
   557  	for i := range result {
   558  		if result[i].Code != 0 {
   559  			msg = append(msg, fmt.Sprintf("instance %s release isolate error: %s", result[i].InstanceId, result[i].Message))
   560  		}
   561  	}
   562  	if len(msg) > 0 {
   563  		return errors.Error(strings.Join(msg, " "))
   564  	}
   565  	return cloudprovider.Wait(time.Second, time.Minute*10, func() (bool, error) {
   566  		instances, _, err := self.ListMySQLInstances(ids, 0, len(ids))
   567  		if err != nil {
   568  			return false, errors.Wrapf(err, "ListMySQLInstances")
   569  		}
   570  		for i := range instances {
   571  			if instances[i].Status == 4 || instances[i].Status == 5 {
   572  				log.Debugf("mysql instance %s(%s) current be isolate", instances[i].InstanceName, instances[i].InstanceId)
   573  				return false, nil
   574  			}
   575  		}
   576  		return true, nil
   577  	})
   578  }
   579  
   580  func (self *SRegion) IsolateMySQLDBInstance(id string) error {
   581  	params := map[string]string{"InstanceId": id}
   582  	resp, err := self.cdbRequest("IsolateDBInstance", params)
   583  	if err != nil {
   584  		return errors.Wrapf(err, "IsolateDBInstance")
   585  	}
   586  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   587  	if len(asyncRequestId) > 0 {
   588  		return self.waitAsyncAction("IsolateDBInstance", id, asyncRequestId)
   589  	}
   590  	return cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) {
   591  		instances, _, err := self.ListMySQLInstances([]string{id}, 0, 1)
   592  		if err != nil {
   593  			return false, errors.Wrapf(err, "ListMySQLInstances(%s)", id)
   594  		}
   595  		statusMap := map[int]string{0: "创建中", 1: "运行中", 4: "隔离中", 5: "已隔离"}
   596  		for _, rds := range instances {
   597  			status, _ := statusMap[rds.Status]
   598  			log.Debugf("instance %s(%s) status %d(%s)", rds.InstanceName, rds.InstanceId, rds.Status, status)
   599  			if rds.Status != 5 {
   600  				return false, nil
   601  			}
   602  		}
   603  		return true, nil
   604  	})
   605  }
   606  
   607  func (self *SRegion) CloseMySQLWanService(id string) error {
   608  	params := map[string]string{"InstanceId": id}
   609  	resp, err := self.cdbRequest("CloseWanService", params)
   610  	if err != nil {
   611  		return errors.Wrapf(err, "CloseWanService")
   612  	}
   613  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   614  	return self.waitAsyncAction("CloseWanService", id, asyncRequestId)
   615  }
   616  
   617  func (self *SRegion) OpenMySQLWanService(id string) error {
   618  	params := map[string]string{"InstanceId": id}
   619  	resp, err := self.cdbRequest("OpenWanService", params)
   620  	if err != nil {
   621  		return errors.Wrapf(err, "OpenWanService")
   622  	}
   623  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   624  	return self.waitAsyncAction("OpenWanService", id, asyncRequestId)
   625  }
   626  
   627  func (self *SRegion) InitMySQLDBInstances(ids []string, password string, parameters map[string]string, vport int) error {
   628  	params := map[string]string{"NewPassword": password}
   629  	for idx, id := range ids {
   630  		params[fmt.Sprintf("InstanceIds.%d", idx)] = id
   631  	}
   632  	i := 0
   633  	for k, v := range parameters {
   634  		params[fmt.Sprintf("Parameters.%d.name", i)] = k
   635  		params[fmt.Sprintf("Parameters.%d.value", i)] = v
   636  		i++
   637  	}
   638  	if vport >= 1024 && vport <= 65535 {
   639  		params["Vport"] = fmt.Sprintf("%d", vport)
   640  	}
   641  	resp, err := self.cdbRequest("InitDBInstances", params)
   642  	if err != nil {
   643  		return errors.Wrapf(err, "InitDBInstances")
   644  	}
   645  	asyncRequestIds := []string{}
   646  	err = resp.Unmarshal(&asyncRequestIds, "AsyncRequestIds")
   647  	if err != nil {
   648  		return errors.Wrapf(err, "resp.Unmarshal")
   649  	}
   650  	for idx, requestId := range asyncRequestIds {
   651  		err = self.waitAsyncAction("InitDBInstances", fmt.Sprintf("%d", idx), requestId)
   652  		if err != nil {
   653  			return err
   654  		}
   655  	}
   656  	return nil
   657  }
   658  
   659  func (self *SRegion) UpgradeMySQLDBInstance(id string, memoryMb int, volumeGb int) error {
   660  	params := map[string]string{
   661  		"InstanceId": id,
   662  		"Memory":     fmt.Sprintf("%d", memoryMb),
   663  		"Volume":     fmt.Sprintf("%d", volumeGb),
   664  	}
   665  	resp, err := self.cdbRequest("UpgradeDBInstance", params)
   666  	if err != nil {
   667  		return errors.Wrapf(err, "UpgradeDBInstance")
   668  	}
   669  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   670  	return self.waitAsyncAction("UpgradeDBInstance", id, asyncRequestId)
   671  }
   672  
   673  func (self *SRegion) ModifyMySQLAutoRenewFlag(ids []string, autoRenew bool) error {
   674  	params := map[string]string{}
   675  	for idx, id := range ids {
   676  		params[fmt.Sprintf("InstanceIds.%d", idx)] = id
   677  	}
   678  	params["AutoRenew"] = "0"
   679  	if autoRenew {
   680  		params["AutoRenew"] = "1"
   681  	}
   682  	_, err := self.cdbRequest("ModifyAutoRenewFlag", params)
   683  	return err
   684  }
   685  
   686  type SMaintenanceTime struct {
   687  	Monday    []string
   688  	Tuesday   []string
   689  	Wednesday []string
   690  	Thursday  []string
   691  	Friday    []string
   692  	Saturday  []string
   693  	Sunday    []string
   694  }
   695  
   696  func (w SMaintenanceTime) String() string {
   697  	windows := []string{}
   698  	for k, v := range map[string][]string{
   699  		"Monday":    w.Monday,
   700  		"Tuesday":   w.Tuesday,
   701  		"Wednesday": w.Wednesday,
   702  		"Thursday":  w.Thursday,
   703  		"Friday":    w.Friday,
   704  		"Saturday":  w.Saturday,
   705  		"Sunday":    w.Sunday,
   706  	} {
   707  		if len(v) > 0 {
   708  			windows = append(windows, fmt.Sprintf("%s: %s", k, strings.Join(v, " ")))
   709  		}
   710  	}
   711  	return strings.Join(windows, "\n")
   712  }
   713  
   714  func (self *SRegion) DescribeMySQLTimeWindow(id string) (*SMaintenanceTime, error) {
   715  	params := map[string]string{"InstanceId": id}
   716  	resp, err := self.cdbRequest("DescribeTimeWindow", params)
   717  	if err != nil {
   718  		return nil, errors.Wrapf(err, "DescribeTimeWindow")
   719  	}
   720  	timeWindow := &SMaintenanceTime{}
   721  	err = resp.Unmarshal(timeWindow)
   722  	if err != nil {
   723  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   724  	}
   725  	return timeWindow, nil
   726  }
   727  
   728  type SDBSecgroup struct {
   729  	ProjectId           int
   730  	CreateTime          time.Time
   731  	SecurityGroupId     string
   732  	SecurityGroupName   string
   733  	SecurityGroupRemark string
   734  }
   735  
   736  func (self *SRegion) DescribeMySQLDBSecurityGroups(instanceId string) ([]SDBSecgroup, error) {
   737  	params := map[string]string{
   738  		"InstanceId": instanceId,
   739  	}
   740  	resp, err := self.cdbRequest("DescribeDBSecurityGroups", params)
   741  	if err != nil {
   742  		return nil, errors.Wrapf(err, "DescribeDBSecurityGroups")
   743  	}
   744  	result := []SDBSecgroup{}
   745  	err = resp.Unmarshal(&result, "Groups")
   746  	if err != nil {
   747  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   748  	}
   749  	return result, nil
   750  }
   751  
   752  func (self *SRegion) CreateMySQLDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (*SMySQLInstance, error) {
   753  	params := map[string]string{
   754  		"InstanceName":  opts.Name,
   755  		"GoodsNum":      "1",
   756  		"Memory":        fmt.Sprintf("%d", opts.VmemSizeMb),
   757  		"Volume":        fmt.Sprintf("%d", opts.DiskSizeGB),
   758  		"EngineVersion": opts.EngineVersion,
   759  	}
   760  	if len(opts.VpcId) > 0 {
   761  		params["UniqVpcId"] = opts.VpcId
   762  	}
   763  	if len(opts.NetworkId) > 0 {
   764  		params["UniqSubnetId"] = opts.NetworkId
   765  	}
   766  	if len(opts.ProjectId) > 0 {
   767  		params["ProjectId"] = opts.ProjectId
   768  	}
   769  	if opts.Port > 1024 && opts.Port < 65535 {
   770  		params["Port"] = fmt.Sprintf("%d", opts.Port)
   771  	}
   772  	if len(opts.Password) > 0 {
   773  		params["Password"] = opts.Password
   774  	}
   775  	for i, secId := range opts.SecgroupIds {
   776  		params[fmt.Sprintf("SecurityGroup.%d", i)] = secId
   777  	}
   778  	action := "CreateDBInstanceHour"
   779  	if opts.BillingCycle != nil {
   780  		params["Period"] = fmt.Sprintf("%d", opts.BillingCycle.GetMonths())
   781  		params["AutoRenewFlag"] = "0"
   782  		if opts.BillingCycle.AutoRenew {
   783  			params["AutoRenewFlag"] = "1"
   784  		}
   785  		action = "CreateDBInstance"
   786  	}
   787  	if len(opts.Zone1) > 0 {
   788  		params["Zone"] = opts.Zone1
   789  	}
   790  	params["DeployMode"] = "0"
   791  	switch opts.Category {
   792  	case api.QCLOUD_DBINSTANCE_CATEGORY_BASIC:
   793  		params["DeviceType"] = strings.ToUpper(opts.Category)
   794  	case api.QCLOUD_DBINSTANCE_CATEGORY_HA:
   795  		params["DeviceType"] = strings.ToUpper(opts.Category)
   796  		if len(opts.Zone2) > 0 {
   797  			params["SlaveZone"] = opts.Zone2
   798  		}
   799  	case api.QCLOUD_DBINSTANCE_CATEGORY_FINANCE:
   800  		params["DeviceType"] = "HA"
   801  		params["ProtectMode"] = "2"
   802  		if len(opts.Zone2) > 0 {
   803  			params["SlaveZone"] = opts.Zone2
   804  		}
   805  		if len(opts.Zone3) > 0 {
   806  			params["BackupZone"] = opts.Zone3
   807  		}
   808  	}
   809  	if len(opts.Zone1) > 0 && len(opts.Zone2) > 0 && opts.Zone1 != opts.Zone2 {
   810  		params["DeployMode"] = "1"
   811  	}
   812  	params["ClientToken"] = utils.GenRequestId(20)
   813  
   814  	i := 0
   815  	for k, v := range opts.Tags {
   816  		params[fmt.Sprintf("ResourceTags.%d.TagKey", i)] = k
   817  		params[fmt.Sprintf("ResourceTags.%d.TagValue", i)] = v
   818  		i++
   819  	}
   820  
   821  	var create = func(action string, params map[string]string) (jsonutils.JSONObject, error) {
   822  		startTime := time.Now()
   823  		var resp jsonutils.JSONObject
   824  		var err error
   825  		for time.Now().Sub(startTime) < time.Minute*10 {
   826  			resp, err = self.cdbRequest(action, params)
   827  			if err != nil {
   828  				if strings.Contains(err.Error(), "OperationDenied.OtherOderInProcess") || strings.Contains(err.Error(), "Message=请求已经在处理中") {
   829  					time.Sleep(time.Second * 20)
   830  					continue
   831  				}
   832  				return nil, errors.Wrapf(err, "cdbRequest")
   833  			}
   834  			return resp, nil
   835  		}
   836  		return resp, err
   837  	}
   838  
   839  	resp, err := create(action, params)
   840  	if err != nil {
   841  		return nil, errors.Wrapf(err, "cdbRequest")
   842  	}
   843  	instanceIds := []string{}
   844  	err = resp.Unmarshal(&instanceIds, "InstanceIds")
   845  	if err != nil {
   846  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   847  	}
   848  	if len(instanceIds) == 0 {
   849  		return nil, fmt.Errorf("%s not return InstanceIds", action)
   850  	}
   851  	err = cloudprovider.Wait(time.Second*10, time.Minute*20, func() (bool, error) {
   852  		instances, _, err := self.ListMySQLInstances(instanceIds, 0, 1)
   853  		if err != nil {
   854  			return false, errors.Wrapf(err, "ListMySQLInstances(%s)", instanceIds)
   855  		}
   856  		for _, rds := range instances {
   857  			log.Debugf("instance %s(%s) task status: %d", rds.InstanceName, rds.InstanceId, rds.TaskStatus)
   858  			if rds.TaskStatus == 1 {
   859  				return false, nil
   860  			}
   861  		}
   862  		return true, nil
   863  	})
   864  	if err != nil {
   865  		return nil, errors.Wrapf(err, "cloudprovider.Wait After create")
   866  	}
   867  	return self.GetMySQLInstanceById(instanceIds[0])
   868  }
   869  
   870  func (self *SRegion) GetMySQLInstanceById(id string) (*SMySQLInstance, error) {
   871  	part, total, err := self.ListMySQLInstances([]string{id}, 0, 20)
   872  	if err != nil {
   873  		return nil, errors.Wrapf(err, "ListMySQLInstances")
   874  	}
   875  	if total > 1 {
   876  		return nil, errors.Wrapf(cloudprovider.ErrDuplicateId, "id: [%s]", id)
   877  	}
   878  	if total < 1 {
   879  		return nil, errors.Wrapf(cloudprovider.ErrNotFound, id)
   880  	}
   881  	part[0].region = self
   882  	return &part[0], nil
   883  }
   884  
   885  func (self *SMySQLInstance) CreateDatabase(opts *cloudprovider.SDBInstanceDatabaseCreateConfig) error {
   886  	return cloudprovider.ErrNotSupported
   887  }
   888  
   889  func (self *SMySQLInstance) CreateAccount(opts *cloudprovider.SDBInstanceAccountCreateConfig) error {
   890  	return self.region.CreateMySQLAccount(self.InstanceId, opts)
   891  }
   892  
   893  func (self *SMySQLInstance) CreateIBackup(opts *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) {
   894  	tables := map[string]string{}
   895  	for _, d := range opts.Databases {
   896  		tables[d] = ""
   897  	}
   898  	return self.region.CreateMySQLBackup(self.InstanceId, tables)
   899  }
   900  
   901  func (self *SMySQLInstance) SetTags(tags map[string]string, replace bool) error {
   902  	return self.region.SetResourceTags("cdb", "instanceId", []string{self.InstanceId}, tags, replace)
   903  }
   904  
   905  func (self *SRegion) GetIMySQLs() ([]cloudprovider.ICloudDBInstance, error) {
   906  	ret := []cloudprovider.ICloudDBInstance{}
   907  	mysql := []SMySQLInstance{}
   908  	for {
   909  		part, total, err := self.ListMySQLInstances([]string{}, len(mysql), 50)
   910  		if err != nil {
   911  			return nil, errors.Wrapf(err, "ListMySQLInstances")
   912  		}
   913  		mysql = append(mysql, part...)
   914  		if len(mysql) >= total {
   915  			break
   916  		}
   917  	}
   918  	for i := range mysql {
   919  		mysql[i].region = self
   920  		ret = append(ret, &mysql[i])
   921  	}
   922  	return ret, nil
   923  }