From 5bce84ca0c94243faf87f59a299644dfcc76ff5e Mon Sep 17 00:00:00 2001 From: sreallymatt <106555974+sreallymatt@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:10:25 -0700 Subject: [PATCH] `azurerm_site_recovery_replicated_vm` - adding property `recovery_load_balancer_backend_address_pool_ids` (#28398) * add recovery_load_balancer_backend_address_pool_ids arg to site_recovery_replicated_vm resource * Fix lint --- .../site_recovery_replicated_vm_resource.go | 38 ++- ...te_recovery_replicated_vm_resource_test.go | 254 ++++++++++++++++++ .../site_recovery_replicated_vm.html.markdown | 6 +- 3 files changed, 286 insertions(+), 12 deletions(-) diff --git a/internal/services/recoveryservices/site_recovery_replicated_vm_resource.go b/internal/services/recoveryservices/site_recovery_replicated_vm_resource.go index ac57e36b8eb4..576f8c700ed4 100644 --- a/internal/services/recoveryservices/site_recovery_replicated_vm_resource.go +++ b/internal/services/recoveryservices/site_recovery_replicated_vm_resource.go @@ -21,10 +21,12 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2022-03-01/proximityplacementgroups" "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2023-04-02/disks" "github.com/hashicorp/go-azure-sdk/resource-manager/compute/2024-03-01/virtualmachines" + "github.com/hashicorp/go-azure-sdk/resource-manager/network/2023-09-01/loadbalancers" "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2024-04-01/replicationfabrics" "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2024-04-01/replicationpolicies" "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2024-04-01/replicationprotecteditems" "github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicessiterecovery/2024-04-01/replicationprotectioncontainers" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" @@ -307,7 +309,7 @@ func resourceSiteRecoveryReplicatedVM() *pluginsdk.Resource { } func networkInterfaceResource() *pluginsdk.Resource { - out := &pluginsdk.Resource{ + return &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "source_network_interface_id": { Type: pluginsdk.TypeString, @@ -354,6 +356,15 @@ func networkInterfaceResource() *pluginsdk.Resource { ValidateFunc: azure.ValidateResourceID, }, + "recovery_load_balancer_backend_address_pool_ids": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: loadbalancers.ValidateLoadBalancerBackendAddressPoolID, + }, + }, + "recovery_public_ip_address_id": { Type: pluginsdk.TypeString, Optional: true, @@ -362,8 +373,6 @@ func networkInterfaceResource() *pluginsdk.Resource { }, }, } - - return out } func diskEncryptionResource() *pluginsdk.Resource { @@ -561,19 +570,25 @@ func resourceSiteRecoveryReplicatedItemUpdateInternal(ctx context.Context, d *pl testSubNetName := vmNicInput["failover_test_subnet_name"].(string) testPublicIpAddressID := vmNicInput["failover_test_public_ip_address_id"].(string) + var recoveryLoadBalancerBackendPoolIds *[]string + if ids, ok := vmNicInput["recovery_load_balancer_backend_address_pool_ids"].(*schema.Set); ok && ids.Len() > 0 { + recoveryLoadBalancerBackendPoolIds = utils.ExpandStringSlice(ids.List()) + } + nicId := findNicId(state, sourceNicId) if nicId == nil { return fmt.Errorf("updating replicated vm %s (vault %s): Trying to update NIC that is not known by Azure %s", name, vaultName, sourceNicId) } ipConfig := []replicationprotecteditems.IPConfigInputDetails{ { - RecoverySubnetName: &targetSubnetName, - RecoveryStaticIPAddress: &targetStaticIp, - RecoveryPublicIPAddressId: &recoveryPublicIPAddressID, - TfoStaticIPAddress: &testStaticIp, - TfoPublicIPAddressId: &testPublicIpAddressID, - TfoSubnetName: &testSubNetName, - IsPrimary: utils.Bool(true), // currently we can only set one IPconfig for a nic, so we dont need to expose this to users. + RecoverySubnetName: &targetSubnetName, + RecoveryStaticIPAddress: &targetStaticIp, + RecoveryLBBackendAddressPoolIds: recoveryLoadBalancerBackendPoolIds, + RecoveryPublicIPAddressId: &recoveryPublicIPAddressID, + TfoStaticIPAddress: &testStaticIp, + TfoPublicIPAddressId: &testPublicIpAddressID, + TfoSubnetName: &testSubNetName, + IsPrimary: utils.Bool(true), // currently we can only set one IPconfig for a nic, so we dont need to expose this to users. }, } vmNics = append(vmNics, replicationprotecteditems.VMNicInputDetails{ @@ -907,6 +922,9 @@ func resourceSiteRecoveryReplicatedItemRead(d *pluginsdk.ResourceData, meta inte if ipConfig.RecoverySubnetName != nil { nicOutput["target_subnet_name"] = *ipConfig.RecoverySubnetName } + if ipConfig.RecoveryLBBackendAddressPoolIds != nil { + nicOutput["recovery_load_balancer_backend_address_pool_ids"] = schema.NewSet(schema.HashString, utils.FlattenStringSlice(ipConfig.RecoveryLBBackendAddressPoolIds)) + } if ipConfig.RecoveryPublicIPAddressId != nil { nicOutput["recovery_public_ip_address_id"] = *ipConfig.RecoveryPublicIPAddressId } diff --git a/internal/services/recoveryservices/site_recovery_replicated_vm_resource_test.go b/internal/services/recoveryservices/site_recovery_replicated_vm_resource_test.go index f039b0cfa89a..a237af6d4bf3 100644 --- a/internal/services/recoveryservices/site_recovery_replicated_vm_resource_test.go +++ b/internal/services/recoveryservices/site_recovery_replicated_vm_resource_test.go @@ -185,6 +185,21 @@ func TestAccSiteRecoveryReplicatedVm_zone2zone(t *testing.T) { }) } +func TestAccSiteRecoveryReplicatedVm_zone2zoneWithLoadBalancerBackendPool(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_site_recovery_replicated_vm", "test") + r := SiteRecoveryReplicatedVmResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.zone2zoneWithLoadBalancerBackendPool(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccSiteRecoveryReplicatedVm_targetDiskEncryption(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_site_recovery_replicated_vm", "test") r := SiteRecoveryReplicatedVmResource{} @@ -1322,6 +1337,245 @@ resource "azurerm_site_recovery_replicated_vm" "test" { `, data.RandomInteger, data.Locations.Primary, data.Locations.Secondary) } +func (SiteRecoveryReplicatedVmResource) zone2zoneWithLoadBalancerBackendPool(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + +resource "azurerm_resource_group" "test1" { + name = "acctestRG-recovery-%[1]d-1" + location = "%[2]s" +} + +resource "azurerm_resource_group" "test2" { + name = "acctestRG-recovery-%[1]d-2" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acct%[1]d" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_network" "test1" { + name = "vnet-%[1]d-1" + resource_group_name = azurerm_resource_group.test1.name + address_space = ["192.168.1.0/24"] + location = azurerm_resource_group.test1.location +} + +resource "azurerm_virtual_network" "test2" { + name = "vnet-%[1]d-2" + resource_group_name = azurerm_resource_group.test2.name + address_space = ["192.168.2.0/24"] + location = azurerm_resource_group.test2.location +} + +resource "azurerm_subnet" "test1" { + name = "subnet-%[1]d-1" + resource_group_name = azurerm_resource_group.test1.name + virtual_network_name = azurerm_virtual_network.test1.name + address_prefixes = ["192.168.1.0/24"] +} + +resource "azurerm_subnet" "test2" { + name = "subnet-%[1]d-1" + resource_group_name = azurerm_resource_group.test2.name + virtual_network_name = azurerm_virtual_network.test2.name + address_prefixes = ["192.168.2.0/24"] +} + +resource "azurerm_public_ip" "test" { + name = "public-ip-%[1]d" + allocation_method = "Static" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + sku = "Standard" + zones = ["1", "2", "3"] +} + +resource "azurerm_nat_gateway" "test" { + name = "nat-gateway-%[1]d" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + sku_name = "Standard" + idle_timeout_in_minutes = 10 +} + +resource "azurerm_nat_gateway_public_ip_association" "test" { + nat_gateway_id = azurerm_nat_gateway.test.id + public_ip_address_id = azurerm_public_ip.test.id +} + +resource "azurerm_subnet_nat_gateway_association" "test" { + nat_gateway_id = azurerm_nat_gateway.test.id + subnet_id = azurerm_subnet.test1.id +} + +resource "azurerm_lb" "test" { + name = "lb-%[1]d" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + sku = "Standard" + + frontend_ip_configuration { + name = "lb-frontend-%[1]d" + subnet_id = azurerm_subnet.test1.id + zones = ["1", "2", "3"] + } +} + +resource "azurerm_lb_backend_address_pool" "test" { + name = "lb-backend-address-pool-%[1]d" + loadbalancer_id = azurerm_lb.test.id +} + +resource "azurerm_network_interface" "test" { + name = "vm-nic-%[1]d" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + + ip_configuration { + name = "ip-config-%[1]d" + subnet_id = azurerm_subnet.test1.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_network_interface_backend_address_pool_association" "test" { + ip_configuration_name = azurerm_network_interface.test.ip_configuration[0].name + network_interface_id = azurerm_network_interface.test.id + backend_address_pool_id = azurerm_lb_backend_address_pool.test.id +} + +resource "azurerm_virtual_machine" "test" { + name = "vm-%[1]d" + location = azurerm_resource_group.test1.location + resource_group_name = azurerm_resource_group.test1.name + vm_size = "Standard_B1s" + network_interface_ids = [azurerm_network_interface.test.id] + zones = ["1"] + + storage_image_reference { + publisher = "OpenLogic" + offer = "CentOS" + sku = "7.5" + version = "latest" + } + + storage_os_disk { + name = "disk-%[1]d" + os_type = "Linux" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Premium_LRS" + } + + os_profile { + admin_username = "testadmin" + admin_password = "Password1234!" + computer_name = "vm-%[1]d" + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + +resource "azurerm_recovery_services_vault" "test" { + name = "recovery-vault-%[1]d" + location = azurerm_resource_group.test2.location + resource_group_name = azurerm_resource_group.test2.name + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test" { + name = "fabric-%[1]d" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + location = azurerm_resource_group.test1.location +} + +resource "azurerm_site_recovery_protection_container" "test1" { + name = "protection-container-%[1]d-1" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test.name +} + +resource "azurerm_site_recovery_protection_container" "test2" { + name = "protection-container-%[1]d-2" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test.name +} + +resource "azurerm_site_recovery_replication_policy" "test" { + name = "policy-%[1]d" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_point_retention_in_minutes = 24 * 60 + application_consistent_snapshot_frequency_in_minutes = 4 * 60 +} + +resource "azurerm_site_recovery_protection_container_mapping" "test" { + name = "container-mapping-%[1]d" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + recovery_fabric_name = azurerm_site_recovery_fabric.test.name + recovery_source_protection_container_name = azurerm_site_recovery_protection_container.test1.name + recovery_target_protection_container_id = azurerm_site_recovery_protection_container.test2.id + recovery_replication_policy_id = azurerm_site_recovery_replication_policy.test.id +} + +resource "azurerm_site_recovery_replicated_vm" "test" { + name = "vm-replication-%[1]d" + resource_group_name = azurerm_resource_group.test2.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + source_recovery_fabric_name = azurerm_site_recovery_fabric.test.name + source_vm_id = azurerm_virtual_machine.test.id + recovery_replication_policy_id = azurerm_site_recovery_replication_policy.test.id + source_recovery_protection_container_name = azurerm_site_recovery_protection_container.test1.name + + target_network_id = azurerm_virtual_network.test1.id + target_resource_group_id = azurerm_resource_group.test2.id + target_recovery_fabric_id = azurerm_site_recovery_fabric.test.id + target_recovery_protection_container_id = azurerm_site_recovery_protection_container.test2.id + target_zone = "2" + + managed_disk { + disk_id = azurerm_virtual_machine.test.storage_os_disk[0].managed_disk_id + staging_storage_account_id = azurerm_storage_account.test.id + target_resource_group_id = azurerm_resource_group.test2.id + target_disk_type = "Premium_LRS" + target_replica_disk_type = "Premium_LRS" + } + + network_interface { + source_network_interface_id = azurerm_network_interface.test.id + target_subnet_name = azurerm_subnet.test2.name + recovery_public_ip_address_id = azurerm_public_ip.test.id + recovery_load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.test.id] + } + + depends_on = [ + azurerm_site_recovery_protection_container_mapping.test, + azurerm_nat_gateway.test, + azurerm_subnet_nat_gateway_association.test, + azurerm_nat_gateway_public_ip_association.test, + ] +} +`, data.RandomInteger, data.Locations.Primary) +} + func (SiteRecoveryReplicatedVmResource) targetDiskEncryption(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/site_recovery_replicated_vm.html.markdown b/website/docs/r/site_recovery_replicated_vm.html.markdown index 4bd99f0427dd..d58697100246 100644 --- a/website/docs/r/site_recovery_replicated_vm.html.markdown +++ b/website/docs/r/site_recovery_replicated_vm.html.markdown @@ -308,13 +308,15 @@ A `network_interface` block supports the following: * `target_static_ip` - (Optional) Static IP to assign when a failover is done. -* `target_subnet_name` - (Optional) Name of the subnet to to use when a failover is done. +* `target_subnet_name` - (Optional) Name of the subnet to use when a failover is done. + +* `recovery_load_balancer_backend_address_pool_ids` - (Optional) A list of IDs of Load Balancer Backend Address Pools to use when a failover is done. * `recovery_public_ip_address_id` - (Optional) Id of the public IP object to use when a failover is done. * `failover_test_static_ip` - (Optional) Static IP to assign when a test failover is done. -* `failover_test_subnet_name` - (Optional) Name of the subnet to to use when a test failover is done. +* `failover_test_subnet_name` - (Optional) Name of the subnet to use when a test failover is done. * `failover_test_public_ip_address_id` - (Optional) Id of the public IP object to use when a test failover is done.