Skip to content

Commit

Permalink
ICD: Promote read replicas (#5738)
Browse files Browse the repository at this point in the history
* Promote read replicas

* Add tests

* Fixes

* Docs

* Fixes

* Add my sql and edb tests

* Update tests

* Fixes

* Update mysql, edb tests

* Fix to tests

---------

Co-authored-by: Lorna-Kelly <[email protected]>
  • Loading branch information
lornakelly and Lorna-Kelly authored Nov 12, 2024
1 parent c3cab41 commit 83ec24d
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 7 deletions.
66 changes: 60 additions & 6 deletions ibm/service/database/resource_ibm_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ func ResourceIBMDatabaseInstance() *schema.Resource {
CustomizeDiff: customdiff.All(
resourceIBMDatabaseInstanceDiff,
validateGroupsDiff,
validateUsersDiff),
validateUsersDiff,
validateRemoteLeaderIDDiff),

Importer: &schema.ResourceImporter{},

Expand Down Expand Up @@ -258,10 +259,14 @@ func ResourceIBMDatabaseInstance() *schema.Resource {
Optional: true,
},
"remote_leader_id": {
Description: "The CRN of leader database",
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: flex.ApplyOnce,
Description: "The CRN of leader database",
Type: schema.TypeString,
Optional: true,
},
"skip_initial_backup": {
Description: "Option to skip the initial backup when promoting a read-only replica. Skipping the initial backup means that your replica becomes available more quickly, but there is no immediate backup available.",
Type: schema.TypeBool,
Optional: true,
},
"key_protect_instance": {
Description: "The CRN of Key protect instance",
Expand Down Expand Up @@ -840,6 +845,7 @@ func ResourceIBMDatabaseInstance() *schema.Resource {
},
}
}

func ResourceIBMICDValidator() *validate.ResourceValidator {

validateSchema := make([]validate.ValidateSchema, 0)
Expand Down Expand Up @@ -1597,7 +1603,6 @@ func resourceIBMDatabaseInstanceRead(context context.Context, d *schema.Resource
if endpoint, ok := instance.Parameters["service-endpoints"]; ok {
d.Set("service_endpoints", endpoint)
}

}

d.Set(flex.ResourceName, *instance.Name)
Expand Down Expand Up @@ -2136,6 +2141,37 @@ func resourceIBMDatabaseInstanceUpdate(context context.Context, d *schema.Resour
}
}

if d.HasChange("remote_leader_id") {
remoteLeaderId := d.Get("remote_leader_id").(string)

if remoteLeaderId == "" {
skipInitialBackup := false
if skip, ok := d.GetOk("skip_initial_backup"); ok {
skipInitialBackup = skip.(bool)
}

promoteReadOnlyReplicaOptions := &clouddatabasesv5.PromoteReadOnlyReplicaOptions{
ID: &instanceID,
Promotion: map[string]interface{}{
"skip_initial_backup": skipInitialBackup,
},
}

promoteReadReplicaResponse, response, err := cloudDatabasesClient.PromoteReadOnlyReplica(promoteReadOnlyReplicaOptions)

if err != nil {
return diag.FromErr(fmt.Errorf("[ERROR] Error promoting read replica: %s\n%s", err, response))
}

taskID := *promoteReadReplicaResponse.Task.ID
_, err = waitForDatabaseTaskComplete(taskID, d, meta, d.Timeout(schema.TimeoutUpdate))

if err != nil {
return diag.FromErr(fmt.Errorf("[ERROR] Error promoting read replica: %s", err))
}
}
}

return resourceIBMDatabaseInstanceRead(context, d, meta)
}

Expand Down Expand Up @@ -3039,6 +3075,24 @@ func expandUserChanges(_oldUsers []interface{}, _newUsers []interface{}) (userCh
return userChanges
}

func validateRemoteLeaderIDDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) (err error) {
_, remoteLeaderIdOk := diff.GetOk("remote_leader_id")
service := diff.Get("service").(string)
crn := diff.Get("resource_crn").(string)

if remoteLeaderIdOk && (service != "databases-for-postgresql" && service != "databases-for-mysql" && service != "databases-for-enterprisedb") {
return fmt.Errorf("[ERROR] remote_leader_id is only supported for databases-for-postgresql, databases-for-enterprisedb and databases-for-mysql")
}

oldValue, newValue := diff.GetChange("remote_leader_id")

if crn != "" && oldValue == "" && newValue != "" {
return fmt.Errorf("[ERROR] You cannot convert an existing instance to a read replica")
}

return nil
}

func (c *userChange) isDelete() bool {
return c.Old != nil && c.New == nil
}
Expand Down
136 changes: 136 additions & 0 deletions ibm/service/database/resource_ibm_database_edb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,93 @@ func TestAccIBMEDBDatabaseInstanceBasic(t *testing.T) {
})
}

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

databaseResourceGroup := "default"

var sourceInstanceCRN string
var replicaInstanceCRN string

serviceName := fmt.Sprintf("tf-edb-%d", acctest.RandIntRange(10, 100))
readReplicaName := serviceName + "-replica"

sourceResource := "ibm_database." + serviceName
replicaReplicaResource := "ibm_database." + readReplicaName

resource.Test(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
Providers: acc.TestAccProviders,
CheckDestroy: testAccCheckIBMDatabaseInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckIBMDatabaseInstanceEDBMinimal(databaseResourceGroup, serviceName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(sourceResource, &sourceInstanceCRN),
resource.TestCheckResourceAttr(sourceResource, "name", serviceName),
resource.TestCheckResourceAttr(sourceResource, "service", "databases-for-enterprisedb"),
resource.TestCheckResourceAttr(sourceResource, "plan", "standard"),
resource.TestCheckResourceAttr(sourceResource, "location", acc.Region()),
),
},
{
Config: acc.ConfigCompose(
testAccCheckIBMDatabaseInstanceEDBMinimal(databaseResourceGroup, serviceName),
testAccCheckIBMDatabaseInstanceEDBMinimal_ReadReplica(databaseResourceGroup, serviceName)),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(replicaReplicaResource, &replicaInstanceCRN),
resource.TestCheckResourceAttr(replicaReplicaResource, "name", readReplicaName),
resource.TestCheckResourceAttr(replicaReplicaResource, "service", "databases-for-enterprisedb"),
resource.TestCheckResourceAttr(replicaReplicaResource, "plan", "standard"),
resource.TestCheckResourceAttr(replicaReplicaResource, "location", acc.Region()),
),
},
{
Config: acc.ConfigCompose(
testAccCheckIBMDatabaseInstanceEDBMinimal(databaseResourceGroup, serviceName),
testAccCheckIBMDatabaseInstanceEDBReadReplicaPromote(databaseResourceGroup, readReplicaName)),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(sourceResource, &sourceInstanceCRN),
testAccCheckIBMDatabaseInstanceExists(replicaReplicaResource, &replicaInstanceCRN),
resource.TestCheckResourceAttr(replicaReplicaResource, "name", readReplicaName),
resource.TestCheckResourceAttr(replicaReplicaResource, "service", "databases-for-enterprisedb"),
resource.TestCheckResourceAttr(replicaReplicaResource, "plan", "standard"),
resource.TestCheckResourceAttr(replicaReplicaResource, "location", acc.Region()),
resource.TestCheckResourceAttr(replicaReplicaResource, "remote_leader_id", ""),
resource.TestCheckResourceAttr(replicaReplicaResource, "skip_initial_backup", "true"),
),
},
},
})
}

func testAccCheckIBMDatabaseInstanceEDBMinimal(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "test_acc" {
is_default = true
# name = "%[1]s"
}
resource "ibm_database" "%[2]s" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s"
service = "databases-for-enterprisedb"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
group {
group_id = "member"
host_flavor {
id = "b3c.4x16.encrypted"
}
disk {
allocation_mb = 20480
}
}
}
`, databaseResourceGroup, name, acc.Region())
}

func testAccCheckIBMDatabaseInstanceEDBBasic(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "test_acc" {
Expand Down Expand Up @@ -207,3 +294,52 @@ func testAccCheckIBMDatabaseInstanceEDBReduced(databaseResourceGroup string, nam
}
`, databaseResourceGroup, name, acc.Region())
}

func testAccCheckIBMDatabaseInstanceEDBMinimal_ReadReplica(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
resource "ibm_database" "%[2]s-replica" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s-replica"
service = "databases-for-enterprisedb"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
remote_leader_id = ibm_database.%[2]s.id
group {
group_id = "member"
host_flavor {
id = "b3c.4x16.encrypted"
}
disk {
allocation_mb = 20480
}
}
}
`, databaseResourceGroup, name, acc.Region())
}

func testAccCheckIBMDatabaseInstanceEDBReadReplicaPromote(databaseResourceGroup string, readReplicaName string) string {
return fmt.Sprintf(`
resource "ibm_database" "%[2]s" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s"
service = "databases-for-enterprisedb"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
remote_leader_id = ""
skip_initial_backup = true
group {
group_id = "member"
host_flavor {
id = "b3c.4x16.encrypted"
}
disk {
allocation_mb = 20480
}
}
}
`, databaseResourceGroup, readReplicaName, acc.Region())
}
106 changes: 106 additions & 0 deletions ibm/service/database/resource_ibm_database_mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,112 @@ func TestAccIBMMysqlDatabaseInstanceBasic(t *testing.T) {
})
}

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

databaseResourceGroup := "default"

var sourceInstanceCRN string
var replicaInstanceCRN string

serviceName := fmt.Sprintf("tf-mysql-%d", acctest.RandIntRange(10, 100))
readReplicaName := serviceName + "-replica"

sourceResource := "ibm_database." + serviceName
replicaReplicaResource := "ibm_database." + readReplicaName

resource.Test(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
Providers: acc.TestAccProviders,
CheckDestroy: testAccCheckIBMDatabaseInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckIBMDatabaseInstanceMySQLMinimal(databaseResourceGroup, serviceName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(sourceResource, &sourceInstanceCRN),
resource.TestCheckResourceAttr(sourceResource, "name", serviceName),
resource.TestCheckResourceAttr(sourceResource, "service", "databases-for-mysql"),
resource.TestCheckResourceAttr(sourceResource, "plan", "standard"),
resource.TestCheckResourceAttr(sourceResource, "location", acc.Region()),
),
},
{
Config: acc.ConfigCompose(
testAccCheckIBMDatabaseInstanceMySQLMinimal(databaseResourceGroup, serviceName),
testAccCheckIBMDatabaseInstanceMySQLMinimal_ReadReplica(databaseResourceGroup, serviceName)),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(replicaReplicaResource, &replicaInstanceCRN),
resource.TestCheckResourceAttr(replicaReplicaResource, "name", readReplicaName),
resource.TestCheckResourceAttr(replicaReplicaResource, "service", "databases-for-mysql"),
resource.TestCheckResourceAttr(replicaReplicaResource, "plan", "standard"),
resource.TestCheckResourceAttr(replicaReplicaResource, "location", acc.Region()),
),
},
{
Config: acc.ConfigCompose(
testAccCheckIBMDatabaseInstanceMySQLMinimal(databaseResourceGroup, serviceName),
testAccCheckIBMDatabaseInstanceMySQLReadReplicaPromote(databaseResourceGroup, readReplicaName)),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIBMDatabaseInstanceExists(replicaReplicaResource, &replicaInstanceCRN),
resource.TestCheckResourceAttr(replicaReplicaResource, "name", readReplicaName),
resource.TestCheckResourceAttr(replicaReplicaResource, "service", "databases-for-mysql"),
resource.TestCheckResourceAttr(replicaReplicaResource, "plan", "standard"),
resource.TestCheckResourceAttr(replicaReplicaResource, "location", acc.Region()),
resource.TestCheckResourceAttr(replicaReplicaResource, "remote_leader_id", ""),
resource.TestCheckResourceAttr(replicaReplicaResource, "skip_initial_backup", "true"),
),
},
},
})
}

func testAccCheckIBMDatabaseInstanceMySQLMinimal(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "test_acc" {
is_default = true
# name = "%[1]s"
}
resource "ibm_database" "%[2]s" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s"
service = "databases-for-mysql"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
}
`, databaseResourceGroup, name, acc.Region())
}

func testAccCheckIBMDatabaseInstanceMySQLMinimal_ReadReplica(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
resource "ibm_database" "%[2]s-replica" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s-replica"
service = "databases-for-mysql"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
remote_leader_id = ibm_database.%[2]s.id
}
`, databaseResourceGroup, name, acc.Region())
}

func testAccCheckIBMDatabaseInstanceMySQLReadReplicaPromote(databaseResourceGroup string, readReplicaName string) string {
return fmt.Sprintf(`
resource "ibm_database" "%[2]s" {
resource_group_id = data.ibm_resource_group.test_acc.id
name = "%[2]s"
service = "databases-for-mysql"
plan = "standard"
location = "%[3]s"
service_endpoints = "public-and-private"
remote_leader_id = ""
skip_initial_backup = true
}
`, databaseResourceGroup, readReplicaName, acc.Region())
}

func testAccCheckIBMDatabaseInstanceMysqlBasic(databaseResourceGroup string, name string) string {
return fmt.Sprintf(`
data "ibm_resource_group" "test_acc" {
Expand Down
Loading

0 comments on commit 83ec24d

Please sign in to comment.