diff --git a/mmv1/products/alloydb/Cluster.yaml b/mmv1/products/alloydb/Cluster.yaml index 4fbed39dd2eb..2b9206b55e30 100644 --- a/mmv1/products/alloydb/Cluster.yaml +++ b/mmv1/products/alloydb/Cluster.yaml @@ -29,9 +29,9 @@ async: !ruby/object:Api::OpAsync base_url: '{{op_id}}' wait_ms: 1000 timeouts: !ruby/object:Api::Timeouts - insert_minutes: 10 - update_minutes: 10 - delete_minutes: 10 + insert_minutes: 30 + update_minutes: 30 + delete_minutes: 30 result: !ruby/object:Api::OpAsync::Result path: 'response' status: !ruby/object:Api::OpAsync::Status @@ -78,8 +78,15 @@ examples: ignore_read_extra: - 'reconciling' - 'update_time' + - !ruby/object:Provider::Terraform::Examples + name: 'alloydb_secondary_cluster_basic' + primary_resource_id: 'secondary' + vars: + alloydb_primary_cluster_name: 'alloydb-primary-cluster' + alloydb_primary_instance_name: 'alloydb-primary-instance' + alloydb_secondary_cluster_name: 'alloydb-secondary-cluster' custom_code: !ruby/object:Provider::Terraform::CustomCode - pre_create: templates/terraform/pre_create/alloydb_restore_cluster.go.erb + pre_create: templates/terraform/pre_create/alloydb_cluster.go.erb parameters: - !ruby/object:Api::Type::String name: 'clusterId' @@ -452,3 +459,24 @@ properties: - !ruby/object:Api::Type::String name: 'sourceType' description: 'Type of migration source.' + - !ruby/object:Api::Type::Enum + name: clusterType + values: + - :PRIMARY + - :SECONDARY + default_value: :PRIMARY + immutable: true + description: | + The type of cluster. If not set, defaults to PRIMARY. + - !ruby/object:Api::Type::NestedObject + name: "secondaryConfig" + description: | + Configuration of the secondary cluster for Cross Region Replication. This should be set if and only if the cluster is of type SECONDARY. + properties: + - !ruby/object:Api::Type::String + name: "primaryClusterName" + immutable: true + required: true + description: | + Name of the primary cluster must be in the format + 'projects/{project}/locations/{location}/clusters/{cluster_id}' diff --git a/mmv1/templates/terraform/examples/alloydb_secondary_cluster_basic.tf.erb b/mmv1/templates/terraform/examples/alloydb_secondary_cluster_basic.tf.erb new file mode 100644 index 000000000000..353cd5a26e6c --- /dev/null +++ b/mmv1/templates/terraform/examples/alloydb_secondary_cluster_basic.tf.erb @@ -0,0 +1,54 @@ +resource "google_alloydb_cluster" "primary" { + cluster_id = "<%= ctx[:vars]['alloydb_primary_cluster_name'] %>" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "<%= ctx[:vars]['alloydb_primary_instance_name'] %>" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "<%= ctx[:primary_resource_id] %>" { + cluster_id = "<%= ctx[:vars]['alloydb_secondary_cluster_name'] %>" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "<%= ctx[:vars]['alloydb_secondary_cluster_name'] %>" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "<%= ctx[:vars]['alloydb_secondary_cluster_name'] %>" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_create/alloydb_restore_cluster.go.erb b/mmv1/templates/terraform/pre_create/alloydb_cluster.go.erb similarity index 55% rename from mmv1/templates/terraform/pre_create/alloydb_restore_cluster.go.erb rename to mmv1/templates/terraform/pre_create/alloydb_cluster.go.erb index 1cce1c5bf5b9..87ac9c897160 100644 --- a/mmv1/templates/terraform/pre_create/alloydb_restore_cluster.go.erb +++ b/mmv1/templates/terraform/pre_create/alloydb_cluster.go.erb @@ -30,4 +30,36 @@ if backupSource != nil || continuousBackupSource != nil { } restoreClusterRequestBody["cluster"] = cluster obj = restoreClusterRequestBody -} \ No newline at end of file +} + + +// Read the secondary cluster config to call the api for creating secondary cluster + +var secondaryConfig interface{} +var clusterType interface{} + +if val, ok := obj["secondaryConfig"]; ok { + secondaryConfig = val +} + +if val, ok := obj["clusterType"]; ok { + clusterType = val +} + +if clusterType == "SECONDARY" { + if secondaryConfig != nil { + // Use createsecondary API if this is a secondary cluster + url = strings.Replace(url, "clusters?clusterId", "clusters:createsecondary?cluster_id", 1) + + // Validation error if secondary_config is not defined + } else { + return fmt.Errorf("Error creating cluster. Can not create secondary cluster without secondary_config field.") + } +} + +// Validation error if secondary_config is defined but, cluster type is not secondary +if secondaryConfig != nil { + if clusterType != "SECONDARY" { + return fmt.Errorf("Error creating cluster. Add {cluster_type: \"SECONDARY\"} if attempting to create a secondary cluster, otherwise remove the secondary_config.") + } +} diff --git a/mmv1/third_party/terraform/services/alloydb/resource_alloydb_secondary_cluster_test.go b/mmv1/third_party/terraform/services/alloydb/resource_alloydb_secondary_cluster_test.go new file mode 100644 index 000000000000..b5f430bb0eb5 --- /dev/null +++ b/mmv1/third_party/terraform/services/alloydb/resource_alloydb_secondary_cluster_test.go @@ -0,0 +1,830 @@ +package alloydb_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +// The cluster creation should succeed with minimal number of arguments +func TestAccAlloydbCluster_secondaryClusterMandatoryFields(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterMandatoryFields(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterMandatoryFields(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// Validation test to ensure proper error is raised if create secondary cluster is called without any secondary_config field +func TestAccAlloydbCluster_secondaryClusterMissingSecondaryConfig(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterMissingSecondaryConfig(context), + ExpectError: regexp.MustCompile("Error creating cluster. Can not create secondary cluster without secondary_config field."), + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterMissingSecondaryConfig(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// Validation test to ensure proper error is raised if secondary_config field is provided but no cluster_type is specified. +func TestAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButMissingClusterTypeSecondary(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButMissingClusterTypeSecondary(context), + ExpectError: regexp.MustCompile("Error creating cluster. Add {cluster_type: \"SECONDARY\"} if attempting to create a secondary cluster, otherwise remove the secondary_config."), + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButMissingClusterTypeSecondary(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// Validation test to ensure proper error is raised if secondary_config field is provided but cluster_type is primary +func TestAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButClusterTypeIsPrimary(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButClusterTypeIsPrimary(context), + ExpectError: regexp.MustCompile("Error creating cluster. Add {cluster_type: \"SECONDARY\"} if attempting to create a secondary cluster, otherwise remove the secondary_config."), + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterDefinedSecondaryConfigButClusterTypeIsPrimary(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "PRIMARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// This test passes if secondary cluster can be updated +func TestAccAlloydbCluster_secondaryClusterUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterMandatoryFields(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + { + Config: testAccAlloydbCluster_secondaryClusterUpdate(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + labels = { + foo = "bar" + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// Test if adding automatedBackupPolicy throws an error as it can not be enabled on secondary cluster +func TestAccAlloydbCluster_secondaryClusterAddAutomatedBackupPolicy(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "hour": 23, + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterMandatoryFields(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + { + // Invalid input check - can not add automated backup policy to a secondary cluster + Config: testAccAlloydbCluster_secondaryClusterAddAutomatedBackupPolicy(context), + ExpectError: regexp.MustCompile("cannot enable automated backups on secondary cluster until it is promoted"), + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterAddAutomatedBackupPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + automated_backup_policy { + location = "us-central1" + backup_window = "1800s" + enabled = true + + weekly_schedule { + days_of_week = ["MONDAY"] + + start_times { + hours = %{hour} + minutes = 0 + seconds = 0 + nanos = 0 + } + } + + quantity_based_retention { + count = 1 + } + + labels = { + test = "tf-test-alloydb-secondary-cluster%{random_suffix}" + } + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +func TestAccAlloydbCluster_secondaryClusterUsingCMEK(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "key_name": "tf-test-key-" + acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterUsingCMEK(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterUsingCMEK(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network = google_compute_network.default.id +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network = google_compute_network.default.id + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + encryption_config { + kms_key_name = google_kms_crypto_key.key.id + } + + depends_on = [google_alloydb_instance.primary, google_kms_crypto_key_iam_binding.crypto_key] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} + +resource "google_kms_key_ring" "keyring" { + name = "%{key_name}" + location = "us-east1" +} + +resource "google_kms_crypto_key" "key" { + name = "%{key_name}" + key_ring = google_kms_key_ring.keyring.id +} + +resource "google_kms_crypto_key_iam_binding" "crypto_key" { + crypto_key_id = google_kms_crypto_key.key.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + members = [ + "serviceAccount:service-${data.google_project.project.number}@gcp-sa-alloydb.iam.gserviceaccount.com", + ] +} +`, context) +} + +// Ensures secondary cluster creation works with networkConfig. +func TestAccAlloydbCluster_secondaryClusterWithNetworkConfig(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterWithNetworkConfig(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterWithNetworkConfig(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network_config { + network = "projects/${data.google_project.project.number}/global/networks/${google_compute_network.default.name}" + } +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network_config { + network = "projects/${data.google_project.project.number}/global/networks/${google_compute_network.default.name}" + } + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +} + +// Ensures secondary cluster creation works with networkConfig and a specified allocated IP range. +func TestAccAlloydbCluster_secondaryClusterWithNetworkConfigAndAllocatedIPRange(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckAlloydbClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccAlloydbCluster_secondaryClusterWithNetworkConfigAndAllocatedIPRange(context), + }, + { + ResourceName: "google_alloydb_cluster.secondary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"initial_user", "restore_backup_source", "restore_continuous_backup_source", "cluster_id", "location", "labels", "annotations", "terraform_labels"}, + }, + }, + }) +} + +func testAccAlloydbCluster_secondaryClusterWithNetworkConfigAndAllocatedIPRange(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_alloydb_cluster" "primary" { + cluster_id = "tf-test-alloydb-primary-cluster%{random_suffix}" + location = "us-central1" + network_config { + network = "projects/${data.google_project.project.number}/global/networks/${google_compute_network.default.name}" + allocated_ip_range = google_compute_global_address.private_ip_alloc.name + } +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.name + instance_id = "tf-test-alloydb-primary-instance%{random_suffix}" + instance_type = "PRIMARY" + + machine_config { + cpu_count = 2 + } + + depends_on = [google_service_networking_connection.vpc_connection] +} + +resource "google_alloydb_cluster" "secondary" { + cluster_id = "tf-test-alloydb-secondary-cluster%{random_suffix}" + location = "us-east1" + network_config { + network = "projects/${data.google_project.project.number}/global/networks/${google_compute_network.default.name}" + allocated_ip_range = google_compute_global_address.private_ip_alloc.name + } + cluster_type = "SECONDARY" + + continuous_backup_config { + enabled = false + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.name + } + + depends_on = [google_alloydb_instance.primary] +} + +data "google_project" "project" {} + +resource "google_compute_network" "default" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" +} + +resource "google_compute_global_address" "private_ip_alloc" { + name = "tf-test-alloydb-secondary-cluster%{random_suffix}" + address_type = "INTERNAL" + purpose = "VPC_PEERING" + prefix_length = 16 + network = google_compute_network.default.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.default.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name] +} +`, context) +}