-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dds): support primary standby switch
- Loading branch information
Showing
4 changed files
with
303 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
--- | ||
subcategory: "Document Database Service (DDS)" | ||
layout: "huaweicloud" | ||
page_title: "HuaweiCloud: huaweicloud_dds_primary_standby_switch" | ||
description: |- | ||
Manages a DDS primary standby switch resource within HuaweiCloud. | ||
--- | ||
|
||
# huaweicloud_dds_primary_standby_switch | ||
|
||
Manages a DDS primary standby switch resource within HuaweiCloud. | ||
|
||
## Example Usage | ||
|
||
### Perform switch for a replica set instance | ||
|
||
```hcl | ||
variable "instance_id" {} | ||
resource "huaweicloud_dds_primary_standby_switch" "test" { | ||
instance_id = var.instance_id | ||
} | ||
``` | ||
|
||
### Promote standby node to primary for replica set node, shard node or config node | ||
|
||
```hcl | ||
variable "instance_id" {} | ||
variable "node_id" {} | ||
resource "huaweicloud_dds_primary_standby_switch" "test" { | ||
instance_id = var.instance_id | ||
node_id = var.node_id | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `region` - (Optional, String, ForceNew) Specifies the region in which to create the resource. | ||
If omitted, the provider-level region will be used. | ||
Changing this creates a new resource. | ||
|
||
* `instance_id` - (Required, String, ForceNew) Specifies the instance ID. | ||
Changing this creates a new resource. | ||
|
||
* `node_id` - (Optional, String, ForceNew) Specifies the ID of replica set node, shard node or config node. | ||
Changing this creates a new resource. | ||
|
||
-> If `node_id` is not specified, perform a primary/secondary switchover in a replica set instance. | ||
|
||
## Attribute Reference | ||
|
||
In addition to all arguments above, the following attributes are exported: | ||
|
||
* `id` - The resource ID. | ||
|
||
## Timeouts | ||
|
||
This resource provides the following timeouts configuration options: | ||
|
||
* `create` - Default is 20 minutes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
huaweicloud/services/acceptance/dds/resource_huaweicloud_dds_primary_standby_switch_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package dds | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
|
||
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" | ||
) | ||
|
||
func TestAccDDSPrimaryStandbySwitch_basic(t *testing.T) { | ||
rName := acceptance.RandomAccResourceName() | ||
|
||
resource.ParallelTest(t, resource.TestCase{ | ||
PreCheck: func() { acceptance.TestAccPreCheck(t) }, | ||
ProviderFactories: acceptance.TestAccProviderFactories, | ||
CheckDestroy: nil, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccDDSPrimaryStandbySwitch_Instance(rName), | ||
Check: resource.ComposeTestCheckFunc(), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccDDSPrimaryStandbySwitch_Instance(rName string) string { | ||
return fmt.Sprintf(` | ||
%s | ||
resource "huaweicloud_dds_primary_standby_switch" "test" { | ||
instance_id = huaweicloud_dds_instance.instance.id | ||
}`, testAccDDSInstanceReplicaSetBasic(rName)) | ||
} | ||
|
||
func TestAccDDSPrimaryStandbySwitch_node(t *testing.T) { | ||
rName := acceptance.RandomAccResourceName() | ||
|
||
resource.ParallelTest(t, resource.TestCase{ | ||
PreCheck: func() { acceptance.TestAccPreCheck(t) }, | ||
ProviderFactories: acceptance.TestAccProviderFactories, | ||
CheckDestroy: nil, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccDDSPrimaryStandbySwitch_Node(rName), | ||
Check: resource.ComposeTestCheckFunc(), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccDDSPrimaryStandbySwitch_Node(rName string) string { | ||
return fmt.Sprintf(` | ||
%s | ||
locals { | ||
node_ids = [for node in huaweicloud_dds_instance.instance.nodes: node.id if node.role == "Secondary"] | ||
} | ||
resource "huaweicloud_dds_primary_standby_switch" "test" { | ||
instance_id = huaweicloud_dds_instance.instance.id | ||
node_id = local.node_ids[0] | ||
lifecycle { | ||
ignore_changes = [ | ||
node_id, | ||
] | ||
} | ||
}`, testAccDDSInstanceV3Config_basic(rName, 8800)) | ||
} |
168 changes: 168 additions & 0 deletions
168
huaweicloud/services/dds/resource_huaweicloud_dds_primary_standby_switch.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package dds | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"time" | ||
|
||
"github.com/hashicorp/go-uuid" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
"github.com/chnsz/golangsdk" | ||
|
||
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" | ||
"github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" | ||
) | ||
|
||
// @API DDS POST /v3/{project_id}/instances/{instance_id}/switchover | ||
// @API DDS POST /v3/{project_id}/instances/{instance_id}/nodes/{node_id}/primary | ||
// @API DDS GET /v3/{project_id}/jobs | ||
func ResourceDDSPrimaryStandbySwitch() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateContext: resourceDDSPrimaryStandbySwitchCreate, | ||
ReadContext: resourceDDSPrimaryStandbySwitchRead, | ||
DeleteContext: resourceDDSPrimaryStandbySwitchDelete, | ||
|
||
Timeouts: &schema.ResourceTimeout{ | ||
Create: schema.DefaultTimeout(20 * time.Minute), | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"region": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Computed: true, | ||
ForceNew: true, | ||
}, | ||
"instance_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"node_id": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceDDSPrimaryStandbySwitchCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
cfg := meta.(*config.Config) | ||
region := cfg.GetRegion(d) | ||
client, err := cfg.NewServiceClient("dds", region) | ||
if err != nil { | ||
return diag.Errorf("error creating DDS client: %s", err) | ||
} | ||
|
||
var jobID string | ||
|
||
if _, ok := d.GetOk("node_id"); ok { | ||
// switch by node, it's a synchronous task | ||
// but need to wait the node become primary for a few seconds | ||
err = promoteStandbyNodeToPrimary(client, d) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
// lintignore:R018 | ||
time.Sleep(30 * time.Second) | ||
} else { | ||
// switch instance, it's a asynchronous task | ||
jobID, err = performSwitchForInstance(client, d) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
} | ||
|
||
id, err := uuid.GenerateUUID() | ||
if err != nil { | ||
return diag.Errorf("unable to generate ID: %s", err) | ||
} | ||
d.SetId(id) | ||
|
||
// wait for job complete | ||
if jobID != "" { | ||
stateConf := &resource.StateChangeConf{ | ||
Pending: []string{"Running"}, | ||
Target: []string{"Completed"}, | ||
Refresh: JobStateRefreshFunc(client, jobID), | ||
Timeout: d.Timeout(schema.TimeoutCreate), | ||
Delay: 10 * time.Second, | ||
PollInterval: 10 * time.Second, | ||
} | ||
|
||
_, err = stateConf.WaitForStateContext(ctx) | ||
if err != nil { | ||
return diag.Errorf("error waiting for the job (%s) completed: %s ", jobID, err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func promoteStandbyNodeToPrimary(client *golangsdk.ServiceClient, d *schema.ResourceData) error { | ||
createHttpUrl := "v3/{project_id}/instances/{instance_id}/nodes/{node_id}/primary" | ||
createPath := client.Endpoint + createHttpUrl | ||
createPath = strings.ReplaceAll(createPath, "{project_id}", client.ProjectID) | ||
createPath = strings.ReplaceAll(createPath, "{instance_id}", d.Get("instance_id").(string)) | ||
createPath = strings.ReplaceAll(createPath, "{node_id}", d.Get("node_id").(string)) | ||
createOpt := golangsdk.RequestOpts{ | ||
KeepResponseBody: true, | ||
MoreHeaders: map[string]string{ | ||
"Content-Type": "application/json", | ||
}, | ||
} | ||
|
||
_, err := client.Request("POST", createPath, &createOpt) | ||
if err != nil { | ||
return fmt.Errorf("error promoting standby node to primary: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func performSwitchForInstance(client *golangsdk.ServiceClient, d *schema.ResourceData) (string, error) { | ||
createHttpUrl := "v3/{project_id}/instances/{instance_id}/switchover" | ||
createPath := client.Endpoint + createHttpUrl | ||
createPath = strings.ReplaceAll(createPath, "{project_id}", client.ProjectID) | ||
createPath = strings.ReplaceAll(createPath, "{instance_id}", d.Get("instance_id").(string)) | ||
createOpt := golangsdk.RequestOpts{ | ||
KeepResponseBody: true, | ||
} | ||
|
||
createResp, err := client.Request("POST", createPath, &createOpt) | ||
if err != nil { | ||
return "", fmt.Errorf("error performing primary standby switch for instance: %s", err) | ||
} | ||
createRespBody, err := utils.FlattenResponse(createResp) | ||
if err != nil { | ||
return "", fmt.Errorf("error flattening response: %s", err) | ||
} | ||
|
||
jobID := utils.PathSearch("job_id", createRespBody, "").(string) | ||
if jobID == "" { | ||
return "", fmt.Errorf("unable to find job ID in API response") | ||
} | ||
|
||
return jobID, nil | ||
} | ||
|
||
func resourceDDSPrimaryStandbySwitchRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
return nil | ||
} | ||
|
||
func resourceDDSPrimaryStandbySwitchDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
errorMsg := "Deleting primary standby switch resource is not supported. The resource is only removed from the state," + | ||
" the instance remains in the cloud." | ||
return diag.Diagnostics{ | ||
diag.Diagnostic{ | ||
Severity: diag.Warning, | ||
Summary: errorMsg, | ||
}, | ||
} | ||
} |