Skip to content

Commit

Permalink
Support Swithcover for MySQL and PostgreSQL (#12646)
Browse files Browse the repository at this point in the history
[upstream:06d96101ab13b27dc9a9f4a46d8f854dbebecb40]

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician committed Jan 11, 2025
1 parent 2cc9635 commit cc3c1e8
Show file tree
Hide file tree
Showing 6 changed files with 1,197 additions and 22 deletions.
6 changes: 6 additions & 0 deletions .changelog/12646.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:enhancement
sql: added `replication_cluster` field to `google_sql_database_instance` resource
```
```release-note:enhancement
sql: added support of switchover for MySQL and PostgreSQL in `google_sql_database_instance` resource
```
17 changes: 17 additions & 0 deletions google/services/sql/data_source_sql_database_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func flattenDatasourceGoogleDatabaseInstancesList(fetchedInstances []*sqladmin.D
}

instance["replica_configuration"] = flattenReplicaConfigurationforDataSource(rawInstance.ReplicaConfiguration)
instance["replication_cluster"] = flattenReplicationClusterForDataSource(rawInstance.ReplicationCluster)

ipAddresses := flattenIpAddresses(rawInstance.IpAddresses)
instance["ip_address"] = ipAddresses
Expand Down Expand Up @@ -200,3 +201,19 @@ func flattenReplicaConfigurationforDataSource(replicaConfiguration *sqladmin.Rep

return rc
}

// flattenReplicationClusterForDataSource converts cloud SQL backend ReplicationCluster (proto) to
// terraform replication_cluster. We explicitly allow the case when ReplicationCluster
// is nil since replication_cluster is computed+optional.
func flattenReplicationClusterForDataSource(replicationCluster *sqladmin.ReplicationCluster) []map[string]interface{} {
data := make(map[string]interface{})
data["failover_dr_replica_name"] = ""
if replicationCluster != nil && replicationCluster.FailoverDrReplicaName != "" {
data["failover_dr_replica_name"] = replicationCluster.FailoverDrReplicaName
}
data["dr_replica"] = false
if replicationCluster != nil {
data["dr_replica"] = replicationCluster.DrReplica
}
return []map[string]interface{}{data}
}
65 changes: 53 additions & 12 deletions google/services/sql/resource_sql_database_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,27 @@ is set to true. Defaults to ZONAL.`,
},
Description: `The replicas of the instance.`,
},
"replication_cluster": {
Type: schema.TypeList,
Computed: true,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"failover_dr_replica_name": {
Type: schema.TypeString,
Optional: true,
Description: fmt.Sprintf(`If the instance is a primary instance, then this field identifies the disaster recovery (DR) replica. The standard format of this field is "your-project:your-instance". You can also set this field to "your-instance", but cloud SQL backend will convert it to the aforementioned standard format.`),
},
"dr_replica": {
Type: schema.TypeBool,
Computed: true,
Description: `Read-only field that indicates whether the replica is a DR replica.`,
},
},
},
Description: "A primary instance and disaster recovery replica pair. Applicable to MySQL and PostgreSQL. This field can be set only after both the primary and replica are created.",
},
"server_ca_cert": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -1721,6 +1742,11 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
if err := d.Set("replica_names", instance.ReplicaNames); err != nil {
return fmt.Errorf("Error setting replica_names: %w", err)
}

// We always set replication_cluster because it is computed+optional.
if err := d.Set("replication_cluster", flattenReplicationCluster(instance.ReplicationCluster, d)); err != nil {
return fmt.Errorf("Error setting replication_cluster: %w", err)
}
ipAddresses := flattenIpAddresses(instance.IpAddresses)
if err := d.Set("ip_address", ipAddresses); err != nil {
log.Printf("[WARN] Failed to set SQL Database Instance IP Addresses")
Expand Down Expand Up @@ -1983,7 +2009,7 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError},
})
if err != nil {
return fmt.Errorf("Error, failed to promote read replica instance as primary stand-alone %s: %s", instance.Name, err)
return fmt.Errorf("Error, failed to promote read replica instance as primary stand-alone %s: %s", d.Get("name"), err)
}
err = SqlAdminOperationWaitTime(config, op, project, "Promote Instance", userAgent, d.Timeout(schema.TimeoutUpdate))
if err != nil {
Expand Down Expand Up @@ -2052,6 +2078,13 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
instance.DatabaseVersion = databaseVersion
}

failoverDrReplicaName := d.Get("replication_cluster.0.failover_dr_replica_name").(string)
if failoverDrReplicaName != "" {
instance.ReplicationCluster = &sqladmin.ReplicationCluster{
FailoverDrReplicaName: failoverDrReplicaName,
}
}

err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() (rerr error) {
op, rerr = config.NewSqlAdminClient(userAgent).Instances.Update(project, d.Get("name").(string), instance).Do()
Expand Down Expand Up @@ -2379,6 +2412,22 @@ func flattenDatabaseFlags(databaseFlags []*sqladmin.DatabaseFlags) []map[string]
return flags
}

// flattenReplicationCluster converts cloud SQL backend ReplicationCluster (proto) to
// terraform replication_cluster. We explicitly allow the case when ReplicationCluster
// is nil since replication_cluster is computed+optional.
func flattenReplicationCluster(replicationCluster *sqladmin.ReplicationCluster, d *schema.ResourceData) []map[string]interface{} {
data := make(map[string]interface{})
data["failover_dr_replica_name"] = ""
if replicationCluster != nil && replicationCluster.FailoverDrReplicaName != "" {
data["failover_dr_replica_name"] = replicationCluster.FailoverDrReplicaName
}
data["dr_replica"] = false
if replicationCluster != nil {
data["dr_replica"] = replicationCluster.DrReplica
}
return []map[string]interface{}{data}
}

func flattenIpConfiguration(ipConfiguration *sqladmin.IpConfiguration, d *schema.ResourceData) interface{} {
data := map[string]interface{}{
"ipv4_enabled": ipConfiguration.Ipv4Enabled,
Expand Down Expand Up @@ -2661,11 +2710,6 @@ func isSwitchoverRequested(d *schema.ResourceData) bool {
if !slices.Contains(newReplicaNames.([]interface{}), originalPrimaryName) {
return false
}
dbVersion := d.Get("database_version")
if !strings.HasPrefix(dbVersion.(string), "SQLSERVER") {
log.Printf("[WARN] Switchover is only supported for SQL Server %q", dbVersion)
return false
}
return true
}

Expand All @@ -2683,10 +2727,6 @@ func isReplicaPromoteRequested(_ context.Context, oldInstanceType interface{}, n
// Check if this resource change is the manual update done on old primary after a switchover. If true, no replacement is needed.
func isSwitchoverFromOldPrimarySide(d *schema.ResourceDiff) bool {
dbVersion := d.Get("database_version")
if !strings.HasPrefix(dbVersion.(string), "SQLSERVER") {
log.Printf("[WARN] Switchover is only supported for SQL Server %q", dbVersion)
return false
}
oldInstanceType, newInstanceType := d.GetChange("instance_type")
oldReplicaNames, newReplicaNames := d.GetChange("replica_names")
_, newMasterInstanceName := d.GetChange("master_instance_name")
Expand All @@ -2701,11 +2741,12 @@ func isSwitchoverFromOldPrimarySide(d *schema.ResourceDiff) bool {
newMasterInOldReplicaNames := slices.Contains(oldReplicaNames.([]interface{}), newMasterInstanceName)
newMasterNotInNewReplicaNames := !slices.Contains(newReplicaNames.([]interface{}), newMasterInstanceName)
isCascadableReplica := cascadableReplicaFieldExists && cascadableReplica.(bool)
isSQLServer := strings.HasPrefix(dbVersion.(string), "SQLSERVER")

return newMasterInstanceName != nil &&
instanceTypeChangedFromPrimaryToReplica &&
newMasterInOldReplicaNames && newMasterNotInNewReplicaNames &&
isCascadableReplica
newMasterInOldReplicaNames && newMasterNotInNewReplicaNames && (!isSQLServer ||
isCascadableReplica)
}

func checkPromoteConfigurations(d *schema.ResourceData) error {
Expand Down
Loading

0 comments on commit cc3c1e8

Please sign in to comment.