diff --git a/.changelog/17301.txt b/.changelog/17301.txt new file mode 100644 index 000000000000..484efdaea3b6 --- /dev/null +++ b/.changelog/17301.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_elasticache_replication_group: Allow changing `cluster_mode.replica_count` without re-creation +``` diff --git a/aws/resource_aws_elasticache_replication_group.go b/aws/resource_aws_elasticache_replication_group.go index 58972cc285f7..e6474ac569b1 100644 --- a/aws/resource_aws_elasticache_replication_group.go +++ b/aws/resource_aws_elasticache_replication_group.go @@ -79,12 +79,8 @@ func resourceAwsElasticacheReplicationGroup() *schema.Resource { Computed: true, }, "cluster_mode": { - Type: schema.TypeList, - Optional: true, - // We allow Computed: true here since using number_cache_clusters - // and a cluster mode enabled parameter_group_name will create - // a single shard replication group with number_cache_clusters - 1 - // read replicas. Otherwise, the resource is marked ForceNew. + Type: schema.TypeList, + Optional: true, Computed: true, MaxItems: 1, ExactlyOneOf: []string{"cluster_mode", "number_cache_clusters"}, @@ -93,7 +89,6 @@ func resourceAwsElasticacheReplicationGroup() *schema.Resource { "replicas_per_node_group": { Type: schema.TypeInt, Required: true, - ForceNew: true, }, "num_node_groups": { Type: schema.TypeInt, @@ -314,7 +309,9 @@ func resourceAwsElasticacheReplicationGroup() *schema.Resource { return nil }, customdiff.ComputedIf("member_clusters", func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool { - return diff.HasChange("number_cache_clusters") || diff.HasChange("cluster_mode.0.num_node_groups") + return diff.HasChange("number_cache_clusters") || + diff.HasChange("cluster_mode.0.num_node_groups") || + diff.HasChange("cluster_mode.0.replicas_per_node_group") }), ), } @@ -570,7 +567,7 @@ func resourceAwsElasticacheReplicationGroupRead(d *schema.ResourceData, meta int func resourceAwsElasticacheReplicationGroupUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).elasticacheconn - if d.HasChange("cluster_mode.0.num_node_groups") { + if d.HasChanges("cluster_mode.0.num_node_groups", "cluster_mode.0.replicas_per_node_group") { err := elasticacheReplicationGroupModifyShardConfiguration(conn, d) if err != nil { return fmt.Errorf("error modifying ElastiCache Replication Group (%s) shard configuration: %w", d.Id(), err) @@ -761,6 +758,24 @@ func flattenElasticacheNodeGroupsToClusterMode(nodeGroups []*elasticache.NodeGro } func elasticacheReplicationGroupModifyShardConfiguration(conn *elasticache.ElastiCache, d *schema.ResourceData) error { + if d.HasChange("cluster_mode.0.num_node_groups") { + err := elasticacheReplicationGroupModifyShardConfigurationNumNodeGroups(conn, d) + if err != nil { + return err + } + } + + if d.HasChange("cluster_mode.0.replicas_per_node_group") { + err := elasticacheReplicationGroupModifyShardConfigurationReplicasPerNodeGroup(conn, d) + if err != nil { + return err + } + } + + return nil +} + +func elasticacheReplicationGroupModifyShardConfigurationNumNodeGroups(conn *elasticache.ElastiCache, d *schema.ResourceData) error { o, n := d.GetChange("cluster_mode.0.num_node_groups") oldNumNodeGroups := o.(int) newNumNodeGroups := n.(int) @@ -796,6 +811,44 @@ func elasticacheReplicationGroupModifyShardConfiguration(conn *elasticache.Elast return nil } +func elasticacheReplicationGroupModifyShardConfigurationReplicasPerNodeGroup(conn *elasticache.ElastiCache, d *schema.ResourceData) error { + o, n := d.GetChange("cluster_mode.0.replicas_per_node_group") + oldReplicas := o.(int) + newReplicas := n.(int) + + if newReplicas > oldReplicas { + input := &elasticache.IncreaseReplicaCountInput{ + ApplyImmediately: aws.Bool(true), + NewReplicaCount: aws.Int64(int64(newReplicas)), + ReplicationGroupId: aws.String(d.Id()), + } + _, err := conn.IncreaseReplicaCount(input) + if err != nil { + return fmt.Errorf("error adding ElastiCache Replication Group (%s) replicas: %w", d.Id(), err) + } + _, err = waiter.ReplicationGroupAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf("error waiting for ElastiCache Replication Group (%s) replica addition: %w", d.Id(), err) + } + } else { + input := &elasticache.DecreaseReplicaCountInput{ + ApplyImmediately: aws.Bool(true), + NewReplicaCount: aws.Int64(int64(newReplicas)), + ReplicationGroupId: aws.String(d.Id()), + } + _, err := conn.DecreaseReplicaCount(input) + if err != nil { + return fmt.Errorf("error removing ElastiCache Replication Group (%s) replicas: %w", d.Id(), err) + } + _, err = waiter.ReplicationGroupAvailable(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return fmt.Errorf("error waiting for ElastiCache Replication Group (%s) replica removal: %w", d.Id(), err) + } + } + + return nil +} + func elasticacheReplicationGroupModifyNumCacheClusters(conn *elasticache.ElastiCache, d *schema.ResourceData) error { o, n := d.GetChange("number_cache_clusters") oldNumberCacheClusters := o.(int) diff --git a/aws/resource_aws_elasticache_replication_group_test.go b/aws/resource_aws_elasticache_replication_group_test.go index cb68301dd788..5eabccd4490a 100644 --- a/aws/resource_aws_elasticache_replication_group_test.go +++ b/aws/resource_aws_elasticache_replication_group_test.go @@ -618,7 +618,7 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_UpdateNumNodeGroups_Scale }) } -func TestAccAWSElasticacheReplicationGroup_ClusterMode_ReplicasPerNodeGroup(t *testing.T) { +func TestAccAWSElasticacheReplicationGroup_ClusterMode_UpdateReplicasPerNodeGroup(t *testing.T) { var rg elasticache.ReplicationGroup rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_elasticache_replication_group.test" @@ -628,6 +628,32 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_ReplicasPerNodeGroup(t *t Providers: testAccProviders, CheckDestroy: testAccCheckAWSElasticacheReplicationDestroy, Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 2, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg), + resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x.cluster.on"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "2"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "1"), + resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "4"), + resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "4"), + ), + }, + { + Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 2, 3), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg), + resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x.cluster.on"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "2"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "3"), + resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "8"), + resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "8"), + ), + }, { Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 2, 2), Check: resource.ComposeTestCheckFunc( @@ -641,11 +667,72 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_ReplicasPerNodeGroup(t *t resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "6"), ), }, + }, + }) +} + +func TestAccAWSElasticacheReplicationGroup_ClusterMode_UpdateNumNodeGroupsAndReplicasPerNodeGroup_ScaleUp(t *testing.T) { + var rg elasticache.ReplicationGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_elasticache_replication_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheReplicationDestroy, + Steps: []resource.TestStep{ { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"apply_immediately"}, + Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 2, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg), + resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x.cluster.on"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "2"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "1"), + resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "4"), + resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "4"), + ), + }, + { + Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 3, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg), + resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x.cluster.on"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "3"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "2"), + resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "9"), + resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "9"), + ), + }, + }, + }) +} + +func TestAccAWSElasticacheReplicationGroup_ClusterMode_UpdateNumNodeGroupsAndReplicasPerNodeGroup_ScaleDown(t *testing.T) { + var rg elasticache.ReplicationGroup + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_elasticache_replication_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheReplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 3, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg), + resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x.cluster.on"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "3"), + resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "2"), + resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "9"), + resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "9"), + ), }, { Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig(rName, 2, 1), diff --git a/website/docs/r/elasticache_replication_group.html.markdown b/website/docs/r/elasticache_replication_group.html.markdown index 5d3ea8a222e6..f95268c0b327 100644 --- a/website/docs/r/elasticache_replication_group.html.markdown +++ b/website/docs/r/elasticache_replication_group.html.markdown @@ -149,7 +149,7 @@ Please note that setting a `snapshot_retention_limit` is not supported on cache. Cluster Mode (`cluster_mode`) supports the following: -* `replicas_per_node_group` - (Required) Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will force a new resource. +* `replicas_per_node_group` - (Required) Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will trigger an online resizing operation before other settings modifications. * `num_node_groups` - (Required) Specify the number of node groups (shards) for this Redis replication group. Changing this number will trigger an online resizing operation before other settings modifications. ## Attributes Reference