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 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
25 changes: 20 additions & 5 deletions azurerm/resource_arm_recovery_services_protected_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ func resourceArmRecoveryServicesProtectedVm() *schema.Resource {
"backup_policy_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},

Expand Down Expand Up @@ -118,10 +117,11 @@ func resourceArmRecoveryServicesProtectedVmCreateUpdate(d *schema.ResourceData,
return fmt.Errorf("Error creating/updating Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}

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

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

Expand Down Expand Up @@ -195,14 +195,14 @@ func resourceArmRecoveryServicesProtectedVmDelete(d *schema.ResourceData, meta i
}
}

if _, err := resourceArmRecoveryServicesProtectedVmWaitForState(client, ctx, false, vaultName, resourceGroup, containerName, protectedItemName); err != nil {
if _, err := resourceArmRecoveryServicesProtectedVmWaitForState(client, ctx, false, vaultName, resourceGroup, containerName, protectedItemName, "", false); err != nil {
return err
}

return nil
}

func resourceArmRecoveryServicesProtectedVmWaitForState(client backup.ProtectedItemsGroupClient, ctx context.Context, found bool, vaultName, resourceGroup, containerName, protectedItemName string) (backup.ProtectedItemResource, error) {
func resourceArmRecoveryServicesProtectedVmWaitForState(client backup.ProtectedItemsGroupClient, ctx context.Context, found bool, vaultName, resourceGroup, containerName, protectedItemName string, policyId string, newResource bool) (backup.ProtectedItemResource, error) {
state := &resource.StateChangeConf{
Timeout: 30 * time.Minute,
MinTimeout: 30 * time.Second,
Expand All @@ -216,8 +216,23 @@ func resourceArmRecoveryServicesProtectedVmWaitForState(client backup.ProtectedI
}

return resp, "Error", fmt.Errorf("Error making Read request on Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
} else if !newResource && policyId != "" {
if properties := resp.Properties; properties != nil {
if vm, ok := properties.AsAzureIaaSComputeVMProtectedItem(); ok {
if v := vm.PolicyID; v != nil {
if strings.Replace(*v, "Subscriptions", "subscriptions", 1) != policyId {
return resp, "NotFound", nil
}
} else {
return resp, "Error", fmt.Errorf("Error reading policy ID attribute nil on Recovery Service Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup)
}
} else {
return resp, "Error", fmt.Errorf("Error reading properties on Recovery Service Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup)
}
} else {
return resp, "Error", fmt.Errorf("Error reading properties on empty Recovery Service Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup)
}
}

return resp, "Found", nil
},
}
Expand Down
252 changes: 252 additions & 0 deletions 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 @@ -331,6 +380,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"
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 = 15
}
}
`, 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"
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