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

Support graceful Linux/Windows VM shutdown #8470

Merged
merged 2 commits into from
Nov 19, 2020
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
1 change: 1 addition & 0 deletions azurerm/internal/features/user_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type UserFeatures struct {

type VirtualMachineFeatures struct {
DeleteOSDiskOnDeletion bool
GracefulShutdown bool
}

type VirtualMachineScaleSetFeatures struct {
Expand Down
11 changes: 9 additions & 2 deletions azurerm/internal/provider/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ func schemaFeatures(supportLegacyTestSuite bool) *schema.Schema {
Type: schema.TypeBool,
Optional: true,
},

"purge_soft_delete_on_destroy": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -64,7 +63,11 @@ func schemaFeatures(supportLegacyTestSuite bool) *schema.Schema {
Schema: map[string]*schema.Schema{
"delete_os_disk_on_deletion": {
Type: schema.TypeBool,
Required: true,
Optional: true,
},
"graceful_shutdown": {
Type: schema.TypeBool,
Optional: true,
},
},
},
Expand Down Expand Up @@ -124,6 +127,7 @@ func expandFeatures(input []interface{}) features.UserFeatures {
},
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: true,
GracefulShutdown: false,
},
VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{
RollInstancesWhenRequired: true,
Expand Down Expand Up @@ -176,6 +180,9 @@ func expandFeatures(input []interface{}) features.UserFeatures {
if v, ok := virtualMachinesRaw["delete_os_disk_on_deletion"]; ok {
features.VirtualMachine.DeleteOSDiskOnDeletion = v.(bool)
}
if v, ok := virtualMachinesRaw["graceful_shutdown"]; ok {
features.VirtualMachine.GracefulShutdown = v.(bool)
}
}
}

Expand Down
13 changes: 11 additions & 2 deletions azurerm/internal/provider/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func TestExpandFeatures(t *testing.T) {
"virtual_machine": []interface{}{
map[string]interface{}{
"delete_os_disk_on_deletion": true,
"graceful_shutdown": true,
},
},
"virtual_machine_scale_set": []interface{}{
Expand All @@ -81,6 +82,7 @@ func TestExpandFeatures(t *testing.T) {
},
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: true,
GracefulShutdown: true,
},
VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{
RollInstancesWhenRequired: true,
Expand All @@ -94,6 +96,7 @@ func TestExpandFeatures(t *testing.T) {
"virtual_machine": []interface{}{
map[string]interface{}{
"delete_os_disk_on_deletion": false,
"graceful_shutdown": false,
},
},
"network_locking": []interface{}{
Expand Down Expand Up @@ -132,6 +135,7 @@ func TestExpandFeatures(t *testing.T) {
},
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: false,
GracefulShutdown: false,
},
VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{
RollInstancesWhenRequired: false,
Expand Down Expand Up @@ -366,40 +370,45 @@ func TestExpandFeaturesVirtualMachine(t *testing.T) {
Expected: features.UserFeatures{
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: true,
GracefulShutdown: false,
},
},
},
{
Name: "Delete OS Disk Enabled",
Name: "Delete OS Disk and Graceful Shutdown Enabled",
Input: []interface{}{
map[string]interface{}{
"virtual_machine": []interface{}{
map[string]interface{}{
"delete_os_disk_on_deletion": true,
"graceful_shutdown": true,
},
},
},
},
Expected: features.UserFeatures{
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: true,
GracefulShutdown: true,
},
},
},
{
Name: "Delete OS Disk Disabled",
Name: "Delete OS Disk and Graceful Shutdown Disabled",
Input: []interface{}{
map[string]interface{}{
"virtual_machine": []interface{}{
map[string]interface{}{
"delete_os_disk_on_deletion": false,
"graceful_shutdown": false,
},
},
},
},
Expected: features.UserFeatures{
VirtualMachine: features.VirtualMachineFeatures{
DeleteOSDiskOnDeletion: false,
GracefulShutdown: false,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1088,10 +1088,8 @@ func resourceLinuxVirtualMachineDelete(d *schema.ResourceData, meta interface{})
// ISSUE: XXX
// shutting down the Virtual Machine prior to removing it means users are no longer charged for the compute
// thus this can be a large cost-saving when deleting larger instances
// in addition - since we're shutting down the machine to remove it, forcing a power-off is fine (as opposed
// to waiting for a graceful shut down)
log.Printf("[DEBUG] Powering Off Linux Virtual Machine %q (Resource Group %q)..", id.Name, id.ResourceGroup)
skipShutdown := true
skipShutdown := !meta.(*clients.Client).Features.VirtualMachine.GracefulShutdown
powerOffFuture, err := client.PowerOff(ctx, id.ResourceGroup, id.Name, utils.Bool(skipShutdown))
if err != nil {
return fmt.Errorf("powering off Linux Virtual Machine %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,42 @@ func TestAccLinuxVirtualMachine_otherEncryptionAtHostEnabledWithCMK(t *testing.T
})
}

func TestAccLinuxVirtualMachine_otherGracefulShutdownDisabled(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: checkLinuxVirtualMachineIsDestroyed,
Steps: []resource.TestStep{
{
Config: testLinuxVirtualMachine_otherGracefulShutdown(data, false),
Check: resource.ComposeTestCheckFunc(
checkLinuxVirtualMachineExists(data.ResourceName),
),
},
},
})
}

func TestAccLinuxVirtualMachine_otherGracefulShutdownEnabled(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_linux_virtual_machine", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: checkLinuxVirtualMachineIsDestroyed,
Steps: []resource.TestStep{
{
Config: testLinuxVirtualMachine_otherGracefulShutdown(data, true),
Check: resource.ComposeTestCheckFunc(
checkLinuxVirtualMachineExists(data.ResourceName),
),
},
},
})
}

func testLinuxVirtualMachine_otherAllowExtensionOperationsDefault(data acceptance.TestData) string {
template := testLinuxVirtualMachine_template(data)
return fmt.Sprintf(`
Expand Down Expand Up @@ -1739,3 +1775,78 @@ resource "azurerm_linux_virtual_machine" "test" {
}
`, template, data.RandomInteger, enabled)
}

func testLinuxVirtualMachine_otherGracefulShutdown(data acceptance.TestData, gracefulShutdown bool) string {
return fmt.Sprintf(`
locals {
first_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN [email protected]"
}

provider "azurerm" {
features {
virtual_machine {
graceful_shutdown = %t
}
}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctestnw-%d"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}

resource "azurerm_subnet" "test" {
name = "internal"
resource_group_name = azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.test.name
address_prefix = "10.0.2.0/24"
}

resource "azurerm_network_interface" "test" {
name = "acctestnic-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name

ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.test.id
private_ip_address_allocation = "Dynamic"
}
}

resource "azurerm_linux_virtual_machine" "test" {
name = "acctestVM-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
size = "Standard_F2"
admin_username = "adminuser"
network_interface_ids = [
azurerm_network_interface.test.id,
]

admin_ssh_key {
username = "adminuser"
public_key = local.first_public_key
}

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}
`, gracefulShutdown, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger)
}
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,48 @@ resource "azurerm_windows_virtual_machine" "test" {
`, template)
}

func TestAccWindowsVirtualMachine_otherGracefulShutdownDisabled(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: checkWindowsVirtualMachineIsDestroyed,
Steps: []resource.TestStep{
{
Config: testWindowsVirtualMachine_otherGracefulShutdown(data, false),
Check: resource.ComposeTestCheckFunc(
checkWindowsVirtualMachineExists(data.ResourceName),
),
},
data.ImportStep(
"admin_password",
),
},
})
}

func TestAccWindowsVirtualMachine_otherGracefulShutdownEnabled(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_windows_virtual_machine", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: checkWindowsVirtualMachineIsDestroyed,
Steps: []resource.TestStep{
{
Config: testWindowsVirtualMachine_otherGracefulShutdown(data, true),
Check: resource.ComposeTestCheckFunc(
checkWindowsVirtualMachineExists(data.ResourceName),
),
},
data.ImportStep(
"admin_password",
),
},
})
}

func testWindowsVirtualMachine_otherAdditionalUnattendContent(data acceptance.TestData) string {
template := testWindowsVirtualMachine_template(data)
return fmt.Sprintf(`
Expand Down Expand Up @@ -2567,3 +2609,74 @@ resource "azurerm_windows_virtual_machine" "test" {
}
`, template, enabled)
}

func testWindowsVirtualMachine_otherGracefulShutdown(data acceptance.TestData, gracefulShutdown bool) string {
return fmt.Sprintf(`
locals {
vm_name = "acctestvm%s"
}

provider "azurerm" {
features {
virtual_machine {
graceful_shutdown = %t
}
}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctestnw-%d"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
}

resource "azurerm_subnet" "test" {
name = "internal"
resource_group_name = azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.test.name
address_prefix = "10.0.2.0/24"
}

resource "azurerm_network_interface" "test" {
name = "acctestnic-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name

ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.test.id
private_ip_address_allocation = "Dynamic"
}
}

resource "azurerm_windows_virtual_machine" "test" {
name = local.vm_name
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
size = "Standard_F2"
admin_username = "adminuser"
admin_password = "P@$$w0rd1234!"
network_interface_ids = [
azurerm_network_interface.test.id,
]

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2016-Datacenter"
version = "latest"
}
}
`, data.RandomString, gracefulShutdown, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}
Original file line number Diff line number Diff line change
Expand Up @@ -1170,10 +1170,8 @@ func resourceWindowsVirtualMachineDelete(d *schema.ResourceData, meta interface{
// ISSUE: XXX
// shutting down the Virtual Machine prior to removing it means users are no longer charged for the compute
// thus this can be a large cost-saving when deleting larger instances
// in addition - since we're shutting down the machine to remove it, forcing a power-off is fine (as opposed
// to waiting for a graceful shut down)
log.Printf("[DEBUG] Powering Off Windows Virtual Machine %q (Resource Group %q)..", id.Name, id.ResourceGroup)
skipShutdown := true
skipShutdown := !meta.(*clients.Client).Features.VirtualMachine.GracefulShutdown
powerOffFuture, err := client.PowerOff(ctx, id.ResourceGroup, id.Name, utils.Bool(skipShutdown))
if err != nil {
return fmt.Errorf("powering off Windows Virtual Machine %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
Expand Down
Loading