Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring number of node groups and replicas per node directly for "Redis (cluster mode disabled)" #16829

Merged
merged 3 commits into from
Jan 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 19 additions & 26 deletions aws/resource_aws_elasticache_replication_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,20 @@ func resourceAwsElasticacheReplicationGroup() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"cluster_enabled": {
Type: schema.TypeBool,
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.
Computed: true,
MaxItems: 1,
Computed: true,
MaxItems: 1,
ExactlyOneOf: []string{"cluster_mode", "number_cache_clusters"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"replicas_per_node_group": {
Expand Down Expand Up @@ -131,9 +136,10 @@ func resourceAwsElasticacheReplicationGroup() *schema.Resource {
ValidateFunc: validateArn,
},
"number_cache_clusters": {
Type: schema.TypeInt,
Computed: true,
Optional: true,
Type: schema.TypeInt,
Computed: true,
Optional: true,
ExactlyOneOf: []string{"cluster_mode", "number_cache_clusters"},
},
"parameter_group_name": {
Type: schema.TypeString,
Expand Down Expand Up @@ -340,14 +346,7 @@ func resourceAwsElasticacheReplicationGroupCreate(d *schema.ResourceData, meta i
params.AuthToken = aws.String(v.(string))
}

clusterMode, clusterModeOk := d.GetOk("cluster_mode")
cacheClusters, cacheClustersOk := d.GetOk("number_cache_clusters")

if !clusterModeOk && !cacheClustersOk || clusterModeOk && cacheClustersOk {
return fmt.Errorf("Either `number_cache_clusters` or `cluster_mode` must be set")
}

if clusterModeOk {
if clusterMode, ok := d.GetOk("cluster_mode"); ok {
clusterModeList := clusterMode.([]interface{})
attributes := clusterModeList[0].(map[string]interface{})

Expand All @@ -360,7 +359,7 @@ func resourceAwsElasticacheReplicationGroupCreate(d *schema.ResourceData, meta i
}
}

if cacheClustersOk {
if cacheClusters, ok := d.GetOk("number_cache_clusters"); ok {
params.NumCacheClusters = aws.Int64(int64(cacheClusters.(int)))
}

Expand Down Expand Up @@ -446,9 +445,10 @@ func resourceAwsElasticacheReplicationGroupRead(d *schema.ResourceData, meta int
if err := d.Set("member_clusters", flattenStringSet(rgp.MemberClusters)); err != nil {
return fmt.Errorf("error setting member_clusters: %w", err)
}
if err := d.Set("cluster_mode", flattenElasticacheNodeGroupsToClusterMode(aws.BoolValue(rgp.ClusterEnabled), rgp.NodeGroups)); err != nil {
if err := d.Set("cluster_mode", flattenElasticacheNodeGroupsToClusterMode(rgp.NodeGroups)); err != nil {
return fmt.Errorf("error setting cluster_mode attribute: %w", err)
}
d.Set("cluster_enabled", rgp.ClusterEnabled)
d.Set("replication_group_id", rgp.ReplicationGroupId)

if rgp.NodeGroups != nil {
Expand Down Expand Up @@ -938,22 +938,15 @@ func deleteElasticacheReplicationGroup(replicationGroupID string, conn *elastica
return err
}

func flattenElasticacheNodeGroupsToClusterMode(clusterEnabled bool, nodeGroups []*elasticache.NodeGroup) []map[string]interface{} {
if !clusterEnabled {
func flattenElasticacheNodeGroupsToClusterMode(nodeGroups []*elasticache.NodeGroup) []map[string]interface{} {
if len(nodeGroups) == 0 {
return []map[string]interface{}{}
}

m := map[string]interface{}{
"num_node_groups": 0,
"replicas_per_node_group": 0,
"num_node_groups": len(nodeGroups),
"replicas_per_node_group": (len(nodeGroups[0].NodeGroupMembers) - 1),
}

if len(nodeGroups) == 0 {
return []map[string]interface{}{m}
}

m["num_node_groups"] = len(nodeGroups)
m["replicas_per_node_group"] = (len(nodeGroups[0].NodeGroupMembers) - 1)
return []map[string]interface{}{m}
}

Expand Down
85 changes: 73 additions & 12 deletions aws/resource_aws_elasticache_replication_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func init() {
func testSweepElasticacheReplicationGroups(region string) error {
client, err := sharedClientForRegion(region)
if err != nil {
return fmt.Errorf("error getting client: %s", err)
return fmt.Errorf("error getting client: %w", err)
}
conn := client.(*AWSClient).elasticacheconn

Expand All @@ -52,7 +52,7 @@ func testSweepElasticacheReplicationGroups(region string) error {
log.Printf("[WARN] Skipping Elasticache Replication Group sweep for %s: %s", region, err)
return nil
}
return fmt.Errorf("Error retrieving Elasticache Replication Groups: %s", err)
return fmt.Errorf("Error retrieving Elasticache Replication Groups: %w", err)
}
return nil
}
Expand All @@ -71,14 +71,14 @@ func TestAccAWSElasticacheReplicationGroup_basic(t *testing.T) {
Config: testAccAWSElasticacheReplicationGroupConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(
resourceName, "cluster_mode.#", "0"),
resource.TestCheckResourceAttr(
resourceName, "number_cache_clusters", "2"),
resource.TestCheckResourceAttr(
resourceName, "member_clusters.#", "2"),
resource.TestCheckResourceAttr(
resourceName, "auto_minor_version_upgrade", "false"),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "2"),
resource.TestCheckResourceAttr(resourceName, "member_clusters.#", "2"),
resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"),
resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "false"),
),
},
{
Expand Down Expand Up @@ -390,6 +390,8 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_Basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "4"),
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"),
Expand All @@ -407,6 +409,40 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_Basic(t *testing.T) {
})
}

func TestAccAWSElasticacheReplicationGroup_ClusterMode_NonClusteredParameterGroup(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_NonClusteredParameterGroup(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(resourceName, "cluster_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis6.x"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.#", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.num_node_groups", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "1"),
resource.TestMatchResourceAttr(resourceName, "primary_endpoint_address", regexp.MustCompile(fmt.Sprintf("%s\\..+\\.%s", rName, testAccGetPartitionDNSSuffix()))),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "2"),
resource.TestCheckNoResourceAttr(resourceName, "configuration_endpoint_address"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"apply_immediately"},
},
},
})
}

func TestAccAWSElasticacheReplicationGroup_ClusterMode_NumNodeGroups(t *testing.T) {
var rg elasticache.ReplicationGroup
rName := acctest.RandomWithPrefix("tf-acc-test")
Expand All @@ -422,6 +458,8 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_NumNodeGroups(t *testing.
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "6"),
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", "1"),
Expand All @@ -438,6 +476,8 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_NumNodeGroups(t *testing.
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "2"),
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", "1"),
resource.TestCheckResourceAttr(resourceName, "cluster_mode.0.replicas_per_node_group", "1"),
Expand All @@ -448,6 +488,8 @@ func TestAccAWSElasticacheReplicationGroup_ClusterMode_NumNodeGroups(t *testing.
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSElasticacheReplicationGroupExists(resourceName, &rg),
resource.TestCheckResourceAttr(resourceName, "number_cache_clusters", "4"),
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"),
Expand All @@ -468,7 +510,7 @@ func TestAccAWSElasticacheReplicationGroup_clusteringAndCacheNodesCausesError(t
Steps: []resource.TestStep{
{
Config: testAccAWSElasticacheReplicationGroupNativeRedisClusterErrorConfig(rInt, rName),
ExpectError: regexp.MustCompile("Either `number_cache_clusters` or `cluster_mode` must be set"),
ExpectError: regexp.MustCompile("only one of `cluster_mode,number_cache_clusters` can be\\s+specified, but `cluster_mode,number_cache_clusters` were specified."),
},
},
})
Expand Down Expand Up @@ -963,7 +1005,7 @@ resource "aws_elasticache_parameter_group" "test" {

# We do not have a data source for "latest" Elasticache family
# so unfortunately we must hardcode this for now
family = "redis5.0"
family = "redis6.x"

name = "%[1]s-${count.index}"

Expand Down Expand Up @@ -1399,6 +1441,25 @@ resource "aws_elasticache_replication_group" "test" {
`, rName, numNodeGroups, replicasPerNodeGroup)
}

func testAccAWSElasticacheReplicationGroupNativeRedisClusterConfig_NonClusteredParameterGroup(rName string) string {
return composeConfig(
testAccAvailableAZsNoOptInConfig(),
fmt.Sprintf(`
resource "aws_elasticache_replication_group" "test" {
replication_group_id = %[1]q
replication_group_description = "test description"
node_type = "cache.t2.medium"
automatic_failover_enabled = false

parameter_group_name = "default.redis6.x"
cluster_mode {
num_node_groups = 1
replicas_per_node_group = 1
}
}
`, rName))
}

func testAccAWSElasticacheReplicationGroup_UseCmkKmsKeyId(rInt int, rString string) string {
return fmt.Sprintf(`
data "aws_availability_zones" "available" {
Expand Down
9 changes: 6 additions & 3 deletions website/docs/r/elasticache_replication_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ actual modification has not yet taken place. You can use the
immediately. Using `apply_immediately` can result in a brief downtime as
servers reboots.

~> **Note:** Be aware of the terminology collision around "cluster" for `aws_elasticache_replication_group`. For example, it is possible to create a ["Cluster Mode Disabled [Redis] Cluster"](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/Clusters.Create.CON.Redis.html). With "Cluster Mode Enabled", the data will be stored in shards (called "node groups"). See [Redis Cluster Configuration](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/cluster-create-determine-requirements.html#redis-cluster-configuration) for a diagram of the differences. To enable cluster mode, use a parameter group that has cluster mode enabled. The default parameter groups provided by AWS end with ".cluster.on", for example `default.redis6.x.cluster.on`.

## Example Usage

### Redis Cluster Mode Disabled
Expand Down Expand Up @@ -100,7 +102,7 @@ The following arguments are supported:

* `replication_group_id` – (Required) The replication group identifier. This parameter is stored as a lowercase string.
* `replication_group_description` – (Required) A user-created description for the replication group.
* `number_cache_clusters` - (Required for Cluster Mode Disabled) The number of cache clusters (primary and replicas) this replication group will have. If Multi-AZ is enabled, the value of this parameter must be at least 2. Updates will occur before other modifications.
* `number_cache_clusters` - (Optional) The number of cache clusters (primary and replicas) this replication group will have. If Multi-AZ is enabled, the value of this parameter must be at least 2. Updates will occur before other modifications. One of `number_cache_clusters` or `cluster_mode` is required.
* `node_type` - (Required) The compute and memory capacity of the nodes in the node group.
* `automatic_failover_enabled` - (Optional) Specifies whether a read-only replica will be automatically promoted to read/write primary if the existing primary fails. If true, Multi-AZ is enabled for this replication group. If false, Multi-AZ is disabled for this replication group. Must be enabled for Redis (cluster mode enabled) replication groups. Defaults to `false`.
* `auto_minor_version_upgrade` - (Optional) Specifies whether a minor engine upgrades will be applied automatically to the underlying Cache Cluster instances during the maintenance window. Defaults to `true`.
Expand All @@ -111,7 +113,7 @@ The following arguments are supported:
* `auth_token` - (Optional) The password used to access a password protected server. Can be specified only if `transit_encryption_enabled = true`.
* `kms_key_id` - (Optional) The ARN of the key that you wish to use if encrypting at rest. If not supplied, uses service managed encryption. Can be specified only if `at_rest_encryption_enabled = true`.
* `engine_version` - (Optional) The version number of the cache engine to be used for the cache clusters in this replication group.
* `parameter_group_name` - (Optional) The name of the parameter group to associate with this replication group. If this argument is omitted, the default cache parameter group for the specified engine is used.
* `parameter_group_name` - (Optional) The name of the parameter group to associate with this replication group. If this argument is omitted, the default cache parameter group for the specified engine is used. To enable "cluster mode", i.e. data sharding, use a parameter group that has the parameter `cluster-enabled` set to true.
* `port` – (Optional) The port number on which each of the cache nodes will accept connections. For Memcache the default is 11211, and for Redis the default port is 6379.
* `subnet_group_name` - (Optional) The name of the cache subnet group to be used for the replication group.
* `security_group_names` - (Optional) A list of cache security group names to associate with this replication group.
Expand All @@ -135,7 +137,7 @@ before being deleted. If the value of SnapshotRetentionLimit is set to zero (0),
Please note that setting a `snapshot_retention_limit` is not supported on cache.t1.micro cache nodes
* `apply_immediately` - (Optional) Specifies whether any modifications are applied immediately, or during the next maintenance window. Default is `false`.
* `tags` - (Optional) A map of tags to assign to the resource. Adding tags to this resource will add or overwrite any existing tags on the clusters in the replication group and not to the group itself.
* `cluster_mode` - (Optional) Create a native redis cluster. `automatic_failover_enabled` must be set to true. Cluster Mode documented below. Only 1 `cluster_mode` block is allowed.
* `cluster_mode` - (Optional) Create a native redis cluster. `automatic_failover_enabled` must be set to true. Cluster Mode documented below. Only 1 `cluster_mode` block is allowed. One of `number_cache_clusters` or `cluster_mode` is required. Note that configuring this block does not enable cluster mode, i.e. data sharding, this requires using a parameter group that has the parameter `cluster-enabled` set to true.

Cluster Mode (`cluster_mode`) supports the following:

Expand All @@ -147,6 +149,7 @@ Cluster Mode (`cluster_mode`) supports the following:
In addition to all arguments above, the following attributes are exported:

* `id` - The ID of the ElastiCache Replication Group.
* `cluster_enabled` - Indicates if cluster mode is enabled.
* `configuration_endpoint_address` - The address of the replication group configuration endpoint when cluster mode is enabled.
* `primary_endpoint_address` - (Redis only) The address of the endpoint for the primary node in the replication group, if the cluster mode is disabled.
* `member_clusters` - The identifiers of all the nodes that are part of this replication group.
Expand Down