Skip to content

Commit 16c2883

Browse files
authored
feat(RDS): rds instance support update charging mode (#6389)
1 parent 8d60ca7 commit 16c2883

File tree

3 files changed

+226
-15
lines changed

3 files changed

+226
-15
lines changed

docs/resources/rds_instance.md

+7-8
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,15 @@ The following arguments are supported:
233233

234234
Defaults to **reliability**.
235235

236-
* `charging_mode` - (Optional, String, ForceNew) Specifies the charging mode of the RDS DB instance. Valid values are
237-
**prePaid** and **postPaid**, defaults to **postPaid**. Changing this creates a new resource.
236+
* `charging_mode` - (Optional, String) Specifies the charging mode of the RDS DB instance. Valid values are **prePaid**
237+
and **postPaid**, defaults to **postPaid**.
238238

239-
* `period_unit` - (Optional, String, ForceNew) Specifies the charging period unit of the RDS DB instance. Valid values
240-
are **month** and **year**. This parameter is mandatory if `charging_mode` is set to **prePaid**. Changing this
241-
creates a new resource.
239+
* `period_unit` - (Optional, String) Specifies the charging period unit of the RDS DB instance. Valid values are **month**
240+
and **year**. This parameter is mandatory if `charging_mode` is set to **prePaid**.
242241

243-
* `period` - (Optional, Int, ForceNew) Specifies the charging period of the RDS DB instance. If `period_unit` is set
244-
to **month**, the value ranges from `1` to `9`. If `period_unit` is set to **year**, the value ranges from `1` to `3`.
245-
This parameter is mandatory if `charging_mode` is set to **prePaid**. Changing this creates a new resource.
242+
* `period` - (Optional, Int) Specifies the charging period of the RDS DB instance. If `period_unit` is set to **month**,
243+
the value ranges from `1` to `9`. If `period_unit` is set to **year**, the value ranges from `1` to `3`. This parameter
244+
is mandatory if `charging_mode` is set to **prePaid**.
246245

247246
* `auto_renew` - (Optional, String) Specifies whether auto-renew is enabled. Valid values are "true" and "false".
248247

huaweicloud/services/acceptance/rds/resource_huaweicloud_rds_instance_test.go

+108
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,37 @@ func TestAccRdsInstance_restore_pg(t *testing.T) {
468468
})
469469
}
470470

471+
func TestAccRdsInstance_change_billing_mode_to_prepaid(t *testing.T) {
472+
var instance instances.RdsInstanceResponse
473+
name := acceptance.RandomAccResourceName()
474+
resourceType := "huaweicloud_rds_instance"
475+
resourceName := "huaweicloud_rds_instance.test"
476+
477+
resource.ParallelTest(t, resource.TestCase{
478+
PreCheck: func() { acceptance.TestAccPreCheck(t) },
479+
ProviderFactories: acceptance.TestAccProviderFactories,
480+
CheckDestroy: testAccCheckRdsInstanceDestroy(resourceType),
481+
Steps: []resource.TestStep{
482+
{
483+
Config: testAccRdsInstance_change_billing_mode_to_prepaid(name),
484+
Check: resource.ComposeTestCheckFunc(
485+
testAccCheckRdsInstanceExists(resourceName, &instance),
486+
resource.TestCheckResourceAttr(resourceName, "name", name),
487+
resource.TestCheckResourceAttr(resourceName, "charging_mode", "postPaid"),
488+
),
489+
},
490+
{
491+
Config: testAccRdsInstance_change_billing_mode_to_prepaid_update(name),
492+
Check: resource.ComposeTestCheckFunc(
493+
testAccCheckRdsInstanceExists(resourceName, &instance),
494+
resource.TestCheckResourceAttr(resourceName, "name", name),
495+
resource.TestCheckResourceAttr(resourceName, "charging_mode", "prePaid"),
496+
),
497+
},
498+
},
499+
})
500+
}
501+
471502
func testAccCheckRdsInstanceDestroy(rsType string) resource.TestCheckFunc {
472503
return func(s *terraform.State) error {
473504
config := acceptance.TestAccProvider.Meta().(*config.Config)
@@ -1384,3 +1415,80 @@ resource "huaweicloud_rds_instance" "test_backup" {
13841415
}
13851416
`, testBackup_pg_basic(name), name, pwd)
13861417
}
1418+
1419+
func testAccRdsInstance_change_billing_mode_to_prepaid(name string) string {
1420+
return fmt.Sprintf(`
1421+
%[1]s
1422+
1423+
data "huaweicloud_rds_flavors" "test" {
1424+
db_type = "PostgreSQL"
1425+
db_version = "14"
1426+
instance_mode = "single"
1427+
group_type = "dedicated"
1428+
vcpus = 8
1429+
}
1430+
1431+
resource "huaweicloud_rds_instance" "test" {
1432+
name = "%[2]s"
1433+
flavor = data.huaweicloud_rds_flavors.test.flavors[0].name
1434+
availability_zone = [data.huaweicloud_availability_zones.test.names[0]]
1435+
security_group_id = huaweicloud_networking_secgroup.test.id
1436+
subnet_id = data.huaweicloud_vpc_subnet.test.id
1437+
vpc_id = data.huaweicloud_vpc.test.id
1438+
time_zone = "UTC+08:00"
1439+
1440+
db {
1441+
password = "Huangwei!120521"
1442+
type = "PostgreSQL"
1443+
version = "14"
1444+
port = 8632
1445+
}
1446+
1447+
volume {
1448+
type = "CLOUDSSD"
1449+
size = 50
1450+
}
1451+
}
1452+
`, testAccRdsInstance_base(name), name)
1453+
}
1454+
1455+
func testAccRdsInstance_change_billing_mode_to_prepaid_update(name string) string {
1456+
return fmt.Sprintf(`
1457+
%[1]s
1458+
1459+
data "huaweicloud_rds_flavors" "test" {
1460+
db_type = "PostgreSQL"
1461+
db_version = "14"
1462+
instance_mode = "single"
1463+
group_type = "dedicated"
1464+
vcpus = 8
1465+
}
1466+
1467+
resource "huaweicloud_rds_instance" "test" {
1468+
name = "%[2]s"
1469+
flavor = data.huaweicloud_rds_flavors.test.flavors[0].name
1470+
availability_zone = [data.huaweicloud_availability_zones.test.names[0]]
1471+
security_group_id = huaweicloud_networking_secgroup.test.id
1472+
subnet_id = data.huaweicloud_vpc_subnet.test.id
1473+
vpc_id = data.huaweicloud_vpc.test.id
1474+
time_zone = "UTC+08:00"
1475+
1476+
db {
1477+
password = "Huangwei!120521"
1478+
type = "PostgreSQL"
1479+
version = "14"
1480+
port = 8632
1481+
}
1482+
1483+
volume {
1484+
type = "CLOUDSSD"
1485+
size = 50
1486+
}
1487+
1488+
charging_mode = "prePaid"
1489+
period_unit = "month"
1490+
period = 1
1491+
auto_renew = true
1492+
}
1493+
`, testAccRdsInstance_base(name), name)
1494+
}

huaweicloud/services/rds/resource_huaweicloud_rds_instance.go

+111-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"net/http"
78
"strconv"
89
"strings"
910
"time"
@@ -61,6 +62,7 @@ type ctxType string
6162
// @API RDS PUT /v3/{project_id}/instances/{instance_id}/ip
6263
// @API RDS PUT /v3/{project_id}/instances/{instance_id}/security-group
6364
// @API RDS POST /v3/{project_id}/instances/{instance_id}/password
65+
// @API RDS POST /v3/{project_id}/instances/{instance_id}/to-period
6466
// @API RDS PUT /v3/{project_id}/instances/{instance_id}/binlog/clear-policy
6567
// @API RDS DELETE /v3/{project_id}/instances/{instance_id}
6668
// @API EPS POST /v1.0/enterprise-projects/{enterprise_project_id}/resources-migrat
@@ -501,12 +503,30 @@ func ResourceRdsInstance() *schema.Resource {
501503
ForceNew: true,
502504
},
503505

504-
// charge info: charging_mode, period_unit, period, auto_renew, auto_pay
505-
"charging_mode": common.SchemaChargingMode(nil),
506-
"period_unit": common.SchemaPeriodUnit(nil),
507-
"period": common.SchemaPeriod(nil),
508-
"auto_renew": common.SchemaAutoRenewUpdatable(nil),
509-
"auto_pay": common.SchemaAutoPay(nil),
506+
// charging_mode, period_unit and period only support changing post-paid to pre-paid billing mode.
507+
"charging_mode": {
508+
Type: schema.TypeString,
509+
Optional: true,
510+
Computed: true,
511+
ValidateFunc: validation.StringInSlice([]string{
512+
"prePaid", "postPaid",
513+
}, false),
514+
},
515+
"period_unit": {
516+
Type: schema.TypeString,
517+
Optional: true,
518+
RequiredWith: []string{"period"},
519+
ValidateFunc: validation.StringInSlice([]string{
520+
"month", "year",
521+
}, false),
522+
},
523+
"period": {
524+
Type: schema.TypeInt,
525+
Optional: true,
526+
RequiredWith: []string{"period_unit"},
527+
},
528+
"auto_renew": common.SchemaAutoRenewUpdatable(nil),
529+
"auto_pay": common.SchemaAutoPay(nil),
510530
},
511531
}
512532
}
@@ -1035,7 +1055,15 @@ func resourceRdsInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta
10351055
}
10361056
}
10371057

1038-
if d.HasChange("auto_renew") {
1058+
if d.HasChange("charging_mode") {
1059+
if d.Get("charging_mode").(string) == "postPaid" {
1060+
return diag.Errorf("error updating the charging mode of the RDS instance (%s): %s", d.Id(),
1061+
"only support changing post-paid instance to pre-paid")
1062+
}
1063+
if err = updateBillingModeToPeriod(ctx, d, cfg, client, instanceID); err != nil {
1064+
return diag.FromErr(err)
1065+
}
1066+
} else if d.HasChange("auto_renew") {
10391067
bssClient, err := cfg.BssV2Client(region)
10401068
if err != nil {
10411069
return diag.Errorf("error creating BSS V2 client: %s", err)
@@ -1674,6 +1702,82 @@ func updateRdsInstanceSSLConfig(ctx context.Context, d *schema.ResourceData, cli
16741702
return configRdsInstanceSSL(ctx, d, client, instanceID)
16751703
}
16761704

1705+
func updateBillingModeToPeriod(ctx context.Context, d *schema.ResourceData, cfg *config.Config, client *golangsdk.ServiceClient,
1706+
instanceID string) error {
1707+
var (
1708+
httpUrl = "v3/{project_id}/instances/{instance_id}/to-period"
1709+
)
1710+
updatePath := client.Endpoint + httpUrl
1711+
updatePath = strings.ReplaceAll(updatePath, "{project_id}", client.ProjectID)
1712+
updatePath = strings.ReplaceAll(updatePath, "{instance_id}", d.Id())
1713+
1714+
updateOpt := golangsdk.RequestOpts{
1715+
KeepResponseBody: true,
1716+
}
1717+
updateOpt.JSONBody = utils.RemoveNil(buildUpdateBillingModeToPeriodBodyParams(d))
1718+
1719+
retryFunc := func() (interface{}, bool, error) {
1720+
res, err := client.Request("POST", updatePath, &updateOpt)
1721+
retry, err := handleMultiOperationsError(err)
1722+
return res, retry, err
1723+
}
1724+
res, err := common.RetryContextWithWaitForState(&common.RetryContextWithWaitForStateParam{
1725+
Ctx: ctx,
1726+
RetryFunc: retryFunc,
1727+
WaitFunc: rdsInstanceStateRefreshFunc(client, d.Id()),
1728+
WaitTarget: []string{"ACTIVE"},
1729+
Timeout: d.Timeout(schema.TimeoutUpdate),
1730+
DelayTimeout: 10 * time.Second,
1731+
PollInterval: 10 * time.Second,
1732+
})
1733+
if err != nil {
1734+
return fmt.Errorf("error updating instance(%s) billing mode from post-paid to pre-paid: %s", d.Id(), err)
1735+
}
1736+
1737+
updateRespBody, err := utils.FlattenResponse(res.(*http.Response))
1738+
if err != nil {
1739+
return err
1740+
}
1741+
1742+
orderId := utils.PathSearch("order_id", updateRespBody, "").(string)
1743+
if orderId == "" {
1744+
return fmt.Errorf("error updating RDS instance (%s) MSDTC hosts: order_id is not found in the API rsponse", d.Id())
1745+
}
1746+
bssClient, err := cfg.BssV2Client(cfg.GetRegion(d))
1747+
if err != nil {
1748+
return fmt.Errorf("error creating BSS v2 client: %s", err)
1749+
}
1750+
// wait for order success
1751+
err = common.WaitOrderComplete(ctx, bssClient, orderId, d.Timeout(schema.TimeoutUpdate))
1752+
if err != nil {
1753+
return err
1754+
}
1755+
1756+
stateConf := &resource.StateChangeConf{
1757+
Target: []string{"ACTIVE"},
1758+
Refresh: rdsInstanceStateRefreshFunc(client, instanceID),
1759+
Timeout: d.Timeout(schema.TimeoutUpdate),
1760+
Delay: 1 * time.Second,
1761+
PollInterval: 10 * time.Second,
1762+
}
1763+
if _, err = stateConf.WaitForStateContext(ctx); err != nil {
1764+
return fmt.Errorf("error waiting for instance (%s) billing mode to be updated: %s", instanceID, err)
1765+
}
1766+
return nil
1767+
}
1768+
1769+
func buildUpdateBillingModeToPeriodBodyParams(d *schema.ResourceData) map[string]interface{} {
1770+
bodyParams := map[string]interface{}{
1771+
"period_type": strings.ToUpper(d.Get("period_unit").(string)),
1772+
"period_num": d.Get("period").(int),
1773+
"auto_pay_policy": "YES",
1774+
}
1775+
if d.Get("auto_renew").(string) == "true" {
1776+
bodyParams["auto_renew_policy"] = "YES"
1777+
}
1778+
return bodyParams
1779+
}
1780+
16771781
func updateConfiguration(ctx context.Context, d *schema.ResourceData, client, clientV31 *golangsdk.ServiceClient,
16781782
instanceID string) (context.Context, error) {
16791783
if !d.HasChange("param_group_id") {

0 commit comments

Comments
 (0)