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

ICD: Promote read replicas #5738

Merged
merged 10 commits into from
Nov 12, 2024
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
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
Loading