Skip to content

Implement multiple versions for regional instance groups #1809

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

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
12 changes: 6 additions & 6 deletions google/resource_compute_instance_group_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,10 @@ func resourceComputeInstanceGroupManagerRead(d *schema.ResourceData, meta interf
return nil
}

// Updates an instance group manager by applying an update strategy (REPLACE, RESTART) respecting a rolling update policy (availability settings,
// interval between updates, and particularly, the type of update PROACTIVE or OPPORTUNISTIC because updates performed by API are considered
// OPPORTUNISTIC by default)
func performUpdate(config *Config, id string, updateStrategy string, rollingUpdatePolicy *computeBeta.InstanceGroupManagerUpdatePolicy, versions []*computeBeta.InstanceGroupManagerVersion, project string, zone string) error {
// Updates an instance group manager by applying the update strategy (REPLACE, RESTART)
// and rolling update policy (PROACTIVE, OPPORTUNISTIC). Updates performed by API
// are OPPORTUNISTIC by default.
func performZoneUpdate(config *Config, id string, updateStrategy string, rollingUpdatePolicy *computeBeta.InstanceGroupManagerUpdatePolicy, versions []*computeBeta.InstanceGroupManagerVersion, project string, zone string) error {
if updateStrategy == "RESTART" {
managedInstances, err := config.clientComputeBeta.InstanceGroupManagers.ListManagedInstances(project, zone, id).Do()
if err != nil {
Expand Down Expand Up @@ -646,7 +646,7 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte

updateStrategy := d.Get("update_strategy").(string)
rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{}))
err = performUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, nil, project, zone)
err = performZoneUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, nil, project, zone)
d.SetPartial("instance_template")
}

Expand All @@ -655,7 +655,7 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte
updateStrategy := d.Get("update_strategy").(string)
rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{}))
versions := expandVersions(d.Get("version").([]interface{}))
err = performUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, versions, project, zone)
err = performZoneUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, versions, project, zone)
if err != nil {
return err
}
Expand Down
144 changes: 122 additions & 22 deletions google/resource_compute_region_instance_group_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,50 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource {

"instance_template": &schema.Schema{
Type: schema.TypeString,
Required: true,
Optional: true,
DiffSuppressFunc: compareSelfLinkRelativePaths,
},

"version": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

"instance_template": &schema.Schema{
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: compareSelfLinkRelativePaths,
},

"target_size": &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"fixed": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},

"percent": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100),
},
},
},
},
},
},
},

"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -242,6 +282,7 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met
NamedPorts: getNamedPortsBeta(d.Get("named_port").([]interface{})),
TargetPools: convertStringSet(d.Get("target_pools").(*schema.Set)),
AutoHealingPolicies: expandAutoHealingPolicies(d.Get("auto_healing_policies").([]interface{})),
Versions: expandVersions(d.Get("version").([]interface{})),
DistributionPolicy: expandDistributionPolicy(d.Get("distribution_policy_zones").(*schema.Set)),
// Force send TargetSize to allow size of 0.
ForceSendFields: []string{"TargetSize"},
Expand Down Expand Up @@ -315,6 +356,9 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta

d.Set("base_instance_name", manager.BaseInstanceName)
d.Set("instance_template", manager.InstanceTemplate)
if err := d.Set("version", flattenVersions(manager.Versions)); err != nil {
return err
}
d.Set("name", manager.Name)
d.Set("region", GetResourceNameFromSelfLink(manager.Region))
d.Set("description", manager.Description)
Expand All @@ -329,6 +373,11 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta
return err
}
d.Set("self_link", ConvertSelfLinkToV1(manager.SelfLink))
update_strategy, ok := d.GetOk("update_strategy")
if !ok {
update_strategy = "NONE"
}
d.Set("update_strategy", update_strategy.(string))

if d.Get("wait_for_instances").(bool) {
conf := resource.StateChangeConf{
Expand All @@ -346,6 +395,63 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta
return nil
}

// Updates an instance group manager by applying the update strategy (REPLACE, RESTART)
// and rolling update policy (PROACTIVE, OPPORTUNISTIC). Updates performed by API
// are OPPORTUNISTIC by default.
func performRegionUpdate(config *Config, id string, updateStrategy string, rollingUpdatePolicy *computeBeta.InstanceGroupManagerUpdatePolicy, versions []*computeBeta.InstanceGroupManagerVersion, project string, region string) error {
if updateStrategy == "RESTART" {
managedInstances, err := config.clientComputeBeta.RegionInstanceGroupManagers.ListManagedInstances(project, region, id).Do()
if err != nil {
return fmt.Errorf("Error getting region instance group managers instances: %s", err)
}

managedInstanceCount := len(managedInstances.ManagedInstances)
instances := make([]string, managedInstanceCount)
for i, v := range managedInstances.ManagedInstances {
instances[i] = v.Instance
}

recreateInstances := &computeBeta.RegionInstanceGroupManagersRecreateRequest{
Instances: instances,
}

op, err := config.clientComputeBeta.RegionInstanceGroupManagers.RecreateInstances(project, region, id, recreateInstances).Do()
if err != nil {
return fmt.Errorf("Error restarting region instance group managers instances: %s", err)
}

// Wait for the operation to complete
err = computeSharedOperationWaitTime(config.clientCompute, op, project, managedInstanceCount*4, "Restarting RegionInstanceGroupManagers instances")
if err != nil {
return err
}
}

if updateStrategy == "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: rollingUpdatePolicy,
Versions: versions,
}

op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Patch(project, region, id, manager).Do()
if err != nil {
return fmt.Errorf("Error updating region managed group instances: %s", err)
}

err = computeSharedOperationWait(config.clientCompute, op, project, "Updating region managed group instances")
if err != nil {
return err
}
}

return nil
}

func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

Expand Down Expand Up @@ -406,29 +512,23 @@ 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
}
updateStrategy := d.Get("update_strategy").(string)
rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{}))
err = performRegionUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, nil, project, region)
d.SetPartial("instance_template")
}

// If version changes then update
if d.HasChange("version") {
updateStrategy := d.Get("update_strategy").(string)
rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{}))
versions := expandVersions(d.Get("version").([]interface{}))
err = performRegionUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, versions, project, region)
if err != nil {
return err
}

d.SetPartial("instance_template")
d.SetPartial("version")
}

if d.HasChange("named_port") {
Expand Down
133 changes: 133 additions & 0 deletions google/resource_compute_region_instance_group_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func TestAccRegionInstanceGroupManager_rollingUpdatePolicy(t *testing.T) {
},
})
}

func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) {
t.Parallel()

Expand All @@ -262,6 +263,36 @@ func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) {
})
}

func TestAccRegionInstanceGroupManager_versions(t *testing.T) {
t.Parallel()

var manager computeBeta.InstanceGroupManager

primaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10))
canaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10))
igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRegionInstanceGroupManager_versions(primaryTemplate, canaryTemplate, igm),
Check: resource.ComposeTestCheckFunc(
testAccCheckRegionInstanceGroupManagerBetaExists("google_compute_region_instance_group_manager.igm-basic", &manager),
testAccCheckRegionInstanceGroupManagerVersions("google_compute_region_instance_group_manager.igm-basic", primaryTemplate, canaryTemplate),
),
},
resource.TestStep{
ResourceName: "google_compute_region_instance_group_manager.igm-basic",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccRegionInstanceGroupManager_autoHealingPolicies(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -477,6 +508,42 @@ func testAccCheckRegionInstanceGroupManagerNamedPorts(n string, np map[string]in
}
}

func testAccCheckRegionInstanceGroupManagerVersions(n string, primaryTemplate string, canaryTemplate string) 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.RegionInstanceGroupManagers.Get(config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do()
if err != nil {
return err
}

if len(manager.Versions) != 2 {
return fmt.Errorf("Expected # of versions to be 2, got %d", len(manager.Versions))
}

primaryVersion := manager.Versions[0]
if !strings.Contains(primaryVersion.InstanceTemplate, primaryTemplate) {
return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", primaryTemplate, primaryVersion.InstanceTemplate)
}

canaryVersion := manager.Versions[1]
if !strings.Contains(canaryVersion.InstanceTemplate, canaryTemplate) {
return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", canaryTemplate, canaryVersion.InstanceTemplate)
}

return nil
}
}

func testAccCheckRegionInstanceGroupManagerAutoHealingPolicies(n, hck string, initialDelaySec int64) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -954,6 +1021,72 @@ resource "google_compute_http_health_check" "zero" {
}
`, template, target, igm, hck)
}
func testAccRegionInstanceGroupManager_versions(primaryTemplate string, canaryTemplate string, igm string) string {
return fmt.Sprintf(`
resource "google_compute_instance_template" "igm-primary" {
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_instance_template" "igm-canary" {
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_region_instance_group_manager" "igm-basic" {
description = "Terraform test region instance group manager"
name = "%s"
base_instance_name = "igm-basic"
region = "us-central1"
target_size = 2

version {
name = "primary"
instance_template = "${google_compute_instance_template.igm-primary.self_link}"
}

version {
name = "canary"
instance_template = "${google_compute_instance_template.igm-canary.self_link}"
target_size {
fixed = 1
}
}
}
`, primaryTemplate, canaryTemplate, igm)
}

func testAccRegionInstanceGroupManager_distributionPolicy(template, igm string, zones []string) string {
return fmt.Sprintf(`
Expand Down
Loading