From 0ff2db35071d999414c9ffecebc765b6b5fb14d9 Mon Sep 17 00:00:00 2001 From: The Magician Date: Fri, 13 Dec 2024 13:40:46 -0800 Subject: [PATCH] Add availability domain field to instance and instance template resource (#12566) (#8914) [upstream:8765b378e648d65db401f302e4ec05a321dcdbf9] Signed-off-by: Modular Magician --- .changelog/12566.txt | 3 + .../compute/compute_instance_helpers.go | 7 ++ .../compute/resource_compute_instance.go | 7 ++ .../resource_compute_instance_template.go | 8 ++ ...resource_compute_instance_template_test.go | 79 +++++++++++++++++ .../compute/resource_compute_instance_test.go | 86 +++++++++++++++++++ ...source_compute_region_instance_template.go | 7 ++ ...e_compute_region_instance_template_test.go | 80 +++++++++++++++++ website/docs/r/compute_instance.html.markdown | 2 + .../r/compute_instance_template.html.markdown | 2 + ...ute_region_instance_template.html.markdown | 2 + 11 files changed, 283 insertions(+) create mode 100644 .changelog/12566.txt diff --git a/.changelog/12566.txt b/.changelog/12566.txt new file mode 100644 index 0000000000..7fef31ebbf --- /dev/null +++ b/.changelog/12566.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +compute: added `availability_domain` field to `google_compute_instance`, `google_compute_instance_template` and `google_compute_region_instance_template` resources. +``` \ No newline at end of file diff --git a/google-beta/services/compute/compute_instance_helpers.go b/google-beta/services/compute/compute_instance_helpers.go index 38ce5ac954..cb2aabaf5f 100644 --- a/google-beta/services/compute/compute_instance_helpers.go +++ b/google-beta/services/compute/compute_instance_helpers.go @@ -144,6 +144,9 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { scheduling.InstanceTerminationAction = v.(string) scheduling.ForceSendFields = append(scheduling.ForceSendFields, "InstanceTerminationAction") } + if v, ok := original["availability_domain"]; ok && v != nil { + scheduling.AvailabilityDomain = int64(v.(int)) + } if v, ok := original["max_run_duration"]; ok { transformedMaxRunDuration, err := expandComputeMaxRunDuration(v) if err != nil { @@ -278,6 +281,7 @@ func flattenScheduling(resp *compute.Scheduling) []map[string]interface{} { "min_node_cpus": resp.MinNodeCpus, "provisioning_model": resp.ProvisioningModel, "instance_termination_action": resp.InstanceTerminationAction, + "availability_domain": resp.AvailabilityDomain, } if resp.AutomaticRestart != nil { @@ -729,6 +733,9 @@ func schedulingHasChangeWithoutReboot(d *schema.ResourceData) bool { if oScheduling["instance_termination_action"] != newScheduling["instance_termination_action"] { return true } + if oScheduling["availability_domain"] != newScheduling["availability_domain"] { + return true + } if oScheduling["host_error_timeout_seconds"] != newScheduling["host_error_timeout_seconds"] { return true } diff --git a/google-beta/services/compute/resource_compute_instance.go b/google-beta/services/compute/resource_compute_instance.go index f4bb4e3615..6fa0132892 100644 --- a/google-beta/services/compute/resource_compute_instance.go +++ b/google-beta/services/compute/resource_compute_instance.go @@ -92,6 +92,7 @@ var ( "scheduling.0.min_node_cpus", "scheduling.0.provisioning_model", "scheduling.0.instance_termination_action", + "scheduling.0.availability_domain", "scheduling.0.max_run_duration", "scheduling.0.on_instance_stop_action", "scheduling.0.maintenance_interval", @@ -882,6 +883,12 @@ func ResourceComputeInstance() *schema.Resource { AtLeastOneOf: schedulingKeys, Description: `Specifies the action GCE should take when SPOT VM is preempted.`, }, + "availability_domain": { + Type: schema.TypeInt, + Optional: true, + AtLeastOneOf: schedulingKeys, + Description: `Specifies the availability domain, which this instance should be scheduled on.`, + }, "max_run_duration": { Type: schema.TypeList, Optional: true, diff --git a/google-beta/services/compute/resource_compute_instance_template.go b/google-beta/services/compute/resource_compute_instance_template.go index 04f6b955ef..5ba56e8303 100644 --- a/google-beta/services/compute/resource_compute_instance_template.go +++ b/google-beta/services/compute/resource_compute_instance_template.go @@ -31,6 +31,7 @@ var ( "scheduling.0.min_node_cpus", "scheduling.0.provisioning_model", "scheduling.0.instance_termination_action", + "scheduling.0.availability_domain", "scheduling.0.max_run_duration", "scheduling.0.on_instance_stop_action", "scheduling.0.maintenance_interval", @@ -712,6 +713,13 @@ Google Cloud KMS.`, AtLeastOneOf: schedulingInstTemplateKeys, Description: `Specifies the action GCE should take when SPOT VM is preempted.`, }, + "availability_domain": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + AtLeastOneOf: schedulingInstTemplateKeys, + Description: `Specifies the availability domain, which this instance should be scheduled on.`, + }, "max_run_duration": { Type: schema.TypeList, Optional: true, diff --git a/google-beta/services/compute/resource_compute_instance_template_test.go b/google-beta/services/compute/resource_compute_instance_template_test.go index 86e143035b..8dc713888e 100644 --- a/google-beta/services/compute/resource_compute_instance_template_test.go +++ b/google-beta/services/compute/resource_compute_instance_template_test.go @@ -727,6 +727,34 @@ func TestAccComputeInstanceTemplate_instanceResourcePolicies(t *testing.T) { }) } +func TestAccComputeInstanceTemplate_instanceResourcePoliciesSpread(t *testing.T) { + t.Parallel() + + var template compute.InstanceTemplate + var policyName = "tf-test-policy-" + acctest.RandString(t, 10) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceTemplate_instanceResourcePolicySpread(acctest.RandString(t, 10), policyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template), + testAccCheckComputeInstanceTemplateHasInstanceResourcePolicies(&template, policyName), + testAccCheckComputeInstanceTemplateHasAvailabilityDomain(&template, 3), + ), + }, + { + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComputeInstanceTemplate_reservationAffinities(t *testing.T) { t.Parallel() @@ -2202,6 +2230,15 @@ func testAccCheckComputeInstanceTemplateHasInstanceResourcePolicies(instanceTemp } +func testAccCheckComputeInstanceTemplateHasAvailabilityDomain(instanceTemplate *compute.InstanceTemplate, availabilityDomain int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Scheduling.AvailabilityDomain != availabilityDomain { + return fmt.Errorf("Expected availability_domain %d, got %d", availabilityDomain, instanceTemplate.Properties.Scheduling.AvailabilityDomain) + } + return nil + } +} + func testAccCheckComputeInstanceTemplateHasReservationAffinity(instanceTemplate *compute.InstanceTemplate, consumeReservationType string, specificReservationNames ...string) resource.TestCheckFunc { if len(specificReservationNames) > 1 { panic("too many specificReservationNames in test") @@ -3569,6 +3606,48 @@ resource "google_compute_instance_template" "foobar" { `, policyName, suffix) } +func testAccComputeInstanceTemplate_instanceResourcePolicySpread(suffix string, policyName string) string { + return fmt.Sprintf(` +resource "google_compute_resource_policy" "foo" { + name = "%s" + region = "us-central1" + group_placement_policy { + availability_domain_count = 5 + } +} + +data "google_compute_image" "my_image" { + family = "debian-11" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "tf-test-instance-template-%s" + machine_type = "e2-standard-4" + + disk { + source_image = data.google_compute_image.my_image.self_link + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + availability_domain = 3 + } + + resource_policies = [google_compute_resource_policy.foo.self_link] + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} +`, policyName, suffix) +} + func testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_nonSpecificReservation(templateName, consumeReservationType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { diff --git a/google-beta/services/compute/resource_compute_instance_test.go b/google-beta/services/compute/resource_compute_instance_test.go index 82d7f34fb8..3b6960caa1 100644 --- a/google-beta/services/compute/resource_compute_instance_test.go +++ b/google-beta/services/compute/resource_compute_instance_test.go @@ -2986,6 +2986,31 @@ func TestAccComputeInstance_resourcePolicyCollocate(t *testing.T) { }) } +func TestAccComputeInstance_resourcePolicySpread(t *testing.T) { + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + var instance compute.Instance + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeInstance_resourcePolicySpread(instanceName, acctest.RandString(t, 10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + t, "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasStatus(&instance, "RUNNING"), + testAccCheckComputeInstanceHasAvailabilityDomain(&instance, 3), + ), + }, + computeInstanceImportStep("us-east4-b", instanceName, []string{"allow_stopping_for_update"}), + }, + }) +} + func TestAccComputeInstance_subnetworkUpdate(t *testing.T) { t.Parallel() instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) @@ -4408,6 +4433,23 @@ func testAccCheckComputeInstanceMaxRunDuration(instance *compute.Instance, insta } } +func testAccCheckComputeInstanceHasAvailabilityDomain(instance *compute.Instance, availabilityDomain int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance == nil { + return fmt.Errorf("instance is nil") + } + if instance.Scheduling == nil { + return fmt.Errorf("no scheduling") + } + + if instance.Scheduling.AvailabilityDomain != availabilityDomain { + return fmt.Errorf("got the wrong availability domain: have %d; want %d", instance.Scheduling.AvailabilityDomain, availabilityDomain) + } + + return nil + } +} + func testAccCheckComputeInstanceLocalSsdRecoveryTimeout(instance *compute.Instance, instanceLocalSsdRecoveryTiemoutWant compute.Duration) resource.TestCheckFunc { return func(s *terraform.State) error { if instance == nil { @@ -9309,6 +9351,50 @@ resource "google_compute_resource_policy" "foo" { `, instance, instance, suffix) } +func testAccComputeInstance_resourcePolicySpread(instance, suffix string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-11" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "e2-standard-4" + zone = "us-east4-b" + can_ip_forward = false + tags = ["foo", "bar"] + + //deletion_protection = false is implicit in this config due to default value + + boot_disk { + initialize_params { + image = data.google_compute_image.my_image.self_link + } + } + + network_interface { + network = "default" + } + + scheduling { + availability_domain = 3 + } + + resource_policies = [google_compute_resource_policy.foo.self_link] +} + +resource "google_compute_resource_policy" "foo" { + name = "tf-test-policy-%s" + region = "us-east4" + group_placement_policy { + availability_domain_count = 3 + } +} + +`, instance, suffix) +} + func testAccComputeInstance_subnetworkUpdate(suffix, instance string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { diff --git a/google-beta/services/compute/resource_compute_region_instance_template.go b/google-beta/services/compute/resource_compute_region_instance_template.go index daccd371fa..ca283b4e8e 100644 --- a/google-beta/services/compute/resource_compute_region_instance_template.go +++ b/google-beta/services/compute/resource_compute_region_instance_template.go @@ -675,6 +675,13 @@ Google Cloud KMS.`, AtLeastOneOf: schedulingInstTemplateKeys, Description: `Specifies the action GCE should take when SPOT VM is preempted.`, }, + "availability_domain": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + AtLeastOneOf: schedulingInstTemplateKeys, + Description: `Specifies the availability domain, which this instance should be scheduled on.`, + }, "max_run_duration": { Type: schema.TypeList, Optional: true, diff --git a/google-beta/services/compute/resource_compute_region_instance_template_test.go b/google-beta/services/compute/resource_compute_region_instance_template_test.go index 585e5c2cd9..26a9930359 100644 --- a/google-beta/services/compute/resource_compute_region_instance_template_test.go +++ b/google-beta/services/compute/resource_compute_region_instance_template_test.go @@ -605,6 +605,34 @@ func TestAccComputeRegionInstanceTemplate_instanceResourcePolicies(t *testing.T) }) } +func TestAccComputeRegionInstanceTemplate_instanceResourcePoliciesSpread(t *testing.T) { + t.Parallel() + + var template compute.InstanceTemplate + var policyName = "tf-test-policy-" + acctest.RandString(t, 10) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeRegionInstanceTemplateDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeRegionInstanceTemplate_instanceResourcePolicySpread(acctest.RandString(t, 10), policyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionInstanceTemplateExists(t, "google_compute_region_instance_template.foobar", &template), + testAccCheckComputeRegionInstanceTemplateHasInstanceResourcePolicies(&template, policyName), + testAccCheckComputeRegionInstanceTemplateHasAvailabilityDomain(&template, 3), + ), + }, + { + ResourceName: "google_compute_region_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccComputeRegionInstanceTemplate_reservationAffinities(t *testing.T) { t.Parallel() @@ -1838,6 +1866,15 @@ func testAccCheckComputeRegionInstanceTemplateHasInstanceResourcePolicies(instan } +func testAccCheckComputeRegionInstanceTemplateHasAvailabilityDomain(instanceTemplate *compute.InstanceTemplate, availabilityDomain int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Scheduling.AvailabilityDomain != availabilityDomain { + return fmt.Errorf("Expected availability_domain %d, got %d", availabilityDomain, instanceTemplate.Properties.Scheduling.AvailabilityDomain) + } + return nil + } +} + func testAccCheckComputeRegionInstanceTemplateHasReservationAffinity(instanceTemplate *compute.InstanceTemplate, consumeReservationType string, specificReservationNames ...string) resource.TestCheckFunc { if len(specificReservationNames) > 1 { panic("too many specificReservationNames in test") @@ -2960,6 +2997,49 @@ resource "google_compute_region_instance_template" "foobar" { `, policyName, suffix) } +func testAccComputeRegionInstanceTemplate_instanceResourcePolicySpread(suffix string, policyName string) string { + return fmt.Sprintf(` +resource "google_compute_resource_policy" "foo" { + name = "%s" + region = "us-central1" + group_placement_policy { + availability_domain_count = 5 + } +} + +data "google_compute_image" "my_image" { + family = "debian-11" + project = "debian-cloud" +} + +resource "google_compute_region_instance_template" "foobar" { + name = "tf-test-instance-template-%s" + machine_type = "e2-standard-4" + region = "us-central1" + + disk { + source_image = data.google_compute_image.my_image.self_link + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + availability_domain = 3 + } + + resource_policies = [google_compute_resource_policy.foo.self_link] + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} +`, policyName, suffix) +} + func testAccComputeRegionInstanceTemplate_reservationAffinityInstanceTemplate_nonSpecificReservation(templateName, consumeReservationType string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { diff --git a/website/docs/r/compute_instance.html.markdown b/website/docs/r/compute_instance.html.markdown index acd46bad24..4b63097d8e 100644 --- a/website/docs/r/compute_instance.html.markdown +++ b/website/docs/r/compute_instance.html.markdown @@ -493,6 +493,8 @@ specified, then this instance will have no external IPv6 Internet access. Struct * `instance_termination_action` - (Optional) Describe the type of termination action for VM. Can be `STOP` or `DELETE`. Read more on [here](https://cloud.google.com/compute/docs/instances/create-use-spot) +* `availability_domain` - (Optional) Specifies the availability domain to place the instance in. The value must be a number between 1 and the number of availability domains specified in the spread placement policy attached to the instance. + * `max_run_duration` - (Optional) The duration of the instance. Instance will run and be terminated after then, the termination action could be defined in `instance_termination_action`. Structure is [documented below](#nested_max_run_duration). diff --git a/website/docs/r/compute_instance_template.html.markdown b/website/docs/r/compute_instance_template.html.markdown index 88a604fa44..b73f4c0953 100644 --- a/website/docs/r/compute_instance_template.html.markdown +++ b/website/docs/r/compute_instance_template.html.markdown @@ -639,6 +639,8 @@ specified, then this instance will have no external IPv6 Internet access. Struct * `instance_termination_action` - (Optional) Describe the type of termination action for `SPOT` VM. Can be `STOP` or `DELETE`. Read more on [here](https://cloud.google.com/compute/docs/instances/create-use-spot) +* `availability_domain` - (Optional) Specifies the availability domain to place the instance in. The value must be a number between 1 and the number of availability domains specified in the spread placement policy attached to the instance. + * `max_run_duration` - (Optional) The duration of the instance. Instance will run and be terminated after then, the termination action could be defined in `instance_termination_action`. Structure is [documented below](#nested_max_run_duration). * `on_instance_stop_action` - (Optional) Specifies the action to be performed when the instance is terminated using `max_run_duration` and `STOP` `instance_termination_action`. Only support `true` `discard_local_ssd` at this point. Structure is [documented below](#nested_on_instance_stop_action). diff --git a/website/docs/r/compute_region_instance_template.html.markdown b/website/docs/r/compute_region_instance_template.html.markdown index 9377858dd0..5626debd1c 100644 --- a/website/docs/r/compute_region_instance_template.html.markdown +++ b/website/docs/r/compute_region_instance_template.html.markdown @@ -605,6 +605,8 @@ specified, then this instance will have no external IPv6 Internet access. Struct * `instance_termination_action` - (Optional) Describe the type of termination action for `SPOT` VM. Can be `STOP` or `DELETE`. Read more on [here](https://cloud.google.com/compute/docs/instances/create-use-spot) +* `availability_domain` - (Optional) Specifies the availability domain to place the instance in. The value must be a number between 1 and the number of availability domains specified in the spread placement policy attached to the instance. + * `host_error_timeout_seconds` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) Specifies the time in seconds for host error detection, the value must be within the range of [90, 330] with the increment of 30, if unset, the default behavior of host error recovery will be used. * `max_run_duration` - (Optional) The duration of the instance. Instance will run and be terminated after then, the termination action could be defined in `instance_termination_action`. Only support `DELETE` `instance_termination_action` at this point. Structure is [documented below](#nested_max_run_duration).