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

[azurerm_recovery_services_protected_vm] - changing backup_policy_id no longer forces a new resource #3822

Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 7 additions & 1 deletion azurerm/resource_arm_recovery_services_protected_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func resourceArmRecoveryServicesProtectedVm() *schema.Resource {
"backup_policy_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ForceNew: false,
hashitop marked this conversation as resolved.
Show resolved Hide resolved
ValidateFunc: azure.ValidateResourceID,
},

Expand Down Expand Up @@ -117,11 +117,17 @@ func resourceArmRecoveryServicesProtectedVmCreateUpdate(d *schema.ResourceData,
if _, err = client.CreateOrUpdate(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, item); err != nil {
return fmt.Errorf("Error creating/updating Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}
// Due to issue reported in PR https://github.com/terraform-providers/terraform-provider-azurerm/pull/3822
// Azure API, at the time, returned invalid state of the resource when backup_policy_id is updated
// Adding a graceful 15 seconds delay prior to the next call to Azure management seem to allow the API to correct itself
// and report back the correct status
time.Sleep(15 * time.Second)
hashitop marked this conversation as resolved.
Show resolved Hide resolved

resp, err := resourceArmRecoveryServicesProtectedVmWaitForState(client, ctx, true, vaultName, resourceGroup, containerName, protectedItemName)
if err != nil {
return err
}

id := strings.Replace(*resp.ID, "Subscriptions", "subscriptions", 1)
d.SetId(id)

Expand Down
253 changes: 252 additions & 1 deletion azurerm/resource_arm_recovery_services_protected_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,55 @@ func TestAccAzureRMRecoveryServicesProtectedVm_separateResourceGroups(t *testing
})
}

func TestAccAzureRMRecoveryServicesProtectedVm_updateBackupPolicyId(t *testing.T) {
virtualMachine := "azurerm_virtual_machine.test"
protectedVmResourceName := "azurerm_recovery_services_protected_vm.test"
fBackupPolicyResourceName := "azurerm_recovery_services_protection_policy_vm.test"
sBackupPolicyResourceName := "azurerm_recovery_services_protection_policy_vm.test_change_backup"

ri := tf.AccRandTimeInt()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMRecoveryServicesProtectedVmDestroy,
Steps: []resource.TestStep{
{ // Create resources and link first backup policy id
ResourceName: fBackupPolicyResourceName,
Config: testAccAzureRMRecoveryServicesProtectedVm_linkFirstBackupPolicy(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(protectedVmResourceName, "backup_policy_id", fBackupPolicyResourceName, "id"),
),
},
{ // Modify backup policy id to the second one
// Set Destroy false to prevent error from cleaning up dangling resource
ResourceName: sBackupPolicyResourceName,
Config: testAccAzureRMRecoveryServicesProtectedVm_linkSecondBackupPolicy(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(protectedVmResourceName, "backup_policy_id", sBackupPolicyResourceName, "id"),
),
},
{ // Remove backup policy link
// Backup policy link will need to be removed first so the VM's backup policy subsequently reverts to Default
// Azure API is quite sensitive, adding the step to control resource cleanup order
ResourceName: fBackupPolicyResourceName,
Config: testAccAzureRMRecoveryServicesProtectedVm_withVM(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(),
},
{ // Then VM can be removed
ResourceName: virtualMachine,
Config: testAccAzureRMRecoveryServicesProtectedVm_withSecondPolicy(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(),
},
{ // Remove backup policies and vault
ResourceName: protectedVmResourceName,
Config: testAccAzureRMRecoveryServicesProtectedVm_basePolicyTest(ri, testLocation()),
Check: resource.ComposeTestCheckFunc(),
},
},
})
}

func testCheckAzureRMRecoveryServicesProtectedVmDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_recovery_services_protected_vm" {
Expand Down Expand Up @@ -321,7 +370,6 @@ resource "azurerm_recovery_services_protection_policy_vm" "test" {
func testAccAzureRMRecoveryServicesProtectedVm_basic(rInt int, location string) string {
return fmt.Sprintf(`
%s

hashitop marked this conversation as resolved.
Show resolved Hide resolved
resource "azurerm_recovery_services_protected_vm" "test" {
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"
Expand All @@ -331,6 +379,209 @@ resource "azurerm_recovery_services_protected_vm" "test" {
`, testAccAzureRMRecoveryServicesProtectedVm_base(rInt, location))
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_basePolicyTest(rInt int, location string) string {
rstr := strconv.Itoa(rInt)
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%[1]d"
location = "%[2]s"
}

resource "azurerm_virtual_network" "test" {
name = "vnet"
location = "${azurerm_resource_group.test.location}"
address_space = ["10.0.0.0/16"]
resource_group_name = "${azurerm_resource_group.test.name}"
}

resource "azurerm_subnet" "test" {
name = "acctest_subnet"
virtual_network_name = "${azurerm_virtual_network.test.name}"
resource_group_name = "${azurerm_resource_group.test.name}"
address_prefix = "10.0.10.0/24"
}

resource "azurerm_network_interface" "test" {
name = "acctest_nic"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"

ip_configuration {
name = "acctestipconfig"
subnet_id = "${azurerm_subnet.test.id}"
private_ip_address_allocation = "Dynamic"
public_ip_address_id = "${azurerm_public_ip.test.id}"
}
}

resource "azurerm_public_ip" "test" {
name = "acctest-ip"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
allocation_method = "Dynamic"
domain_name_label = "acctestip%[1]d"
}

resource "azurerm_storage_account" "test" {
name = "acctest%[3]s"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
account_tier = "Standard"
account_replication_type = "LRS"
}

resource "azurerm_managed_disk" "test" {
name = "acctest-datadisk"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
storage_account_type = "Standard_LRS"
create_option = "Empty"
disk_size_gb = "1023"
}
`, rInt, location, rstr[len(rstr)-5:])
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_withVault(rInt int, location string) string {
return fmt.Sprintf(`
%[1]s

resource "azurerm_recovery_services_vault" "test" {
name = "acctest-%[2]d"
hashitop marked this conversation as resolved.
Show resolved Hide resolved
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
sku = "Standard"
}
`, testAccAzureRMRecoveryServicesProtectedVm_basePolicyTest(rInt, location), rInt)
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_withFirstPolicy(rInt int, location string) string {
return fmt.Sprintf(`
%[1]s

resource "azurerm_recovery_services_protection_policy_vm" "test" {
name = "acctest-%[2]d"
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"

backup {
frequency = "Daily"
time = "23:00"
}

retention_daily {
count = 10
}
}
`, testAccAzureRMRecoveryServicesProtectedVm_withVault(rInt, location), rInt)
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_withSecondPolicy(rInt int, location string) string {
return fmt.Sprintf(`
%[1]s

resource "azurerm_recovery_services_protection_policy_vm" "test_change_backup" {
name = "acctest2-%[2]d"
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"

backup {
frequency = "Daily"
time = "23:00"
}

retention_daily {
count = 55
}
}
`, testAccAzureRMRecoveryServicesProtectedVm_withFirstPolicy(rInt, location), rInt)
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_withVM(rInt int, location string) string {
return fmt.Sprintf(`
%[1]s

resource "azurerm_virtual_machine" "test" {
name = "acctestvm-%[2]d"
hashitop marked this conversation as resolved.
Show resolved Hide resolved
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
vm_size = "Standard_A0"
network_interface_ids = ["${azurerm_network_interface.test.id}"]
delete_os_disk_on_termination = true

storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}

storage_os_disk {
name = "acctest-osdisk"
managed_disk_type = "Standard_LRS"
caching = "ReadWrite"
create_option = "FromImage"
}

storage_data_disk {
name = "acctest-datadisk"
managed_disk_id = "${azurerm_managed_disk.test.id}"
managed_disk_type = "Standard_LRS"
disk_size_gb = "${azurerm_managed_disk.test.disk_size_gb}"
create_option = "Attach"
lun = 0
}

os_profile {
computer_name = "acctest"
admin_username = "vmadmin"
admin_password = "Password123!@#"
}

os_profile_linux_config {
disable_password_authentication = false
}

boot_diagnostics {
enabled = true
storage_uri = "${azurerm_storage_account.test.primary_blob_endpoint}"
}
}
`, testAccAzureRMRecoveryServicesProtectedVm_withSecondPolicy(rInt, location), rInt)
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_linkFirstBackupPolicy(rInt int, location string) string {
return fmt.Sprintf(`
%s

resource "azurerm_recovery_services_protected_vm" "test" {
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"
source_vm_id = "${azurerm_virtual_machine.test.id}"
backup_policy_id = "${azurerm_recovery_services_protection_policy_vm.test.id}"
}
`, testAccAzureRMRecoveryServicesProtectedVm_withVM(rInt, location))
}

// For update backup policy id test
func testAccAzureRMRecoveryServicesProtectedVm_linkSecondBackupPolicy(rInt int, location string) string {
return fmt.Sprintf(`
%s

resource "azurerm_recovery_services_protected_vm" "test" {
resource_group_name = "${azurerm_resource_group.test.name}"
recovery_vault_name = "${azurerm_recovery_services_vault.test.name}"
source_vm_id = "${azurerm_virtual_machine.test.id}"
backup_policy_id = "${azurerm_recovery_services_protection_policy_vm.test_change_backup.id}"
}
`, testAccAzureRMRecoveryServicesProtectedVm_withVM(rInt, location))
}

func testAccAzureRMRecoveryServicesProtectedVm_requiresImport(rInt int, location string) string {
return fmt.Sprintf(`
%s
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/recovery_services_protected_vm.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The following arguments are supported:

* `source_vm_id` - (Required) Specifies the ID of the VM to backup. Changing this forces a new resource to be created.

* `backup_policy_id` - (Required) Specifies the id of the backup policy to use. Changing this forces a new resource to be created.
* `backup_policy_id` - (Required) Specifies the id of the backup policy to use.

* `tags` - (Optional) A mapping of tags to assign to the resource.

Expand Down