yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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 hcso 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "github.com/pkg/errors" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 27 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 28 api "yunion.io/x/cloudmux/pkg/apis/compute" 29 "yunion.io/x/cloudmux/pkg/cloudprovider" 30 "yunion.io/x/cloudmux/pkg/multicloud" 31 "yunion.io/x/cloudmux/pkg/multicloud/huawei" 32 "yunion.io/x/onecloud/pkg/util/billing" 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 params := map[string]string{} 104 dbinstances := []SDBInstance{} 105 err := doListAllWithOffset(region.ecsClient.DBInstance.List, params, &dbinstances) 106 return dbinstances, err 107 } 108 109 func (region *SRegion) GetDBInstance(instanceId string) (*SDBInstance, error) { 110 if len(instanceId) == 0 { 111 return nil, cloudprovider.ErrNotFound 112 } 113 instance := SDBInstance{region: region} 114 err := DoGet(region.ecsClient.DBInstance.Get, instanceId, nil, &instance) 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.ecsClient.DBInstance.Delete(instanceId, nil) 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.ecsClient.DBInstance.Create(jsonutils.Marshal(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 job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) 512 if err != nil { 513 return false, nil 514 } 515 status, _ := job.GetString("status") 516 process, _ := job.GetString("process") 517 log.Debugf("create dbinstance job %s status: %s process: %s", jobId, status, process) 518 if status == "Completed" { 519 return true, nil 520 } 521 if status == "Failed" { 522 return false, fmt.Errorf("create failed") 523 } 524 return false, nil 525 }) 526 } 527 return instance, err 528 } 529 530 func (rds *SDBInstance) Reboot() error { 531 return rds.region.RebootDBInstance(rds.Id) 532 } 533 534 func (rds *SDBInstance) OpenPublicConnection() error { 535 return fmt.Errorf("Huawei current not support this operation") 536 //return rds.region.PublicConnectionAction(rds.Id, "openRC") 537 } 538 539 func (rds *SDBInstance) ClosePublicConnection() error { 540 return fmt.Errorf("Huawei current not support this operation") 541 //return rds.region.PublicConnectionAction(rds.Id, "closeRC") 542 } 543 544 func (region *SRegion) PublicConnectionAction(instanceId string, action string) error { 545 resp, err := region.ecsClient.DBInstance.PerformAction2(action, instanceId, nil, "") 546 if err != nil { 547 return errors.Wrapf(err, "rds.%s", action) 548 } 549 550 if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { 551 err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { 552 job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) 553 if err != nil { 554 log.Errorf("failed to get job %s info error: %v", jobId, err) 555 return false 556 } 557 status, _ := job.GetString("status") 558 process, _ := job.GetString("process") 559 if status == "Completed" { 560 return true 561 } 562 log.Debugf("%s dbinstance job %s status: %s process: %s", action, jobId, status, process) 563 return false 564 }) 565 } 566 567 return nil 568 569 } 570 571 func (region *SRegion) RebootDBInstance(instanceId string) error { 572 params := jsonutils.Marshal(map[string]interface{}{ 573 "restart": map[string]string{}, 574 }) 575 resp, err := region.ecsClient.DBInstance.PerformAction2("action", instanceId, params, "") 576 if err != nil { 577 return err 578 } 579 if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { 580 err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { 581 job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) 582 if err != nil { 583 log.Errorf("failed to get job %s info error: %v", jobId, err) 584 return false 585 } 586 status, _ := job.GetString("status") 587 process, _ := job.GetString("process") 588 if status == "Completed" { 589 return true 590 } 591 log.Debugf("reboot dbinstance job %s status: %s process: %s", jobId, status, process) 592 return false 593 }) 594 } 595 return err 596 } 597 598 func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { 599 return rds.region.CreateDBInstanceAccount(rds.Id, conf.Name, conf.Password) 600 } 601 602 func (region *SRegion) CreateDBInstanceAccount(instanceId, account, password string) error { 603 params := map[string]string{ 604 "name": account, 605 "password": password, 606 } 607 _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/db_user", instanceId), jsonutils.Marshal(params), "") 608 return err 609 } 610 611 func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { 612 return rds.region.CreateDBInstanceDatabase(rds.Id, conf.Name, conf.CharacterSet) 613 } 614 615 func (region *SRegion) CreateDBInstanceDatabase(instanceId, database, characterSet string) error { 616 params := map[string]string{ 617 "name": database, 618 "character_set": characterSet, 619 } 620 _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/database", instanceId), jsonutils.Marshal(params), "") 621 return err 622 } 623 624 func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { 625 return rds.region.ChangeDBInstanceConfig(rds.Id, desc.InstanceType, desc.DiskSizeGB) 626 } 627 628 func (region *SRegion) ChangeDBInstanceConfig(instanceId string, instanceType string, diskSizeGb int) error { 629 instance, err := region.GetIDBInstanceById(instanceId) 630 if err != nil { 631 return errors.Wrapf(err, "region.GetIDBInstanceById(%s)", instanceId) 632 } 633 634 if len(instanceType) > 0 { 635 params := map[string]map[string]string{ 636 "resize_flavor": map[string]string{ 637 "spec_code": instanceType, 638 }, 639 } 640 _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) 641 if err != nil { 642 return errors.Wrap(err, "resize_flavor") 643 } 644 cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) 645 } 646 if diskSizeGb > 0 { 647 params := map[string]map[string]int{ 648 "enlarge_volume": map[string]int{ 649 "size": diskSizeGb, 650 }, 651 } 652 _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) 653 if err != nil { 654 return errors.Wrap(err, "enlarge_volume") 655 } 656 cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) 657 } 658 return nil 659 } 660 661 func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { 662 if len(conf.OriginDBInstanceExternalId) == 0 { 663 conf.OriginDBInstanceExternalId = rds.Id 664 } 665 return rds.region.RecoveryDBInstanceFromBackup(rds.Id, conf.OriginDBInstanceExternalId, conf.BackupId, conf.Databases) 666 } 667 668 func (region *SRegion) RecoveryDBInstanceFromBackup(target, origin string, backupId string, databases map[string]string) error { 669 source := map[string]interface{}{ 670 "type": "backup", 671 "backup_id": backupId, 672 } 673 if len(origin) > 0 { 674 source["instance_id"] = origin 675 } 676 if len(databases) > 0 { 677 source["database_name"] = databases 678 } 679 params := map[string]interface{}{ 680 "source": source, 681 "target": map[string]string{ 682 "instance_id": target, 683 }, 684 } 685 _, err := region.ecsClient.DBInstance.PerformAction("", "recovery", jsonutils.Marshal(params)) 686 if err != nil { 687 return errors.Wrap(err, "dbinstance.recovery") 688 } 689 return nil 690 } 691 692 func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { 693 return cloudprovider.ErrNotSupported 694 }