Skip to content

Commit f7c19aa

Browse files
authored
feat: pgsql - support db_node_set for multiple available zones. (#838)
* feat: pgsql - support db_node_set for multiple available zones. * fix: pgsql - db_node_set.zone description * fix: pgsql poling status and docs
1 parent 72c5688 commit f7c19aa

File tree

12 files changed

+3503
-148
lines changed

12 files changed

+3503
-148
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require (
2929
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.283
3030
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.199
3131
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.291
32-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.330
32+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.332
3333
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.199
3434
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cynosdb v1.0.199
3535
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dayu v1.0.199
@@ -41,7 +41,7 @@ require (
4141
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.199
4242
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/mongodb v1.0.199
4343
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/monitor v1.0.329
44-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.306
44+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.332
4545
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.290
4646
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/redis v1.0.199
4747
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.275

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.306 h1:wQe/
479479
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.306/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
480480
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.330 h1:yPUoeUIxqrrYoKhwYRNIDKpnZ5VIMK3qNwuuB55DZtY=
481481
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.330/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
482+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.332 h1:sIRdZ4tihXEJEVL6EM0ZiK/ySOUYDMfTI9D0jA6CdPU=
483+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.332/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
482484
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.199 h1:ajgJogYSIQ5u1PIbiV5nsvr5K0fYpm1/T7Dy+mxEM6U=
483485
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.199/go.mod h1:AqyM/ZZMD7q5mHBqNY9YImbSpEpoEe7E/vrTbUWX+po=
484486
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cynosdb v1.0.199 h1:L0twFkJMOZzLkX08w8S14nX6oanD8YxMQDIaYXVim6A=
@@ -508,6 +510,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.199 h1:Op
508510
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.199/go.mod h1:Rh/4NXBd0aqmaRGDYcW4gL2Zi8JShGZiB23zrfVaS90=
509511
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.306 h1:R/nD8qAexHLwcKhWOTmr9rzIrvnH1C4sEudpS4qUBWY=
510512
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.306/go.mod h1:Rh/4NXBd0aqmaRGDYcW4gL2Zi8JShGZiB23zrfVaS90=
513+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.332 h1:SVk3UU8oGyAMJOlyYvJW1luD8GqLnfnBLg0BEzUyH6k=
514+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/postgres v1.0.332/go.mod h1:Rh/4NXBd0aqmaRGDYcW4gL2Zi8JShGZiB23zrfVaS90=
511515
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.290 h1:osYZxh9ApEc8UpvIMwjAUfdl7ytRcWUpcnnqLIpiJ/U=
512516
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.290/go.mod h1:En+pdagcHkAASorHT1l8R6tUtieRNNxaQ7nfyqWPefk=
513517
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/redis v1.0.199 h1:lXCng7HQqvubF7uwa7x5COsDZlJEjEJ/RBpaeYGc0+I=

tencentcloud/extension_postgresql.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@ var POSTSQL_DB_CHARSET = []string{POSTGRESQL_DB_CHARSET_UTF8, POSTGRESQL_DB_CHAR
3030
const (
3131
POSTGRESQL_STAUTS_RUNNING = "running"
3232
)
33+
34+
var POSTGRES_RETRYABLE_STATUS = []string{
35+
"initing",
36+
"expanding",
37+
"switching",
38+
// deployment changing not exposed at response struct but actually exists
39+
"deployment changing",
40+
}

tencentcloud/resource_tc_postgresql_instance.go

Lines changed: 222 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,61 @@ resource "tencentcloud_postgresql_instance" "foo" {
4444
}
4545
```
4646
47+
Create a multi available zone bucket
48+
49+
```hcl
50+
variable "availability_zone" {
51+
default = "ap-guangzhou-6"
52+
}
53+
54+
variable "standby_availability_zone" {
55+
default = "ap-guangzhou-7"
56+
}
57+
58+
# create vpc
59+
resource "tencentcloud_vpc" "vpc" {
60+
name = "guagua_vpc_instance_test"
61+
cidr_block = "10.0.0.0/16"
62+
}
63+
64+
# create vpc subnet
65+
resource "tencentcloud_subnet" "subnet" {
66+
availability_zone = var.availability_zone
67+
name = "guagua_vpc_subnet_test"
68+
vpc_id = tencentcloud_vpc.vpc.id
69+
cidr_block = "10.0.20.0/28"
70+
is_multicast = false
71+
}
72+
73+
# create postgresql
74+
resource "tencentcloud_postgresql_instance" "foo" {
75+
name = "example"
76+
availability_zone = var.availability_zone
77+
charge_type = "POSTPAID_BY_HOUR"
78+
vpc_id = tencentcloud_vpc.vpc.id
79+
subnet_id = tencentcloud_subnet.subnet.id
80+
engine_version = "10.4"
81+
root_user = "root123"
82+
root_password = "Root123$"
83+
charset = "UTF8"
84+
project_id = 0
85+
memory = 2
86+
storage = 10
87+
88+
db_node_set {
89+
role = "Primary"
90+
zone = var.availability_zone
91+
}
92+
db_node_set {
93+
zone = var.standby_availability_zone
94+
}
95+
96+
tags = {
97+
test = "tf"
98+
}
99+
}
100+
```
101+
47102
Import
48103
49104
postgresql instance can be imported using the id, e.g.
@@ -143,10 +198,26 @@ func resourceTencentCloudPostgresqlInstance() *schema.Resource {
143198
},
144199
"availability_zone": {
145200
Type: schema.TypeString,
201+
Required: true,
146202
ForceNew: true,
147-
Optional: true,
148-
Computed: true,
149-
Description: "Availability zone.",
203+
Description: "Availability zone. NOTE: If value modified but included in `db_node_set`, the diff will be suppressed.",
204+
DiffSuppressFunc: func(k, o, n string, d *schema.ResourceData) bool {
205+
if o == "" {
206+
return false
207+
}
208+
raw, ok := d.GetOk("db_node_set")
209+
if !ok {
210+
return n == o
211+
}
212+
nodeZones := raw.(*schema.Set).List()
213+
for i := range nodeZones {
214+
item := nodeZones[i].(map[string]interface{})
215+
if zone, ok := item["zone"].(string); ok && zone == n {
216+
return true
217+
}
218+
}
219+
return n == o
220+
},
150221
},
151222
"root_user": {
152223
Type: schema.TypeString,
@@ -193,6 +264,26 @@ func resourceTencentCloudPostgresqlInstance() *schema.Resource {
193264
Computed: true,
194265
Description: "max_standby_streaming_delay applies when WAL data is being received via streaming replication. Units are milliseconds if not specified.",
195266
},
267+
"db_node_set": {
268+
Type: schema.TypeSet,
269+
Optional: true,
270+
Description: "Specify instance node info for disaster migration.",
271+
Elem: &schema.Resource{
272+
Schema: map[string]*schema.Schema{
273+
"role": {
274+
Type: schema.TypeString,
275+
Optional: true,
276+
Default: "Standby",
277+
Description: "Indicates node type, available values:`Primary`, `Standby`. Default: `Standby`.",
278+
},
279+
"zone": {
280+
Type: schema.TypeString,
281+
Required: true,
282+
Description: "Indicates the node available zone.",
283+
},
284+
},
285+
},
286+
},
196287
// Computed values
197288
"public_access_host": {
198289
Type: schema.TypeString,
@@ -250,6 +341,7 @@ func resourceTencentCloudPostgresqlInstanceCreate(d *schema.ResourceData, meta i
250341
username = d.Get("root_user").(string)
251342
password = d.Get("root_password").(string)
252343
charset = d.Get("charset").(string)
344+
nodeSet = d.Get("db_node_set").(*schema.Set).List()
253345
)
254346

255347
var period = 1
@@ -265,8 +357,8 @@ func resourceTencentCloudPostgresqlInstanceCreate(d *schema.ResourceData, meta i
265357
requestSecurityGroup = append(requestSecurityGroup, v.(string))
266358
}
267359

268-
// get speccode with engine_version and memory
269-
outErr = resource.Retry(readRetryTimeout, func() *resource.RetryError {
360+
// get specCode with engine_version and memory
361+
outErr = resource.Retry(readRetryTimeout*5, func() *resource.RetryError {
270362
speccodes, inErr := postgresqlService.DescribeSpecinfos(ctx, zone)
271363
if inErr != nil {
272364
return retryError(inErr)
@@ -301,6 +393,28 @@ func resourceTencentCloudPostgresqlInstanceCreate(d *schema.ResourceData, meta i
301393
return fmt.Errorf(`The "memory" value: %d is invalid, Valid values are one of: %s`, memory, strings.Join(allowMemory, `, `))
302394
}
303395

396+
var dbNodeSet []*postgresql.DBNode
397+
if len(nodeSet) > 0 {
398+
399+
for i := range nodeSet {
400+
var (
401+
item = nodeSet[i].(map[string]interface{})
402+
role = item["role"].(string)
403+
zone = item["zone"].(string)
404+
node = &postgresql.DBNode{
405+
Role: &role,
406+
Zone: &zone,
407+
}
408+
)
409+
dbNodeSet = append(dbNodeSet, node)
410+
}
411+
412+
// check if availability_zone and node_set consists
413+
if include, z, nzs := checkZoneSetInclude(d); !include {
414+
return fmt.Errorf("`availability_zone`: %s is not included in `db_node_set`: %s", z, nzs)
415+
}
416+
}
417+
304418
outErr = resource.Retry(writeRetryTimeout, func() *resource.RetryError {
305419
instanceId, inErr = postgresqlService.CreatePostgresqlInstance(ctx,
306420
name,
@@ -316,7 +430,9 @@ func resourceTencentCloudPostgresqlInstanceCreate(d *schema.ResourceData, meta i
316430
storage,
317431
username,
318432
password,
319-
charset)
433+
charset,
434+
dbNodeSet,
435+
)
320436
if inErr != nil {
321437
return retryError(inErr)
322438
}
@@ -568,6 +684,40 @@ func resourceTencentCloudPostgresqlInstanceUpdate(d *schema.ResourceData, meta i
568684
d.SetPartial("security_groups")
569685
}
570686

687+
if d.HasChange("db_node_set") {
688+
689+
if include, z, nzs := checkZoneSetInclude(d); !include {
690+
return fmt.Errorf("`availability_zone`: %s is not included in `db_node_set`: %s", z, nzs)
691+
}
692+
693+
nodeSet := d.Get("db_node_set").(*schema.Set).List()
694+
request := postgresql.NewModifyDBInstanceDeploymentRequest()
695+
request.DBInstanceId = helper.String(d.Id())
696+
request.SwitchTag = helper.Int64(0)
697+
for i := range nodeSet {
698+
var (
699+
node = nodeSet[i].(map[string]interface{})
700+
role = node["role"].(string)
701+
zone = node["zone"].(string)
702+
)
703+
request.DBNodeSet = append(request.DBNodeSet, &postgresql.DBNode{
704+
Role: &role,
705+
Zone: &zone,
706+
})
707+
}
708+
709+
if err := postgresqlService.ModifyDBInstanceDeployment(ctx, request); err != nil {
710+
return err
711+
}
712+
713+
d.SetPartial("db_node_set")
714+
}
715+
716+
if d.HasChange("zone") {
717+
log.Printf("[WARN] argument `zone` modified, skip process")
718+
d.SetPartial("zone")
719+
}
720+
571721
if d.HasChange("tags") {
572722

573723
oldValue, newValue := d.GetChange("tags")
@@ -622,22 +772,28 @@ func resourceTencentCloudPostgresqlInstanceRead(d *schema.ResourceData, meta int
622772
logId := getLogId(contextNil)
623773
ctx := context.WithValue(context.TODO(), logIdKey, logId)
624774

625-
var outErr, inErr error
775+
var (
776+
instance *postgresql.DBInstance
777+
has bool
778+
outErr,
779+
inErr error
780+
)
781+
// Check if import
626782
postgresqlService := PostgresqlService{client: meta.(*TencentCloudClient).apiV3Conn}
627-
instance, has, outErr := postgresqlService.DescribePostgresqlInstanceById(ctx, d.Id())
628-
if outErr != nil {
629-
outErr = resource.Retry(readRetryTimeout, func() *resource.RetryError {
630-
instance, has, inErr = postgresqlService.DescribePostgresqlInstanceById(ctx, d.Id())
631-
if inErr != nil {
632-
ee, ok := inErr.(*sdkErrors.TencentCloudSDKError)
633-
if ok && ee.GetCode() == "ResourceNotFound.InstanceNotFoundError" {
634-
return nil
635-
}
636-
return retryError(inErr)
783+
outErr = resource.Retry(readRetryTimeout, func() *resource.RetryError {
784+
instance, has, inErr = postgresqlService.DescribePostgresqlInstanceById(ctx, d.Id())
785+
if inErr != nil {
786+
ee, ok := inErr.(*sdkErrors.TencentCloudSDKError)
787+
if ok && ee.GetCode() == "ResourceNotFound.InstanceNotFoundError" {
788+
return nil
637789
}
638-
return nil
639-
})
640-
}
790+
return retryError(inErr)
791+
}
792+
if IsContains(POSTGRES_RETRYABLE_STATUS, *instance.DBInstanceStatus) {
793+
return resource.RetryableError(fmt.Errorf("instance %s is %s, retrying", *instance.DBInstanceId, *instance.DBInstanceStatus))
794+
}
795+
return nil
796+
})
641797
if outErr != nil {
642798
return outErr
643799
}
@@ -708,6 +864,35 @@ func resourceTencentCloudPostgresqlInstanceRead(d *schema.ResourceData, meta int
708864
_ = d.Set("security_groups", sg)
709865
}
710866

867+
attrRequest := postgresql.NewDescribeDBInstanceAttributeRequest()
868+
attrRequest.DBInstanceId = helper.String(d.Id())
869+
870+
ins, err := postgresqlService.DescribeDBInstanceAttribute(ctx, attrRequest)
871+
if err != nil {
872+
return err
873+
}
874+
nodeSet := ins.DBNodeSet
875+
zoneSet := schema.NewSet(schema.HashString, nil)
876+
if nodeCount := len(nodeSet); nodeCount > 0 {
877+
var dbNodeSet = make([]interface{}, 0, nodeCount)
878+
for i := range nodeSet {
879+
item := nodeSet[i]
880+
node := map[string]interface{}{
881+
"role": item.Role,
882+
"zone": item.Zone,
883+
}
884+
zoneSet.Add(*item.Zone)
885+
dbNodeSet = append(dbNodeSet, node)
886+
}
887+
888+
// skip default set (single AZ and zone includes)
889+
_, nodeSetOk := d.GetOk("db_node_set")
890+
importedMaz := zoneSet.Len() > 1 && zoneSet.Contains(*instance.Zone)
891+
892+
if nodeSetOk || importedMaz {
893+
_ = d.Set("db_node_set", dbNodeSet)
894+
}
895+
}
711896
// computed
712897
_ = d.Set("create_time", instance.CreateTime)
713898
_ = d.Set("status", instance.DBInstanceStatus)
@@ -857,3 +1042,20 @@ func resourceTencentCLoudPostgresqlInstanceDelete(d *schema.ResourceData, meta i
8571042

8581043
return nil
8591044
}
1045+
1046+
// check availability_zone included in db_node_set
1047+
func checkZoneSetInclude(d *schema.ResourceData) (included bool, zone string, nodeZoneList []string) {
1048+
zone = d.Get("availability_zone").(string)
1049+
dbNodeSet := d.Get("db_node_set").(*schema.Set).List()
1050+
1051+
for i := range dbNodeSet {
1052+
item := dbNodeSet[i].(map[string]interface{})
1053+
nodeZone := item["zone"].(string)
1054+
if nodeZone == zone {
1055+
included = true
1056+
}
1057+
nodeZoneList = append(nodeZoneList, nodeZone)
1058+
}
1059+
1060+
return
1061+
}

0 commit comments

Comments
 (0)