Skip to content

Commit b6af26f

Browse files
committed
feat(rdb-pn): handle update private network
1 parent 10f0caf commit b6af26f

17 files changed

+4227
-5911
lines changed

scaleway/data_source_rdb_acl_test.go

+21-10
Original file line numberDiff line numberDiff line change
@@ -18,60 +18,71 @@ func TestAccScalewayDataSourceRDBAcl_Basic(t *testing.T) {
1818
Steps: []resource.TestStep{
1919
{
2020
Config: fmt.Sprintf(`
21-
resource "scaleway_rdb_instance" "rdbAcl" {
21+
resource "scaleway_rdb_instance" "main" {
2222
name = "%s"
2323
node_type = "db-dev-s"
2424
engine = "PostgreSQL-12"
2525
is_ha_cluster = false
2626
}
2727
2828
resource "scaleway_rdb_acl" "main" {
29-
instance_id = scaleway_rdb_instance.rdbAcl.id
29+
instance_id = scaleway_rdb_instance.main.id
3030
acl_rules {
3131
ip = "1.2.3.4/32"
3232
description = "foo"
3333
}
34+
35+
acl_rules {
36+
ip = "4.5.6.7/32"
37+
description = "bar"
38+
}
3439
}
3540
`, instanceName),
3641
Check: resource.ComposeTestCheckFunc(
37-
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.0.ip", "1.2.3.4/32"),
42+
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.1.ip", "1.2.3.4/32"),
43+
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.0.ip", "4.5.6.7/32"),
3844
),
3945
},
4046
{
4147
Config: fmt.Sprintf(`
42-
resource "scaleway_rdb_instance" "rdbAcl" {
48+
resource "scaleway_rdb_instance" "main" {
4349
name = "%s"
4450
node_type = "db-dev-s"
4551
engine = "PostgreSQL-12"
4652
is_ha_cluster = false
4753
}
4854
4955
resource "scaleway_rdb_acl" "main" {
50-
instance_id = scaleway_rdb_instance.rdbAcl.id
56+
instance_id = scaleway_rdb_instance.main.id
5157
acl_rules {
5258
ip = "1.2.3.4/32"
5359
description = "foo"
5460
}
61+
62+
acl_rules {
63+
ip = "4.5.6.7/32"
64+
description = "bar"
65+
}
5566
}
5667
data "scaleway_rdb_acl" "maindata" {
57-
instance_id = scaleway_rdb_instance.rdbAcl.id
68+
instance_id = scaleway_rdb_instance.main.id
5869
5970
}`, instanceName),
6071
Check: resource.ComposeTestCheckFunc(
61-
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.0.ip", "1.2.3.4/32"),
62-
resource.TestCheckResourceAttr("data.scaleway_rdb_acl.maindata", "acl_rules.0.ip", "1.2.3.4/32"),
72+
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.1.ip", "1.2.3.4/32"),
73+
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.0.ip", "4.5.6.7/32"),
6374
),
6475
},
6576
{
6677
Config: fmt.Sprintf(`
67-
resource "scaleway_rdb_instance" "rdbAcl" {
78+
resource "scaleway_rdb_instance" "main" {
6879
name = "%s"
6980
node_type = "db-dev-s"
7081
engine = "PostgreSQL-12"
7182
is_ha_cluster = false
7283
}`, instanceName),
7384
Check: resource.ComposeTestCheckFunc(
74-
resource.TestCheckResourceAttr("scaleway_rdb_instance.rdbAcl", "name", "data-source-rdb-acl-basic"),
85+
resource.TestCheckResourceAttr("scaleway_rdb_instance.main", "name", "data-source-rdb-acl-basic"),
7586
),
7687
},
7788
},

scaleway/helpers_rdb.go

+53
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scaleway
22

33
import (
4+
"reflect"
45
"time"
56

67
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -101,6 +102,58 @@ func expandLoadBalancer() []*rdb.EndpointSpec {
101102
return res
102103
}
103104

105+
func getRdbEndpointsToRemove(endPoints []*rdb.Endpoint, updates interface{}) (map[string]bool, error) {
106+
actions := make(map[string]bool)
107+
configs := make(map[string]*rdb.EndpointPrivateNetworkDetails)
108+
privateIDToEndpointID := make(map[string]string)
109+
for _, e := range endPoints {
110+
// skip load balancer
111+
if e.PrivateNetwork != nil {
112+
pnZonedID := newZonedIDString(e.PrivateNetwork.Zone, e.PrivateNetwork.PrivateNetworkID)
113+
actions[e.ID] = true
114+
configs[pnZonedID] = e.PrivateNetwork
115+
privateIDToEndpointID[pnZonedID] = e.ID
116+
}
117+
}
118+
119+
// compare private networks are persisted
120+
for _, endpoint := range updates.([]interface{}) {
121+
r := endpoint.(map[string]interface{})
122+
locality, id, err := parseLocalizedID(r["pn_id"].(string))
123+
if err != nil {
124+
return nil, err
125+
}
126+
pn := &rdb.EndpointPrivateNetworkDetails{
127+
PrivateNetworkID: id,
128+
ServiceIP: expandIPNet(r["ip"].(string)),
129+
Zone: scw.Zone(locality),
130+
}
131+
132+
pnZonedID := newZonedIDString(pn.Zone, pn.PrivateNetworkID)
133+
if _, configExist := configs[pnZonedID]; configExist {
134+
isEqual := isEndPointEqual(configs[pnZonedID], pn)
135+
// match the endpoint id for a private network
136+
if _, endPointExist := privateIDToEndpointID[pnZonedID]; endPointExist {
137+
actions[privateIDToEndpointID[pnZonedID]] = !isEqual
138+
}
139+
}
140+
}
141+
142+
return actions, nil
143+
}
144+
145+
func isEndPointEqual(A, B interface{}) bool {
146+
// Find out the diff Private Network or not
147+
if _, ok := A.(*rdb.EndpointPrivateNetworkDetails); ok {
148+
if _, ok := B.(*rdb.EndpointPrivateNetworkDetails); ok {
149+
detailsA := A.(*rdb.EndpointPrivateNetworkDetails)
150+
detailsB := B.(*rdb.EndpointPrivateNetworkDetails)
151+
return reflect.DeepEqual(detailsA, detailsB)
152+
}
153+
}
154+
return false
155+
}
156+
104157
func flattenPrivateNetwork(readEndpoints []*rdb.Endpoint) (interface{}, bool) {
105158
pnI := []map[string]interface{}(nil)
106159
for _, readPN := range readEndpoints {

scaleway/helpers_rdb_test.go

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package scaleway
2+
3+
import (
4+
"net"
5+
"testing"
6+
7+
"github.com/scaleway/scaleway-sdk-go/api/rdb/v1"
8+
"github.com/scaleway/scaleway-sdk-go/scw"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestIsEndPointEqual(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
A *rdb.EndpointPrivateNetworkDetails
16+
B *rdb.EndpointPrivateNetworkDetails
17+
expected bool
18+
}{
19+
{
20+
name: "isEqualPrivateNetworkDetails",
21+
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
22+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
23+
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
24+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
25+
expected: true,
26+
},
27+
{
28+
name: "notEqualIP",
29+
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
30+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
31+
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
32+
IP: net.IPv4(0x1, 0x1, 0x1, 0x2), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
33+
expected: false,
34+
},
35+
{
36+
name: "notEqualZone",
37+
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
38+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
39+
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
40+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0x0, 0x0}}}, Zone: scw.ZoneFrPar2},
41+
expected: false,
42+
},
43+
{
44+
name: "notEqualMask",
45+
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
46+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}}, Zone: scw.ZoneFrPar1},
47+
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
48+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
49+
expected: false,
50+
},
51+
}
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
assert.Equal(t, tt.expected, isEndPointEqual(tt.A, tt.B))
55+
})
56+
}
57+
}
58+
59+
func TestGetRdbEndpointsToRemove(t *testing.T) {
60+
tests := []struct {
61+
name string
62+
Endpoints []*rdb.Endpoint
63+
Updates []interface{}
64+
Expected map[string]bool
65+
}{
66+
{
67+
name: "removeAll",
68+
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
69+
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
70+
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
71+
ServiceIP: scw.IPNet{IPNet: net.IPNet{
72+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}},
73+
Zone: scw.ZoneFrPar1},
74+
}},
75+
Expected: map[string]bool{
76+
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": true,
77+
},
78+
},
79+
{
80+
name: "shouldUpdatePrivateNetwork",
81+
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
82+
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
83+
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
84+
ServiceIP: scw.IPNet{IPNet: net.IPNet{
85+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.IPMask{0xff, 0xff, 0xff, 0xff}}},
86+
Zone: scw.ZoneFrPar1},
87+
}},
88+
Updates: []interface{}{map[string]interface{}{"pn_id": "fr-par-1/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "ip": "192.168.1.43/24"}},
89+
Expected: map[string]bool{
90+
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": true,
91+
},
92+
},
93+
{
94+
name: "shouldNotUpdatePrivateNetwork",
95+
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
96+
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
97+
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
98+
ServiceIP: scw.IPNet{IPNet: net.IPNet{
99+
IP: net.IPv4(0x1, 0x1, 0x1, 0x1), Mask: net.CIDRMask(24, 32)}},
100+
Zone: scw.ZoneFrPar1},
101+
}},
102+
Updates: []interface{}{map[string]interface{}{"pn_id": "fr-par-1/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "ip": "1.1.1.1/24"}},
103+
Expected: map[string]bool{
104+
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": false,
105+
},
106+
},
107+
}
108+
for _, tt := range tests {
109+
t.Run(tt.name, func(t *testing.T) {
110+
result, err := getRdbEndpointsToRemove(tt.Endpoints, tt.Updates)
111+
assert.NoError(t, err)
112+
assert.Equal(t, tt.Expected, result)
113+
})
114+
}
115+
}

scaleway/resource_rdb_instance.go

+67-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func resourceScalewayRdbInstance() *schema.Resource {
184184
"load_balancer": {
185185
Type: schema.TypeBool,
186186
Computed: true,
187-
Description: "Load balancers to forward the traffic to the right node based on the node state.",
187+
Description: "Load balancer of the database instance",
188188
},
189189
// Common
190190
"region": regionSchema(),
@@ -500,6 +500,72 @@ func resourceScalewayRdbInstanceUpdate(ctx context.Context, d *schema.ResourceDa
500500
}
501501
}
502502

503+
if d.HasChanges("private_network") {
504+
retryInterval := defaultWaitRDBRetryInterval
505+
// retrieve state
506+
res, err := rdbAPI.WaitForInstance(&rdb.WaitForInstanceRequest{
507+
Region: region,
508+
InstanceID: ID,
509+
Timeout: scw.TimeDurationPtr(defaultInstanceServerWaitTimeout * 3), // upgrade takes some time
510+
RetryInterval: &retryInterval,
511+
}, scw.WithContext(ctx))
512+
if err != nil {
513+
return diag.FromErr(err)
514+
}
515+
// get endpoints to detach. It will handle only private networks
516+
endPointsToRemove, err := getRdbEndpointsToRemove(res.Endpoints, d.Get("private_network"))
517+
if err != nil {
518+
diag.FromErr(err)
519+
}
520+
for endPointID, remove := range endPointsToRemove {
521+
if remove {
522+
err := rdbAPI.DeleteEndpoint(
523+
&rdb.DeleteEndpointRequest{
524+
EndpointID: endPointID, Region: region},
525+
scw.WithContext(ctx))
526+
if err != nil {
527+
diag.FromErr(err)
528+
}
529+
}
530+
}
531+
532+
// retrieve state
533+
res, err = rdbAPI.WaitForInstance(&rdb.WaitForInstanceRequest{
534+
Region: region,
535+
InstanceID: ID,
536+
Timeout: scw.TimeDurationPtr(defaultInstanceServerWaitTimeout * 3), // upgrade takes some time
537+
RetryInterval: &retryInterval,
538+
}, scw.WithContext(ctx))
539+
if err != nil {
540+
return diag.FromErr(err)
541+
}
542+
543+
// set new endpoints
544+
pn, pnExist := d.GetOk("private_network")
545+
if pnExist {
546+
privateEndpoints := expandPrivateNetwork(pn, pnExist)
547+
for _, e := range privateEndpoints {
548+
_, err := rdbAPI.CreateEndpoint(
549+
&rdb.CreateEndpointRequest{Region: region, InstanceID: ID, EndpointSpec: e},
550+
scw.WithContext(ctx))
551+
if err != nil {
552+
diag.FromErr(err)
553+
}
554+
}
555+
}
556+
557+
// retrieve state
558+
_, err = rdbAPI.WaitForInstance(&rdb.WaitForInstanceRequest{
559+
Region: region,
560+
InstanceID: ID,
561+
Timeout: scw.TimeDurationPtr(defaultInstanceServerWaitTimeout * 3), // upgrade takes some time
562+
RetryInterval: &retryInterval,
563+
}, scw.WithContext(ctx))
564+
if err != nil {
565+
return diag.FromErr(err)
566+
}
567+
}
568+
503569
return resourceScalewayRdbInstanceRead(ctx, d, meta)
504570
}
505571

0 commit comments

Comments
 (0)