From 6a46b2480b01cc7cbec8959202705a27558fb1ec Mon Sep 17 00:00:00 2001 From: ishashchuk Date: Thu, 15 Mar 2018 14:10:09 -0400 Subject: [PATCH] Rolling update support for instance group manager (#1137) --- ...resource_compute_instance_group_manager.go | 129 +++++++++++- ...rce_compute_instance_group_manager_test.go | 193 ++++++++++++++++++ ...mpute_instance_group_manager.html.markdown | 38 +++- 3 files changed, 354 insertions(+), 6 deletions(-) diff --git a/google/resource_compute_instance_group_manager.go b/google/resource_compute_instance_group_manager.go index 00a7bcab849..f220bbb29a5 100644 --- a/google/resource_compute_instance_group_manager.go +++ b/google/resource_compute_instance_group_manager.go @@ -14,7 +14,10 @@ import ( ) var InstanceGroupManagerBaseApiVersion = v1 -var InstanceGroupManagerVersionedFeatures = []Feature{Feature{Version: v0beta, Item: "auto_healing_policies"}} +var InstanceGroupManagerVersionedFeatures = []Feature{ + Feature{Version: v0beta, Item: "auto_healing_policies"}, + Feature{Version: v0beta, Item: "rolling_update_policy"}, +} func resourceComputeInstanceGroupManager() *schema.Resource { return &schema.Resource{ @@ -102,7 +105,7 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Type: schema.TypeString, Optional: true, Default: "RESTART", - ValidateFunc: validation.StringInSlice([]string{"RESTART", "NONE"}, false), + ValidateFunc: validation.StringInSlice([]string{"RESTART", "NONE", "ROLLING_UPDATE"}, false), }, "target_pools": &schema.Schema{ @@ -140,6 +143,60 @@ func resourceComputeInstanceGroupManager() *schema.Resource { }, }, }, + "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: 1, + 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: 1, + 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), + }, + }, + }, + }, }, } } @@ -184,6 +241,10 @@ func resourceComputeInstanceGroupManagerCreate(d *schema.ResourceData, meta inte 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'") + } + // Build the parameter manager := &computeBeta.InstanceGroupManager{ Name: d.Get("name").(string), @@ -380,6 +441,10 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte 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 target_pools changes then update if d.HasChange("target_pools") { targetPools := convertStringSet(d.Get("target_pools").(*schema.Set)) @@ -536,6 +601,28 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte } } + 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.InstanceGroupManagers.Patch( + project, zone, 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") } @@ -732,6 +819,44 @@ func expandAutoHealingPolicies(configured []interface{}) []*computeBeta.Instance return autoHealingPolicies } +func expandUpdatePolicy(configured []interface{}) *computeBeta.InstanceGroupManagerUpdatePolicy { + updatePolicy := &computeBeta.InstanceGroupManagerUpdatePolicy{} + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + updatePolicy.MinimalAction = data["minimal_action"].(string) + updatePolicy.Type = data["type"].(string) + + // percent and fixed values are conflicting + // when the percent values are set, the fixed values will be ignored + if v := data["max_surge_percent"]; v.(int) > 0 { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + } + } else { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_surge_fixed"].(int)), + } + } + + if v := data["max_unavailable_percent"]; v.(int) > 0 { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + } + } else { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_unavailable_fixed"].(int)), + } + } + + if v, ok := data["min_ready_sec"]; ok { + updatePolicy.MinReadySec = int64(v.(int)) + } + } + return updatePolicy +} + func flattenAutoHealingPolicies(autoHealingPolicies []*computeBeta.InstanceGroupManagerAutoHealingPolicy) []map[string]interface{} { autoHealingPoliciesSchema := make([]map[string]interface{}, 0, len(autoHealingPolicies)) for _, autoHealingPolicy := range autoHealingPolicies { diff --git a/google/resource_compute_instance_group_manager_test.go b/google/resource_compute_instance_group_manager_test.go index 1acd2411939..80636e89373 100644 --- a/google/resource_compute_instance_group_manager_test.go +++ b/google/resource_compute_instance_group_manager_test.go @@ -3,6 +3,7 @@ package google import ( "fmt" "reflect" + "strconv" "strings" "testing" @@ -186,6 +187,62 @@ func TestAccInstanceGroupManager_updateStrategy(t *testing.T) { }) } +func TestAccInstanceGroupManager_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: testAccInstanceGroupManager_rollingUpdatePolicy(igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceGroupManagerBetaExists( + "google_compute_instance_group_manager.igm-rolling-update-policy", &manager), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_percent", "50"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_percent", "50"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), + ), + }, + resource.TestStep{ + Config: testAccInstanceGroupManager_rollingUpdatePolicy2(igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceGroupManagerBetaExists( + "google_compute_instance_group_manager.igm-rolling-update-policy", &manager), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "2"), + resource.TestCheckResourceAttr( + "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), + testAccCheckInstanceGroupManagerRollingUpdatePolicy( + &manager, "google_compute_instance_group_manager.igm-rolling-update-policy"), + ), + }, + }, + }) +} + func TestAccInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() @@ -521,6 +578,50 @@ func testAccCheckInstanceGroupManagerUpdateStrategy(n, strategy string) resource } } +func testAccCheckInstanceGroupManagerRollingUpdatePolicy(manager *computeBeta.InstanceGroupManager, resource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs := s.RootModule().Resources[resource] + + updatePolicy := manager.UpdatePolicy + + surgeFixed, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_surge_fixed"], 10, 64) + if updatePolicy.MaxSurge.Fixed != surgeFixed { + return fmt.Errorf("Expected update policy MaxSurge to be %d, got %d", surgeFixed, updatePolicy.MaxSurge.Fixed) + } + + surgePercent, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_surge_percent"], 10, 64) + if updatePolicy.MaxSurge.Percent != surgePercent { + return fmt.Errorf("Expected update policy MaxSurge to be %d, got %d", surgePercent, updatePolicy.MaxSurge.Percent) + } + + unavailableFixed, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_unavailable_fixed"], 10, 64) + if updatePolicy.MaxUnavailable.Fixed != unavailableFixed { + return fmt.Errorf("Expected update policy MaxUnavailable to be %d, got %d", unavailableFixed, updatePolicy.MaxUnavailable.Fixed) + } + + unavailablePercent, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_unavailable_percent"], 10, 64) + if updatePolicy.MaxUnavailable.Percent != unavailablePercent { + return fmt.Errorf("Expected update policy MaxUnavailable to be %d, got %d", unavailablePercent, updatePolicy.MaxUnavailable.Percent) + } + + policyType := rs.Primary.Attributes["rolling_update_policy.0.type"] + if updatePolicy.Type != policyType { + return fmt.Errorf("Expected update policy Type to be \"%s\", got \"%s\"", policyType, updatePolicy.Type) + } + + policyAction := rs.Primary.Attributes["rolling_update_policy.0.minimal_action"] + if updatePolicy.MinimalAction != policyAction { + return fmt.Errorf("Expected update policy MinimalAction to be \"%s\", got \"%s\"", policyAction, updatePolicy.MinimalAction) + } + + minReadySec, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.min_ready_sec"], 10, 64) + if updatePolicy.MinReadySec != minReadySec { + return fmt.Errorf("Expected update policy MinReadySec to be %d, got %d", minReadySec, updatePolicy.MinReadySec) + } + return nil + } +} + func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) string { return fmt.Sprintf(` resource "google_compute_instance_template" "igm-basic" { @@ -828,6 +929,98 @@ func testAccInstanceGroupManager_updateStrategy(igm string) string { }`, igm) } +func testAccInstanceGroupManager_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_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" + zone = "us-central1-c" + target_size = 3 + update_strategy = "ROLLING_UPDATE" + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_percent = 50 + max_unavailable_percent = 50 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_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_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" + zone = "us-central1-c" + target_size = 3 + 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 testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { return fmt.Sprintf(` resource "google_compute_instance_template" "igm-basic" { diff --git a/website/docs/r/compute_instance_group_manager.html.markdown b/website/docs/r/compute_instance_group_manager.html.markdown index e6c6ebed7b7..97967511f18 100644 --- a/website/docs/r/compute_instance_group_manager.html.markdown +++ b/website/docs/r/compute_instance_group_manager.html.markdown @@ -90,8 +90,8 @@ The following arguments are supported: * `update_strategy` - (Optional, Default `"RESTART"`) 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 `"RESTART"` will - restart all of the instances at once. In the future, as the GCE API matures - we will support `"ROLLING_UPDATE"` as well. + restart all of the instances at once. `"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 @@ -106,13 +106,43 @@ 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). -The `named_port` block supports: (Include a `named_port` block for each named-port required). +* `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/instanceGroupManagers/patch) + +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"` + +* `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`. If neither is set, defaults to 1 + +* `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`. + +* `max_unavailable_fixed` - (Optional), The maximum number of instances that can be unavailable during the update process. Conflicts with `max_unavailable_percent`. If neither is set, defaults to 1 + +* `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`. + +* `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.