From b4a4eb5a60cefe140f5f7828501d8ac63e863f6d Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 23 Jul 2024 13:41:04 -0700 Subject: [PATCH] Bigtable: add data_boost_isolation_read_only support for the Data Boost feature. (#11138) (#7789) [upstream:cc890e7fedc695ecf781d9b69cb336f44d1b832f] Signed-off-by: Modular Magician --- .changelog/11138.txt | 3 + .../bigtable/resource_bigtable_app_profile.go | 87 +++++++++++ .../resource_bigtable_app_profile_test.go | 135 ++++++++++++++++++ .../docs/r/bigtable_app_profile.html.markdown | 12 ++ 4 files changed, 237 insertions(+) create mode 100644 .changelog/11138.txt diff --git a/.changelog/11138.txt b/.changelog/11138.txt new file mode 100644 index 0000000000..a0e8badca4 --- /dev/null +++ b/.changelog/11138.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +bigtable: added `data_boost_isolation_read_only` and `data_boost_isolation_read_only.compute_billing_owner` fields to `google_bigtable_app_profile` resource +``` \ No newline at end of file diff --git a/google-beta/services/bigtable/resource_bigtable_app_profile.go b/google-beta/services/bigtable/resource_bigtable_app_profile.go index 9eccba6a19..835587c604 100644 --- a/google-beta/services/bigtable/resource_bigtable_app_profile.go +++ b/google-beta/services/bigtable/resource_bigtable_app_profile.go @@ -62,6 +62,23 @@ func ResourceBigtableAppProfile() *schema.Resource { ForceNew: true, Description: `The unique name of the app profile in the form '[_a-zA-Z0-9][-_.a-zA-Z0-9]*'.`, }, + "data_boost_isolation_read_only": { + Type: schema.TypeList, + Optional: true, + Description: `Specifies that this app profile is intended for read-only usage via the Data Boost feature.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "compute_billing_owner": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidateEnum([]string{"HOST_PAYS"}), + Description: `The Compute Billing Owner for this Data Boost App Profile. Possible values: ["HOST_PAYS"]`, + }, + }, + }, + ConflictsWith: []string{"standard_isolation"}, + }, "description": { Type: schema.TypeString, Optional: true, @@ -126,6 +143,7 @@ It is unsafe to send these requests to the same table/row/column in multiple clu }, }, }, + ConflictsWith: []string{"data_boost_isolation_read_only"}, }, "name": { Type: schema.TypeString, @@ -184,6 +202,12 @@ func resourceBigtableAppProfileCreate(d *schema.ResourceData, meta interface{}) } else if v, ok := d.GetOkExists("standard_isolation"); !tpgresource.IsEmptyValue(reflect.ValueOf(standardIsolationProp)) && (ok || !reflect.DeepEqual(v, standardIsolationProp)) { obj["standardIsolation"] = standardIsolationProp } + dataBoostIsolationReadOnlyProp, err := expandBigtableAppProfileDataBoostIsolationReadOnly(d.Get("data_boost_isolation_read_only"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("data_boost_isolation_read_only"); !tpgresource.IsEmptyValue(reflect.ValueOf(dataBoostIsolationReadOnlyProp)) && (ok || !reflect.DeepEqual(v, dataBoostIsolationReadOnlyProp)) { + obj["dataBoostIsolationReadOnly"] = dataBoostIsolationReadOnlyProp + } obj, err = resourceBigtableAppProfileEncoder(d, meta, obj) if err != nil { @@ -296,6 +320,9 @@ func resourceBigtableAppProfileRead(d *schema.ResourceData, meta interface{}) er if err := d.Set("standard_isolation", flattenBigtableAppProfileStandardIsolation(res["standardIsolation"], d, config)); err != nil { return fmt.Errorf("Error reading AppProfile: %s", err) } + if err := d.Set("data_boost_isolation_read_only", flattenBigtableAppProfileDataBoostIsolationReadOnly(res["dataBoostIsolationReadOnly"], d, config)); err != nil { + return fmt.Errorf("Error reading AppProfile: %s", err) + } return nil } @@ -340,6 +367,12 @@ func resourceBigtableAppProfileUpdate(d *schema.ResourceData, meta interface{}) } else if v, ok := d.GetOkExists("standard_isolation"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, standardIsolationProp)) { obj["standardIsolation"] = standardIsolationProp } + dataBoostIsolationReadOnlyProp, err := expandBigtableAppProfileDataBoostIsolationReadOnly(d.Get("data_boost_isolation_read_only"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("data_boost_isolation_read_only"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, dataBoostIsolationReadOnlyProp)) { + obj["dataBoostIsolationReadOnly"] = dataBoostIsolationReadOnlyProp + } obj, err = resourceBigtableAppProfileEncoder(d, meta, obj) if err != nil { @@ -370,6 +403,10 @@ func resourceBigtableAppProfileUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("standard_isolation") { updateMask = append(updateMask, "standardIsolation") } + + if d.HasChange("data_boost_isolation_read_only") { + updateMask = append(updateMask, "dataBoostIsolationReadOnly") + } // updateMask is a URL parameter but not present in the schema, so ReplaceVars // won't set it url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) @@ -396,6 +433,16 @@ func resourceBigtableAppProfileUpdate(d *schema.ResourceData, meta interface{}) } } } + + _, hasStandardIsolation := obj["standardIsolation"] + _, hasDataBoostIsolationReadOnly := obj["dataBoostIsolationReadOnly"] + if hasStandardIsolation && hasDataBoostIsolationReadOnly { + // Due to the "conflicts" both fields should be present only if neither was + // previously specified and the user is now manually adding dataBoostIsolationReadOnly. + delete(obj, "standardIsolation") + updateMask = append(updateMask, "dataBoostIsolationReadOnly") + } + // updateMask is a URL parameter but not present in the schema, so ReplaceVars // won't set it url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) @@ -566,6 +613,23 @@ func flattenBigtableAppProfileStandardIsolationPriority(v interface{}, d *schema return v } +func flattenBigtableAppProfileDataBoostIsolationReadOnly(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["compute_billing_owner"] = + flattenBigtableAppProfileDataBoostIsolationReadOnlyComputeBillingOwner(original["computeBillingOwner"], d, config) + return []interface{}{transformed} +} +func flattenBigtableAppProfileDataBoostIsolationReadOnlyComputeBillingOwner(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func expandBigtableAppProfileDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } @@ -643,6 +707,29 @@ func expandBigtableAppProfileStandardIsolationPriority(v interface{}, d tpgresou return v, nil } +func expandBigtableAppProfileDataBoostIsolationReadOnly(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{}) + + transformedComputeBillingOwner, err := expandBigtableAppProfileDataBoostIsolationReadOnlyComputeBillingOwner(original["compute_billing_owner"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedComputeBillingOwner); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["computeBillingOwner"] = transformedComputeBillingOwner + } + + return transformed, nil +} + +func expandBigtableAppProfileDataBoostIsolationReadOnlyComputeBillingOwner(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func resourceBigtableAppProfileEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { // Instance is a URL parameter only, so replace self-link/path with resource name only. if err := d.Set("instance", tpgresource.GetResourceNameFromSelfLink(d.Get("instance").(string))); err != nil { diff --git a/google-beta/services/bigtable/resource_bigtable_app_profile_test.go b/google-beta/services/bigtable/resource_bigtable_app_profile_test.go index 79f99d535e..a87c4e2fc9 100644 --- a/google-beta/services/bigtable/resource_bigtable_app_profile_test.go +++ b/google-beta/services/bigtable/resource_bigtable_app_profile_test.go @@ -433,3 +433,138 @@ resource "google_bigtable_app_profile" "ap" { } `, instanceName, instanceName, instanceName, instanceName, instanceName, instanceName) } + +func testAccBigtableAppProfile_updateSSDWithPriority(instanceName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + storage_type = "SSD" + } + + cluster { + cluster_id = "%s2" + zone = "us-central1-a" + num_nodes = 1 + storage_type = "SSD" + } + + cluster { + cluster_id = "%s3" + zone = "us-central1-c" + num_nodes = 1 + storage_type = "SSD" + } + + deletion_protection = false +} + +resource "google_bigtable_app_profile" "ap" { + instance = google_bigtable_instance.instance.id + app_profile_id = "test" + + single_cluster_routing { + cluster_id = %q + allow_transactional_writes = false + } + + standard_isolation { + priority = "PRIORITY_MEDIUM" + } + + ignore_warnings = true +} +`, instanceName, instanceName, instanceName, instanceName, instanceName) +} + +func testAccBigtableAppProfile_updateDataBoost(instanceName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + storage_type = "SSD" + } + + cluster { + cluster_id = "%s2" + zone = "us-central1-a" + num_nodes = 1 + storage_type = "SSD" + } + + cluster { + cluster_id = "%s3" + zone = "us-central1-c" + num_nodes = 1 + storage_type = "SSD" + } + + deletion_protection = false +} + +resource "google_bigtable_app_profile" "ap" { + instance = google_bigtable_instance.instance.id + app_profile_id = "test" + + single_cluster_routing { + cluster_id = %q + allow_transactional_writes = false + } + + data_boost_isolation_read_only { + compute_billing_owner = "HOST_PAYS" + } + + ignore_warnings = true +} +`, instanceName, instanceName, instanceName, instanceName, instanceName) +} + +func TestAccBigtableAppProfile_updateStandardIsolationToDataBoost(t *testing.T) { + // bigtable instance does not use the shared HTTP client, this test creates an instance + acctest.SkipIfVcr(t) + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBigtableAppProfileDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigtableAppProfile_updateSSDWithPriority(instanceName), + }, + { + ResourceName: "google_bigtable_app_profile.ap", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ignore_warnings"}, + }, + { + Config: testAccBigtableAppProfile_updateDataBoost(instanceName), + }, + { + ResourceName: "google_bigtable_app_profile.ap", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ignore_warnings"}, + }, + { + Config: testAccBigtableAppProfile_updateSSDWithPriority(instanceName), + }, + { + ResourceName: "google_bigtable_app_profile.ap", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ignore_warnings"}, + }, + }, + }) +} diff --git a/website/docs/r/bigtable_app_profile.html.markdown b/website/docs/r/bigtable_app_profile.html.markdown index c5f0a2d058..cde5e82c18 100644 --- a/website/docs/r/bigtable_app_profile.html.markdown +++ b/website/docs/r/bigtable_app_profile.html.markdown @@ -219,6 +219,11 @@ The following arguments are supported: The standard options used for isolating this app profile's traffic from other use cases. Structure is [documented below](#nested_standard_isolation). +* `data_boost_isolation_read_only` - + (Optional) + Specifies that this app profile is intended for read-only usage via the Data Boost feature. + Structure is [documented below](#nested_data_boost_isolation_read_only). + * `instance` - (Optional) The name of the instance to create the app profile within. @@ -249,6 +254,13 @@ The following arguments are supported: The priority of requests sent using this app profile. Possible values are: `PRIORITY_LOW`, `PRIORITY_MEDIUM`, `PRIORITY_HIGH`. +The `data_boost_isolation_read_only` block supports: + +* `compute_billing_owner` - + (Required) + The Compute Billing Owner for this Data Boost App Profile. + Possible values are: `HOST_PAYS`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: