From bd84b37a6bf41cbad08ec507c60a17e670c1088b Mon Sep 17 00:00:00 2001 From: Arcturus Date: Wed, 14 Apr 2021 22:26:19 +0800 Subject: [PATCH] Support health extension for rolling ugrade mode (#9136) * Support health extension for automatic ugrade mode * Fix * Resolve comments * Fix test * terrafmt * resolve comments * terrafmt * gofmt * minor change * rename one test case to align with others * fix test case * fixing test cases --- ...rtual_machine_scale_set_extensions_test.go | 152 ++++++++++++++++ ...inux_virtual_machine_scale_set_resource.go | 21 ++- .../compute/virtual_machine_scale_set.go | 21 ++- ...rtual_machine_scale_set_extensions_test.go | 165 ++++++++++++++++++ ...dows_virtual_machine_scale_set_resource.go | 21 ++- 5 files changed, 360 insertions(+), 20 deletions(-) diff --git a/azurerm/internal/services/compute/linux_virtual_machine_scale_set_extensions_test.go b/azurerm/internal/services/compute/linux_virtual_machine_scale_set_extensions_test.go index a869300a9aa1..2c6cf44ce42b 100644 --- a/azurerm/internal/services/compute/linux_virtual_machine_scale_set_extensions_test.go +++ b/azurerm/internal/services/compute/linux_virtual_machine_scale_set_extensions_test.go @@ -157,6 +157,36 @@ func TestAccLinuxVirtualMachineScaleSet_extensionsUpdate(t *testing.T) { }) } +func TestAccLinuxVirtualMachineScaleSet_extensionsRollingUpgradeWithHealthExtension(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine_scale_set", "test") + r := LinuxVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.extensionsRollingUpgradeWithHealthExtension(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password", "extension.0.protected_settings"), + }) +} + +func TestAccLinuxVirtualMachineScaleSet_extensionsAutomaticUpgradeWithHealthExtension(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine_scale_set", "test") + r := LinuxVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.extensionsAutomaticUpgradeWithHealthExtension(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password", "extension.0.protected_settings"), + }) +} + func TestAccLinuxVirtualMachineScaleSet_extensionWithTimeBudget(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine_scale_set", "test") r := LinuxVirtualMachineScaleSetResource{} @@ -623,6 +653,128 @@ resource "azurerm_linux_virtual_machine_scale_set" "test" { `, r.template(data), data.RandomInteger) } +func (r LinuxVirtualMachineScaleSetResource) extensionsRollingUpgradeWithHealthExtension(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s +provider "azurerm" { + features {} +} +resource "azurerm_linux_virtual_machine_scale_set" "test" { + name = "acctestvmss-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_F2" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + disable_password_authentication = false + upgrade_mode = "Rolling" + rolling_upgrade_policy { + max_batch_instance_percent = 21 + max_unhealthy_instance_percent = 22 + max_unhealthy_upgraded_instance_percent = 23 + pause_time_between_batches = "PT30S" + } + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + network_interface { + name = "example" + primary = true + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + extension { + name = "HealthExtension" + publisher = "Microsoft.ManagedServices" + type = "ApplicationHealthLinux" + type_handler_version = "1.0" + auto_upgrade_minor_version = true + settings = jsonencode({ + protocol = "https" + port = 443 + }) + } + tags = { + accTest = "true" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r LinuxVirtualMachineScaleSetResource) extensionsAutomaticUpgradeWithHealthExtension(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s +provider "azurerm" { + features {} +} +resource "azurerm_linux_virtual_machine_scale_set" "test" { + name = "acctestvmss-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_F2" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + disable_password_authentication = false + upgrade_mode = "Automatic" + automatic_os_upgrade_policy { + disable_automatic_rollback = true + enable_automatic_os_upgrade = true + } + rolling_upgrade_policy { + max_batch_instance_percent = 21 + max_unhealthy_instance_percent = 22 + max_unhealthy_upgraded_instance_percent = 23 + pause_time_between_batches = "PT30S" + } + source_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + network_interface { + name = "example" + primary = true + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + extension { + name = "HealthExtension" + publisher = "Microsoft.ManagedServices" + type = "ApplicationHealthLinux" + type_handler_version = "1.0" + auto_upgrade_minor_version = true + settings = jsonencode({ + protocol = "https" + port = 443 + }) + } + tags = { + accTest = "true" + } +} +`, r.template(data), data.RandomInteger) +} + func (r LinuxVirtualMachineScaleSetResource) extensionWithTimeBudget(data acceptance.TestData, duration string) string { template := r.template(data) return fmt.Sprintf(` diff --git a/azurerm/internal/services/compute/linux_virtual_machine_scale_set_resource.go b/azurerm/internal/services/compute/linux_virtual_machine_scale_set_resource.go index bcbe679df140..0cc172ec9ba5 100644 --- a/azurerm/internal/services/compute/linux_virtual_machine_scale_set_resource.go +++ b/azurerm/internal/services/compute/linux_virtual_machine_scale_set_resource.go @@ -349,10 +349,6 @@ func resourceLinuxVirtualMachineScaleSetCreate(d *schema.ResourceData, meta inte return fmt.Errorf("An `automatic_os_upgrade_policy` block cannot be specified when `upgrade_mode` is not set to `Automatic`") } - if upgradeMode == compute.Automatic && len(automaticOSUpgradePolicyRaw) > 0 && healthProbeId == "" { - return fmt.Errorf("`healthProbeId` must be set when `upgrade_mode` is set to %q and `automatic_os_upgrade_policy` block exists", string(upgradeMode)) - } - shouldHaveRollingUpgradePolicy := upgradeMode == compute.Automatic || upgradeMode == compute.Rolling if !shouldHaveRollingUpgradePolicy && len(rollingUpgradePolicyRaw) > 0 { return fmt.Errorf("A `rolling_upgrade_policy` block cannot be specified when `upgrade_mode` is set to %q", string(upgradeMode)) @@ -419,8 +415,9 @@ func resourceLinuxVirtualMachineScaleSetCreate(d *schema.ResourceData, meta inte }, } + hasHealthExtension := false if vmExtensionsRaw, ok := d.GetOk("extension"); ok { - virtualMachineProfile.ExtensionProfile, err = expandVirtualMachineScaleSetExtensions(vmExtensionsRaw.([]interface{})) + virtualMachineProfile.ExtensionProfile, hasHealthExtension, err = expandVirtualMachineScaleSetExtensions(vmExtensionsRaw.([]interface{})) if err != nil { return err } @@ -433,6 +430,18 @@ func resourceLinuxVirtualMachineScaleSetCreate(d *schema.ResourceData, meta inte virtualMachineProfile.ExtensionProfile.ExtensionsTimeBudget = utils.String(v.(string)) } + // otherwise the service return the error: + // Automatic OS Upgrade is not supported for this Virtual Machine Scale Set because a health probe or health extension was not specified. + if upgradeMode == compute.Automatic && len(automaticOSUpgradePolicyRaw) > 0 && (healthProbeId == "" && !hasHealthExtension) { + return fmt.Errorf("`health_probe_id` must be set or a health extension must be specified when `upgrade_mode` is set to %q and `automatic_os_upgrade_policy` block exists", string(upgradeMode)) + } + + // otherwise the service return the error: + // Rolling Upgrade mode is not supported for this Virtual Machine Scale Set because a health probe or health extension was not provided. + if upgradeMode == compute.Rolling && (healthProbeId == "" && !hasHealthExtension) { + return fmt.Errorf("`health_probe_id` must be set or a health extension must be specified when `upgrade_mode` is set to %q", string(upgradeMode)) + } + if adminPassword, ok := d.GetOk("admin_password"); ok { virtualMachineProfile.OsProfile.AdminPassword = utils.String(adminPassword.(string)) } @@ -803,7 +812,7 @@ func resourceLinuxVirtualMachineScaleSetUpdate(d *schema.ResourceData, meta inte if d.HasChanges("extension", "extensions_time_budget") { updateInstances = true - extensionProfile, err := expandVirtualMachineScaleSetExtensions(d.Get("extension").([]interface{})) + extensionProfile, _, err := expandVirtualMachineScaleSetExtensions(d.Get("extension").([]interface{})) if err != nil { return err } diff --git a/azurerm/internal/services/compute/virtual_machine_scale_set.go b/azurerm/internal/services/compute/virtual_machine_scale_set.go index b08abbb93504..65a1cb934e86 100644 --- a/azurerm/internal/services/compute/virtual_machine_scale_set.go +++ b/azurerm/internal/services/compute/virtual_machine_scale_set.go @@ -1408,10 +1408,10 @@ func VirtualMachineScaleSetExtensionsSchema() *schema.Schema { } } -func expandVirtualMachineScaleSetExtensions(input []interface{}) (*compute.VirtualMachineScaleSetExtensionProfile, error) { - result := &compute.VirtualMachineScaleSetExtensionProfile{} +func expandVirtualMachineScaleSetExtensions(input []interface{}) (extensionProfile *compute.VirtualMachineScaleSetExtensionProfile, hasHealthExtension bool, err error) { + extensionProfile = &compute.VirtualMachineScaleSetExtensionProfile{} if len(input) == 0 { - return result, nil + return nil, false, nil } extensions := make([]compute.VirtualMachineScaleSetExtension, 0) @@ -1420,15 +1420,20 @@ func expandVirtualMachineScaleSetExtensions(input []interface{}) (*compute.Virtu extension := compute.VirtualMachineScaleSetExtension{ Name: utils.String(extensionRaw["name"].(string)), } + extensionType := extensionRaw["type"].(string) extensionProps := compute.VirtualMachineScaleSetExtensionProperties{ Publisher: utils.String(extensionRaw["publisher"].(string)), - Type: utils.String(extensionRaw["type"].(string)), + Type: &extensionType, TypeHandlerVersion: utils.String(extensionRaw["type_handler_version"].(string)), AutoUpgradeMinorVersion: utils.Bool(extensionRaw["auto_upgrade_minor_version"].(bool)), ProvisionAfterExtensions: utils.ExpandStringSlice(extensionRaw["provision_after_extensions"].([]interface{})), } + if extensionType == "ApplicationHealthLinux" || extensionType == "ApplicationHealthWindows" { + hasHealthExtension = true + } + if forceUpdateTag := extensionRaw["force_update_tag"]; forceUpdateTag != nil { extensionProps.ForceUpdateTag = utils.String(forceUpdateTag.(string)) } @@ -1436,7 +1441,7 @@ func expandVirtualMachineScaleSetExtensions(input []interface{}) (*compute.Virtu if val, ok := extensionRaw["settings"]; ok && val.(string) != "" { settings, err := structure.ExpandJsonFromString(val.(string)) if err != nil { - return nil, fmt.Errorf("failed to parse JSON from `settings`: %+v", err) + return nil, false, fmt.Errorf("failed to parse JSON from `settings`: %+v", err) } extensionProps.Settings = settings } @@ -1444,7 +1449,7 @@ func expandVirtualMachineScaleSetExtensions(input []interface{}) (*compute.Virtu if val, ok := extensionRaw["protected_settings"]; ok && val.(string) != "" { protectedSettings, err := structure.ExpandJsonFromString(val.(string)) if err != nil { - return nil, fmt.Errorf("failed to parse JSON from `protected_settings`: %+v", err) + return nil, false, fmt.Errorf("failed to parse JSON from `protected_settings`: %+v", err) } extensionProps.ProtectedSettings = protectedSettings } @@ -1452,9 +1457,9 @@ func expandVirtualMachineScaleSetExtensions(input []interface{}) (*compute.Virtu extension.VirtualMachineScaleSetExtensionProperties = &extensionProps extensions = append(extensions, extension) } - result.Extensions = &extensions + extensionProfile.Extensions = &extensions - return result, nil + return extensionProfile, hasHealthExtension, nil } func flattenVirtualMachineScaleSetExtensions(input *compute.VirtualMachineScaleSetExtensionProfile, d *schema.ResourceData) ([]map[string]interface{}, error) { diff --git a/azurerm/internal/services/compute/windows_virtual_machine_scale_set_extensions_test.go b/azurerm/internal/services/compute/windows_virtual_machine_scale_set_extensions_test.go index f0b021862984..200d1a6155d3 100644 --- a/azurerm/internal/services/compute/windows_virtual_machine_scale_set_extensions_test.go +++ b/azurerm/internal/services/compute/windows_virtual_machine_scale_set_extensions_test.go @@ -157,6 +157,40 @@ func TestAccWindowsVirtualMachineScaleSet_extensionUpdate(t *testing.T) { }) } +func TestAccWindowsVirtualMachineScaleSet_extensionsRollingUpgradeWithHealthExtension(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine_scale_set", "test") + r := WindowsVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.extensionsRollingUpgradeWithHealthExtension(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("admin_password", "extension.0.protected_settings"), + }) +} + +func TestAccWindowsVirtualMachineScaleSet_extensionsAutomaticUpgradeWithHealthExtension(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine_scale_set", "test") + r := WindowsVirtualMachineScaleSetResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.extensionsAutomaticUpgradeWithHealthExtension(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep( + "admin_password", + "extension.0.protected_settings", + "enable_automatic_updates", + ), + }) +} + func TestAccWindowsVirtualMachineScaleSet_extensionWithTimeBudget(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine_scale_set", "test") r := WindowsVirtualMachineScaleSetResource{} @@ -585,6 +619,137 @@ resource "azurerm_windows_virtual_machine_scale_set" "test" { `, r.template(data)) } +func (r WindowsVirtualMachineScaleSetResource) extensionsRollingUpgradeWithHealthExtension(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_windows_virtual_machine_scale_set" "test" { + name = local.vm_name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_F2" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + upgrade_mode = "Rolling" + + rolling_upgrade_policy { + max_batch_instance_percent = 21 + max_unhealthy_instance_percent = 22 + max_unhealthy_upgraded_instance_percent = 23 + pause_time_between_batches = "PT30S" + } + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2019-Datacenter" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + + extension { + name = "HealthExtension" + publisher = "Microsoft.ManagedServices" + type = "ApplicationHealthWindows" + type_handler_version = "1.0" + auto_upgrade_minor_version = true + settings = jsonencode({ + protocol = "https" + port = 443 + requestPath = "/" + }) + } +} +`, r.template(data)) +} + +func (r WindowsVirtualMachineScaleSetResource) extensionsAutomaticUpgradeWithHealthExtension(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_windows_virtual_machine_scale_set" "test" { + name = local.vm_name + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard_F2" + instances = 1 + admin_username = "adminuser" + admin_password = "P@ssword1234!" + upgrade_mode = "Automatic" + + automatic_os_upgrade_policy { + disable_automatic_rollback = true + enable_automatic_os_upgrade = true + } + + rolling_upgrade_policy { + max_batch_instance_percent = 21 + max_unhealthy_instance_percent = 22 + max_unhealthy_upgraded_instance_percent = 23 + pause_time_between_batches = "PT30S" + } + + source_image_reference { + publisher = "MicrosoftWindowsServer" + offer = "WindowsServer" + sku = "2019-Datacenter" + version = "latest" + } + + os_disk { + storage_account_type = "Standard_LRS" + caching = "ReadWrite" + } + + network_interface { + name = "example" + primary = true + ip_configuration { + name = "internal" + primary = true + subnet_id = azurerm_subnet.test.id + } + } + + extension { + name = "HealthExtension" + publisher = "Microsoft.ManagedServices" + type = "ApplicationHealthWindows" + type_handler_version = "1.0" + auto_upgrade_minor_version = true + settings = jsonencode({ + protocol = "https" + port = 443 + requestPath = "/" + }) + } +} +`, r.template(data)) +} + func (r WindowsVirtualMachineScaleSetResource) extensionWithTimeBudget(data acceptance.TestData, duration string) string { template := r.template(data) return fmt.Sprintf(` diff --git a/azurerm/internal/services/compute/windows_virtual_machine_scale_set_resource.go b/azurerm/internal/services/compute/windows_virtual_machine_scale_set_resource.go index 7e9ab750c7f5..06ee0eca7319 100644 --- a/azurerm/internal/services/compute/windows_virtual_machine_scale_set_resource.go +++ b/azurerm/internal/services/compute/windows_virtual_machine_scale_set_resource.go @@ -365,10 +365,6 @@ func resourceWindowsVirtualMachineScaleSetCreate(d *schema.ResourceData, meta in return fmt.Errorf("An `automatic_os_upgrade_policy` block cannot be specified when `upgrade_mode` is not set to `Automatic`") } - if upgradeMode == compute.Automatic && len(automaticOSUpgradePolicyRaw) > 0 && healthProbeId == "" { - return fmt.Errorf("`healthProbeId` must be set when `upgrade_mode` is set to %q and `automatic_os_upgrade_policy` block exists", string(upgradeMode)) - } - shouldHaveRollingUpgradePolicy := upgradeMode == compute.Automatic || upgradeMode == compute.Rolling if !shouldHaveRollingUpgradePolicy && len(rollingUpgradePolicyRaw) > 0 { return fmt.Errorf("A `rolling_upgrade_policy` block cannot be specified when `upgrade_mode` is set to %q", string(upgradeMode)) @@ -435,8 +431,9 @@ func resourceWindowsVirtualMachineScaleSetCreate(d *schema.ResourceData, meta in }, } + hasHealthExtension := false if vmExtensionsRaw, ok := d.GetOk("extension"); ok { - virtualMachineProfile.ExtensionProfile, err = expandVirtualMachineScaleSetExtensions(vmExtensionsRaw.([]interface{})) + virtualMachineProfile.ExtensionProfile, hasHealthExtension, err = expandVirtualMachineScaleSetExtensions(vmExtensionsRaw.([]interface{})) if err != nil { return err } @@ -449,6 +446,18 @@ func resourceWindowsVirtualMachineScaleSetCreate(d *schema.ResourceData, meta in virtualMachineProfile.ExtensionProfile.ExtensionsTimeBudget = utils.String(v.(string)) } + // otherwise the service return the error: + // Automatic OS Upgrade is not supported for this Virtual Machine Scale Set because a health probe or health extension was not specified. + if upgradeMode == compute.Automatic && len(automaticOSUpgradePolicyRaw) > 0 && (healthProbeId == "" && !hasHealthExtension) { + return fmt.Errorf("`health_probe_id` must be set or a health extension must be specified when `upgrade_mode` is set to %q and `automatic_os_upgrade_policy` block exists", string(upgradeMode)) + } + + // otherwise the service return the error: + // Rolling Upgrade mode is not supported for this Virtual Machine Scale Set because a health probe or health extension was not provided. + if upgradeMode == compute.Rolling && (healthProbeId == "" && !hasHealthExtension) { + return fmt.Errorf("`health_probe_id` must be set or a health extension must be specified when `upgrade_mode` is set to %q", string(upgradeMode)) + } + enableAutomaticUpdates := d.Get("enable_automatic_updates").(bool) if upgradeMode != compute.Automatic { virtualMachineProfile.OsProfile.WindowsConfiguration.EnableAutomaticUpdates = utils.Bool(enableAutomaticUpdates) @@ -834,7 +843,7 @@ func resourceWindowsVirtualMachineScaleSetUpdate(d *schema.ResourceData, meta in if d.HasChanges("extension", "extensions_time_budget") { updateInstances = true - extensionProfile, err := expandVirtualMachineScaleSetExtensions(d.Get("extension").([]interface{})) + extensionProfile, _, err := expandVirtualMachineScaleSetExtensions(d.Get("extension").([]interface{})) if err != nil { return err }