Skip to content

Commit

Permalink
Fixes #10284
Browse files Browse the repository at this point in the history
* AccTest for replicaset scaling

* Raw implementation of scalable Postgres replicaset

* Only change SKU for replica

* Extend checks for acctests

* Simplification by removing downscaling replicas from primary

* Little refactor to increase readability

* Fix createReplica test and reuse testcode

* Comment fixes

* Linting

* Update website/docs/r/postgresql_server.html.markdown

Co-authored-by: WS <[email protected]>

Co-authored-by: WS <[email protected]>
  • Loading branch information
aristosvo and WodansSon authored Mar 6, 2021
1 parent cf361b4 commit f3732c9
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 29 deletions.
5 changes: 5 additions & 0 deletions azurerm/internal/services/postgres/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Client struct {
ServerSecurityAlertPoliciesClient *postgresql.ServerSecurityAlertPoliciesClient
VirtualNetworkRulesClient *postgresql.VirtualNetworkRulesClient
ServerAdministratorsClient *postgresql.ServerAdministratorsClient
ReplicasClient *postgresql.ReplicasClient
}

func NewClient(o *common.ClientOptions) *Client {
Expand Down Expand Up @@ -41,6 +42,9 @@ func NewClient(o *common.ClientOptions) *Client {
serverAdministratorsClient := postgresql.NewServerAdministratorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&serverAdministratorsClient.Client, o.ResourceManagerAuthorizer)

replicasClient := postgresql.NewReplicasClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&replicasClient.Client, o.ResourceManagerAuthorizer)

return &Client{
ConfigurationsClient: &configurationsClient,
DatabasesClient: &databasesClient,
Expand All @@ -50,5 +54,6 @@ func NewClient(o *common.ClientOptions) *Client {
ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient,
VirtualNetworkRulesClient: &virtualNetworkRulesClient,
ServerAdministratorsClient: &serverAdministratorsClient,
ReplicasClient: &replicasClient,
}
}
129 changes: 105 additions & 24 deletions azurerm/internal/services/postgres/postgresql_server_resource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package postgres

import (
"context"
"fmt"
"log"
"strconv"
Expand All @@ -11,11 +12,13 @@ import (
"github.com/Azure/go-autorest/autorest/date"
"github.com/hashicorp/go-azure-helpers/response"
"github.com/hashicorp/terraform-plugin-sdk/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
Expand All @@ -28,6 +31,29 @@ const (
postgreSQLServerResourceName = "azurerm_postgresql_server"
)

var skuList = []string{
"B_Gen4_1",
"B_Gen4_2",
"B_Gen5_1",
"B_Gen5_2",
"GP_Gen4_2",
"GP_Gen4_4",
"GP_Gen4_8",
"GP_Gen4_16",
"GP_Gen4_32",
"GP_Gen5_2",
"GP_Gen5_4",
"GP_Gen5_8",
"GP_Gen5_16",
"GP_Gen5_32",
"GP_Gen5_64",
"MO_Gen5_2",
"MO_Gen5_4",
"MO_Gen5_8",
"MO_Gen5_16",
"MO_Gen5_32",
}

func resourcePostgreSQLServer() *schema.Resource {
return &schema.Resource{
Create: resourcePostgreSQLServerCreate,
Expand Down Expand Up @@ -70,30 +96,9 @@ func resourcePostgreSQLServer() *schema.Resource {
"resource_group_name": azure.SchemaResourceGroupName(),

"sku_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"B_Gen4_1",
"B_Gen4_2",
"B_Gen5_1",
"B_Gen5_2",
"GP_Gen4_2",
"GP_Gen4_4",
"GP_Gen4_8",
"GP_Gen4_16",
"GP_Gen4_32",
"GP_Gen5_2",
"GP_Gen5_4",
"GP_Gen5_8",
"GP_Gen5_16",
"GP_Gen5_32",
"GP_Gen5_64",
"MO_Gen5_2",
"MO_Gen5_4",
"MO_Gen5_8",
"MO_Gen5_16",
"MO_Gen5_32",
}, false),
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(skuList, false),
},

"version": {
Expand Down Expand Up @@ -538,6 +543,19 @@ func resourcePostgreSQLServerCreate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("waiting for creation of PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err)
}

log.Printf("[DEBUG] Waiting for PostgreSQL Server %q (Resource Group %q) to become available", name, resourceGroup)
stateConf := &resource.StateChangeConf{
Pending: []string{string(postgresql.ServerStateInaccessible)},
Target: []string{string(postgresql.ServerStateReady)},
Refresh: postgreSqlStateRefreshFunc(ctx, client, resourceGroup, name),
MinTimeout: 15 * time.Second,
Timeout: d.Timeout(schema.TimeoutCreate),
}

if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for PostgreSQL Server %q (Resource Group %q)to become available: %+v", name, resourceGroup, err)
}

read, err := client.Get(ctx, resourceGroup, name)
if err != nil {
return fmt.Errorf("retrieving PostgreSQL Server %q (Resource Group %q): %+v", name, resourceGroup, err)
Expand Down Expand Up @@ -569,6 +587,7 @@ func resourcePostgreSQLServerCreate(d *schema.ResourceData, meta interface{}) er
func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Postgres.ServersClient
securityClient := meta.(*clients.Client).Postgres.ServerSecurityAlertPoliciesClient
replicasClient := meta.(*clients.Client).Postgres.ReplicasClient
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand All @@ -581,11 +600,47 @@ func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("parsing Postgres Server ID : %v", err)
}

// Locks for upscaling of replicas
mode := postgresql.CreateMode(d.Get("create_mode").(string))
primaryID := id.String()
if mode == postgresql.CreateModeReplica {
primaryID = d.Get("creation_source_server_id").(string)
}
locks.ByID(primaryID)
defer locks.UnlockByID(primaryID)

sku, err := expandServerSkuName(d.Get("sku_name").(string))
if err != nil {
return fmt.Errorf("expanding `sku_name` for PostgreSQL Server %s (Resource Group %q): %v", id.Name, id.ResourceGroup, err)
}

if d.HasChange("sku_name") && mode != postgresql.CreateModeReplica {
oldRaw, newRaw := d.GetChange("sku_name")
old := oldRaw.(string)
new := newRaw.(string)

if indexOfSku(old) < indexOfSku(new) {
listReplicas, err := replicasClient.ListByServer(ctx, id.ResourceGroup, id.Name)
if err != nil {
return fmt.Errorf("listing replicas for PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}

propertiesReplica := postgresql.ServerUpdateParameters{
Sku: sku,
}
for _, replica := range *listReplicas.Value {
future, err := client.Update(ctx, id.ResourceGroup, *replica.Name, propertiesReplica)
if err != nil {
return fmt.Errorf("upscaling PostgreSQL Server Replica %q (Resource Group %q): %+v", *replica.Name, id.ResourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting for update of PostgreSQL Server Replica %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
}
}
}
}

publicAccess := postgresql.PublicNetworkAccessEnumEnabled
if v := d.Get("public_network_access_enabled"); !v.(bool) {
publicAccess = postgresql.PublicNetworkAccessEnumDisabled
Expand Down Expand Up @@ -750,6 +805,15 @@ func resourcePostgreSQLServerDelete(d *schema.ResourceData, meta interface{}) er
return nil
}

func indexOfSku(skuName string) int {
for k, v := range skuList {
if skuName == v {
return k
}
}
return -1 // not found.
}

func expandServerSkuName(skuName string) (*postgresql.Sku, error) {
parts := strings.Split(skuName, "_")
if len(parts) != 3 {
Expand Down Expand Up @@ -970,3 +1034,20 @@ func flattenSecurityAlertPolicySet(input *[]string) []interface{} {

return utils.FlattenStringSlice(input)
}

func postgreSqlStateRefreshFunc(ctx context.Context, client *postgresql.ServersClient, resourceGroup string, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, resourceGroup, name)
if !utils.ResponseWasNotFound(res.Response) && err != nil {
return nil, "", fmt.Errorf("retrieving status of PostgreSQL Server %s (Resource Group %q): %+v", name, resourceGroup, err)
}

// This is an issue with the RP, there is a 10 to 15 second lag before the
// service will actually return the server
if utils.ResponseWasNotFound(res.Response) {
return res, string(postgresql.ServerStateInaccessible), nil
}

return res, string(res.ServerProperties.UserVisibleState), nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func TestAccPostgreSQLServer_createReplica(t *testing.T) {
},
data.ImportStep("administrator_login_password"),
{
Config: r.createReplica(data, "11"),
Config: r.createReplica(data, "GP_Gen5_2"),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
Expand All @@ -321,6 +321,49 @@ func TestAccPostgreSQLServer_createReplica(t *testing.T) {
})
}

func TestAccPostgreSQLServer_scaleReplicas(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_postgresql_server", "test")
r := PostgreSQLServerResource{}
data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.createReplicas(data, "GP_Gen5_2"),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("sku_name").HasValue("GP_Gen5_2"),
check.That("azurerm_postgresql_server.replica1").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica1").Key("sku_name").HasValue("GP_Gen5_2"),
check.That("azurerm_postgresql_server.replica2").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica2").Key("sku_name").HasValue("GP_Gen5_2"),
),
},
data.ImportStep("administrator_login_password"),
{
Config: r.createReplicas(data, "GP_Gen5_4"),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("sku_name").HasValue("GP_Gen5_4"),
check.That("azurerm_postgresql_server.replica1").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica1").Key("sku_name").HasValue("GP_Gen5_4"),
check.That("azurerm_postgresql_server.replica2").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica2").Key("sku_name").HasValue("GP_Gen5_4"),
),
},
data.ImportStep("administrator_login_password"),
{
Config: r.createReplicas(data, "GP_Gen5_2"),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("sku_name").HasValue("GP_Gen5_2"),
check.That("azurerm_postgresql_server.replica1").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica1").Key("sku_name").HasValue("GP_Gen5_2"),
check.That("azurerm_postgresql_server.replica2").ExistsInAzure(r),
check.That("azurerm_postgresql_server.replica2").Key("sku_name").HasValue("GP_Gen5_2"),
),
},
data.ImportStep("administrator_login_password"),
})
}

func TestAccPostgreSQLServer_createPointInTimeRestore(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_postgresql_server", "test")
r := PostgreSQLServerResource{}
Expand Down Expand Up @@ -709,7 +752,7 @@ resource "azurerm_postgresql_server" "test" {
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, sku, version)
}

func (r PostgreSQLServerResource) createReplica(data acceptance.TestData, version string) string {
func (r PostgreSQLServerResource) createReplica(data acceptance.TestData, sku string) string {
return fmt.Sprintf(`
%[1]s
Expand All @@ -718,15 +761,49 @@ resource "azurerm_postgresql_server" "replica" {
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "GP_Gen5_2"
version = "%[3]s"
sku_name = "%[3]s"
version = "11"
create_mode = "Replica"
creation_source_server_id = azurerm_postgresql_server.test.id
ssl_enforcement_enabled = true
}
`, r.template(data, sku, "11"), data.RandomInteger, sku)
}

func (r PostgreSQLServerResource) createReplicas(data acceptance.TestData, sku string) string {
return fmt.Sprintf(`
%[1]s
resource "azurerm_postgresql_server" "replica1" {
name = "acctest-psql-server-%[2]d-replica1"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "%[3]s"
version = "11"
create_mode = "Replica"
creation_source_server_id = azurerm_postgresql_server.test.id
ssl_enforcement_enabled = true
}
resource "azurerm_postgresql_server" "replica2" {
name = "acctest-psql-server-%[2]d-replica2"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "%[3]s"
version = "11"
create_mode = "Replica"
creation_source_server_id = azurerm_postgresql_server.test.id
ssl_enforcement_enabled = true
}
`, r.basic(data, version), data.RandomInteger, version)
`, r.template(data, sku, "11"), data.RandomInteger, sku)
}

func (r PostgreSQLServerResource) createPointInTimeRestore(data acceptance.TestData, version, restoreTime string) string {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/postgresql_server.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ The following arguments are supported:

* `sku_name` - (Required) Specifies the SKU Name for this PostgreSQL Server. The name of the SKU, follows the `tier` + `family` + `cores` pattern (e.g. `B_Gen4_1`, `GP_Gen5_8`). For more information see the [product documentation](https://docs.microsoft.com/en-us/rest/api/postgresql/servers/create#sku).

~> **NOTE:** When replication is set up and `sku_name` is changed to a higher tier or more capacity for the primary, all replicas are scaled up to the same tier/capacity. This is an Azure requirement, for more information see the [replica scaling documentation](https://docs.microsoft.com/en-us/azure/postgresql/concepts-read-replicas#scaling)

* `version` - (Required) Specifies the version of PostgreSQL to use. Valid values are `9.5`, `9.6`, `10`, `10.0`, and `11`. Changing this forces a new resource to be created.

* `administrator_login` - (Optional) The Administrator Login for the PostgreSQL Server. Required when `create_mode` is `Default`. Changing this forces a new resource to be created.
Expand Down

0 comments on commit f3732c9

Please sign in to comment.