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

Add cross-region replication support to AlloyDB #6474

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
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-beta/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
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
Loading