diff --git a/google/resource_compute_instance_group_manager.go b/google/resource_compute_instance_group_manager.go index 9fdd0122f8a..4717e6f6951 100644 --- a/google/resource_compute_instance_group_manager.go +++ b/google/resource_compute_instance_group_manager.go @@ -7,12 +7,14 @@ import ( "time" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" ) var InstanceGroupManagerBaseApiVersion = v1 +var InstanceGroupManagerVersionedFeatures = []Feature{Feature{Version: v0beta, Item: "auto_healing_policies"}} func resourceComputeInstanceGroupManager() *schema.Resource { return &schema.Resource{ @@ -116,6 +118,27 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Computed: true, Optional: true, }, + + "auto_healing_policies": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "health_check": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: compareSelfLinkRelativePaths, + }, + + "initial_delay_sec": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(0, 3600), + }, + }, + }, + }, }, } } @@ -147,7 +170,7 @@ func getNamedPortsBeta(nps []interface{}) []*computeBeta.NamedPort { } func resourceComputeInstanceGroupManagerCreate(d *schema.ResourceData, meta interface{}) error { - computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, []Feature{}) + computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, InstanceGroupManagerVersionedFeatures) config := meta.(*Config) project, err := getProject(d, config) @@ -192,6 +215,10 @@ func resourceComputeInstanceGroupManagerCreate(d *schema.ResourceData, meta inte return fmt.Errorf("Update strategy must be \"NONE\" or \"RESTART\"") } + if v, ok := d.GetOk("auto_healing_policies"); ok { + manager.AutoHealingPolicies = expandAutoHealingPolicies(v.([]interface{})) + } + log.Printf("[DEBUG] InstanceGroupManager insert request: %#v", manager) var op interface{} switch computeApiVersion { @@ -246,7 +273,7 @@ func flattenNamedPortsBeta(namedPorts []*computeBeta.NamedPort) []map[string]int } func resourceComputeInstanceGroupManagerRead(d *schema.ResourceData, meta interface{}) error { - computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, []Feature{}) + computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, InstanceGroupManagerVersionedFeatures) config := meta.(*Config) project, err := getProject(d, config) @@ -354,12 +381,13 @@ func resourceComputeInstanceGroupManagerRead(d *schema.ResourceData, meta interf update_strategy = "RESTART" } d.Set("update_strategy", update_strategy.(string)) + d.Set("auto_healing_policies", flattenAutoHealingPolicies(manager.AutoHealingPolicies)) return nil } func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta interface{}) error { - computeApiVersion := getComputeApiVersionUpdate(d, InstanceGroupManagerBaseApiVersion, []Feature{}, []Feature{}) + computeApiVersion := getComputeApiVersionUpdate(d, InstanceGroupManagerBaseApiVersion, InstanceGroupManagerVersionedFeatures, []Feature{}) config := meta.(*Config) project, err := getProject(d, config) @@ -604,13 +632,36 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte d.SetPartial("target_size") } + // We will always be in v0beta inside this conditional + if d.HasChange("auto_healing_policies") { + setAutoHealingPoliciesRequest := &computeBeta.InstanceGroupManagersSetAutoHealingRequest{} + if v, ok := d.GetOk("auto_healing_policies"); ok { + setAutoHealingPoliciesRequest.AutoHealingPolicies = expandAutoHealingPolicies(v.([]interface{})) + } + + op, err := config.clientComputeBeta.InstanceGroupManagers.SetAutoHealingPolicies( + project, d.Get("zone").(string), d.Id(), setAutoHealingPoliciesRequest).Do() + + if err != nil { + return fmt.Errorf("Error updating AutoHealingPolicies: %s", err) + } + + // Wait for the operation to complete + err = computeSharedOperationWaitZone(config, op, project, d.Get("zone").(string), "Updating AutoHealingPolicies") + if err != nil { + return err + } + + d.SetPartial("auto_healing_policies") + } + d.Partial(false) return resourceComputeInstanceGroupManagerRead(d, meta) } func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta interface{}) error { - computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, []Feature{}) + computeApiVersion := getComputeApiVersion(d, InstanceGroupManagerBaseApiVersion, InstanceGroupManagerVersionedFeatures) config := meta.(*Config) project, err := getProject(d, config) @@ -686,3 +737,30 @@ func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta inte d.SetId("") return nil } + +func expandAutoHealingPolicies(configured []interface{}) []*computeBeta.InstanceGroupManagerAutoHealingPolicy { + autoHealingPolicies := make([]*computeBeta.InstanceGroupManagerAutoHealingPolicy, 0, len(configured)) + for _, raw := range configured { + data := raw.(map[string]interface{}) + autoHealingPolicy := computeBeta.InstanceGroupManagerAutoHealingPolicy{ + HealthCheck: data["health_check"].(string), + InitialDelaySec: int64(data["initial_delay_sec"].(int)), + } + + autoHealingPolicies = append(autoHealingPolicies, &autoHealingPolicy) + } + return autoHealingPolicies +} + +func flattenAutoHealingPolicies(autoHealingPolicies []*computeBeta.InstanceGroupManagerAutoHealingPolicy) []map[string]interface{} { + autoHealingPoliciesSchema := make([]map[string]interface{}, 0, len(autoHealingPolicies)) + for _, autoHealingPolicy := range autoHealingPolicies { + data := map[string]interface{}{ + "health_check": autoHealingPolicy.HealthCheck, + "initial_delay_sec": autoHealingPolicy.InitialDelaySec, + } + + autoHealingPoliciesSchema = append(autoHealingPoliciesSchema, data) + } + return autoHealingPoliciesSchema +} diff --git a/google/resource_compute_instance_group_manager_test.go b/google/resource_compute_instance_group_manager_test.go index c8a02b512a7..953cf6f6837 100644 --- a/google/resource_compute_instance_group_manager_test.go +++ b/google/resource_compute_instance_group_manager_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" "github.com/hashicorp/terraform/helper/acctest" @@ -185,6 +186,58 @@ func TestAccInstanceGroupManager_separateRegions(t *testing.T) { }) } +func TestAccInstanceGroupManager_autoHealingPolicies(t *testing.T) { + var manager computeBeta.InstanceGroupManager + + template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceGroupManagerBetaExists( + "google_compute_instance_group_manager.igm-basic", &manager), + testAccCheckInstanceGroupManagerAutoHealingPolicies("google_compute_instance_group_manager.igm-basic", hck, 10), + ), + }, + }, + }) +} + +// This test is to make sure that a single version resource can link to a versioned resource +// without perpetual diffs because the self links mismatch. +// Once auto_healing_policies is no longer beta, we will need to use a new field or resource +// with Beta fields. +func TestAccInstanceGroupManager_selfLinkStability(t *testing.T) { + var manager computeBeta.InstanceGroupManager + + template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + autoscaler := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccInstanceGroupManager_selfLinkStability(template, target, igm, hck, autoscaler), + Check: testAccCheckInstanceGroupManagerBetaExists( + "google_compute_instance_group_manager.igm-basic", &manager), + }, + }, + }) +} + func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -231,6 +284,35 @@ func testAccCheckInstanceGroupManagerExists(n string, manager *compute.InstanceG } } +func testAccCheckInstanceGroupManagerBetaExists(n string, manager *computeBeta.InstanceGroupManager) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientComputeBeta.InstanceGroupManagers.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("InstanceGroupManager not found") + } + + *manager = *found + + return nil + } +} + func testAccCheckInstanceGroupManagerUpdated(n string, size int64, targetPool string, template string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -307,6 +389,41 @@ func testAccCheckInstanceGroupManagerNamedPorts(n string, np map[string]int64, i } } +func testAccCheckInstanceGroupManagerAutoHealingPolicies(n, hck string, initialDelaySec int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + manager, err := config.clientComputeBeta.InstanceGroupManagers.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return err + } + + if len(manager.AutoHealingPolicies) != 1 { + return fmt.Errorf("Expected # of auto healing policies to be 1, got %d", len(manager.AutoHealingPolicies)) + } + autoHealingPolicy := manager.AutoHealingPolicies[0] + + if !strings.Contains(autoHealingPolicy.HealthCheck, hck) { + return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", hck, autoHealingPolicy.HealthCheck) + } + + if autoHealingPolicy.InitialDelaySec != initialDelaySec { + return fmt.Errorf("Expected auto healing policy inital delay to be %d, got %d", initialDelaySec, autoHealingPolicy.InitialDelaySec) + } + return nil + } +} + func testAccCheckInstanceGroupManagerTemplateTags(n string, tags []string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -704,6 +821,128 @@ func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { `, igm1, igm2) } +func testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck string) string { + return fmt.Sprintf(` +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "debian-cloud/debian-8-jessie-v20160803" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +// This test is to make sure that a single version resource can link to a versioned resource +// without perpetual diffs because the self links mismatch. +// Once auto_healing_policies is no longer beta, we will need to use a new field or resource +// with Beta fields. +func testAccInstanceGroupManager_selfLinkStability(template, target, igm, hck, autoscaler string) string { + return fmt.Sprintf(` +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "debian-cloud/debian-8-jessie-v20160803" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_autoscaler" "foobar" { + name = "%s" + zone = "us-central1-c" + target = "${google_compute_instance_group_manager.igm-basic.self_link}" + autoscaling_policy = { + max_replicas = 10 + min_replicas = 1 + cooldown_period = 60 + cpu_utilization = { + target = 0.5 + } + } +} +`, template, target, igm, hck, autoscaler) +} + func resourceSplitter(resource string) string { splits := strings.Split(resource, "/") diff --git a/website/docs/r/compute_instance_group_manager.html.markdown b/website/docs/r/compute_instance_group_manager.html.markdown index dcfdb367ee4..915387978fd 100644 --- a/website/docs/r/compute_instance_group_manager.html.markdown +++ b/website/docs/r/compute_instance_group_manager.html.markdown @@ -81,12 +81,24 @@ The following arguments are supported: instances in the group are added. Updating the target pools attribute does not affect existing instances. +--- + +* `auto_healing_policies` - (Optional, Beta) The autohealing policies for this managed instance +group. You can specify only one value. Structure is documented below. + The `named_port` block supports: (Include a `named_port` block for each named-port required). * `name` - (Required) The name of the port. * `port` - (Required) The port number. +The `auto_healing_policies` block supports: + +* `health_check` - (Required) The health check that signals autohealing. + +* `initial_delay_sec` - (Required) The number of seconds that the managed instance group waits before + it applies autohealing policies to new instances or recently recreated instances. Between 0 and 3600. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are