Skip to content

Commit

Permalink
Add cross-region replication support to AlloyDB (#9012) (#16197)
Browse files Browse the repository at this point in the history
[upstream:79d5d56903e5ad2b38e9f855164132e5c53da8f6]

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician committed Oct 10, 2023
1 parent 86dc7b9 commit 6e2d089
Show file tree
Hide file tree
Showing 5 changed files with 1,139 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/9012.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
alloydb: added `cluster_type` and `secondary_config` fields to support secondary clusters in `google_alloydb_cluster` resource.
```
138 changes: 135 additions & 3 deletions google/services/alloydb/resource_alloydb_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ func ResourceAlloydbCluster() *schema.Resource {
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
Create: schema.DefaultTimeout(30 * time.Minute),
Update: schema.DefaultTimeout(30 * time.Minute),
Delete: schema.DefaultTimeout(30 * time.Minute),
},

CustomizeDiff: customdiff.All(
Expand Down Expand Up @@ -216,6 +216,14 @@ A duration in seconds with up to nine fractional digits, terminated by 's'. Exam
},
},
},
"cluster_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: verify.ValidateEnum([]string{"PRIMARY", "SECONDARY", ""}),
Description: `The type of cluster. If not set, defaults to PRIMARY. Default value: "PRIMARY" Possible values: ["PRIMARY", "SECONDARY"]`,
Default: "PRIMARY",
},
"continuous_backup_config": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -392,6 +400,23 @@ It is specified in the form: "projects/{projectNumber}/global/networks/{network_
},
ConflictsWith: []string{"restore_backup_source"},
},
"secondary_config": {
Type: schema.TypeList,
Optional: true,
Description: `Configuration of the secondary cluster for Cross Region Replication. This should be set if and only if the cluster is of type SECONDARY.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"primary_cluster_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `Name of the primary cluster must be in the format
'projects/{project}/locations/{location}/clusters/{cluster_id}'`,
},
},
},
},
"backup_source": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -626,6 +651,18 @@ func resourceAlloydbClusterCreate(d *schema.ResourceData, meta interface{}) erro
} else if v, ok := d.GetOkExists("automated_backup_policy"); !tpgresource.IsEmptyValue(reflect.ValueOf(automatedBackupPolicyProp)) && (ok || !reflect.DeepEqual(v, automatedBackupPolicyProp)) {
obj["automatedBackupPolicy"] = automatedBackupPolicyProp
}
clusterTypeProp, err := expandAlloydbClusterClusterType(d.Get("cluster_type"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("cluster_type"); !tpgresource.IsEmptyValue(reflect.ValueOf(clusterTypeProp)) && (ok || !reflect.DeepEqual(v, clusterTypeProp)) {
obj["clusterType"] = clusterTypeProp
}
secondaryConfigProp, err := expandAlloydbClusterSecondaryConfig(d.Get("secondary_config"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("secondary_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(secondaryConfigProp)) && (ok || !reflect.DeepEqual(v, secondaryConfigProp)) {
obj["secondaryConfig"] = secondaryConfigProp
}
labelsProp, err := expandAlloydbClusterEffectiveLabels(d.Get("effective_labels"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -691,6 +728,37 @@ func resourceAlloydbClusterCreate(d *schema.ResourceData, meta interface{}) erro
restoreClusterRequestBody["cluster"] = cluster
obj = restoreClusterRequestBody
}

// 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.")
}
}
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Expand Down Expand Up @@ -820,6 +888,12 @@ func resourceAlloydbClusterRead(d *schema.ResourceData, meta interface{}) error
if err := d.Set("migration_source", flattenAlloydbClusterMigrationSource(res["migrationSource"], d, config)); err != nil {
return fmt.Errorf("Error reading Cluster: %s", err)
}
if err := d.Set("cluster_type", flattenAlloydbClusterClusterType(res["clusterType"], d, config)); err != nil {
return fmt.Errorf("Error reading Cluster: %s", err)
}
if err := d.Set("secondary_config", flattenAlloydbClusterSecondaryConfig(res["secondaryConfig"], d, config)); err != nil {
return fmt.Errorf("Error reading Cluster: %s", err)
}
if err := d.Set("terraform_labels", flattenAlloydbClusterTerraformLabels(res["labels"], d, config)); err != nil {
return fmt.Errorf("Error reading Cluster: %s", err)
}
Expand Down Expand Up @@ -897,6 +971,12 @@ func resourceAlloydbClusterUpdate(d *schema.ResourceData, meta interface{}) erro
} else if v, ok := d.GetOkExists("automated_backup_policy"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, automatedBackupPolicyProp)) {
obj["automatedBackupPolicy"] = automatedBackupPolicyProp
}
secondaryConfigProp, err := expandAlloydbClusterSecondaryConfig(d.Get("secondary_config"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("secondary_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, secondaryConfigProp)) {
obj["secondaryConfig"] = secondaryConfigProp
}
labelsProp, err := expandAlloydbClusterEffectiveLabels(d.Get("effective_labels"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -950,6 +1030,10 @@ func resourceAlloydbClusterUpdate(d *schema.ResourceData, meta interface{}) erro
updateMask = append(updateMask, "automatedBackupPolicy")
}

if d.HasChange("secondary_config") {
updateMask = append(updateMask, "secondaryConfig")
}

if d.HasChange("effective_labels") {
updateMask = append(updateMask, "labels")
}
Expand Down Expand Up @@ -1569,6 +1653,27 @@ func flattenAlloydbClusterMigrationSourceSourceType(v interface{}, d *schema.Res
return v
}

func flattenAlloydbClusterClusterType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func flattenAlloydbClusterSecondaryConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["primary_cluster_name"] =
flattenAlloydbClusterSecondaryConfigPrimaryClusterName(original["primaryClusterName"], d, config)
return []interface{}{transformed}
}
func flattenAlloydbClusterSecondaryConfigPrimaryClusterName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func flattenAlloydbClusterTerraformLabels(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
if v == nil {
return v
Expand Down Expand Up @@ -2065,6 +2170,33 @@ func expandAlloydbClusterAutomatedBackupPolicyEnabled(v interface{}, d tpgresour
return v, nil
}

func expandAlloydbClusterClusterType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}

func expandAlloydbClusterSecondaryConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedPrimaryClusterName, err := expandAlloydbClusterSecondaryConfigPrimaryClusterName(original["primary_cluster_name"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedPrimaryClusterName); val.IsValid() && !tpgresource.IsEmptyValue(val) {
transformed["primaryClusterName"] = transformedPrimaryClusterName
}

return transformed, nil
}

func expandAlloydbClusterSecondaryConfigPrimaryClusterName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}

func expandAlloydbClusterEffectiveLabels(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) {
if v == nil {
return map[string]string{}, nil
Expand Down
84 changes: 84 additions & 0 deletions google/services/alloydb/resource_alloydb_cluster_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,90 @@ resource "google_compute_network" "default" {
`, context)
}

func TestAccAlloydbCluster_alloydbSecondaryClusterBasicExample(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_alloydbSecondaryClusterBasicExample(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_alloydbSecondaryClusterBasicExample(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)
}

func testAccCheckAlloydbClusterDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
Expand Down

0 comments on commit 6e2d089

Please sign in to comment.