Skip to content

Commit

Permalink
Rolling update support for instance group manager (hashicorp#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
ishashchuk authored and Ashish Amarnath committed Mar 20, 2018
1 parent eefbf65 commit 6a46b24
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 6 deletions.
129 changes: 127 additions & 2 deletions google/resource_compute_instance_group_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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),
},
},
},
},
},
}
}
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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")
}

Expand Down Expand Up @@ -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 {
Expand Down
193 changes: 193 additions & 0 deletions google/resource_compute_instance_group_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package google
import (
"fmt"
"reflect"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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" {
Expand Down
Loading

0 comments on commit 6a46b24

Please sign in to comment.