yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/dbinstance.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 hcs 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "yunion.io/x/jsonutils" 23 "yunion.io/x/log" 24 "yunion.io/x/pkg/errors" 25 26 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 27 api "yunion.io/x/cloudmux/pkg/apis/compute" 28 "yunion.io/x/cloudmux/pkg/cloudprovider" 29 "yunion.io/x/cloudmux/pkg/multicloud" 30 "yunion.io/x/cloudmux/pkg/multicloud/huawei" 31 "yunion.io/x/onecloud/pkg/util/billing" 32 "yunion.io/x/onecloud/pkg/util/httputils" 33 ) 34 35 type SBackupStrategy struct { 36 KeepDays int 37 StartTime string 38 } 39 40 type SDatastore struct { 41 Type string 42 Version string 43 } 44 45 type SHa struct { 46 ReplicationMode string 47 } 48 49 type SNonde struct { 50 AvailabilityZone string 51 Id string 52 Name string 53 Role string 54 Staus string 55 } 56 57 type SVolume struct { 58 Size int 59 Type string 60 } 61 62 type SRelatedInstance struct { 63 Id string 64 Type string 65 } 66 67 type SDBInstance struct { 68 multicloud.SDBInstanceBase 69 huawei.HuaweiTags 70 region *SRegion 71 72 flavorCache []SDBInstanceFlavor 73 74 BackupStrategy SBackupStrategy 75 Created string //time.Time 76 Datastore SDatastore 77 DbUserName string 78 DIskEncryptionId string 79 FlavorRef string 80 Ha SHa 81 Id string 82 MaintenanceWindow string 83 Name string 84 Nodes []SNonde 85 Port int 86 PrivateIps []string 87 PublicIps []string 88 Region string 89 RelatedInstance []SRelatedInstance 90 SecurityGroupId string 91 Status string 92 SubnetId string 93 SwitchStrategy string 94 TimeZone string 95 Type string 96 Updated string //time.Time 97 Volume SVolume 98 VpcId string 99 EnterpriseProjectId string 100 } 101 102 func (region *SRegion) GetDBInstances() ([]SDBInstance, error) { 103 dbinstances := []SDBInstance{} 104 err := region.rdsList("instances", nil, nil) 105 return dbinstances, err 106 } 107 108 func (region *SRegion) GetDBInstance(instanceId string) (*SDBInstance, error) { 109 if len(instanceId) == 0 { 110 return nil, cloudprovider.ErrNotFound 111 } 112 instance := SDBInstance{region: region} 113 res := &SDBInstance{} 114 err := region.rdsGet(fmt.Sprintf("instance/%s", instanceId), res) 115 return &instance, err 116 } 117 118 func (rds *SDBInstance) GetName() string { 119 return rds.Name 120 } 121 122 func (rds *SDBInstance) GetId() string { 123 return rds.Id 124 } 125 126 func (rds *SDBInstance) GetGlobalId() string { 127 return rds.GetId() 128 } 129 130 // 值为“BUILD”,表示实例正在创建。 131 // 值为“ACTIVE”,表示实例正常。 132 // 值为“FAILED”,表示实例异常。 133 // 值为“FROZEN”,表示实例冻结。 134 // 值为“MODIFYING”,表示实例正在扩容。 135 // 值为“REBOOTING”,表示实例正在重启。 136 // 值为“RESTORING”,表示实例正在恢复。 137 // 值为“MODIFYING INSTANCE TYPE”,表示实例正在转主备。 138 // 值为“SWITCHOVER”,表示实例正在主备切换。 139 // 值为“MIGRATING”,表示实例正在迁移。 140 // 值为“BACKING UP”,表示实例正在进行备份。 141 // 值为“MODIFYING DATABASE PORT”,表示实例正在修改数据库端口。 142 // 值为“STORAGE FULL”,表示实例磁盘空间满。 143 144 func (rds *SDBInstance) GetStatus() string { 145 switch rds.Status { 146 case "BUILD", "MODIFYING", "MODIFYING INSTANCE TYPE", "SWITCHOVER", "MODIFYING DATABASE PORT": 147 return api.DBINSTANCE_DEPLOYING 148 case "ACTIVE": 149 return api.DBINSTANCE_RUNNING 150 case "FAILED", "FROZEN", "STORAGE FULL": 151 return api.DBINSTANCE_UNKNOWN 152 case "REBOOTING": 153 return api.DBINSTANCE_REBOOTING 154 case "RESTORING": 155 return api.DBINSTANCE_RESTORING 156 case "MIGRATING": 157 return api.DBINSTANCE_MIGRATING 158 case "BACKING UP": 159 return api.DBINSTANCE_BACKING_UP 160 } 161 return rds.Status 162 } 163 164 func (rds *SDBInstance) GetBillingType() string { 165 return billing_api.BILLING_TYPE_POSTPAID 166 } 167 168 func (rds *SDBInstance) GetSecurityGroupIds() ([]string, error) { 169 return []string{rds.SecurityGroupId}, nil 170 } 171 172 func (rds *SDBInstance) fetchFlavor() error { 173 if len(rds.flavorCache) > 0 { 174 return nil 175 } 176 flavors, err := rds.region.GetDBInstanceFlavors(rds.Datastore.Type, rds.Datastore.Version) 177 if err != nil { 178 return err 179 } 180 rds.flavorCache = flavors 181 return nil 182 } 183 184 func (rds *SDBInstance) GetExpiredAt() time.Time { 185 return time.Time{} 186 } 187 188 func (rds *SDBInstance) GetStorageType() string { 189 return rds.Volume.Type 190 } 191 192 func (rds *SDBInstance) GetCreatedAt() time.Time { 193 t, err := time.Parse("2006-01-02T15:04:05Z0700", rds.Created) 194 if err != nil { 195 return time.Time{} 196 } 197 return t 198 } 199 200 func (rds *SDBInstance) GetEngine() string { 201 return rds.Datastore.Type 202 } 203 204 func (rds *SDBInstance) GetEngineVersion() string { 205 return rds.Datastore.Version 206 } 207 208 func (rds *SDBInstance) GetInstanceType() string { 209 return rds.FlavorRef 210 } 211 212 func (rds *SDBInstance) GetCategory() string { 213 switch rds.Type { 214 case "Single": 215 return api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE 216 case "Ha": 217 return api.HUAWEI_DBINSTANCE_CATEGORY_HA 218 case "Replica": 219 return api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA 220 } 221 return rds.Type 222 } 223 224 func (rds *SDBInstance) GetVcpuCount() int { 225 err := rds.fetchFlavor() 226 if err != nil { 227 log.Errorf("failed to fetch flavors: %v", err) 228 return 0 229 } 230 for _, flavor := range rds.flavorCache { 231 if flavor.SpecCode == rds.FlavorRef { 232 return flavor.Vcpus 233 } 234 } 235 return 0 236 } 237 238 func (rds *SDBInstance) GetVmemSizeMB() int { 239 err := rds.fetchFlavor() 240 if err != nil { 241 log.Errorf("failed to fetch flavors: %v", err) 242 return 0 243 } 244 for _, flavor := range rds.flavorCache { 245 if flavor.SpecCode == rds.FlavorRef { 246 return flavor.Ram * 1024 247 } 248 } 249 return 0 250 } 251 252 func (rds *SDBInstance) GetDiskSizeGB() int { 253 return rds.Volume.Size 254 } 255 256 func (rds *SDBInstance) GetPort() int { 257 return rds.Port 258 } 259 260 func (rds *SDBInstance) GetMaintainTime() string { 261 return rds.MaintenanceWindow 262 } 263 264 func (rds *SDBInstance) GetIVpcId() string { 265 return rds.VpcId 266 } 267 268 func (rds *SDBInstance) GetProjectId() string { 269 return rds.EnterpriseProjectId 270 } 271 272 func (rds *SDBInstance) Refresh() error { 273 instance, err := rds.region.GetDBInstance(rds.Id) 274 if err != nil { 275 return err 276 } 277 return jsonutils.Update(rds, instance) 278 } 279 280 func (rds *SDBInstance) GetZone1Id() string { 281 return rds.GetZoneIdByRole("master") 282 } 283 284 func (rds *SDBInstance) GetZoneIdByRole(role string) string { 285 // for _, node := range rds.Nodes { 286 // if node.Role == role { 287 // zone, err := rds.region.getZoneById(node.AvailabilityZone) 288 // if err != nil { 289 // log.Errorf("failed to found zone %s for rds %s error: %v", node.AvailabilityZone, rds.Name, err) 290 // return "" 291 // } 292 // return zone.GetGlobalId() 293 // } 294 // } 295 return "" 296 } 297 298 func (rds *SDBInstance) GetZone2Id() string { 299 return rds.GetZoneIdByRole("slave") 300 } 301 302 func (rds *SDBInstance) GetZone3Id() string { 303 return "" 304 } 305 306 type SRdsNetwork struct { 307 SubnetId string 308 IP string 309 } 310 311 func (rds *SDBInstance) GetDBNetworks() ([]cloudprovider.SDBInstanceNetwork, error) { 312 ret := []cloudprovider.SDBInstanceNetwork{} 313 for _, ip := range rds.PrivateIps { 314 network := cloudprovider.SDBInstanceNetwork{ 315 IP: ip, 316 NetworkId: rds.SubnetId, 317 } 318 ret = append(ret, network) 319 } 320 return ret, nil 321 } 322 323 func (rds *SDBInstance) GetInternalConnectionStr() string { 324 for _, ip := range rds.PrivateIps { 325 return ip 326 } 327 return "" 328 } 329 330 func (rds *SDBInstance) GetConnectionStr() string { 331 for _, ip := range rds.PublicIps { 332 return ip 333 } 334 return "" 335 } 336 337 func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { 338 dbinstance, err := region.GetDBInstance(instanceId) 339 if err != nil { 340 log.Errorf("failed to get dbinstance by id %s error: %v", instanceId, err) 341 } 342 return dbinstance, err 343 } 344 345 func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { 346 instances, err := region.GetDBInstances() 347 if err != nil { 348 return nil, errors.Wrapf(err, "region.GetDBInstances()") 349 } 350 idbinstances := []cloudprovider.ICloudDBInstance{} 351 for i := 0; i < len(instances); i++ { 352 instances[i].region = region 353 idbinstances = append(idbinstances, &instances[i]) 354 } 355 return idbinstances, nil 356 } 357 358 func (rds *SDBInstance) GetIDBInstanceParameters() ([]cloudprovider.ICloudDBInstanceParameter, error) { 359 parameters, err := rds.region.GetDBInstanceParameters(rds.Id) 360 if err != nil { 361 return nil, err 362 } 363 iparameters := []cloudprovider.ICloudDBInstanceParameter{} 364 for i := 0; i < len(parameters); i++ { 365 iparameters = append(iparameters, ¶meters[i]) 366 } 367 return iparameters, nil 368 } 369 370 func (rds *SDBInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { 371 databases, err := rds.region.GetDBInstanceDatabases(rds.Id) 372 if err != nil { 373 return nil, errors.Wrap(err, "rds.region.GetDBInstanceDatabases(rds.Id)") 374 } 375 376 idatabase := []cloudprovider.ICloudDBInstanceDatabase{} 377 for i := 0; i < len(databases); i++ { 378 databases[i].instance = rds 379 idatabase = append(idatabase, &databases[i]) 380 } 381 return idatabase, nil 382 } 383 384 func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) { 385 accounts, err := rds.region.GetDBInstanceAccounts(rds.Id) 386 if err != nil { 387 return nil, errors.Wrap(err, "rds.region.GetDBInstanceAccounts(rds.Id)") 388 } 389 390 user := "root" 391 if rds.GetEngine() == api.DBINSTANCE_TYPE_SQLSERVER { 392 user = "rduser" 393 } 394 395 accounts = append(accounts, SDBInstanceAccount{ 396 Name: user, 397 instance: rds, 398 }) 399 400 iaccounts := []cloudprovider.ICloudDBInstanceAccount{} 401 for i := 0; i < len(accounts); i++ { 402 accounts[i].instance = rds 403 iaccounts = append(iaccounts, &accounts[i]) 404 } 405 return iaccounts, nil 406 } 407 408 func (rds *SDBInstance) Delete() error { 409 return rds.region.DeleteDBInstance(rds.Id) 410 } 411 412 func (region *SRegion) DeleteDBInstance(instanceId string) error { 413 err := region.client.rdsDelete(region.Id, instanceId) 414 return err 415 } 416 417 func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { 418 zoneIds := []string{} 419 zones, err := region.GetIZones() 420 if err != nil { 421 return nil, err 422 } 423 for _, zone := range zones { 424 zoneIds = append(zoneIds, zone.GetId()) 425 } 426 427 if len(desc.SecgroupIds) == 0 { 428 return nil, fmt.Errorf("Missing secgroupId") 429 } 430 431 params := map[string]interface{}{ 432 "region": region.Id, 433 "name": desc.Name, 434 "datastore": map[string]string{ 435 "type": desc.Engine, 436 "version": desc.EngineVersion, 437 }, 438 "password": desc.Password, 439 "volume": map[string]interface{}{ 440 "type": desc.StorageType, 441 "size": desc.DiskSizeGB, 442 }, 443 "vpc_id": desc.VpcId, 444 "subnet_id": desc.NetworkId, 445 "security_group_id": desc.SecgroupIds[0], 446 } 447 448 if len(desc.ProjectId) > 0 { 449 params["enterprise_project_id"] = desc.ProjectId 450 } 451 452 if len(desc.MasterInstanceId) > 0 { 453 params["replica_of_id"] = desc.MasterInstanceId 454 delete(params, "security_group_id") 455 } 456 457 if len(desc.RdsId) > 0 && len(desc.BackupId) > 0 { 458 params["restore_point"] = map[string]interface{}{ 459 "backup_id": desc.BackupId, 460 "instance_id": desc.RdsId, 461 "type": "backup", 462 } 463 } 464 465 switch desc.Category { 466 case api.HUAWEI_DBINSTANCE_CATEGORY_HA: 467 switch desc.Engine { 468 case api.DBINSTANCE_TYPE_MYSQL, api.DBINSTANCE_TYPE_POSTGRESQL: 469 params["ha"] = map[string]string{ 470 "mode": "Ha", 471 "replication_mode": "async", 472 } 473 case api.DBINSTANCE_TYPE_SQLSERVER: 474 params["ha"] = map[string]string{ 475 "mode": "Ha", 476 "replication_mode": "sync", 477 } 478 } 479 case api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE: 480 case api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA: 481 } 482 483 if desc.BillingCycle != nil { 484 periodType := "month" 485 periodNum := desc.BillingCycle.GetMonths() 486 if desc.BillingCycle.GetYears() > 0 { 487 periodType = "year" 488 periodNum = desc.BillingCycle.GetYears() 489 } 490 params["charge_info"] = map[string]interface{}{ 491 "charge_mode": "prePaid", 492 "period_type": periodType, 493 "period_num": periodNum, 494 "is_auto_renew": false, 495 } 496 } 497 params["flavor_ref"] = desc.InstanceType 498 params["availability_zone"] = desc.ZoneId 499 resp, err := region.client.request(httputils.POST, region.client._url("instance", "v3", region.Id, "instance"), nil, params) 500 if err != nil { 501 return nil, errors.Wrapf(err, "Create") 502 } 503 504 instance := &SDBInstance{region: region} 505 err = resp.Unmarshal(instance, "instance") 506 if err != nil { 507 return nil, errors.Wrap(err, `resp.Unmarshal(&instance, "instance")`) 508 } 509 if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { 510 err = cloudprovider.Wait(10*time.Second, 20*time.Minute, func() (bool, error) { 511 512 job, err := region.client.request(httputils.POST, region.client._url(fmt.Sprintf("instance/%s", jobId), "v3", region.Id, "instance"), nil, params) 513 if err != nil { 514 return false, nil 515 } 516 status, _ := job.GetString("status") 517 process, _ := job.GetString("process") 518 log.Debugf("create dbinstance job %s status: %s process: %s", jobId, status, process) 519 if status == "Completed" { 520 return true, nil 521 } 522 if status == "Failed" { 523 return false, fmt.Errorf("create failed") 524 } 525 return false, nil 526 }) 527 } 528 return instance, err 529 } 530 531 func (rds *SDBInstance) Reboot() error { 532 // return rds.region.RebootDBInstance(rds.Id) 533 return nil 534 } 535 536 func (rds *SDBInstance) OpenPublicConnection() error { 537 return fmt.Errorf("Huawei current not support this operation") 538 //return rds.region.PublicConnectionAction(rds.Id, "openRC") 539 } 540 541 func (rds *SDBInstance) ClosePublicConnection() error { 542 return fmt.Errorf("Huawei current not support this operation") 543 //return rds.region.PublicConnectionAction(rds.Id, "closeRC") 544 } 545 546 func (region *SRegion) PublicConnectionAction(instanceId string, action string) error { 547 return cloudprovider.ErrNotImplemented 548 /*resp, err := region.ecsClient.DBInstance.PerformAction2(action, instanceId, nil, "") 549 if err != nil { 550 return errors.Wrapf(err, "rds.%s", action) 551 } 552 553 if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { 554 err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { 555 job := struct { 556 Status string 557 Process string 558 }{} 559 query := url.Values{} 560 query.Add("id", jobId) 561 err := region.rdsJobGet("jobs", query, job) 562 if err != nil { 563 log.Errorf("failed to get job %s info error: %v", jobId, err) 564 return false 565 } 566 if job.Status == "Completed" { 567 return true 568 } 569 log.Debugf("%s dbinstance job %s status: %s process: %s", action, jobId, job.Status, job.Process) 570 return false 571 }) 572 } 573 574 return nil 575 */ 576 } 577 578 func (region *SRegion) RebootDBInstance(instanceId string) error { 579 return cloudprovider.ErrNotImplemented 580 /*params := jsonutils.Marshal(map[string]interface{}{ 581 "restart": map[string]string{}, 582 }) 583 resp, err := region.ecsClient.DBInstance.PerformAction2("action", instanceId, params, "") 584 if err != nil { 585 return err 586 } 587 if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { 588 err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { 589 job := struct { 590 Status string 591 Process string 592 }{} 593 query := url.Values{} 594 query.Add("id", jobId) 595 err := region.rdsJobGet("jobs", query, job) 596 if err != nil { 597 log.Errorf("failed to get job %s info error: %v", jobId, err) 598 return false 599 } 600 if job.Status == "Completed" { 601 return true 602 } 603 log.Debugf("%s dbinstance job %s status: %s process: %s", action, jobId, job.Status, job.Process) 604 return false 605 }) 606 } 607 return err*/ 608 } 609 610 func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { 611 return rds.region.CreateDBInstanceAccount(rds.Id, conf.Name, conf.Password) 612 } 613 614 func (region *SRegion) CreateDBInstanceAccount(instanceId, account, password string) error { 615 params := map[string]interface{}{ 616 "name": account, 617 "password": password, 618 } 619 620 err := region.rdsCreate(fmt.Sprintf("instances/%s/db_user", instanceId), params, nil) 621 return err 622 } 623 624 func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { 625 return rds.region.CreateDBInstanceDatabase(rds.Id, conf.Name, conf.CharacterSet) 626 } 627 628 func (region *SRegion) CreateDBInstanceDatabase(instanceId, database, characterSet string) error { 629 params := map[string]interface{}{ 630 "name": database, 631 "character_set": characterSet, 632 } 633 err := region.rdsCreate(fmt.Sprintf("instances/%s/database", instanceId), params, nil) 634 return err 635 } 636 637 func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { 638 return rds.region.ChangeDBInstanceConfig(rds.Id, desc.InstanceType, desc.DiskSizeGB) 639 } 640 641 func (region *SRegion) ChangeDBInstanceConfig(instanceId string, instanceType string, diskSizeGb int) error { 642 instance, err := region.GetIDBInstanceById(instanceId) 643 if err != nil { 644 return errors.Wrapf(err, "region.GetIDBInstanceById(%s)", instanceId) 645 } 646 647 if len(instanceType) > 0 { 648 params := map[string]interface{}{ 649 "resize_flavor": map[string]string{ 650 "spec_code": instanceType, 651 }, 652 } 653 err := region.rdsPerform(fmt.Sprintf("instances/%s", instanceId), "action", params, nil) 654 if err != nil { 655 return errors.Wrap(err, "resize_flavor") 656 } 657 cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) 658 } 659 if diskSizeGb > 0 { 660 params := map[string]interface{}{ 661 "enlarge_volume": map[string]int{ 662 "size": diskSizeGb, 663 }, 664 } 665 err := region.rdsPerform(fmt.Sprintf("instances/%s", instanceId), "action", params, nil) 666 if err != nil { 667 return errors.Wrap(err, "enlarge_volume") 668 } 669 cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) 670 } 671 return nil 672 } 673 674 func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { 675 if len(conf.OriginDBInstanceExternalId) == 0 { 676 conf.OriginDBInstanceExternalId = rds.Id 677 } 678 return rds.region.RecoveryDBInstanceFromBackup(rds.Id, conf.OriginDBInstanceExternalId, conf.BackupId, conf.Databases) 679 } 680 681 func (region *SRegion) RecoveryDBInstanceFromBackup(target, origin string, backupId string, databases map[string]string) error { 682 source := map[string]interface{}{ 683 "type": "backup", 684 "backup_id": backupId, 685 } 686 if len(origin) > 0 { 687 source["instance_id"] = origin 688 } 689 if len(databases) > 0 { 690 source["database_name"] = databases 691 } 692 params := map[string]interface{}{ 693 "source": source, 694 "target": map[string]string{ 695 "instance_id": target, 696 }, 697 } 698 err := region.rdsPerform("instance", "recovery", params, nil) 699 if err != nil { 700 return errors.Wrap(err, "dbinstance.recovery") 701 } 702 return nil 703 } 704 705 func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { 706 return cloudprovider.ErrNotSupported 707 }