diff --git a/src/RecoveryServices/RecoveryServices.Backup.Models/CmdletParamEnums.cs b/src/RecoveryServices/RecoveryServices.Backup.Models/CmdletParamEnums.cs index 63039b65afee..2a7a556fd148 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Models/CmdletParamEnums.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Models/CmdletParamEnums.cs @@ -80,6 +80,12 @@ public enum RestoreWLBackupItemParams WLRecoveryConfig } + public enum CRRParams + { + UseSecondaryRegion, + SecondaryRegion + } + public enum WorkloadRecoveryConfigParams { PointInTime, diff --git a/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs b/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs index e9b527cc5b9f..5cd588bfd656 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs @@ -102,7 +102,8 @@ public List ListProtectedItemsByContainer( CmdletModel.ContainerBase container, CmdletModel.PolicyBase policy, string backupManagementType, - string dataSourceType) + string dataSourceType, + bool UseSecondaryRegion = false) { ODataQuery queryParams = policy != null ? new ODataQuery( @@ -113,16 +114,30 @@ public List ListProtectedItemsByContainer( new ODataQuery( q => q.BackupManagementType == backupManagementType && - q.ItemType == dataSourceType); + q.ItemType == dataSourceType); List protectedItems = new List(); string skipToken = null; - var listResponse = ServiceClientAdapter.ListProtectedItem( + + // fetching backup items from secondary region + if (UseSecondaryRegion) + { + var listResponse = ServiceClientAdapter.ListCrrProtectedItem( queryParams, skipToken, vaultName: vaultName, resourceGroupName: resourceGroupName); - protectedItems.AddRange(listResponse); + protectedItems.AddRange(listResponse); + } + else + { + var listResponse = ServiceClientAdapter.ListProtectedItem( + queryParams, + skipToken, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + protectedItems.AddRange(listResponse); + } if (container != null) { @@ -200,7 +215,6 @@ public List ListProtectedItemsByContainer( } } - List itemModels = ConversionHelpers.GetItemModelList(protectedItems); if (!string.IsNullOrEmpty(itemName)) @@ -279,7 +293,7 @@ public void ValidateSQLSchedulePolicy(CmdletModel.SchedulePolicyBase policy) // call validation policy.Validate(); } - + public void ValidateLongTermRetentionPolicy(CmdletModel.RetentionPolicyBase policy, string backupManagementType = "") { if (policy == null || policy.GetType() != typeof(CmdletModel.LongTermRetentionPolicy)) @@ -357,6 +371,7 @@ public List ListRecoveryPoints(Dictionary Provi DateTime endDate = (DateTime)(ProviderData[RecoveryPointParams.EndDate]); string restorePointQueryType = ProviderData.ContainsKey(RecoveryPointParams.RestorePointQueryType) ? (string)ProviderData[RecoveryPointParams.RestorePointQueryType] : "All"; + bool secondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; ItemBase item = ProviderData[RecoveryPointParams.Item] as ItemBase; @@ -394,12 +409,27 @@ public List ListRecoveryPoints(Dictionary Provi ODataQuery queryFilter = new ODataQuery(); queryFilter.Filter = queryFilterString; - List rpListResponse = ServiceClientAdapter.GetRecoveryPoints( + List rpListResponse; + if (secondaryRegion) + { + //fetch recovery points from secondary region + rpListResponse = ServiceClientAdapter.GetRecoveryPointsFromSecondaryRegion( containerUri, protectedItemName, queryFilter, vaultName: vaultName, resourceGroupName: resourceGroupName); + } + else + { + rpListResponse = ServiceClientAdapter.GetRecoveryPoints( + containerUri, + protectedItemName, + queryFilter, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + } + return RecoveryPointConversions.GetPSAzureRecoveryPoints(rpListResponse, item); } diff --git a/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/AzureWorkloadPsBackupProvider.cs b/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/AzureWorkloadPsBackupProvider.cs index 7e79b79b39cf..7b8def6adf29 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/AzureWorkloadPsBackupProvider.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/AzureWorkloadPsBackupProvider.cs @@ -265,6 +265,7 @@ public List ListProtectedItems() (ItemProtectionState)ProviderData[ItemParams.ProtectionState]; CmdletModel.WorkloadType workloadType = (CmdletModel.WorkloadType)ProviderData[ItemParams.WorkloadType]; + bool UseSecondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; PolicyBase policy = (PolicyBase)ProviderData[PolicyParams.ProtectionPolicy]; // 1. Filter by container @@ -274,7 +275,8 @@ public List ListProtectedItems() container, policy, ServiceClientModel.BackupManagementType.AzureWorkload, - DataSourceType.SQLDataBase); + DataSourceType.SQLDataBase, + UseSecondaryRegion); List protectedItemGetResponses = new List(); @@ -403,6 +405,9 @@ public RestAzureNS.AzureOperationResponse TriggerRestore() (AzureWorkloadRecoveryConfig)ProviderData[RestoreWLBackupItemParams.WLRecoveryConfig]; RestoreRequestResource triggerRestoreRequest = new RestoreRequestResource(); + bool useSecondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; + String secondaryRegion = useSecondaryRegion ? (string)ProviderData[CRRParams.SecondaryRegion] : null; + if (wLRecoveryConfig.RecoveryPoint.ContainerName != null && wLRecoveryConfig.FullRP == null) { AzureWorkloadSQLRestoreRequest azureWorkloadSQLRestoreRequest = @@ -481,14 +486,39 @@ public RestAzureNS.AzureOperationResponse TriggerRestore() triggerRestoreRequest.Properties = azureWorkloadSQLPointInTimeRestoreRequest; } - var response = ServiceClientAdapter.RestoreDisk( + if (useSecondaryRegion) + { + AzureRecoveryPoint rp = (AzureRecoveryPoint)wLRecoveryConfig.RecoveryPoint; + + // get access token + CrrAccessToken accessToken = ServiceClientAdapter.GetCRRAccessToken(rp, secondaryRegion, vaultName: vaultName, resourceGroupName: resourceGroupName); + + // AzureWorkload CRR Request + Logger.Instance.WriteDebug("Triggering Restore to secondary region: " + secondaryRegion); + + CrossRegionRestoreRequest crrRestoreRequest = new CrossRegionRestoreRequest(); + crrRestoreRequest.CrossRegionRestoreAccessDetails = accessToken; + crrRestoreRequest.RestoreRequest = triggerRestoreRequest.Properties; + + // storage account location isn't required in case of workload restore + var response = ServiceClientAdapter.RestoreDiskSecondryRegion( + rp, + triggerCRRRestoreRequest: crrRestoreRequest, + secondaryRegion: secondaryRegion); + return response; + } + else + { + var response = ServiceClientAdapter.RestoreDisk( (AzureRecoveryPoint)wLRecoveryConfig.RecoveryPoint, "LocationNotRequired", triggerRestoreRequest, vaultName: vaultName, resourceGroupName: resourceGroupName, vaultLocation: vaultLocation); - return response; + return response; + } + } private RestAzureNS.AzureOperationResponse CreateorModifyPolicy() diff --git a/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/IaasVmPsBackupProvider.cs b/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/IaasVmPsBackupProvider.cs index ab1a69e4f966..0246dd81fb74 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/IaasVmPsBackupProvider.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Providers/Providers/IaasVmPsBackupProvider.cs @@ -19,7 +19,6 @@ using Microsoft.Azure.Commands.RecoveryServices.Backup.Properties; using Microsoft.Azure.Management.Internal.Resources.Models; using Microsoft.Azure.Management.RecoveryServices.Backup.Models; -using Microsoft.Rest; using Microsoft.Rest.Azure.OData; using System; using System.Collections.Generic; @@ -391,6 +390,8 @@ public RestAzureNS.AzureOperationResponse TriggerRestore() SwitchParameter restoreAsUnmanagedDisks = (SwitchParameter)ProviderData[RestoreVMBackupItemParams.RestoreAsUnmanagedDisks]; String DiskEncryptionSetId = ProviderData.ContainsKey(RestoreVMBackupItemParams.DiskEncryptionSetId) ? (string)ProviderData[RestoreVMBackupItemParams.DiskEncryptionSetId].ToString() : null; + bool useSecondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; + String secondaryRegion = useSecondaryRegion ? (string)ProviderData[CRRParams.SecondaryRegion]: null; Dictionary uriDict = HelperUtils.ParseUri(rp.Id); string containerUri = HelperUtils.GetContainerUri(uriDict, rp.Id); @@ -453,15 +454,40 @@ public RestAzureNS.AzureOperationResponse TriggerRestore() RestoreRequestResource triggerRestoreRequest = new RestoreRequestResource(); triggerRestoreRequest.Properties = restoreRequest; - - var response = ServiceClientAdapter.RestoreDisk( - rp, - storageAccountResource.Location, - triggerRestoreRequest, - vaultName: vaultName, - resourceGroupName: resourceGroupName, - vaultLocation: vaultLocation ?? ServiceClientAdapter.BmsAdapter.GetResourceLocation()); - return response; + + if (useSecondaryRegion) + { + // get access token + CrrAccessToken accessToken = ServiceClientAdapter.GetCRRAccessToken(rp, secondaryRegion, vaultName: vaultName, resourceGroupName: resourceGroupName); + + // Iaas VM CRR Request + Logger.Instance.WriteDebug("Triggering Restore to secondary region: " + secondaryRegion); + restoreRequest.Region = secondaryRegion; + restoreRequest.AffinityGroup = ""; + + CrossRegionRestoreRequest crrRestoreRequest = new CrossRegionRestoreRequest(); + crrRestoreRequest.CrossRegionRestoreAccessDetails = accessToken; + crrRestoreRequest.RestoreRequest = restoreRequest; + + var response = ServiceClientAdapter.RestoreDiskSecondryRegion( + rp, + crrRestoreRequest, + storageAccountResource.Location, + secondaryRegion: secondaryRegion); + + return response; + } + else + { + var response = ServiceClientAdapter.RestoreDisk( + rp, + storageAccountResource.Location, + triggerRestoreRequest, + vaultName: vaultName, + resourceGroupName: resourceGroupName, + vaultLocation: vaultLocation ?? ServiceClientAdapter.BmsAdapter.GetResourceLocation()); + return response; + } } public ProtectedItemResource GetProtectedItem() @@ -827,7 +853,7 @@ public List ListProtectedItems() (CmdletModel.WorkloadType)ProviderData[ItemParams.WorkloadType]; ItemDeleteState deleteState = (ItemDeleteState)ProviderData[ItemParams.DeleteState]; - + bool UseSecondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; PolicyBase policy = (PolicyBase)ProviderData[PolicyParams.ProtectionPolicy]; // 1. Filter by container @@ -837,7 +863,8 @@ public List ListProtectedItems() container, policy, ServiceClientModel.BackupManagementType.AzureIaasVM, - DataSourceType.VM); + DataSourceType.VM, + UseSecondaryRegion); // 2. Filter by item name List itemModels = AzureWorkloadProviderHelper.ListProtectedItemsByItemName( diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/ItemAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/ItemAPIs.cs index 8bb3f07c8f48..9cc605162f7f 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/ItemAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/ItemAPIs.cs @@ -122,6 +122,35 @@ public List ListProtectedItem( return HelperUtils.GetPagedList(listAsync, listNextAsync); } + /// + /// List protected items protected from secondary region by the Recovery Services vault according to the query params + /// and pagination params. + /// + /// Query params + /// Skip token used for pagination + /// List of protected items + public List ListCrrProtectedItem( + ODataQuery queryFilter, + string skipToken = default(string), + string vaultName = null, + string resourceGroupName = null) + { + Func> listAsync = + () => BmsAdapter.Client.BackupProtectedItemsCrr.ListWithHttpMessagesAsync( + vaultName ?? BmsAdapter.GetResourceName(), + resourceGroupName ?? BmsAdapter.GetResourceGroupName(), + queryFilter, + skipToken, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + Func> listNextAsync = + nextLink => BmsAdapter.Client.BackupProtectedItemsCrr.ListNextWithHttpMessagesAsync( + nextLink, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + return HelperUtils.GetPagedList(listAsync, listNextAsync); + } + /// /// Triggers backup on the specified item /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/JobAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/JobAPIs.cs index 196bc1310178..81d11a9b3c40 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/JobAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/JobAPIs.cs @@ -40,6 +40,51 @@ public JobResource GetJob( cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; } + /// + /// Gets CRR job details + /// + /// secondaryRegion for the vault + /// JobId, ResourceId for the Job to be fetched + /// Job response returned by the service + public JobResource GetCRRJobDetails( + string secondaryRegion, + CrrJobRequest jobRequest + ) + { + return BmsAdapter.Client.BackupCrrJobDetails.GetWithHttpMessagesAsync(secondaryRegion, jobRequest).Result.Body; + } + + public List GetCrrJobs(string vaultId, + string jobId, + string status, + string operation, + DateTime startTime, + DateTime endTime, + string backupManagementType, + string azureRegion = null) + { + ODataQuery queryFilter = GetQueryObject( + backupManagementType, + startTime, + endTime, + jobId, + status, + operation); + + CrrJobRequest crrJobRequest = new CrrJobRequest(); + crrJobRequest.ResourceId = vaultId; + + Func> listAsync = + () => BmsAdapter.Client.BackupCrrJobs.ListWithHttpMessagesAsync(azureRegion, crrJobRequest, queryFilter, cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; // , crrJobRequest , queryFilter + + Func> listNextAsync = + nextLink => BmsAdapter.Client.BackupJobs.ListNextWithHttpMessagesAsync( + nextLink, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + return HelperUtils.GetPagedList(listAsync, listNextAsync); + } + /// /// Lists jobs according to the parameters /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/OperationStatusAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/OperationStatusAPIs.cs index 0d2df4a7d71d..9fb55648e9da 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/OperationStatusAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/OperationStatusAPIs.cs @@ -38,6 +38,21 @@ public RestAzureNS.AzureOperationResponse operationId).Result; } + /// + /// Gets status of a generic operation on the protected item using the operation ID + /// + /// ID of the operation in progress + /// Operation status response returned by the service + public RestAzureNS.AzureOperationResponse + GetCrrOperationStatus( + string secondaryRegion, + string operationId) + { + return BmsAdapter.Client.CrrOperationStatus.GetWithHttpMessagesAsync( + secondaryRegion, + operationId).Result; + } + /// /// Gets result of a generic operation on the protection policy using the operation ID /// @@ -136,6 +151,7 @@ public ServiceClientModel.PrepareDataMoveResponse var prepareResponseSerialized = JsonConvert.SerializeObject(prepareResponseBase); PrepareDataMoveResponse prepareResponseDerived = JsonConvert.DeserializeObject(prepareResponseSerialized); + return prepareResponseDerived; } diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs index 7f3f7c6b0095..dc8bc3a24277 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs @@ -82,6 +82,39 @@ public List GetRecoveryPoints( return response; } + /// + /// Lists recovery points from Secondary region for CRR + /// + /// Name of the container which the item belongs to + /// Name of the item + /// Query filter + /// List of recovery points + public List GetRecoveryPointsFromSecondaryRegion( + string containerName, + string protectedItemName, + ODataQuery queryFilter, + string vaultName = null, + string resourceGroupName = null) + { + Func> listAsync = + () => BmsAdapter.Client.RecoveryPointsCrr.ListWithHttpMessagesAsync( + vaultName ?? BmsAdapter.GetResourceName(), + resourceGroupName ?? BmsAdapter.GetResourceGroupName(), + AzureFabricName, + containerName, + protectedItemName, + queryFilter, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + Func> listNextAsync = + nextLink => BmsAdapter.Client.RecoveryPointsCrr.ListNextWithHttpMessagesAsync( + nextLink, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + var response = HelperUtils.GetPagedList(listAsync, listNextAsync); + return response; + } + /// /// provision item level recovery connection identified by the input parameters /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RestoreDiskAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RestoreDiskAPIs.cs index a677ff15da21..fe4735cf9214 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RestoreDiskAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RestoreDiskAPIs.cs @@ -64,5 +64,59 @@ public RestAzureNS.AzureOperationResponse RestoreDisk( return response; } + + + /// + /// Gets the access token for CRR operation + /// + /// Recovery point to restore the disk to + /// secondary region where to trigger the restore + /// Name of recovery services vault + /// Name of the vault resource group + /// CRR access token + public CrrAccessToken GetCRRAccessToken( + AzureRecoveryPoint rp, + string secondaryRegion, + string vaultName = null, + string resourceGroupName = null) + { + Dictionary uriDict = HelperUtils.ParseUri(rp.Id); + string containerUri = HelperUtils.GetContainerUri(uriDict, rp.Id); + string protectedItemUri = HelperUtils.GetProtectedItemUri(uriDict, rp.Id); + string recoveryPointId = rp.RecoveryPointId; + + AADPropertiesResource userInfo = GetAADProperties(secondaryRegion); + var accessToken = BmsAdapter.Client.RecoveryPoints.GetAccessTokenWithHttpMessagesAsync(vaultName ?? BmsAdapter.GetResourceName(), resourceGroupName ?? BmsAdapter.GetResourceGroupName(), + AzureFabricName, containerUri, protectedItemUri, recoveryPointId, userInfo).Result.Body; + + return accessToken.Properties; + } + + /// + /// Restores the disk to the secondaryRegion based on the recovery point and other input parameters + /// + /// Recovery point to restore the disk to + /// ID of the storage account where to restore the disk + /// Location of the storage account where to restore the disk + /// Type of the storage account where to restore the disk + /// Job created by this operation + public RestAzureNS.AzureOperationResponse RestoreDiskSecondryRegion( + AzureRecoveryPoint rp, + CrossRegionRestoreRequest triggerCRRRestoreRequest, + string storageAccountLocation = null, + string secondaryRegion = null) + { + //validation block + if (!triggerCRRRestoreRequest.RestoreRequest.GetType().IsSubclassOf(typeof(AzureWorkloadRestoreRequest))) + { + if (storageAccountLocation != secondaryRegion) + { + throw new Exception(Resources.TriggerRestoreIncorrectRegion); + } + } + + var response = BmsAdapter.Client.CrossRegionRestore.TriggerWithHttpMessagesAsync(secondaryRegion, triggerCRRRestoreRequest).Result; + return response; + } } } \ No newline at end of file diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/VaultAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/VaultAPIs.cs index d92ebd9f2713..7296dd60dc58 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/VaultAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/VaultAPIs.cs @@ -85,13 +85,25 @@ public RestAzureNS.AzureOperationResponse UpdateVaultEncryptionConfig(string res /// /// Name of the resouce group /// Name of the vault - /// Azure Recovery Services Vault. - public Vault GetVault(string resouceGroupName, string vaultName) + /// Azure Recovery Services Vault + public ARSVault GetVault(string resouceGroupName, string vaultName) { - var response = RSAdapter.Client.Vaults.GetWithHttpMessagesAsync(resouceGroupName, vaultName, - cancellationToken: RSAdapter.CmdletCancellationToken).Result; - return response.Body; + Vault response = RSAdapter.Client.Vaults.GetWithHttpMessagesAsync(resouceGroupName, vaultName, + cancellationToken: RSAdapter.CmdletCancellationToken).Result.Body; + + ARSVault vault = new ARSVault(response); + return vault; } + /// + /// Method to get secondary region AAD properties + /// + /// Azure region to fetch AAD properties + /// vault response object. + public AADPropertiesResource GetAADProperties(string azureRegion) + { + AADPropertiesResource aadProperties = BmsAdapter.Client.AadProperties.GetWithHttpMessagesAsync(azureRegion).Result.Body; + return aadProperties; + } } } diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/RecoveryServices.Backup.ServiceClientAdapter.csproj b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/RecoveryServices.Backup.ServiceClientAdapter.csproj index 873de9ce4ae0..a0f2d635ee64 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/RecoveryServices.Backup.ServiceClientAdapter.csproj +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/RecoveryServices.Backup.ServiceClientAdapter.csproj @@ -1,4 +1,4 @@ - + RecoveryServices diff --git a/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.cs b/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.cs index ffb261be9fa9..d398d10e36ea 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.cs @@ -148,5 +148,14 @@ public void TestAzureRSVaultMSI() TestController.NewInstance.RunPsTest( _logger, PsBackupProviderTypes.IaasVm, "Test-AzureRSVaultMSI"); } + + [Fact(Skip = "to be fixed in upcoming release")] + [Trait(Category.AcceptanceType, Category.CheckIn)] + [Trait(TestConstants.Workload, TestConstants.AzureVM)] + public void TestAzureVMCrossRegionRestore() + { + TestController.NewInstance.RunPsTest( + _logger, PsBackupProviderTypes.IaasVm, "Test-AzureVMCrossRegionRestore"); + } } } diff --git a/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.ps1 b/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.ps1 index 10c4282638d8..56d80397a860 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.ps1 +++ b/src/RecoveryServices/RecoveryServices.Backup.Test/ScenarioTests/IaasVm/ItemTests.ps1 @@ -12,6 +12,40 @@ # limitations under the License. # ---------------------------------------------------------------------------------- +function Test-AzureVMCrossRegionRestore +{ + $location = "southeastasia" + $resourceGroupName = Create-ResourceGroup $location 24 + + try + { + # Setup + $vault = Create-RecoveryServicesVault $resourceGroupName $location 25 + + # waiting for service to reflect + Start-TestSleep 20000 + + # Assert that the vault isn't CRR enabled + $crr = Get-AzRecoveryServicesBackupProperty -Vault $vault + Assert-True { $crr.CrossRegionRestore -eq $false } + + # Enable CRR + Set-AzRecoveryServicesBackupProperty -Vault $vault -EnableCrossRegionRestore + + # waiting for service to reflect + Start-TestSleep 30000 + + # Assert that the vault is now CRR enabled + $crr = Get-AzRecoveryServicesBackupProperty -Vault $vault + Assert-True { $crr.CrossRegionRestore -eq $true } + } + finally + { + # Cleanup + Cleanup-ResourceGroup $resourceGroupName + } +} + function Test-AzureRSVaultMSI { try diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Item/GetAzureRmRecoveryServicesBackupItem.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Item/GetAzureRmRecoveryServicesBackupItem.cs index 668a772b339d..ecc42da7d9c8 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Item/GetAzureRmRecoveryServicesBackupItem.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Item/GetAzureRmRecoveryServicesBackupItem.cs @@ -115,6 +115,12 @@ public class GetAzureRmRecoveryServicesBackupItem : RSBackupVaultCmdletBase [ValidateNotNullOrEmpty] public string FriendlyName { get; set; } + /// + /// Fetches the VM Bakup Items from Secondary Region. + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Common.UseSecondaryReg)] + public SwitchParameter UseSecondaryRegion; + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -138,12 +144,12 @@ public override void ExecuteCmdlet() { ItemParams.ProtectionStatus, ProtectionStatus }, { ItemParams.ProtectionState, ProtectionState }, { ItemParams.WorkloadType, WorkloadType }, - { ItemParams.FriendlyName, FriendlyName } + { ItemParams.FriendlyName, FriendlyName }, + { CRRParams.UseSecondaryRegion, UseSecondaryRegion.IsPresent} }, ServiceClientAdapter); IPsBackupProvider psBackupProvider = null; List itemModels = null; - if (BackupManagementType == BackupManagementType.MAB) { AzureWorkloadProviderHelper provider = new AzureWorkloadProviderHelper(ServiceClientAdapter); diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJob.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJob.cs index 90e00ad62279..111928c8d4e2 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJob.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJob.cs @@ -28,12 +28,12 @@ namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets /// [Cmdlet("Get", ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "RecoveryServicesBackupJob"), OutputType(typeof(JobBase))] public class GetAzureRmRecoveryServicesBackupJob : RSBackupVaultCmdletBase - { + { /// /// List of supported BackupManagementTypes for this cmdlet. Used in help text creation. /// private const string validBackupManagementTypes = "AzureVM, AzureStorage, AzureWorkload, MAB"; - + /// /// Filter value for status of job. /// @@ -83,6 +83,13 @@ public class GetAzureRmRecoveryServicesBackupJob : RSBackupVaultCmdletBase [ValidateNotNullOrEmpty] public BackupManagementType? BackupManagementType { get; set; } + /// + /// Switch param to filter jobs based on secondary region (Cross Region Restore). + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Common.UseSecondaryReg)] + [ValidateNotNullOrEmpty] + public SwitchParameter UseSecondaryRegion { get; set; } + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -148,28 +155,50 @@ public override void ExecuteCmdlet() List result = new List(); WriteDebug(string.Format("Filters provided are: StartTime - {0} " + - "EndTime - {1} Status - {2} Operation - {3} Type - {4}", + "EndTime - {1} Status - {2} Operation - {3} Type - {4} UseSecondaryRegion - {5}", From, To, Status, Operation, - BackupManagementType)); + BackupManagementType, + UseSecondaryRegion.ToString())); int resultCount = 0; - var adapterResponse = ServiceClientAdapter.GetJobs( - JobId, - ServiceClientHelpers.GetServiceClientJobStatus(Status), - Operation.ToString(), - rangeStart, - rangeEnd, - ServiceClientHelpers.GetServiceClientBackupManagementType( - BackupManagementType), - vaultName: vaultName, - resourceGroupName: resourceGroupName); - - JobConversions.AddServiceClientJobsToPSList( + if (UseSecondaryRegion.IsPresent) + { + ARSVault vault = ServiceClientAdapter.GetVault(resourceGroupName, vaultName); + string secondaryRegion = BackupUtils.regionMap[vault.Location]; + + WriteDebug(" Getting CRR jobs from secondary region: " + secondaryRegion); + var adapterResponse = ServiceClientAdapter.GetCrrJobs(VaultId, + JobId, + ServiceClientHelpers.GetServiceClientJobStatus(Status), + Operation.ToString(), + rangeStart, + rangeEnd, + ServiceClientHelpers.GetServiceClientBackupManagementType(BackupManagementType), + secondaryRegion); + + JobConversions.AddServiceClientJobsToPSList( + adapterResponse, result, ref resultCount); + } + else + { + var adapterResponse = ServiceClientAdapter.GetJobs( + JobId, + ServiceClientHelpers.GetServiceClientJobStatus(Status), + Operation.ToString(), + rangeStart, + rangeEnd, + ServiceClientHelpers.GetServiceClientBackupManagementType( + BackupManagementType), + vaultName: vaultName, + resourceGroupName: resourceGroupName); + + JobConversions.AddServiceClientJobsToPSList( adapterResponse, result, ref resultCount); + } WriteDebug("Number of jobs fetched: " + result.Count); WriteObject(result, enumerateCollection: true); diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJobDetails.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJobDetails.cs index edaba06d7613..01a4e5bfff3a 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJobDetails.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Jobs/GetAzureRmRecoveryServicesBackupJobDetails.cs @@ -16,6 +16,7 @@ using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models; using Microsoft.Azure.Commands.RecoveryServices.Backup.Helpers; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; +using Microsoft.Azure.Management.RecoveryServices.Backup.Models; using Microsoft.WindowsAzure.Commands.Common.CustomAttributes; namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets @@ -47,6 +48,13 @@ public class GetAzureRmRecoveryServicesBackupJobDetails : RSBackupVaultCmdletBas [ValidateNotNullOrEmpty] public string JobId { get; set; } + /// + /// Switch param to filter job based on secondary region (Cross Region Restore). + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Common.UseSecondaryReg)] + [ValidateNotNullOrEmpty] + public SwitchParameter UseSecondaryRegion { get; set; } + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -64,11 +72,26 @@ public override void ExecuteCmdlet() WriteDebug("Fetching job with ID: " + JobId); - var adapterResponse = ServiceClientAdapter.GetJob( + JobResource jobDetails; + if (UseSecondaryRegion.IsPresent) { + CrrJobRequest jobRequest = new CrrJobRequest(); + jobRequest.JobName = JobId; + jobRequest.ResourceId = VaultId; + + ARSVault vault = ServiceClientAdapter.GetVault(resourceGroupName, vaultName); + string secondaryRegion = BackupUtils.regionMap[vault.Location]; + + jobDetails = ServiceClientAdapter.GetCRRJobDetails(secondaryRegion, jobRequest); + } + else + { + jobDetails = ServiceClientAdapter.GetJob( JobId, vaultName: vaultName, resourceGroupName: resourceGroupName); - WriteObject(JobConversions.GetPSJob(adapterResponse)); + } + + WriteObject(JobConversions.GetPSJob(jobDetails)); }); } } diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/RecoveryPoint/GetAzureRmRecoveryServicesBackupRecoveryPoint.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/RecoveryPoint/GetAzureRmRecoveryServicesBackupRecoveryPoint.cs index fc034c6a0bfd..e7fc032a11b9 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/RecoveryPoint/GetAzureRmRecoveryServicesBackupRecoveryPoint.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/RecoveryPoint/GetAzureRmRecoveryServicesBackupRecoveryPoint.cs @@ -93,6 +93,13 @@ public class GetAzureRmRecoveryServicesBackupRecoveryPoint : RSBackupVaultCmdlet [ValidateNotNullOrEmpty] public string KeyFileDownloadLocation { get; set; } + /// + /// Switch param to filter RecoveryPoints based on secondary region (Cross Region Restore). + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Common.UseSecondaryReg)] + [ValidateNotNullOrEmpty] + public SwitchParameter UseSecondaryRegion { get; set; } + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -112,6 +119,8 @@ public override void ExecuteCmdlet() providerParameters.Add(VaultParams.VaultName, vaultName); providerParameters.Add(VaultParams.ResourceGroupName, resourceGroupName); providerParameters.Add(RecoveryPointParams.Item, Item); + providerParameters.Add(CRRParams.UseSecondaryRegion, UseSecondaryRegion.IsPresent); + if (ParameterSetName == DateTimeFilterParameterSet || ParameterSetName == NoFilterParameterSet) diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/RestoreAzureRMRecoveryServicesBackupItem.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/RestoreAzureRMRecoveryServicesBackupItem.cs index 057e5997ab2f..1dcf6f825d88 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/RestoreAzureRMRecoveryServicesBackupItem.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/RestoreAzureRMRecoveryServicesBackupItem.cs @@ -19,8 +19,10 @@ using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; using Microsoft.Azure.Management.RecoveryServices.Backup.Models; +using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Linq; using System.Management.Automation; using BackupManagementType = Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models.BackupManagementType; using WorkloadType = Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models.WorkloadType; @@ -216,6 +218,13 @@ public class RestoreAzureRmRecoveryServicesBackupItem : RSBackupVaultCmdletBase HelpMessage = ParamHelpMsgs.RestoreFS.MultipleSourceFilePath)]*/ public string DiskEncryptionSetId { get; set; } + /// + /// Switch param to trigger restore to secondary region (Cross Region Restore). + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.RestoreDisk.UseSecondaryReg)] + [ValidateNotNullOrEmpty] + public SwitchParameter RestoreToSecondaryRegion { get; set; } + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -226,6 +235,14 @@ public override void ExecuteCmdlet() string vaultName = resourceIdentifier.ResourceName; string resourceGroupName = resourceIdentifier.ResourceGroupName; Dictionary providerParameters = new Dictionary(); + + string secondaryRegion = ""; + if (RestoreToSecondaryRegion.IsPresent) + { + ARSVault vault = ServiceClientAdapter.GetVault(resourceGroupName, vaultName); + secondaryRegion = BackupUtils.regionMap[vault.Location]; + providerParameters.Add(CRRParams.SecondaryRegion, secondaryRegion); + } providerParameters.Add(VaultParams.VaultName, vaultName); providerParameters.Add(VaultParams.ResourceGroupName, resourceGroupName); @@ -241,15 +258,15 @@ public override void ExecuteCmdlet() providerParameters.Add(RestoreVMBackupItemParams.RestoreDiskList, RestoreDiskList); providerParameters.Add(RestoreVMBackupItemParams.RestoreOnlyOSDisk, RestoreOnlyOSDisk); providerParameters.Add(RestoreVMBackupItemParams.RestoreAsUnmanagedDisks, RestoreAsUnmanagedDisks); - + providerParameters.Add(CRRParams.UseSecondaryRegion, RestoreToSecondaryRegion.IsPresent); + if (DiskEncryptionSetId != null) { AzureVmRecoveryPoint rp = (AzureVmRecoveryPoint)RecoveryPoint; BackupResourceEncryptionConfigResource vaultEncryptionSettings = ServiceClientAdapter.GetVaultEncryptionConfig(resourceGroupName, vaultName); - // do not allow for CRR - to be added - if ((vaultEncryptionSettings.Properties.EncryptionAtRestType == "CustomerManaged") && rp.IsManagedVirtualMachine && !(rp.EncryptionEnabled)) + if ((vaultEncryptionSettings.Properties.EncryptionAtRestType == "CustomerManaged") && rp.IsManagedVirtualMachine && !(rp.EncryptionEnabled) && !(RestoreToSecondaryRegion.IsPresent)) { providerParameters.Add(RestoreVMBackupItemParams.DiskEncryptionSetId, DiskEncryptionSetId); } @@ -293,13 +310,26 @@ public override void ExecuteCmdlet() psBackupProvider = providerManager.GetProviderInstance( WorkloadType.MSSQL, BackupManagementType.AzureWorkload); } - var jobResponse = psBackupProvider.TriggerRestore(); - WriteDebug(string.Format("Restore submitted")); - HandleCreatedJob( + var jobResponse = psBackupProvider.TriggerRestore(); + + if (RestoreToSecondaryRegion.IsPresent) + { + var operationId = jobResponse.Request.RequestUri.Segments.Last(); + var response = ServiceClientAdapter.GetCrrOperationStatus(secondaryRegion, operationId); + + string jobIDJson = JsonConvert.SerializeObject(response.Body.Properties); + string[] jobSplits = jobIDJson.Split(new char[] { '\"' }); + string jobID = jobSplits[jobSplits.Length - 2]; + WriteObject(GetCrrJobObject(secondaryRegion, VaultId, jobID)); + } + else + { + HandleCreatedJob( jobResponse, Resources.RestoreOperation, vaultName: vaultName, resourceGroupName: resourceGroupName); + } }, ShouldProcess(RecoveryPoint != null ? RecoveryPoint.ItemName : WLRecoveryConfig.ToString(), VerbsData.Restore)); } } diff --git a/src/RecoveryServices/RecoveryServices.Backup/Helpers/BackupUtils.cs b/src/RecoveryServices/RecoveryServices.Backup/Helpers/BackupUtils.cs index 961cb9fe3499..d752dee399c4 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Helpers/BackupUtils.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Helpers/BackupUtils.cs @@ -22,6 +22,62 @@ namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets public class BackupUtils { + /// + /// secondary region mapping + /// + public static Dictionary regionMap = new Dictionary(){ + {"eastasia", "southeastasia"}, + {"southeastasia", "eastasia"}, + {"australiaeast", "australiasoutheast"}, + {"australiasoutheast", "australiaeast"}, + {"australiacentral", "australiacentral2"}, + {"australiacentral2", "australiacentral"}, + {"brazilsouth", "southcentralus"}, + {"canadacentral", "canadaeast"}, + {"canadaeast", "canadacentral"}, + {"chinanorth", "chinaeast"}, + {"chinaeast", "chinanorth"}, + {"chinanorth2", "chinaeast2"}, + {"chinaeast2", "chinanorth2"}, + {"northeurope", "westeurope"}, + {"westeurope", "northeurope"}, + {"francecentral", "francesouth"}, + {"francesouth", "francecentral"}, + {"germanycentral", "germanynortheast"}, + {"germanynortheast", "germanycentral"}, + {"centralindia", "southindia"}, + {"southindia", "centralindia"}, + {"westindia", "southindia"}, + {"japaneast", "japanwest"}, + {"japanwest", "japaneast"}, + {"koreacentral", "koreasouth"}, + {"koreasouth", "koreacentral"}, + {"eastus", "westus"}, + {"westus", "eastus"}, + {"eastus2", "centralus"}, + {"centralus", "eastus2"}, + {"northcentralus", "southcentralus"}, + {"southcentralus", "northcentralus"}, + {"westus2", "westcentralus"}, + {"westcentralus", "westus2"}, + {"centraluseuap", "eastus2euap"}, + {"eastus2euap", "centraluseuap"}, + {"southafricanorth", "southafricawest"}, + {"southafricawest", "southafricanorth"}, + {"switzerlandnorth", "switzerlandwest"}, + {"switzerlandwest", "switzerlandnorth"}, + {"ukwest", "uksouth"}, + {"uksouth", "ukwest"}, + {"uaenorth", "uaecentral"}, + {"uaecentral", "uaenorth"}, + {"usdodeast", "usdodcentral"}, + {"usdodcentral", "usdodeast"}, + {"usgovarizona", "usgovtexas"}, + {"usgovtexas", "usgovarizona"}, + {"usgoviowa", "usgovvirginia"}, + {"usgovvirginia", "usgovtexas"} + }; + /// /// Get Protected Items for particular workload type /// diff --git a/src/RecoveryServices/RecoveryServices.Backup/ParamHelpMsgs.cs b/src/RecoveryServices/RecoveryServices.Backup/ParamHelpMsgs.cs index 48b9e1dc8330..c0cb8894f320 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/ParamHelpMsgs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/ParamHelpMsgs.cs @@ -47,6 +47,7 @@ internal static class Common public const string ConfirmationMessage = "Don't ask for confirmation."; public const string BackupManagementType = "The class of resources being protected. Currently the values supported for this cmdlet are "; public const string IdentityType = "The MSI type assigned to Recovery Services Vault. Input 'None' if MSI has to be removed."; + public const string UseSecondaryReg = "Filters from Secondary Region for Cross Region Restore"; } internal static class Policy @@ -141,6 +142,7 @@ internal static class RestoreDisk public const string StorageAccountName = "Storage account name where the disks need to be recovered"; public const string StorageAccountResourceGroupName = "Resource group name of Storage account name where the disks need to be recovered"; public const string RecoveryConfig = "Recovery config"; + public const string UseSecondaryReg = "Trigger restore to secondary region (Cross Region Restore)"; } internal static class RestoreVM diff --git a/src/RecoveryServices/RecoveryServices.Backup/RSBackupVaultCmdletBase.cs b/src/RecoveryServices/RecoveryServices.Backup/RSBackupVaultCmdletBase.cs index a1bc8097f5fd..11557cc89235 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/RSBackupVaultCmdletBase.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/RSBackupVaultCmdletBase.cs @@ -1,4 +1,5 @@ -using Microsoft.Azure.Commands.RecoveryServices.Backup.Helpers; +using Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models; +using Microsoft.Azure.Commands.RecoveryServices.Backup.Helpers; using Microsoft.Azure.Commands.RecoveryServices.Backup.Properties; using Microsoft.Azure.Management.RecoveryServices.Backup.Models; using System; @@ -30,7 +31,7 @@ public CmdletModel.JobBase GetJobObject(string jobId, string vaultName = null, s jobId, vaultName: vaultName, resourceGroupName: resourceGroupName)); - } + } /// /// Gets list of job PS models after fetching the job objects from the service given the list of job IDs. @@ -50,6 +51,24 @@ public CmdletModel.JobBase GetJobObject(string jobId, string vaultName = null, s return result; } + /// + /// Get the job PS model after fetching the job object from the service given the job ID. + /// + /// ID of the job to be fetched + /// + public CmdletModel.JobBase GetCrrJobObject(string secondaryRegion, string vaultId, string jobId) + { + CrrJobRequest jobRequest = new CrrJobRequest(); + jobRequest.JobName = jobId; + jobRequest.ResourceId = vaultId; + + JobBase job = JobConversions.GetPSJob(ServiceClientAdapter.GetCRRJobDetails( + secondaryRegion, + jobRequest)); + + return job; + } + /// /// Based on the response from the service, handles the job created in the service appropriately. /// diff --git a/src/RecoveryServices/RecoveryServices.SiteRecovery.Test/RecoveryServices.SiteRecovery.Test.csproj b/src/RecoveryServices/RecoveryServices.SiteRecovery.Test/RecoveryServices.SiteRecovery.Test.csproj index 9e827ce8caa1..35d7e027f014 100644 --- a/src/RecoveryServices/RecoveryServices.SiteRecovery.Test/RecoveryServices.SiteRecovery.Test.csproj +++ b/src/RecoveryServices/RecoveryServices.SiteRecovery.Test/RecoveryServices.SiteRecovery.Test.csproj @@ -17,6 +17,7 @@ + diff --git a/src/RecoveryServices/RecoveryServices/ChangeLog.md b/src/RecoveryServices/RecoveryServices/ChangeLog.md index 3359fcf83725..f8e8a808eee0 100644 --- a/src/RecoveryServices/RecoveryServices/ChangeLog.md +++ b/src/RecoveryServices/RecoveryServices/ChangeLog.md @@ -18,6 +18,7 @@ - Additional information about change #1 --> ## Upcoming Release +* Added Cross Region Restore feature. * Blocked getting workload config when target item is an availability group. ## Version 3.2.0 diff --git a/src/RecoveryServices/RecoveryServices/Common/PSRecoveryServicesVaultClient.cs b/src/RecoveryServices/RecoveryServices/Common/PSRecoveryServicesVaultClient.cs index 47408c62aabb..4516c4273d3e 100644 --- a/src/RecoveryServices/RecoveryServices/Common/PSRecoveryServicesVaultClient.cs +++ b/src/RecoveryServices/RecoveryServices/Common/PSRecoveryServicesVaultClient.cs @@ -119,13 +119,27 @@ public void UpdateVaultStorageType(string resouceGroupName, string vaultName, vaultName, resouceGroupName, backupStorageConfig, GetRequestHeaders()); } + /// + /// Method to Patch Azure Recovery Services Vault Backup Properties + /// + /// Name of the resouce group + /// Name of the vault + /// Backup Properties Update + /// Azure Operation response object. + public void PatchVaultStorageConfigProperties(string resouceGroupName, string vaultName, + BackupResourceConfigResource backupStorageConfig) + { + GetRecoveryServicesBackupClient.BackupResourceStorageConfigs.PatchWithHttpMessagesAsync( + vaultName, resouceGroupName, backupStorageConfig, GetRequestHeaders()); + } + /// /// Method to Get Azure Recovery Services Vault Backup Properties /// /// Name of the resouce group /// Name of the vault /// Azure Resource Storage response object. - public BackupResourceConfigResource GetVaultStorageType(string resouceGroupName, string vaultName) + public BackupResourceConfigResource GetVaultStorageConfig(string resouceGroupName, string vaultName) { return GetRecoveryServicesBackupClient.BackupResourceStorageConfigs.GetWithHttpMessagesAsync( vaultName, resouceGroupName, GetRequestHeaders()).Result.Body; diff --git a/src/RecoveryServices/RecoveryServices/Models/PSObjects.cs b/src/RecoveryServices/RecoveryServices/Models/PSObjects.cs index 865f45001e39..6af96074f83c 100644 --- a/src/RecoveryServices/RecoveryServices/Models/PSObjects.cs +++ b/src/RecoveryServices/RecoveryServices/Models/PSObjects.cs @@ -165,6 +165,11 @@ public class ASRVaultBackupProperties /// public string BackupStorageRedundancy { get; set; } + /// + /// Gets or sets CrossRegionRestore Flag. + /// + public bool CrossRegionRestore { get; set; } + #endregion } diff --git a/src/RecoveryServices/RecoveryServices/RecoveryServices.csproj b/src/RecoveryServices/RecoveryServices/RecoveryServices.csproj index 047e5d99adf4..94a3d478d7b7 100644 --- a/src/RecoveryServices/RecoveryServices/RecoveryServices.csproj +++ b/src/RecoveryServices/RecoveryServices/RecoveryServices.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/RecoveryServices/RecoveryServices/Vault/GetAzureRmRecoveryServicesBackupProperty.cs b/src/RecoveryServices/RecoveryServices/Vault/GetAzureRmRecoveryServicesBackupProperty.cs index ff873bfb1cfe..a884a7b5185f 100644 --- a/src/RecoveryServices/RecoveryServices/Vault/GetAzureRmRecoveryServicesBackupProperty.cs +++ b/src/RecoveryServices/RecoveryServices/Vault/GetAzureRmRecoveryServicesBackupProperty.cs @@ -50,10 +50,11 @@ public override void ExecuteCmdlet() { try { - BackupResourceConfigResource getStorageResponse = RecoveryServicesClient.GetVaultStorageType( + BackupResourceConfigResource getStorageResponse = RecoveryServicesClient.GetVaultStorageConfig( this.Vault.ResourceGroupName, this.Vault.Name); ASRVaultBackupProperties vaultBackupProperties = new ASRVaultBackupProperties(); vaultBackupProperties.BackupStorageRedundancy = getStorageResponse.Properties.StorageType; + vaultBackupProperties.CrossRegionRestore = (bool)getStorageResponse.Properties.CrossRegionRestoreFlag; this.WriteObject(vaultBackupProperties); } catch (Exception exception) diff --git a/src/RecoveryServices/RecoveryServices/Vault/SetAzureRmRecoveryServicesBackupProperties.cs b/src/RecoveryServices/RecoveryServices/Vault/SetAzureRmRecoveryServicesBackupProperties.cs index 93655ebcd1f7..3869bd7c4933 100644 --- a/src/RecoveryServices/RecoveryServices/Vault/SetAzureRmRecoveryServicesBackupProperties.cs +++ b/src/RecoveryServices/RecoveryServices/Vault/SetAzureRmRecoveryServicesBackupProperties.cs @@ -41,6 +41,12 @@ public class SetAzureRmRecoveryServicesBackupProperties : RecoveryServicesCmdlet [Parameter(Mandatory = false)] public AzureRmRecoveryServicesBackupStorageRedundancyType? BackupStorageRedundancy { get; set; } + /// + /// Gets or sets CrossRegionRestore flag. + /// + [Parameter(Mandatory = false)] + public SwitchParameter EnableCrossRegionRestore { get; set; } + #endregion Parameters /// @@ -52,15 +58,27 @@ public override void ExecuteCmdlet() { try { - if (this.BackupStorageRedundancy.HasValue) - { - BackupResourceConfigResource vaultStorageRequest = new BackupResourceConfigResource(); - BackupResourceConfig properties = new BackupResourceConfig(); - vaultStorageRequest.Properties = properties; + BackupResourceConfigResource vaultStorageRequest = new BackupResourceConfigResource(); + BackupResourceConfig properties = new BackupResourceConfig(); + vaultStorageRequest.Properties = properties; + + if (this.BackupStorageRedundancy.HasValue) + { vaultStorageRequest.Properties.StorageModelType = BackupStorageRedundancy.ToString(); + if (this.EnableCrossRegionRestore.IsPresent) + { + vaultStorageRequest.Properties.CrossRegionRestoreFlag = true; + } RecoveryServicesClient.UpdateVaultStorageType( this.Vault.ResourceGroupName, this.Vault.Name, vaultStorageRequest); } + else if(this.EnableCrossRegionRestore.IsPresent) + { + vaultStorageRequest.Properties.CrossRegionRestoreFlag = true; + + RecoveryServicesClient.PatchVaultStorageConfigProperties( + this.Vault.ResourceGroupName, this.Vault.Name, vaultStorageRequest); + } else { throw new Exception(Properties.Resources.NoBackupPropertiesProvided); diff --git a/src/RecoveryServices/RecoveryServices/help/Copy-AzRecoveryServicesVault.md b/src/RecoveryServices/RecoveryServices/help/Copy-AzRecoveryServicesVault.md index 947113b9b87d..e878b84e8e31 100644 --- a/src/RecoveryServices/RecoveryServices/help/Copy-AzRecoveryServicesVault.md +++ b/src/RecoveryServices/RecoveryServices/help/Copy-AzRecoveryServicesVault.md @@ -23,7 +23,7 @@ The **Copy-AzRecoveryServicesVault** cmdlet copies data from a vault in one regi ## EXAMPLES ### Example 1: Copy data from vault1 to vault2 -```powershell +``` PS C:\> $sourceVault = Get-AzRecoveryServicesVault -ResourceGroupName "rgName1" -Name "vault1" PS C:\> $targetVault = Get-AzRecoveryServicesVault -ResourceGroupName "rgName2" -Name "vault2" PS C:\> Copy-AzRecoveryServicesVault -SourceVault $sourceVault -TargetVault $targetVault @@ -33,11 +33,11 @@ The first two cmdlets fetch Recovery Services Vault - vault1 and vault2 respecti The second command triggers a complete data move from vault1 to vault2. ### Example 2: Copy data from vault1 to vault2 with only failed items -```powershell +``` PS C:\> $sourceVault = Get-AzRecoveryServicesVault -ResourceGroupName "rgName1" -Name "vault1" PS C:\> $targetVault = Get-AzRecoveryServicesVault -ResourceGroupName "rgName2" -Name "vault2" PS C:\> Copy-AzRecoveryServicesVault -SourceVault $sourceVault -TargetVault $targetVault -RetryOnlyFailed -``` +``` The first two cmdlets fetch Recovery Services Vault - vault1 and vault2 respectively. The second command triggers a partial data move from vault1 to vault2 with only those items which failed in previous move operations. diff --git a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupItem.md b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupItem.md index 5bfc3b67dded..351745670187 100644 --- a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupItem.md +++ b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupItem.md @@ -19,7 +19,7 @@ Gets the items from a container in Backup. Get-AzRecoveryServicesBackupItem [-Container] [[-Name] ] [[-ProtectionStatus] ] [[-ProtectionState] ] [-WorkloadType] [[-DeleteState] ] [-FriendlyName ] [-VaultId ] - [-DefaultProfile ] [] + [-DefaultProfile ] [-UseSecondaryRegion] [] ``` ### GetItemsForVault @@ -27,7 +27,7 @@ Get-AzRecoveryServicesBackupItem [-Container] [[-Name] ] Get-AzRecoveryServicesBackupItem [-BackupManagementType] [[-Name] ] [[-ProtectionStatus] ] [[-ProtectionState] ] [-WorkloadType] [[-DeleteState] ] [-FriendlyName ] [-VaultId ] - [-DefaultProfile ] [] + [-DefaultProfile ] [-UseSecondaryRegion] [] ``` ### GetItemsForPolicy @@ -35,7 +35,7 @@ Get-AzRecoveryServicesBackupItem [-BackupManagementType] Get-AzRecoveryServicesBackupItem [-Policy] [[-Name] ] [[-ProtectionStatus] ] [[-ProtectionState] ] [[-DeleteState] ] [-FriendlyName ] [-VaultId ] - [-DefaultProfile ] [] + [-DefaultProfile ] [-UseSecondaryRegion] [] ``` ## DESCRIPTION diff --git a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJob.md b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJob.md index cded53e040ad..6872e82a7328 100644 --- a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJob.md +++ b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJob.md @@ -17,7 +17,7 @@ Gets Backup jobs. ``` Get-AzRecoveryServicesBackupJob [[-Status] ] [[-Operation] ] [[-From] ] [[-To] ] [[-JobId] ] [[-Job] ] [-BackupManagementType ] - [-VaultId ] [-DefaultProfile ] [] + [-UseSecondaryRegion] [-VaultId ] [-DefaultProfile ] [] ``` ## DESCRIPTION diff --git a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJobDetail.md b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJobDetail.md index ad4d352925b0..d5ee107af89c 100644 --- a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJobDetail.md +++ b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupJobDetail.md @@ -16,14 +16,14 @@ Gets details for a Backup job. ### JobFilterSet (Default) ``` -Get-AzRecoveryServicesBackupJobDetail [-Job] [-VaultId ] - [-DefaultProfile ] [] +Get-AzRecoveryServicesBackupJobDetail [-Job] [-UseSecondaryRegion] + [-VaultId ] [-DefaultProfile ] [] ``` ### IdFilterSet ``` -Get-AzRecoveryServicesBackupJobDetail [-JobId] [-VaultId ] - [-DefaultProfile ] [] +Get-AzRecoveryServicesBackupJobDetail [-JobId] [-UseSecondaryRegion] + [-VaultId ] [-DefaultProfile ] [] ``` ## DESCRIPTION diff --git a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md index 89628c086b56..c151d1e971c3 100644 --- a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md +++ b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md @@ -16,20 +16,20 @@ Gets the recovery points for a backed up item. ### NoFilterParameterSet (Default) ``` -Get-AzRecoveryServicesBackupRecoveryPoint [-Item] [-VaultId ] +Get-AzRecoveryServicesBackupRecoveryPoint [-Item] [-UseSecondaryRegion] [-VaultId ] [-DefaultProfile ] [] ``` ### DateTimeFilter ``` Get-AzRecoveryServicesBackupRecoveryPoint [[-StartDate] ] [[-EndDate] ] [-Item] - [-VaultId ] [-DefaultProfile ] [] + [-UseSecondaryRegion] [-VaultId ] [-DefaultProfile ] [] ``` ### RecoveryPointId ``` Get-AzRecoveryServicesBackupRecoveryPoint [-Item] [-RecoveryPointId] - [[-KeyFileDownloadLocation] ] [-VaultId ] [-DefaultProfile ] + [[-KeyFileDownloadLocation] ] [-UseSecondaryRegion] [-VaultId ] [-DefaultProfile ] [] ``` diff --git a/src/RecoveryServices/RecoveryServices/help/Restore-AzRecoveryServicesBackupItem.md b/src/RecoveryServices/RecoveryServices/help/Restore-AzRecoveryServicesBackupItem.md index 15b436758569..644b777f7f85 100644 --- a/src/RecoveryServices/RecoveryServices/help/Restore-AzRecoveryServicesBackupItem.md +++ b/src/RecoveryServices/RecoveryServices/help/Restore-AzRecoveryServicesBackupItem.md @@ -19,7 +19,7 @@ The same command is used to restore Azure Virtual machines, databases running wi ``` Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] [-StorageAccountName] [-StorageAccountResourceGroupName] [-RestoreOnlyOSDisk] - [-RestoreDiskList ] [-DiskEncryptionSetId ] [-VaultId ] [-DefaultProfile ] [-WhatIf] + [-RestoreDiskList ] [-DiskEncryptionSetId ] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` @@ -27,7 +27,7 @@ Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] ``` Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] [-StorageAccountName] [-StorageAccountResourceGroupName] [-TargetResourceGroupName] - [-RestoreOnlyOSDisk] [-RestoreDiskList ] [-DiskEncryptionSetId ] [-VaultId ] + [-RestoreOnlyOSDisk] [-RestoreDiskList ] [-DiskEncryptionSetId ] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` @@ -35,7 +35,7 @@ Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] ``` Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] [-StorageAccountName] [-StorageAccountResourceGroupName] [-RestoreOnlyOSDisk] - [-RestoreDiskList ] [-RestoreAsUnmanagedDisks] [-VaultId ] + [-RestoreDiskList ] [-RestoreAsUnmanagedDisks] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` @@ -43,7 +43,7 @@ Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] ``` Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] [-StorageAccountName] [-StorageAccountResourceGroupName] [-UseOriginalStorageAccount] - [-RestoreOnlyOSDisk] [-RestoreDiskList ] [-VaultId ] + [-RestoreOnlyOSDisk] [-RestoreDiskList ] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` @@ -52,13 +52,13 @@ Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-RecoveryPoint] -ResolveConflict [-SourceFilePath ] [-SourceFileType ] [-TargetStorageAccountName ] [-TargetFileShareName ] - [-TargetFolder ] [-MultipleSourceFilePath ] [-VaultId ] + [-TargetFolder ] [-MultipleSourceFilePath ] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ### AzureWorkloadParameterSet ``` -Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-WLRecoveryConfig] +Restore-AzRecoveryServicesBackupItem [-VaultLocation ] [-WLRecoveryConfig] [-RestoreToSecondaryRegion] [-VaultId ] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` diff --git a/src/RecoveryServices/RecoveryServices/help/Set-AzRecoveryServicesBackupProperty.md b/src/RecoveryServices/RecoveryServices/help/Set-AzRecoveryServicesBackupProperty.md index 40358a54ecf1..bf523a2d8f30 100644 --- a/src/RecoveryServices/RecoveryServices/help/Set-AzRecoveryServicesBackupProperty.md +++ b/src/RecoveryServices/RecoveryServices/help/Set-AzRecoveryServicesBackupProperty.md @@ -14,9 +14,8 @@ Sets the properties for backup management. ## SYNTAX ``` -Set-AzRecoveryServicesBackupProperty -Vault - [-BackupStorageRedundancy ] - [-DefaultProfile ] [-WhatIf] [-Confirm] [] +Set-AzRecoveryServicesBackupProperty -Vault [-BackupStorageRedundancy ] + [-EnableCrossRegionRestore] [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION