From d17c5a2a889db8b3af8d330c622c77b2cab5e285 Mon Sep 17 00:00:00 2001 From: Irena Shashchuk Date: Fri, 23 Mar 2018 16:51:30 -0400 Subject: [PATCH] rIGM rolling update --- ...resource_compute_instance_group_manager.go | 4 + ...e_compute_region_instance_group_manager.go | 93 ++++++++ ...pute_region_instance_group_manager_test.go | 217 ++++++++++++++++++ ...egion_instance_group_manager.html.markdown | 39 +++- 4 files changed, 351 insertions(+), 2 deletions(-) diff --git a/google/resource_compute_instance_group_manager.go b/google/resource_compute_instance_group_manager.go index 82bca515d94..62e8b72668d 100644 --- a/google/resource_compute_instance_group_manager.go +++ b/google/resource_compute_instance_group_manager.go @@ -870,6 +870,8 @@ func expandUpdatePolicy(configured []interface{}) *computeBeta.InstanceGroupMana } else { updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ Fixed: int64(data["max_surge_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, } } @@ -880,6 +882,8 @@ func expandUpdatePolicy(configured []interface{}) *computeBeta.InstanceGroupMana } else { updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ Fixed: int64(data["max_unavailable_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, } } diff --git a/google/resource_compute_region_instance_group_manager.go b/google/resource_compute_region_instance_group_manager.go index 81acd019ef7..bcf87538643 100644 --- a/google/resource_compute_region_instance_group_manager.go +++ b/google/resource_compute_region_instance_group_manager.go @@ -19,6 +19,7 @@ var RegionInstanceGroupManagerBaseApiVersion = v1 var RegionInstanceGroupManagerVersionedFeatures = []Feature{ Feature{Version: v0beta, Item: "auto_healing_policies"}, Feature{Version: v0beta, Item: "distribution_policy_zones"}, + Feature{Version: v0beta, Item: "rolling_update_policy"}, } func resourceComputeRegionInstanceGroupManager() *schema.Resource { @@ -107,6 +108,13 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Computed: true, }, + "update_strategy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "NONE", + ValidateFunc: validation.StringInSlice([]string{"NONE", "ROLLING_UPDATE"}, false), + }, + "target_pools": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -162,6 +170,61 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { DiffSuppressFunc: compareSelfLinkOrResourceName, }, }, + + "rolling_update_policy": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "minimal_action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"RESTART", "REPLACE"}, false), + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"OPPORTUNISTIC", "PROACTIVE"}, false), + }, + + "max_surge_fixed": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + ConflictsWith: []string{"rolling_update_policy.0.max_surge_percent"}, + }, + + "max_surge_percent": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"rolling_update_policy.0.max_surge_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "max_unavailable_fixed": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + ConflictsWith: []string{"rolling_update_policy.0.max_unavailable_percent"}, + }, + + "max_unavailable_percent": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"rolling_update_policy.0.max_unavailable_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "min_ready_sec": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 3600), + }, + }, + }, + }, }, } } @@ -175,6 +238,10 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met return err } + if _, ok := d.GetOk("rolling_update_policy"); d.Get("update_strategy") == "ROLLING_UPDATE" && !ok { + return fmt.Errorf("[rolling_update_policy] must be set when 'update_strategy' is set to 'ROLLING_UPDATE'") + } + manager := &computeBeta.InstanceGroupManager{ Name: d.Get("name").(string), Description: d.Get("description").(string), @@ -323,6 +390,10 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met d.Partial(true) + if _, ok := d.GetOk("rolling_update_policy"); d.Get("update_strategy") == "ROLLING_UPDATE" && !ok { + return fmt.Errorf("[rolling_update_policy] must be set when 'update_strategy' is set to 'ROLLING_UPDATE'") + } + if d.HasChange("target_pools") { targetPools := convertStringSet(d.Get("target_pools").(*schema.Set)) @@ -405,6 +476,28 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met return err } + if d.Get("update_strategy").(string) == "ROLLING_UPDATE" { + // UpdatePolicy is set for InstanceGroupManager on update only, because it is only relevant for `Patch` calls. + // Other tools(gcloud and UI) capable of executing the same `ROLLING UPDATE` call + // expect those values to be provided by user as part of the call + // or provide their own defaults without respecting what was previously set on UpdateManager. + // To follow the same logic, we provide policy values on relevant update change only. + manager := &computeBeta.InstanceGroupManager{ + UpdatePolicy: expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{})), + } + + op, err = config.clientComputeBeta.RegionInstanceGroupManagers.Patch( + project, region, d.Id(), manager).Do() + if err != nil { + return fmt.Errorf("Error updating managed group instances: %s", err) + } + + err = computeSharedOperationWait(config.clientCompute, op, project, "Updating managed group instances") + if err != nil { + return err + } + } + d.SetPartial("instance_template") } diff --git a/google/resource_compute_region_instance_group_manager_test.go b/google/resource_compute_region_instance_group_manager_test.go index 9bf705ca87c..fab041ee9c5 100644 --- a/google/resource_compute_region_instance_group_manager_test.go +++ b/google/resource_compute_region_instance_group_manager_test.go @@ -151,6 +151,85 @@ func TestAccRegionInstanceGroupManager_updateLifecycle(t *testing.T) { }) } +func TestAccRegionInstanceGroupManager_updateStrategy(t *testing.T) { + t.Parallel() + + var manager compute.InstanceGroupManager + igm := 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: testAccRegionInstanceGroupManager_updateStrategy(igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckRegionInstanceGroupManagerExists( + "google_compute_region_instance_group_manager.igm-update-strategy", &manager), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-update-strategy", "update_strategy", "NONE"), + ), + }, + }, + }) +} + +func TestAccRegionInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { + t.Parallel() + + var manager computeBeta.InstanceGroupManager + + igm := 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: testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckRegionInstanceGroupManagerBetaExists( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", &manager), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "2"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), + ), + }, + resource.TestStep{ + Config: testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckRegionInstanceGroupManagerBetaExists( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", &manager), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "0"), + resource.TestCheckResourceAttr( + "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "10"), + testAccCheckInstanceGroupManagerRollingUpdatePolicy( + &manager, "google_compute_region_instance_group_manager.igm-rolling-update-policy"), + ), + }, + }, + }) +} func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() @@ -901,3 +980,141 @@ resource "google_compute_region_instance_group_manager" "igm-basic" { } `, template, igm, strings.Join(zones, "\",\"")) } + +func testAccRegionInstanceGroupManager_updateStrategy(igm string) string { + return fmt.Sprintf(` +resource "google_compute_instance_template" "igm-update-strategy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "debian-cloud/debian-8-jessie-v20160803" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-update-strategy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update-strategy.self_link}" + base_instance_name = "rigm-update-strategy" + region = "us-central1" + target_size = 2 + update_strategy = "NONE" + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm string) string { + return fmt.Sprintf(` +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "debian-cloud/debian-8-jessie-v20160803" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + target_size = 4 + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + update_strategy = "ROLLING_UPDATE" + + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm string) string { + return fmt.Sprintf(` +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "debian-cloud/debian-8-jessie-v20160803" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + target_size = 3 + update_strategy = "ROLLING_UPDATE" + + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 0 + min_ready_sec = 10 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} diff --git a/website/docs/r/compute_region_instance_group_manager.html.markdown b/website/docs/r/compute_region_instance_group_manager.html.markdown index 0ba202bb1dd..55b392fad0c 100644 --- a/website/docs/r/compute_region_instance_group_manager.html.markdown +++ b/website/docs/r/compute_region_instance_group_manager.html.markdown @@ -87,6 +87,12 @@ The following arguments are supported: * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. +* `update_strategy` - (Optional, Default `"NONE"`) If the `instance_template` + resource is modified, a value of `"NONE"` will prevent any of the managed + instances from being restarted by Terraform. A value of `"ROLLING_UPDATE"` + is supported as [Beta feature]. A value of `"ROLLING_UPDATE"` requires + `rolling_update_policy` block to be set + * `target_size` - (Optional) The target number of running instances for this managed instance group. This value should always be explicitly set unless this resource is attached to an autoscaler, in which case it should never be set. Defaults to `0`. @@ -104,17 +110,46 @@ The following arguments are supported: * `auto_healing_policies` - (Optional, [Beta](/docs/providers/google/index.html#beta-features)) The autohealing policies for this managed instance group. You can specify only one value. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/creating-groups-of-managed-instances#monitoring_groups). +* `rolling_update_policy` - (Optional, [Beta](/docs/providers/google/index.html#beta-features)) The update policy for this managed instance group. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/updating-managed-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/rest/beta/regionInstanceGroupManagers/patch) + * `distribution_policy_zones` - (Optional, [Beta](/docs/providers/google/index.html#beta-features)) The distribution policy for this managed instance group. You can specify one or more values. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/distributing-instances-with-regional-instance-groups#selectingzones). +The **rolling_update_policy** block supports: + +```hcl +rolling_update_policy{ + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_percent = 20 + max_unavailable_fixed = 2 + min_ready_sec = 50 +} +``` + +* `minimal_action` - (Required) - Minimal action to be taken on an instance. Valid values are `"RESTART"`, `"REPLACE"` + +* `type` - (Required) - The type of update. Valid values are `"OPPORTUNISTIC"`, `"PROACTIVE"` -The `named_port` block supports: (Include a `named_port` block for each named-port required). +* `max_surge_fixed` - (Optional), The maximum number of instances that can be created above the specified targetSize during the update process. Conflicts with `max_surge_percent`. It has to be either 0 or at least equal to the number of zones. If fixed values are used, at least one of `max_unavailable_fixed` or `max_surge_fixed` must be greater than 0. + +* `max_surge_percent` - (Optional), The maximum number of instances(calculated as percentage) that can be created above the specified targetSize during the update process. Conflicts with `max_surge_fixed`. Percent value is only allowed for regional managed instance groups with size at least 10. + +* `max_unavailable_fixed` - (Optional), The maximum number of instances that can be unavailable during the update process. Conflicts with `max_unavailable_percent`. It has to be either 0 or at least equal to the number of zones. If fixed values are used, at least one of `max_unavailable_fixed` or `max_surge_fixed` must be greater than 0. + +* `max_unavailable_percent` - (Optional), The maximum number of instances(calculated as percentage) that can be unavailable during the update process. Conflicts with `max_unavailable_fixed`. Percent value is only allowed for regional managed instance groups with size at least 10. + +* `min_ready_sec` - (Optional), Minimum number of seconds to wait for after a newly created instance becomes available. This value must be from range [0, 3600] +- - - + +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: +The **auto_healing_policies** block supports: * `health_check` - (Required) The health check resource that signals autohealing.