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

feat(rdb): add rdb private network support #915

Merged
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
73 changes: 73 additions & 0 deletions docs/resources/rdb_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,60 @@ resource "scaleway_rdb_instance" "main" {
backup_schedule_frequency = 24 # every day
backup_schedule_retention = 7 # keep it one week
}

# with private network and dhcp configuration
resource scaleway_vpc_private_network pn02 {
name = "my_private_network"
}

resource scaleway_vpc_public_gateway_dhcp main {
subnet = "192.168.1.0/24"
}

resource scaleway_vpc_public_gateway_ip main {
}

resource scaleway_vpc_public_gateway main {
name = "foobar"
type = "VPC-GW-S"
ip_id = scaleway_vpc_public_gateway_ip.main.id
}

resource scaleway_vpc_public_gateway_pat_rule main {
gateway_id = scaleway_vpc_public_gateway.main.id
private_ip = scaleway_vpc_public_gateway_dhcp.main.address
private_port = scaleway_rdb_instance.main.private_network.0.port
public_port = 42
protocol = "both"
depends_on = [scaleway_vpc_gateway_network.main, scaleway_vpc_private_network.pn02]
}

resource scaleway_vpc_gateway_network main {
gateway_id = scaleway_vpc_public_gateway.main.id
private_network_id = scaleway_vpc_private_network.pn02.id
dhcp_id = scaleway_vpc_public_gateway_dhcp.main.id
cleanup_dhcp = true
enable_masquerade = true
depends_on = [scaleway_vpc_public_gateway_ip.main, scaleway_vpc_private_network.pn02]
}

resource scaleway_rdb_instance main {
name = "test-rdb"
node_type = "db-dev-s"
engine = "PostgreSQL-11"
is_ha_cluster = false
disable_backup = true
user_name = "my_initial_user"
password = "thiZ_is_v&ry_s3cret"
region= "fr-par"
tags = [ "terraform-test", "scaleway_rdb_instance", "volume", "rdb_pn" ]
volume_type = "bssd"
volume_size_in_gb = 10
private_network {
ip_net = "192.168.1.254/24" #pool high
pn_id = "${scaleway_vpc_private_network.pn02.id}"
}
}
```

## Arguments Reference
Expand Down Expand Up @@ -81,6 +135,13 @@ The following arguments are supported:

- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the Database Instance is associated with.

## Private Network

~> **Important:** Updates to `private_network` will recreate the attachment Instance.

- `ip_net` - (Required) The IP network where to con.
- `pn_id` - (Required) The ID of the private network. If not provided it will be randomly generated.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand All @@ -92,6 +153,18 @@ In addition to all arguments above, the following attributes are exported:
- `ip` - IP of the replica.
- `port` - Port of the replica.
- `name` - Name of the replica.
- `load_balancer` - List of load balancer endpoints of the database instance.
- `endpoint_id` - The ID of the endpoint of the load balancer.
- `ip` - IP of the endpoint.
- `port` - Port of the endpoint.
- `name` - Name of the endpoint.
- `hostname` - Name of the endpoint.
- `private_network` - List of private networks endpoints of the database instance.
- `endpoint_id` - The ID of the endpoint of the private network.
- `ip` - IP of the endpoint.
- `port` - Port of the endpoint.
- `name` - Name of the endpoint.
- `hostname` - Name of the endpoint.
- `certificate` - Certificate of the database instance.
- `organization_id` - The organization ID the Database Instance is associated with.

Expand Down
133 changes: 133 additions & 0 deletions scaleway/helpers_rdb.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package scaleway

import (
"context"
"reflect"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -71,3 +73,134 @@ func expandInstanceSettings(i interface{}) []*rdb.InstanceSetting {

return res
}

func waitInstance(ctx context.Context, api *rdb.API, region scw.Region, id string) (*rdb.Instance, error) {
retryInterval := defaultWaitRDBRetryInterval
return api.WaitForInstance(&rdb.WaitForInstanceRequest{
Region: region,
InstanceID: id,
Timeout: scw.TimeDurationPtr(defaultInstanceServerWaitTimeout * 3), // upgrade takes some time
RetryInterval: &retryInterval,
}, scw.WithContext(ctx))
}

func expandPrivateNetwork(data interface{}, exist bool) []*rdb.EndpointSpec {
if data == nil || !exist {
return nil
}

var res []*rdb.EndpointSpec
for _, pn := range data.([]interface{}) {
r := pn.(map[string]interface{})
spec := &rdb.EndpointSpec{
PrivateNetwork: &rdb.EndpointSpecPrivateNetwork{
PrivateNetworkID: expandID(r["pn_id"].(string)),
ServiceIP: expandIPNet(r["ip_net"].(string)),
},
}
res = append(res, spec)
}

return res
}

func expandLoadBalancer() []*rdb.EndpointSpec {
var res []*rdb.EndpointSpec

res = append(res, &rdb.EndpointSpec{
LoadBalancer: &rdb.EndpointSpecLoadBalancer{}})

return res
}

func endpointsToRemove(endPoints []*rdb.Endpoint, updates interface{}) (map[string]bool, error) {
actions := make(map[string]bool)
endpoints := make(map[string]*rdb.Endpoint)
for _, e := range endPoints {
// skip load balancer
if e.PrivateNetwork != nil {
actions[e.ID] = true
endpoints[newZonedIDString(e.PrivateNetwork.Zone, e.PrivateNetwork.PrivateNetworkID)] = e
}
}

// compare if private networks are persisted
for _, raw := range updates.([]interface{}) {
r := raw.(map[string]interface{})
pnZonedID := r["pn_id"].(string)
locality, id, err := parseLocalizedID(pnZonedID)
if err != nil {
return nil, err
}

pnUpdated := newEndPointPrivateNetworkDetails(id, r["ip_net"].(string), locality)
endpoint, exist := endpoints[pnZonedID]
if !exist {
continue
}
// match the endpoint id for a private network
actions[endpoint.ID] = !isEndPointEqual(endpoints[pnZonedID].PrivateNetwork, pnUpdated)
}

return actions, nil
}

func newEndPointPrivateNetworkDetails(id, ip, locality string) *rdb.EndpointPrivateNetworkDetails {
return &rdb.EndpointPrivateNetworkDetails{
PrivateNetworkID: id,
ServiceIP: expandIPNet(ip),
Zone: scw.Zone(locality),
}
}

func isEndPointEqual(A, B interface{}) bool {
// Find out the diff Private Network or not
if _, ok := A.(*rdb.EndpointPrivateNetworkDetails); ok {
if _, ok := B.(*rdb.EndpointPrivateNetworkDetails); ok {
detailsA := A.(*rdb.EndpointPrivateNetworkDetails)
detailsB := B.(*rdb.EndpointPrivateNetworkDetails)
return reflect.DeepEqual(detailsA, detailsB)
}
}
return false
}

func flattenPrivateNetwork(endpoints []*rdb.Endpoint) (interface{}, bool) {
pnI := []map[string]interface{}(nil)
for _, endpoint := range endpoints {
if endpoint.PrivateNetwork != nil {
pn := endpoint.PrivateNetwork
pnZonedID := newZonedIDString(pn.Zone, pn.PrivateNetworkID)
pnI = append(pnI, map[string]interface{}{
"endpoint_id": endpoint.ID,
"ip": flattenIPPtr(endpoint.IP),
"port": int(endpoint.Port),
"name": endpoint.Name,
"ip_net": flattenIPNet(pn.ServiceIP),
"pn_id": pnZonedID,
"hostname": flattenStringPtr(endpoint.Hostname),
})
return pnI, true
}
}

return pnI, false
}

func flattenLoadBalancer(endpoints []*rdb.Endpoint) interface{} {
flat := []map[string]interface{}(nil)
for _, endpoint := range endpoints {
if endpoint.LoadBalancer != nil {
flat = append(flat, map[string]interface{}{
"endpoint_id": endpoint.ID,
"ip": flattenIPPtr(endpoint.IP),
"port": int(endpoint.Port),
"name": endpoint.Name,
"hostname": flattenStringPtr(endpoint.Hostname),
})
return flat
}
}

return flat
}
120 changes: 120 additions & 0 deletions scaleway/helpers_rdb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package scaleway

import (
"net"
"testing"

"github.com/scaleway/scaleway-sdk-go/api/rdb/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/assert"
)

func TestIsEndPointEqual(t *testing.T) {
tests := []struct {
name string
A *rdb.EndpointPrivateNetworkDetails
B *rdb.EndpointPrivateNetworkDetails
expected bool
}{
{
name: "isEqualPrivateNetworkDetails",
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
expected: true,
},
{
name: "notEqualIP",
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 2), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
expected: false,
},
{
name: "notEqualZone",
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar2},
expected: false,
},
{
name: "notEqualMask",
A: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(25, 32)}}, Zone: scw.ZoneFrPar1},
B: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}}, Zone: scw.ZoneFrPar1},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, isEndPointEqual(tt.A, tt.B))
})
}
}

func TestEndpointsToRemove(t *testing.T) {
tests := []struct {
name string
Endpoints []*rdb.Endpoint
Updates []interface{}
Expected map[string]bool
}{
{
name: "removeAll",
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}},
Zone: scw.ZoneFrPar1},
}},
Expected: map[string]bool{
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": true,
},
},
{
name: "shouldUpdatePrivateNetwork",
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}},
Zone: scw.ZoneFrPar1},
}},
Updates: []interface{}{map[string]interface{}{"pn_id": "fr-par-1/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "ip_net": "192.168.1.43/24"}},
Expected: map[string]bool{
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": true,
},
},
{
name: "shouldNotUpdatePrivateNetwork",
Endpoints: []*rdb.Endpoint{&rdb.Endpoint{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c1",
PrivateNetwork: &rdb.EndpointPrivateNetworkDetails{PrivateNetworkID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
ServiceIP: scw.IPNet{IPNet: net.IPNet{
IP: net.IPv4(1, 1, 1, 1), Mask: net.CIDRMask(24, 32)}},
Zone: scw.ZoneFrPar1},
}},
Updates: []interface{}{map[string]interface{}{"pn_id": "fr-par-1/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "ip_net": "1.1.1.1/24"}},
Expected: map[string]bool{
"6ba7b810-9dad-11d1-80b4-00c04fd430c1": false,
},
},
{
name: "shouldAddPrivateNetwork",
Updates: []interface{}{map[string]interface{}{"pn_id": "fr-par-1/6ba7b810-9dad-11d1-80b4-00c04fd430c8", "ip_net": "1.1.1.1/24"}},
Expected: map[string]bool{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := endpointsToRemove(tt.Endpoints, tt.Updates)
assert.NoError(t, err)
assert.Equal(t, tt.Expected, result)
})
}
}
14 changes: 13 additions & 1 deletion scaleway/resource_rdb_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func init() {
func TestAccScalewayRdbACL_Basic(t *testing.T) {
tt := NewTestTools(t)
defer tt.Cleanup()
instanceName := "TestAccScalewayRdbACL_Basic"
instanceName := "rdb-acl-basic"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: tt.ProviderFactories,
Expand Down Expand Up @@ -79,6 +79,18 @@ func TestAccScalewayRdbACL_Basic(t *testing.T) {
resource.TestCheckResourceAttr("scaleway_rdb_acl.main", "acl_rules.1.description", "foo"),
),
},
{
Config: fmt.Sprintf(`
resource scaleway_rdb_instance main {
name = "%s"
node_type = "db-dev-s"
engine = "PostgreSQL-12"
is_ha_cluster = false
}`, instanceName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("scaleway_rdb_instance.main", "name", "rdb-acl-basic"),
),
},
},
})
}
Loading