diff --git a/google/resource_sql_database_instance.go b/google/resource_sql_database_instance.go index b892716e729..3f019ea4372 100644 --- a/google/resource_sql_database_instance.go +++ b/google/resource_sql_database_instance.go @@ -685,6 +685,8 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{}) if v, ok := d.GetOk("master_instance_name"); ok { instance.MasterInstanceName = v.(string) + mutexKV.Lock(instanceMutexKey(project, instance.MasterInstanceName)) + defer mutexKV.Unlock(instanceMutexKey(project, instance.MasterInstanceName)) } op, err := config.clientSqlAdmin.Instances.Insert(project, instance).Do() @@ -763,7 +765,7 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e log.Printf("[WARN] Failed to set SQL Database Instance Settings") } - if err := d.Set("replica_configuration", flattenReplicaConfiguration(instance.ReplicaConfiguration)); err != nil { + if err := d.Set("replica_configuration", flattenReplicaConfiguration(instance.ReplicaConfiguration, d)); err != nil { log.Printf("[WARN] Failed to set SQL Database Instance Replica Configuration") } @@ -1054,6 +1056,13 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{}) d.Partial(false) + // Lock on the master_instance_name just in case updating any replica + // settings causes operations on the master. + if v, ok := d.GetOk("master_instance_name"); ok { + mutexKV.Lock(instanceMutexKey(project, v.(string))) + defer mutexKV.Unlock(instanceMutexKey(project, v.(string))) + } + op, err := config.clientSqlAdmin.Instances.Update(project, instance.Name, instance).Do() if err != nil { return fmt.Errorf("Error, failed to update instance %s: %s", instance.Name, err) @@ -1075,6 +1084,13 @@ func resourceSqlDatabaseInstanceDelete(d *schema.ResourceData, meta interface{}) return err } + // Lock on the master_instance_name just in case deleting a replica causes + // operations on the master. + if v, ok := d.GetOk("master_instance_name"); ok { + mutexKV.Lock(instanceMutexKey(project, v.(string))) + defer mutexKV.Unlock(instanceMutexKey(project, v.(string))) + } + op, err := config.clientSqlAdmin.Instances.Delete(project, d.Get("name").(string)).Do() if err != nil { @@ -1208,7 +1224,7 @@ func flattenMaintenanceWindow(maintenanceWindow *sqladmin.MaintenanceWindow) int return []map[string]interface{}{data} } -func flattenReplicaConfiguration(replicaConfiguration *sqladmin.ReplicaConfiguration) []map[string]interface{} { +func flattenReplicaConfiguration(replicaConfiguration *sqladmin.ReplicaConfiguration, d *schema.ResourceData) []map[string]interface{} { rc := []map[string]interface{}{} if replicaConfiguration != nil { @@ -1217,7 +1233,18 @@ func flattenReplicaConfiguration(replicaConfiguration *sqladmin.ReplicaConfigura // Don't attempt to assign anything from replicaConfiguration.MysqlReplicaConfiguration, // since those fields are set on create and then not stored. See description at - // https://cloud.google.com/sql/docs/mysql/admin-api/v1beta4/instances + // https://cloud.google.com/sql/docs/mysql/admin-api/v1beta4/instances. + // Instead, set them to the values they previously had so we don't set them all to zero. + "ca_certificate": d.Get("replica_configuration.0.ca_certificate"), + "client_certificate": d.Get("replica_configuration.0.client_certificate"), + "client_key": d.Get("replica_configuration.0.client_key"), + "connect_retry_interval": d.Get("replica_configuration.0.connect_retry_interval"), + "dump_file_path": d.Get("replica_configuration.0.dump_file_path"), + "master_heartbeat_period": d.Get("replica_configuration.0.master_heartbeat_period"), + "password": d.Get("replica_configuration.0.password"), + "ssl_cipher": d.Get("replica_configuration.0.ssl_cipher"), + "username": d.Get("replica_configuration.0.username"), + "verify_server_certificate": d.Get("replica_configuration.0.verify_server_certificate"), } rc = append(rc, data) } diff --git a/google/resource_sql_database_instance_test.go b/google/resource_sql_database_instance_test.go index a15e2b13b75..468a442408d 100644 --- a/google/resource_sql_database_instance_test.go +++ b/google/resource_sql_database_instance_test.go @@ -21,6 +21,21 @@ import ( "google.golang.org/api/sqladmin/v1beta4" ) +// Fields that should be ignored in import tests because they aren't returned +// from GCP (and thus can't be imported) +var ignoredReplicaConfigurationFields = []string{ + "replica_configuration.0.ca_certificate", + "replica_configuration.0.client_certificate", + "replica_configuration.0.client_key", + "replica_configuration.0.connect_retry_interval", + "replica_configuration.0.dump_file_path", + "replica_configuration.0.master_heartbeat_period", + "replica_configuration.0.password", + "replica_configuration.0.ssl_cipher", + "replica_configuration.0.username", + "replica_configuration.0.verify_server_certificate", +} + func init() { resource.AddTestSweepers("gcp_sql_db_instance", &resource.Sweeper{ Name: "gcp_sql_db_instance", @@ -288,6 +303,41 @@ func TestAccSqlDatabaseInstance_settings_basic(t *testing.T) { }) } +func TestAccSqlDatabaseInstance_replica(t *testing.T) { + t.Parallel() + + databaseID := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccSqlDatabaseInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf( + testGoogleSqlDatabaseInstance_replica, databaseID, databaseID, databaseID), + }, + resource.TestStep{ + ResourceName: "google_sql_database_instance.instance_master", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_sql_database_instance.replica1", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + resource.TestStep{ + ResourceName: "google_sql_database_instance.replica2", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + }, + }) +} + func TestAccSqlDatabaseInstance_slave(t *testing.T) { t.Parallel() @@ -938,17 +988,14 @@ resource "google_sql_database_instance" "instance" { } ` -// Note - this test is not feasible to run unless we generate -// backups first. var testGoogleSqlDatabaseInstance_replica = ` resource "google_sql_database_instance" "instance_master" { name = "tf-lw-%d" database_version = "MYSQL_5_6" - region = "us-east1" + region = "us-central1" settings { - tier = "D0" - crash_safe_replication = true + tier = "db-n1-standard-1" backup_configuration { enabled = true @@ -958,21 +1005,39 @@ resource "google_sql_database_instance" "instance_master" { } } -resource "google_sql_database_instance" "instance" { - name = "tf-lw-%d" +resource "google_sql_database_instance" "replica1" { + name = "tf-lw-%d-1" database_version = "MYSQL_5_6" - region = "us-central" + region = "us-central1" settings { - tier = "D0" + tier = "db-n1-standard-1" + } + + master_instance_name = "${google_sql_database_instance.instance_master.name}" + + replica_configuration { + connect_retry_interval = 100 + master_heartbeat_period = 10000 + password = "password" + username = "username" + ssl_cipher = "ALL" + verify_server_certificate = false + } +} + +resource "google_sql_database_instance" "replica2" { + name = "tf-lw-%d-2" + database_version = "MYSQL_5_6" + region = "us-central1" + + settings { + tier = "db-n1-standard-1" } master_instance_name = "${google_sql_database_instance.instance_master.name}" replica_configuration { - ca_certificate = "${file("~/tmp/fake.pem")}" - client_certificate = "${file("~/tmp/fake.pem")}" - client_key = "${file("~/tmp/fake.pem")}" connect_retry_interval = 100 master_heartbeat_period = 10000 password = "password"