Skip to content

Commit 12f973d

Browse files
modular-magicianHimani Khanduja
andauthored
Add support to enable read replicas on existing redis instances (#5959) (#4259)
* 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 <[email protected]> Signed-off-by: Modular Magician <[email protected]> Co-authored-by: Himani Khanduja <[email protected]>
1 parent 04363cd commit 12f973d

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

.changelog/5959.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
redis: added multi read replica field `readReplicasMode` and `secondaryIpRange` in `google_redis_instance`
3+
```

google-beta/resource_redis_instance.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ func isRedisVersionDecreasingFunc(old, new interface{}) bool {
5959
return newVersion < oldVersion
6060
}
6161

62+
// returns true if old=new or old='auto'
63+
func secondaryIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
64+
if (strings.ToLower(new) == "auto" && old != "") || old == new {
65+
return true
66+
}
67+
return false
68+
}
69+
6270
func resourceRedisInstance() *schema.Resource {
6371
return &schema.Resource{
6472
Create: resourceRedisInstanceCreate,
@@ -287,7 +295,6 @@ resolution and up to nine fractional digits.`,
287295
Type: schema.TypeString,
288296
Computed: true,
289297
Optional: true,
290-
ForceNew: true,
291298
ValidateFunc: validateEnum([]string{"READ_REPLICAS_DISABLED", "READ_REPLICAS_ENABLED", ""}),
292299
Description: `Optional. Read replica mode. Can only be specified when trying to create the instance.
293300
If not set, Memorystore Redis backend will default to READ_REPLICAS_DISABLED.
@@ -338,6 +345,16 @@ instance. If not provided, the service will choose an unused /29
338345
block, for example, 10.0.0.0/29 or 192.168.0.0/29. Ranges must be
339346
unique and non-overlapping with existing subnets in an authorized
340347
network.`,
348+
},
349+
"secondary_ip_range": {
350+
Type: schema.TypeString,
351+
Computed: true,
352+
Optional: true,
353+
DiffSuppressFunc: secondaryIpDiffSuppress,
354+
Description: `Optional. Additional IP range for node placement. Required when enabling read replicas on
355+
an existing instance. For DIRECT_PEERING mode value must be a CIDR range of size /28, or
356+
"auto". For PRIVATE_SERVICE_ACCESS mode value must be the name of an allocated address
357+
range associated with the private service access connection, or "auto".`,
341358
},
342359
"tier": {
343360
Type: schema.TypeString,
@@ -593,6 +610,12 @@ func resourceRedisInstanceCreate(d *schema.ResourceData, meta interface{}) error
593610
} else if v, ok := d.GetOkExists("read_replicas_mode"); !isEmptyValue(reflect.ValueOf(readReplicasModeProp)) && (ok || !reflect.DeepEqual(v, readReplicasModeProp)) {
594611
obj["readReplicasMode"] = readReplicasModeProp
595612
}
613+
secondaryIpRangeProp, err := expandRedisInstanceSecondaryIpRange(d.Get("secondary_ip_range"), d, config)
614+
if err != nil {
615+
return err
616+
} else if v, ok := d.GetOkExists("secondary_ip_range"); !isEmptyValue(reflect.ValueOf(secondaryIpRangeProp)) && (ok || !reflect.DeepEqual(v, secondaryIpRangeProp)) {
617+
obj["secondaryIpRange"] = secondaryIpRangeProp
618+
}
596619

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

805831
return nil
806832
}
@@ -869,6 +895,18 @@ func resourceRedisInstanceUpdate(d *schema.ResourceData, meta interface{}) error
869895
} else if v, ok := d.GetOkExists("replica_count"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, replicaCountProp)) {
870896
obj["replicaCount"] = replicaCountProp
871897
}
898+
readReplicasModeProp, err := expandRedisInstanceReadReplicasMode(d.Get("read_replicas_mode"), d, config)
899+
if err != nil {
900+
return err
901+
} else if v, ok := d.GetOkExists("read_replicas_mode"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, readReplicasModeProp)) {
902+
obj["readReplicasMode"] = readReplicasModeProp
903+
}
904+
secondaryIpRangeProp, err := expandRedisInstanceSecondaryIpRange(d.Get("secondary_ip_range"), d, config)
905+
if err != nil {
906+
return err
907+
} else if v, ok := d.GetOkExists("secondary_ip_range"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, secondaryIpRangeProp)) {
908+
obj["secondaryIpRange"] = secondaryIpRangeProp
909+
}
872910

873911
obj, err = resourceRedisInstanceEncoder(d, meta, obj)
874912
if err != nil {
@@ -914,6 +952,14 @@ func resourceRedisInstanceUpdate(d *schema.ResourceData, meta interface{}) error
914952
if d.HasChange("replica_count") {
915953
updateMask = append(updateMask, "replicaCount")
916954
}
955+
956+
if d.HasChange("read_replicas_mode") {
957+
updateMask = append(updateMask, "readReplicasMode")
958+
}
959+
960+
if d.HasChange("secondary_ip_range") {
961+
updateMask = append(updateMask, "secondaryIpRange")
962+
}
917963
// updateMask is a URL parameter but not present in the schema, so replaceVars
918964
// won't set it
919965
url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
@@ -1440,6 +1486,10 @@ func flattenRedisInstanceReadReplicasMode(v interface{}, d *schema.ResourceData,
14401486
return v
14411487
}
14421488

1489+
func flattenRedisInstanceSecondaryIpRange(v interface{}, d *schema.ResourceData, config *Config) interface{} {
1490+
return v
1491+
}
1492+
14431493
func expandRedisInstanceAlternativeLocationId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
14441494
return v, nil
14451495
}
@@ -1724,6 +1774,10 @@ func expandRedisInstanceReadReplicasMode(v interface{}, d TerraformResourceData,
17241774
return v, nil
17251775
}
17261776

1777+
func expandRedisInstanceSecondaryIpRange(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
1778+
return v, nil
1779+
}
1780+
17271781
func resourceRedisInstanceEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
17281782
config := meta.(*Config)
17291783
region, err := getRegionFromSchema("region", "location_id", d, config)

google-beta/resource_redis_instance_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,151 @@ func TestAccRedisInstance_update(t *testing.T) {
4040
})
4141
}
4242

43+
// Validate that read replica is enabled on the instance without having to recreate
44+
func TestAccRedisInstance_updateReadReplicasMode(t *testing.T) {
45+
t.Parallel()
46+
47+
name := fmt.Sprintf("tf-test-%d", randInt(t))
48+
49+
vcrTest(t, resource.TestCase{
50+
PreCheck: func() { testAccPreCheck(t) },
51+
Providers: testAccProviders,
52+
CheckDestroy: testAccCheckRedisInstanceDestroyProducer(t),
53+
Steps: []resource.TestStep{
54+
{
55+
Config: testAccRedisInstanceReadReplicasUnspecified(name, true),
56+
},
57+
{
58+
ResourceName: "google_redis_instance.test",
59+
ImportState: true,
60+
ImportStateVerify: true,
61+
},
62+
{
63+
Config: testAccRedisInstanceReadReplicasEnabled(name, true),
64+
},
65+
{
66+
ResourceName: "google_redis_instance.test",
67+
ImportState: true,
68+
ImportStateVerify: true,
69+
},
70+
{
71+
Config: testAccRedisInstanceReadReplicasUnspecified(name, false),
72+
},
73+
},
74+
})
75+
}
76+
77+
/* Validate that read replica is enabled on the instance without recreate
78+
* and secondaryIp is auto provisioned when passed as 'auto' */
79+
func TestAccRedisInstance_updateReadReplicasModeWithAutoSecondaryIp(t *testing.T) {
80+
t.Parallel()
81+
82+
name := fmt.Sprintf("tf-test-%d", randInt(t))
83+
84+
vcrTest(t, resource.TestCase{
85+
PreCheck: func() { testAccPreCheck(t) },
86+
Providers: testAccProviders,
87+
CheckDestroy: testAccCheckRedisInstanceDestroyProducer(t),
88+
Steps: []resource.TestStep{
89+
{
90+
Config: testAccRedisInstanceReadReplicasUnspecified(name, true),
91+
},
92+
{
93+
ResourceName: "google_redis_instance.test",
94+
ImportState: true,
95+
ImportStateVerify: true,
96+
},
97+
{
98+
Config: testAccRedisInstanceReadReplicasEnabledWithAutoSecondaryIP(name, true),
99+
},
100+
{
101+
ResourceName: "google_redis_instance.test",
102+
ImportState: true,
103+
ImportStateVerify: true,
104+
},
105+
{
106+
Config: testAccRedisInstanceReadReplicasUnspecified(name, false),
107+
},
108+
},
109+
})
110+
}
111+
112+
func testAccRedisInstanceReadReplicasUnspecified(name string, preventDestroy bool) string {
113+
lifecycleBlock := ""
114+
if preventDestroy {
115+
lifecycleBlock = `
116+
lifecycle {
117+
prevent_destroy = true
118+
}`
119+
}
120+
return fmt.Sprintf(`
121+
resource "google_redis_instance" "test" {
122+
name = "%s"
123+
display_name = "redissss"
124+
memory_size_gb = 5
125+
tier = "STANDARD_HA"
126+
region = "us-central1"
127+
%s
128+
redis_configs = {
129+
maxmemory-policy = "allkeys-lru"
130+
notify-keyspace-events = "KEA"
131+
}
132+
}
133+
`, name, lifecycleBlock)
134+
}
135+
136+
func testAccRedisInstanceReadReplicasEnabled(name string, preventDestroy bool) string {
137+
lifecycleBlock := ""
138+
if preventDestroy {
139+
lifecycleBlock = `
140+
lifecycle {
141+
prevent_destroy = true
142+
}`
143+
}
144+
return fmt.Sprintf(`
145+
resource "google_redis_instance" "test" {
146+
name = "%s"
147+
display_name = "redissss"
148+
memory_size_gb = 5
149+
tier = "STANDARD_HA"
150+
region = "us-central1"
151+
%s
152+
redis_configs = {
153+
maxmemory-policy = "allkeys-lru"
154+
notify-keyspace-events = "KEA"
155+
}
156+
read_replicas_mode = "READ_REPLICAS_ENABLED"
157+
secondary_ip_range = "10.79.0.0/28"
158+
}
159+
`, name, lifecycleBlock)
160+
}
161+
162+
func testAccRedisInstanceReadReplicasEnabledWithAutoSecondaryIP(name string, preventDestroy bool) string {
163+
lifecycleBlock := ""
164+
if preventDestroy {
165+
lifecycleBlock = `
166+
lifecycle {
167+
prevent_destroy = true
168+
}`
169+
}
170+
return fmt.Sprintf(`
171+
resource "google_redis_instance" "test" {
172+
name = "%s"
173+
display_name = "redissss"
174+
memory_size_gb = 5
175+
tier = "STANDARD_HA"
176+
region = "us-central1"
177+
%s
178+
redis_configs = {
179+
maxmemory-policy = "allkeys-lru"
180+
notify-keyspace-events = "KEA"
181+
}
182+
read_replicas_mode = "READ_REPLICAS_ENABLED"
183+
secondary_ip_range = "auto"
184+
}
185+
`, name, lifecycleBlock)
186+
}
187+
43188
func TestAccRedisInstance_regionFromLocation(t *testing.T) {
44189
t.Parallel()
45190

@@ -106,6 +251,45 @@ func TestAccRedisInstance_redisInstanceAuthEnabled(t *testing.T) {
106251
})
107252
}
108253

254+
func TestSecondaryIpDiffSuppress(t *testing.T) {
255+
cases := map[string]struct {
256+
Old, New string
257+
ExpectDiffSuppress bool
258+
}{
259+
"empty strings": {
260+
Old: "",
261+
New: "",
262+
ExpectDiffSuppress: true,
263+
},
264+
"auto range": {
265+
Old: "",
266+
New: "auto",
267+
ExpectDiffSuppress: false,
268+
},
269+
"auto on already applied range": {
270+
Old: "10.0.0.0/28",
271+
New: "auto",
272+
ExpectDiffSuppress: true,
273+
},
274+
"same ranges": {
275+
Old: "10.0.0.0/28",
276+
New: "10.0.0.0/28",
277+
ExpectDiffSuppress: true,
278+
},
279+
"different ranges": {
280+
Old: "10.0.0.0/28",
281+
New: "10.1.2.3/28",
282+
ExpectDiffSuppress: false,
283+
},
284+
}
285+
286+
for tn, tc := range cases {
287+
if secondaryIpDiffSuppress("whatever", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress {
288+
t.Fatalf("bad: %s, '%s' => '%s' expect %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress)
289+
}
290+
}
291+
}
292+
109293
func TestAccRedisInstance_downgradeRedisVersion(t *testing.T) {
110294
t.Parallel()
111295

website/docs/r/redis_instance.html.markdown

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ The following arguments are supported:
315315
can scale up and down the number of replicas.
316316
Possible values are `READ_REPLICAS_DISABLED` and `READ_REPLICAS_ENABLED`.
317317

318+
* `secondary_ip_range` -
319+
(Optional)
320+
Optional. Additional IP range for node placement. Required when enabling read replicas on
321+
an existing instance. For DIRECT_PEERING mode value must be a CIDR range of size /28, or
322+
"auto". For PRIVATE_SERVICE_ACCESS mode value must be the name of an allocated address
323+
range associated with the private service access connection, or "auto".
324+
318325
* `region` -
319326
(Optional)
320327
The name of the Redis region of the instance.

0 commit comments

Comments
 (0)