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 }