diff --git a/docs/resources/dds_instance.md b/docs/resources/dds_instance.md index 4a7562d22f..7f128e363c 100644 --- a/docs/resources/dds_instance.md +++ b/docs/resources/dds_instance.md @@ -209,8 +209,9 @@ The `configuration` block supports: + For a Community Edition single node instance, the value is **single**. Changing this creates a new instance. -* `id` - (Required, String, ForceNew) Specifies the ID of the template. - Changing this creates a new instance. +* `id` - (Required, String) Specifies the ID of the template. + + -> Atfer updating the `configuration.id`, please check whether the instance needs to be restarted. The `flavor` block supports: diff --git a/huaweicloud/services/acceptance/dds/resource_huaweicloud_dds_instance_v3_test.go b/huaweicloud/services/acceptance/dds/resource_huaweicloud_dds_instance_v3_test.go index 990e7a924f..86428c3d84 100644 --- a/huaweicloud/services/acceptance/dds/resource_huaweicloud_dds_instance_v3_test.go +++ b/huaweicloud/services/acceptance/dds/resource_huaweicloud_dds_instance_v3_test.go @@ -227,16 +227,29 @@ func TestAccDDSV3Instance_withConfigurationSharding(t *testing.T) { CheckDestroy: rc.CheckResourceDestroy(), Steps: []resource.TestStep{ { - Config: testAccDDSInstanceV3Config_withShardingConfiguration(rName, 8800), + Config: testAccDDSInstanceV3Config_withShardingConfiguration(rName), Check: resource.ComposeTestCheckFunc( rc.CheckResourceExists(), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "ssl", "true"), - resource.TestCheckResourceAttr(resourceName, "port", "8800"), - resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), - resource.TestCheckResourceAttr(resourceName, "tags.owner", "terraform"), - resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.start_time", "08:00-09:00"), - resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.keep_days", "8"), + resource.TestCheckResourceAttr(resourceName, "configuration.0.type", "mongos"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.0.id", "huaweicloud_dds_parameter_template.mongos1", "id"), + resource.TestCheckResourceAttr(resourceName, "configuration.1.type", "shard"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.1.id", "huaweicloud_dds_parameter_template.shard1", "id"), + resource.TestCheckResourceAttr(resourceName, "configuration.2.type", "config"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.2.id", "huaweicloud_dds_parameter_template.config1", "id"), + ), + }, + { + Config: testAccDDSInstanceV3Config_withShardingConfigurationUpdate(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "configuration.0.type", "mongos"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.0.id", "huaweicloud_dds_parameter_template.mongos2", "id"), + resource.TestCheckResourceAttr(resourceName, "configuration.1.type", "shard"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.1.id", "huaweicloud_dds_parameter_template.shard2", "id"), + resource.TestCheckResourceAttr(resourceName, "configuration.2.type", "config"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.2.id", "huaweicloud_dds_parameter_template.config2", "id"), ), }, }, @@ -260,17 +273,22 @@ func TestAccDDSV3Instance_withConfigurationReplicaSet(t *testing.T) { CheckDestroy: rc.CheckResourceDestroy(), Steps: []resource.TestStep{ { - Config: testAccDDSInstanceV3Config_withReplicaSetConfiguration(rName, 8900), + Config: testAccDDSInstanceV3Config_withReplicaSetConfiguration(rName), Check: resource.ComposeTestCheckFunc( rc.CheckResourceExists(), resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "ssl", "true"), - resource.TestCheckResourceAttr(resourceName, "port", "8900"), - resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), - resource.TestCheckResourceAttr(resourceName, "tags.owner", "terraform"), - resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.start_time", "08:00-09:00"), - resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.keep_days", "8"), resource.TestCheckResourceAttr(resourceName, "replica_set_name", "replicaName"), + resource.TestCheckResourceAttr(resourceName, "configuration.0.type", "replica"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.0.id", "huaweicloud_dds_parameter_template.replica1", "id"), + ), + }, + { + Config: testAccDDSInstanceV3Config_withReplicaSetConfigurationUpdate(rName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "configuration.0.type", "replica"), + resource.TestCheckResourceAttrPair(resourceName, "configuration.0.id", "huaweicloud_dds_parameter_template.replica2", "id"), ), }, }, @@ -867,71 +885,73 @@ resource "huaweicloud_dds_instance" "instance" { }`, common.TestBaseNetwork(rName), rName) } -func testAccDDSInstanceV3Config_withShardingConfiguration(rName string, port int) string { +func testAAccDDSInstance_templete(rName, nodeType string, i, value int) string { return fmt.Sprintf(` -%[1]s -data "huaweicloud_availability_zones" "test" {} -resource "huaweicloud_dds_parameter_template" "mongos" { - name = "%[2]s_mongos" - description = "test description" - node_type = "mongos" - node_version = "3.4" - parameter_values = { - connPoolMaxConnsPerHost = 800 - connPoolMaxShardedConnsPerHost = 800 - } -} -resource "huaweicloud_dds_parameter_template" "shard" { - name = "%[2]s_shard" +resource "huaweicloud_dds_parameter_template" "%[3]s%[1]v" { + name = "%[2]s_%[3]s%[1]v" description = "test description" - node_type = "shard" + node_type = "%[3]s" node_version = "3.4" + parameter_values = { - connPoolMaxConnsPerHost = 1000 - connPoolMaxShardedConnsPerHost = 1000 + connPoolMaxConnsPerHost = %[4]v + connPoolMaxShardedConnsPerHost = %[4]v } } -resource "huaweicloud_dds_parameter_template" "config" { - name = "%[2]s_config" - description = "test description" - node_type = "config" - node_version = "3.4" - parameter_values = { - connPoolMaxConnsPerHost = 400 - connPoolMaxShardedConnsPerHost = 400 - } +`, i, rName, nodeType, value) } + +func testAccDDSInstanceV3Config_withShardingConfiguration(rName string) string { + templateMongos1 := testAAccDDSInstance_templete(rName, "mongos", 1, 800) + templateShard1 := testAAccDDSInstance_templete(rName, "shard", 1, 1000) + templateConfig1 := testAAccDDSInstance_templete(rName, "config", 1, 400) + return fmt.Sprintf(` +%[1]s + +%[2]s + +%[3]s + +%[4]s + +data "huaweicloud_availability_zones" "test" {} + resource "huaweicloud_dds_instance" "instance" { - name = "%[2]s" + name = "%[5]s" availability_zone = data.huaweicloud_availability_zones.test.names[0] vpc_id = huaweicloud_vpc.test.id subnet_id = huaweicloud_vpc_subnet.test.id security_group_id = huaweicloud_networking_secgroup.test.id password = "Terraform@123" mode = "Sharding" - port = %[3]d + datastore { type = "DDS-Community" version = "3.4" storage_engine = "wiredTiger" } + configuration { type = "mongos" - id = huaweicloud_dds_parameter_template.mongos.id + id = huaweicloud_dds_parameter_template.mongos1.id } + configuration { type = "shard" - id = huaweicloud_dds_parameter_template.shard.id + id = huaweicloud_dds_parameter_template.shard1.id } + configuration { type = "config" - id = huaweicloud_dds_parameter_template.config.id + id = huaweicloud_dds_parameter_template.config1.id } + flavor { type = "mongos" num = 2 spec_code = "dds.mongodb.s6.large.2.mongos" } + flavor { type = "shard" num = 2 @@ -939,6 +959,7 @@ resource "huaweicloud_dds_instance" "instance" { size = 20 spec_code = "dds.mongodb.s6.large.2.shard" } + flavor { type = "config" num = 1 @@ -946,41 +967,95 @@ resource "huaweicloud_dds_instance" "instance" { size = 20 spec_code = "dds.mongodb.s6.large.2.config" } - backup_strategy { - start_time = "08:00-09:00" - keep_days = "8" - period = "1,5" - } - tags = { - foo = "bar" - owner = "terraform" - } -}`, common.TestBaseNetwork(rName), rName, port) +}`, common.TestBaseNetwork(rName), templateMongos1, templateShard1, templateConfig1, rName) } -func testAccDDSInstanceV3Config_withReplicaSetConfiguration(rName string, port int) string { +func testAccDDSInstanceV3Config_withShardingConfigurationUpdate(rName string) string { + templateMongos2 := testAAccDDSInstance_templete(rName, "mongos", 2, 500) + templateShard2 := testAAccDDSInstance_templete(rName, "shard", 2, 800) + templateConfig2 := testAAccDDSInstance_templete(rName, "config", 2, 600) return fmt.Sprintf(` %[1]s + +%[2]s + +%[3]s + +%[4]s + data "huaweicloud_availability_zones" "test" {} -resource "huaweicloud_dds_parameter_template" "replica" { - name = "%[2]s_replica" - description = "test description" - node_type = "replica" - node_version = "3.4" - parameter_values = { - connPoolMaxConnsPerHost = 400 - connPoolMaxShardedConnsPerHost = 400 + +resource "huaweicloud_dds_instance" "instance" { + name = "%[5]s" + availability_zone = data.huaweicloud_availability_zones.test.names[0] + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + password = "Terraform@123" + mode = "Sharding" + + datastore { + type = "DDS-Community" + version = "3.4" + storage_engine = "wiredTiger" + } + + configuration { + type = "mongos" + id = huaweicloud_dds_parameter_template.mongos2.id + } + + configuration { + type = "shard" + id = huaweicloud_dds_parameter_template.shard2.id + } + + configuration { + type = "config" + id = huaweicloud_dds_parameter_template.config2.id + } + + flavor { + type = "mongos" + num = 2 + spec_code = "dds.mongodb.s6.large.4.mongos" + } + + flavor { + type = "shard" + num = 2 + storage = "ULTRAHIGH" + size = 20 + spec_code = "dds.mongodb.s6.large.2.shard" + } + + flavor { + type = "config" + num = 1 + storage = "ULTRAHIGH" + size = 20 + spec_code = "dds.mongodb.s6.large.2.config" } +}`, common.TestBaseNetwork(rName), templateMongos2, templateShard2, templateConfig2, rName) } + +func testAccDDSInstanceV3Config_withReplicaSetConfiguration(rName string) string { + templateRreplica1 := testAAccDDSInstance_templete(rName, "replica", 1, 400) + return fmt.Sprintf(` +%[1]s + +%[2]s + +data "huaweicloud_availability_zones" "test" {} + resource "huaweicloud_dds_instance" "instance" { - name = "%[2]s" + name = "%[3]s" availability_zone = data.huaweicloud_availability_zones.test.names[0] vpc_id = huaweicloud_vpc.test.id subnet_id = huaweicloud_vpc_subnet.test.id security_group_id = huaweicloud_networking_secgroup.test.id password = "Terraform@123" mode = "ReplicaSet" - port = %[3]d replica_set_name = "replicaName" datastore { @@ -990,7 +1065,7 @@ resource "huaweicloud_dds_instance" "instance" { } configuration { type = "replica" - id = huaweicloud_dds_parameter_template.replica.id + id = huaweicloud_dds_parameter_template.replica1.id } flavor { type = "replica" @@ -999,16 +1074,45 @@ resource "huaweicloud_dds_instance" "instance" { size = 20 spec_code = "dds.mongodb.s6.large.2.repset" } - backup_strategy { - start_time = "08:00-09:00" - keep_days = "8" - period = "1,5" +}`, common.TestBaseNetwork(rName), templateRreplica1, rName) +} + +func testAccDDSInstanceV3Config_withReplicaSetConfigurationUpdate(rName string) string { + templateRreplica2 := testAAccDDSInstance_templete(rName, "replica", 2, 700) + return fmt.Sprintf(` +%[1]s + +%[2]s + +data "huaweicloud_availability_zones" "test" {} + +resource "huaweicloud_dds_instance" "instance" { + name = "%[3]s" + availability_zone = data.huaweicloud_availability_zones.test.names[0] + vpc_id = huaweicloud_vpc.test.id + subnet_id = huaweicloud_vpc_subnet.test.id + security_group_id = huaweicloud_networking_secgroup.test.id + password = "Terraform@123" + mode = "ReplicaSet" + replica_set_name = "replicaName" + + datastore { + type = "DDS-Community" + version = "3.4" + storage_engine = "wiredTiger" } - tags = { - foo = "bar" - owner = "terraform" + configuration { + type = "replica" + id = huaweicloud_dds_parameter_template.replica2.id } -}`, common.TestBaseNetwork(rName), rName, port) + flavor { + type = "replica" + storage = "ULTRAHIGH" + num = 1 + size = 20 + spec_code = "dds.mongodb.s6.large.2.repset" + } +}`, common.TestBaseNetwork(rName), templateRreplica2, rName) } func testAccDDSInstanceV3Config_secondLevelMonitoring(rName string) string { diff --git a/huaweicloud/services/dds/resource_huaweicloud_dds_instance_v3.go b/huaweicloud/services/dds/resource_huaweicloud_dds_instance_v3.go index d6db1bdd66..fc89d71eb0 100644 --- a/huaweicloud/services/dds/resource_huaweicloud_dds_instance_v3.go +++ b/huaweicloud/services/dds/resource_huaweicloud_dds_instance_v3.go @@ -26,6 +26,8 @@ import ( "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" ) +type ctxType string + // @API DDS POST /v3/{project_id}/instances // @API DDS GET /v3/{project_id}/instances // @API DDS POST /v3/{project_id}/instances/{id}/tags/action @@ -48,6 +50,7 @@ import ( // @API DDS PUT /v3/{project_id}/instances/{instance_id}/monitoring-by-seconds/switch // @API DDS PUT /v3/{project_id}/instances/{instance_id}/replica-set/name // @API DDS GET /v3/{project_id}/instances/{instance_id}/replica-set/name +// @API DDS PUT /v3/{project_id}/configurations/{config_id}/apply // @API BSS POST /v2/orders/subscriptions/resources/unsubscribe // @API BSS GET /v2/orders/customer-orders/details/{order_id} // @API BSS POST /v2/orders/suscriptions/resources/query @@ -156,7 +159,6 @@ func ResourceDdsInstanceV3() *schema.Resource { "id": { Type: schema.TypeString, Required: true, - ForceNew: true, }, }, }, @@ -560,6 +562,7 @@ func updateReplicaSetName(ctx context.Context, client *golangsdk.ServiceClient, WaitFunc: ddsInstanceStateRefreshFunc(client, instanceId), WaitTarget: []string{"normal"}, Timeout: timeout, + DelayTimeout: 10 * time.Second, PollInterval: 10 * time.Second, }) if err != nil { @@ -571,6 +574,7 @@ func updateReplicaSetName(ctx context.Context, client *golangsdk.ServiceClient, Target: []string{"Completed"}, Refresh: JobStateRefreshFunc(client, resp.JobId), Timeout: timeout, + Delay: 10 * time.Second, PollInterval: 10 * time.Second, } @@ -582,7 +586,7 @@ func updateReplicaSetName(ctx context.Context, client *golangsdk.ServiceClient, return nil } -func resourceDdsInstanceV3Read(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceDdsInstanceV3Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conf := meta.(*config.Config) client, err := conf.DdsV3Client(conf.GetRegion(d)) if err != nil { @@ -691,6 +695,16 @@ func resourceDdsInstanceV3Read(_ context.Context, d *schema.ResourceData, meta i return diag.Errorf("Error setting dds instance fields: %s", err) } + if ctx.Value(ctxType("configurationIdChanged")) == "true" { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Configuration ID Changed", + Detail: "Configuration ID changed, please check whether the instance needs to be restarted.", + }, + } + } + return nil } @@ -931,6 +945,35 @@ func resourceDdsInstanceV3Update(ctx context.Context, d *schema.ResourceData, me } } + // update configuration + if d.HasChange("configuration") { + for i := range d.Get("configuration").([]interface{}) { + configTypePath := fmt.Sprintf("configuration.%d.type", i) + configIdPath := fmt.Sprintf("configuration.%d.id", i) + if d.HasChange(configIdPath) { + // If the DB instance type is cluster and the shard or config parameter template is to be changed, the + // param is the group ID. If the parameter template of the mongos node is changed, the param is the + // node ID. If the DB instance to be changed is a replica set instance, the param is the instance ID. + var ids []string + configType := d.Get(configTypePath).(string) + if configType == "replica" { + ids = []string{instanceId} + } else { + ids, err = getDdsInstanceV3GroupIDOrNodeID(client, instanceId, configType) + if err != nil { + return diag.FromErr(err) + } + } + + // update config ID + if ctx, err = applyConfigurationToEntity(ctx, client, d.Timeout(schema.TimeoutUpdate), d.Id(), + d.Get(configIdPath).(string), ids); err != nil { + return diag.FromErr(err) + } + } + } + } + if d.HasChange("enterprise_project_id") { migrateOpts := enterpriseprojects.MigrateResourceOpts{ ResourceId: instanceId, @@ -1069,72 +1112,47 @@ func flattenDdsInstanceV3Nodes(dds instances.InstanceResponse) interface{} { return nodesList } -func getDdsInstanceV3ShardGroupID(client *golangsdk.ServiceClient, d *schema.ResourceData) ([]string, error) { - groupIDs := make([]string, 0) +func getDdsInstanceV3GroupIDOrNodeID(client *golangsdk.ServiceClient, instanceID, getTpye string) ([]string, error) { + ids := make([]string, 0) - instanceID := d.Id() opts := instances.ListInstanceOpts{ Id: instanceID, } allPages, err := instances.List(client, &opts).AllPages() if err != nil { - return groupIDs, fmt.Errorf("Error fetching DDS instance: %s", err) + return ids, fmt.Errorf("error fetching DDS instance: %s", err) } instanceList, err := instances.ExtractInstances(allPages) if err != nil { - return groupIDs, fmt.Errorf("Error extracting DDS instance: %s", err) + return ids, fmt.Errorf("error extracting DDS instance: %s", err) } if instanceList.TotalCount == 0 { log.Printf("[WARN] DDS instance (%s) was not found", instanceID) - return groupIDs, nil + return ids, nil } insts := instanceList.Instances instanceObj := insts[0] log.Printf("[DEBUG] Retrieved instance %s: %#v", instanceID, instanceObj) - for _, group := range instanceObj.Groups { - if group.Type == "shard" { - groupIDs = append(groupIDs, group.Id) + switch getTpye { + case "shard", "config": + for _, group := range instanceObj.Groups { + if group.Type == getTpye { + ids = append(ids, group.Id) + } } - } - - return groupIDs, nil -} - -func getDdsInstanceV3MongosNodeID(client *golangsdk.ServiceClient, d *schema.ResourceData) ([]string, error) { - nodeIDs := make([]string, 0) - - instanceID := d.Id() - opts := instances.ListInstanceOpts{ - Id: instanceID, - } - allPages, err := instances.List(client, &opts).AllPages() - if err != nil { - return nodeIDs, fmt.Errorf("Error fetching DDS instance: %s", err) - } - instanceList, err := instances.ExtractInstances(allPages) - if err != nil { - return nodeIDs, fmt.Errorf("Error extracting DDS instance: %s", err) - } - if instanceList.TotalCount == 0 { - log.Printf("[WARN] DDS instance (%s) was not found", instanceID) - return nodeIDs, nil - } - insts := instanceList.Instances - instanceObj := insts[0] - - log.Printf("[DEBUG] Retrieved instance %s: %#v", instanceID, instanceObj) - - for _, group := range instanceObj.Groups { - if group.Type == "mongos" { - for _, node := range group.Nodes { - nodeIDs = append(nodeIDs, node.Id) + case "mongos": + for _, group := range instanceObj.Groups { + if group.Type == "mongos" { + for _, node := range group.Nodes { + ids = append(ids, node.Id) + } } } } - return nodeIDs, nil + return ids, nil } func flavorUpdate(ctx context.Context, conf *config.Config, client *golangsdk.ServiceClient, d *schema.ResourceData, @@ -1255,7 +1273,7 @@ func flavorSizeUpdate(ctx context.Context, conf *config.Config, client *golangsd } if groupType == "shard" { - groupIDs, err := getDdsInstanceV3ShardGroupID(client, d) + groupIDs, err := getDdsInstanceV3GroupIDOrNodeID(client, d.Id(), "shard") if err != nil { return err } @@ -1317,7 +1335,7 @@ func flavorSpecCodeUpdate(ctx context.Context, conf *config.Config, client *gola } switch groupType { case "mongos": - nodeIDs, err := getDdsInstanceV3MongosNodeID(client, d) + nodeIDs, err := getDdsInstanceV3GroupIDOrNodeID(client, d.Id(), "mongos") if err != nil { return err } @@ -1346,7 +1364,7 @@ func flavorSpecCodeUpdate(ctx context.Context, conf *config.Config, client *gola } } case "shard": - groupIDs, err := getDdsInstanceV3ShardGroupID(client, d) + groupIDs, err := getDdsInstanceV3GroupIDOrNodeID(client, d.Id(), "shard") if err != nil { return err } @@ -1401,3 +1419,49 @@ func flavorSpecCodeUpdate(ctx context.Context, conf *config.Config, client *gola return nil } + +func applyConfigurationToEntity(ctx context.Context, client *golangsdk.ServiceClient, timeout time.Duration, + instID, configID string, entityIDs []string) (context.Context, error) { + applyConfigurationToEntityHttpUrl := "v3/{project_id}/configurations/{config_id}/apply" + applyConfigurationToEntityPath := client.Endpoint + applyConfigurationToEntityHttpUrl + applyConfigurationToEntityPath = strings.ReplaceAll(applyConfigurationToEntityPath, "{project_id}", client.ProjectID) + applyConfigurationToEntityPath = strings.ReplaceAll(applyConfigurationToEntityPath, "{config_id}", configID) + + applyConfigurationToEntityOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + JSONBody: map[string]interface{}{ + "entity_ids": entityIDs, + }, + } + + // retry, the job_id in return is useless + retryFunc := func() (interface{}, bool, error) { + resp, err := client.Request("PUT", applyConfigurationToEntityPath, &applyConfigurationToEntityOpt) + retry, err := handleMultiOperationsError(err) + return resp, retry, err + } + + _, err := common.RetryContextWithWaitForState(&common.RetryContextWithWaitForStateParam{ + Ctx: ctx, + RetryFunc: retryFunc, + WaitFunc: ddsInstanceStateRefreshFunc(client, instID), + WaitTarget: []string{"normal"}, + Timeout: timeout, + DelayTimeout: 10 * time.Second, + PollInterval: 10 * time.Second, + }) + if err != nil { + return ctx, fmt.Errorf("error apply configuration(%s): %s", configID, err) + } + + // wait for job complete + err = waitForInstanceReady(ctx, client, instID, timeout) + if err != nil { + return ctx, err + } + + // Sending configurationIdChanged to Read to warn users the instance needs a reboot. + ctx = context.WithValue(ctx, ctxType("configurationIdChanged"), "true") + + return ctx, nil +}