Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rolling update support for instance group manager #1137

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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