Skip to content

Commit

Permalink
Add support to enable read replicas on existing redis instances (#5959)…
Browse files Browse the repository at this point in the history
… (#11592)

* Add support to enable read replicas on existing redis instances

Add support to enable read replicas on existing redis instances

* Add support to enable read replicas on existing redis instances

Add support to enable read replicas on existing redis instances

* Add support to enable read replicas on existing redis instances

Add support to enable read replicas on existing redis instances

* removing redis_instance_test

Co-authored-by: Himani Khanduja <khimani@google.com>
Signed-off-by: Modular Magician <magic-modules@google.com>

Co-authored-by: Himani Khanduja <khimani@google.com>
  • Loading branch information
modular-magician and Himani Khanduja committed Apr 26, 2022
1 parent 8b47585 commit e0f4316
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/5959.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
redis: added multi read replica field `readReplicasMode` and `secondaryIpRange` in `google_redis_instance`
```
56 changes: 55 additions & 1 deletion google/resource_redis_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ func isRedisVersionDecreasingFunc(old, new interface{}) bool {
return newVersion < oldVersion
}

// returns true if old=new or old='auto'
func secondaryIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
if (strings.ToLower(new) == "auto" && old != "") || old == new {
return true
}
return false
}

func resourceRedisInstance() *schema.Resource {
return &schema.Resource{
Create: resourceRedisInstanceCreate,
Expand Down Expand Up @@ -287,7 +295,6 @@ resolution and up to nine fractional digits.`,
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
ValidateFunc: validateEnum([]string{"READ_REPLICAS_DISABLED", "READ_REPLICAS_ENABLED", ""}),
Description: `Optional. Read replica mode. Can only be specified when trying to create the instance.
If not set, Memorystore Redis backend will default to READ_REPLICAS_DISABLED.
Expand Down Expand Up @@ -338,6 +345,16 @@ instance. If not provided, the service will choose an unused /29
block, for example, 10.0.0.0/29 or 192.168.0.0/29. Ranges must be
unique and non-overlapping with existing subnets in an authorized
network.`,
},
"secondary_ip_range": {
Type: schema.TypeString,
Computed: true,
Optional: true,
DiffSuppressFunc: secondaryIpDiffSuppress,
Description: `Optional. Additional IP range for node placement. Required when enabling read replicas on
an existing instance. For DIRECT_PEERING mode value must be a CIDR range of size /28, or
"auto". For PRIVATE_SERVICE_ACCESS mode value must be the name of an allocated address
range associated with the private service access connection, or "auto".`,
},
"tier": {
Type: schema.TypeString,
Expand Down Expand Up @@ -593,6 +610,12 @@ func resourceRedisInstanceCreate(d *schema.ResourceData, meta interface{}) error
} else if v, ok := d.GetOkExists("read_replicas_mode"); !isEmptyValue(reflect.ValueOf(readReplicasModeProp)) && (ok || !reflect.DeepEqual(v, readReplicasModeProp)) {
obj["readReplicasMode"] = readReplicasModeProp
}
secondaryIpRangeProp, err := expandRedisInstanceSecondaryIpRange(d.Get("secondary_ip_range"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("secondary_ip_range"); !isEmptyValue(reflect.ValueOf(secondaryIpRangeProp)) && (ok || !reflect.DeepEqual(v, secondaryIpRangeProp)) {
obj["secondaryIpRange"] = secondaryIpRangeProp
}

obj, err = resourceRedisInstanceEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -801,6 +824,9 @@ func resourceRedisInstanceRead(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("read_replicas_mode", flattenRedisInstanceReadReplicasMode(res["readReplicasMode"], d, config)); err != nil {
return fmt.Errorf("Error reading Instance: %s", err)
}
if err := d.Set("secondary_ip_range", flattenRedisInstanceSecondaryIpRange(res["secondaryIpRange"], d, config)); err != nil {
return fmt.Errorf("Error reading Instance: %s", err)
}

return nil
}
Expand Down Expand Up @@ -869,6 +895,18 @@ func resourceRedisInstanceUpdate(d *schema.ResourceData, meta interface{}) error
} else if v, ok := d.GetOkExists("replica_count"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, replicaCountProp)) {
obj["replicaCount"] = replicaCountProp
}
readReplicasModeProp, err := expandRedisInstanceReadReplicasMode(d.Get("read_replicas_mode"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("read_replicas_mode"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, readReplicasModeProp)) {
obj["readReplicasMode"] = readReplicasModeProp
}
secondaryIpRangeProp, err := expandRedisInstanceSecondaryIpRange(d.Get("secondary_ip_range"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("secondary_ip_range"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, secondaryIpRangeProp)) {
obj["secondaryIpRange"] = secondaryIpRangeProp
}

obj, err = resourceRedisInstanceEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -914,6 +952,14 @@ func resourceRedisInstanceUpdate(d *schema.ResourceData, meta interface{}) error
if d.HasChange("replica_count") {
updateMask = append(updateMask, "replicaCount")
}

if d.HasChange("read_replicas_mode") {
updateMask = append(updateMask, "readReplicasMode")
}

if d.HasChange("secondary_ip_range") {
updateMask = append(updateMask, "secondaryIpRange")
}
// updateMask is a URL parameter but not present in the schema, so replaceVars
// won't set it
url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
Expand Down Expand Up @@ -1440,6 +1486,10 @@ func flattenRedisInstanceReadReplicasMode(v interface{}, d *schema.ResourceData,
return v
}

func flattenRedisInstanceSecondaryIpRange(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func expandRedisInstanceAlternativeLocationId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}
Expand Down Expand Up @@ -1724,6 +1774,10 @@ func expandRedisInstanceReadReplicasMode(v interface{}, d TerraformResourceData,
return v, nil
}

func expandRedisInstanceSecondaryIpRange(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func resourceRedisInstanceEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
config := meta.(*Config)
region, err := getRegionFromSchema("region", "location_id", d, config)
Expand Down
184 changes: 184 additions & 0 deletions google/resource_redis_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,151 @@ func TestAccRedisInstance_update(t *testing.T) {
})
}

// Validate that read replica is enabled on the instance without having to recreate
func TestAccRedisInstance_updateReadReplicasMode(t *testing.T) {
t.Parallel()

name := fmt.Sprintf("tf-test-%d", randInt(t))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRedisInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccRedisInstanceReadReplicasUnspecified(name, true),
},
{
ResourceName: "google_redis_instance.test",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccRedisInstanceReadReplicasEnabled(name, true),
},
{
ResourceName: "google_redis_instance.test",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccRedisInstanceReadReplicasUnspecified(name, false),
},
},
})
}

/* Validate that read replica is enabled on the instance without recreate
* and secondaryIp is auto provisioned when passed as 'auto' */
func TestAccRedisInstance_updateReadReplicasModeWithAutoSecondaryIp(t *testing.T) {
t.Parallel()

name := fmt.Sprintf("tf-test-%d", randInt(t))

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRedisInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccRedisInstanceReadReplicasUnspecified(name, true),
},
{
ResourceName: "google_redis_instance.test",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccRedisInstanceReadReplicasEnabledWithAutoSecondaryIP(name, true),
},
{
ResourceName: "google_redis_instance.test",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccRedisInstanceReadReplicasUnspecified(name, false),
},
},
})
}

func testAccRedisInstanceReadReplicasUnspecified(name string, preventDestroy bool) string {
lifecycleBlock := ""
if preventDestroy {
lifecycleBlock = `
lifecycle {
prevent_destroy = true
}`
}
return fmt.Sprintf(`
resource "google_redis_instance" "test" {
name = "%s"
display_name = "redissss"
memory_size_gb = 5
tier = "STANDARD_HA"
region = "us-central1"
%s
redis_configs = {
maxmemory-policy = "allkeys-lru"
notify-keyspace-events = "KEA"
}
}
`, name, lifecycleBlock)
}

func testAccRedisInstanceReadReplicasEnabled(name string, preventDestroy bool) string {
lifecycleBlock := ""
if preventDestroy {
lifecycleBlock = `
lifecycle {
prevent_destroy = true
}`
}
return fmt.Sprintf(`
resource "google_redis_instance" "test" {
name = "%s"
display_name = "redissss"
memory_size_gb = 5
tier = "STANDARD_HA"
region = "us-central1"
%s
redis_configs = {
maxmemory-policy = "allkeys-lru"
notify-keyspace-events = "KEA"
}
read_replicas_mode = "READ_REPLICAS_ENABLED"
secondary_ip_range = "10.79.0.0/28"
}
`, name, lifecycleBlock)
}

func testAccRedisInstanceReadReplicasEnabledWithAutoSecondaryIP(name string, preventDestroy bool) string {
lifecycleBlock := ""
if preventDestroy {
lifecycleBlock = `
lifecycle {
prevent_destroy = true
}`
}
return fmt.Sprintf(`
resource "google_redis_instance" "test" {
name = "%s"
display_name = "redissss"
memory_size_gb = 5
tier = "STANDARD_HA"
region = "us-central1"
%s
redis_configs = {
maxmemory-policy = "allkeys-lru"
notify-keyspace-events = "KEA"
}
read_replicas_mode = "READ_REPLICAS_ENABLED"
secondary_ip_range = "auto"
}
`, name, lifecycleBlock)
}

func TestAccRedisInstance_regionFromLocation(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -106,6 +251,45 @@ func TestAccRedisInstance_redisInstanceAuthEnabled(t *testing.T) {
})
}

func TestSecondaryIpDiffSuppress(t *testing.T) {
cases := map[string]struct {
Old, New string
ExpectDiffSuppress bool
}{
"empty strings": {
Old: "",
New: "",
ExpectDiffSuppress: true,
},
"auto range": {
Old: "",
New: "auto",
ExpectDiffSuppress: false,
},
"auto on already applied range": {
Old: "10.0.0.0/28",
New: "auto",
ExpectDiffSuppress: true,
},
"same ranges": {
Old: "10.0.0.0/28",
New: "10.0.0.0/28",
ExpectDiffSuppress: true,
},
"different ranges": {
Old: "10.0.0.0/28",
New: "10.1.2.3/28",
ExpectDiffSuppress: false,
},
}

for tn, tc := range cases {
if secondaryIpDiffSuppress("whatever", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress {
t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress)
}
}
}

func TestAccRedisInstance_downgradeRedisVersion(t *testing.T) {
t.Parallel()

Expand Down
7 changes: 7 additions & 0 deletions website/docs/r/redis_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,13 @@ The following arguments are supported:
can scale up and down the number of replicas.
Possible values are `READ_REPLICAS_DISABLED` and `READ_REPLICAS_ENABLED`.

* `secondary_ip_range` -
(Optional)
Optional. Additional IP range for node placement. Required when enabling read replicas on
an existing instance. For DIRECT_PEERING mode value must be a CIDR range of size /28, or
"auto". For PRIVATE_SERVICE_ACCESS mode value must be the name of an allocated address
range associated with the private service access connection, or "auto".

* `region` -
(Optional)
The name of the Redis region of the instance.
Expand Down

0 comments on commit e0f4316

Please sign in to comment.