From 7c8b1c0d9438b217233cf9e6848ce93a1a2358e5 Mon Sep 17 00:00:00 2001 From: Himanshu Agarwal Date: Tue, 6 Jun 2023 08:40:30 +0530 Subject: [PATCH] Regenerated CRR SDK. Fixed issues with SQL CRR. Fixed bug with rp expiry time, making 30 days expiry time for adhoc backup as default from client side. Added example to fetch pruned recovery points after modify policy. Fixed the documentation for suspend backups with immutability. Updated changelog --- .../Generated/IRecoveryPointsCrrOperations.cs | 39 +++ .../Generated/RecoveryPointsCrrOperations.cs | 229 ++++++++++++++++++ .../RecoveryPointsCrrOperationsExtensions.cs | 68 ++++++ .../README.md | 2 +- .../Conversions/RecoveryPointConversions.cs | 36 ++- .../AzureWorkloadProviderHelper.cs | 19 +- .../BMSAPIs/RecoveryPointsAPIs.cs | 29 ++- ...BackupAzureRmRecoveryServicesBackupItem.cs | 5 + ...eryServicesBackupWorkloadRecoveryConfig.cs | 161 ++++++++++-- .../RecoveryServices/ChangeLog.md | 4 + .../Backup-AzRecoveryServicesBackupItem.md | 4 +- ...able-AzRecoveryServicesBackupProtection.md | 5 +- ...t-AzRecoveryServicesBackupRecoveryPoint.md | 45 ++++ 13 files changed, 603 insertions(+), 43 deletions(-) diff --git a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/IRecoveryPointsCrrOperations.cs b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/IRecoveryPointsCrrOperations.cs index 50b592e81e93..f9605ca33a61 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/IRecoveryPointsCrrOperations.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/IRecoveryPointsCrrOperations.cs @@ -63,6 +63,45 @@ public partial interface IRecoveryPointsCrrOperations /// Task>> ListWithHttpMessagesAsync(string vaultName, string resourceGroupName, string fabricName, string containerName, string protectedItemName, ODataQuery odataQuery = default(ODataQuery), Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); /// + /// Provides the information of the backed up data identified using + /// RecoveryPointID. + /// + /// + /// The name of the recovery services vault. + /// + /// + /// The name of the resource group where the recovery services vault is + /// present. + /// + /// + /// Fabric name associated with backed up item. + /// + /// + /// Container name associated with backed up item. + /// + /// + /// Backed up item name whose backup data needs to be fetched. + /// + /// + /// RecoveryPointID represents the backed up data to be fetched. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task> GetWithHttpMessagesAsync(string vaultName, string resourceGroupName, string fabricName, string containerName, string protectedItemName, string recoveryPointId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// /// Lists the backup copies for the backed up item. /// /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperations.cs b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperations.cs index dfdc86105e7c..b5b0f9b11231 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperations.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperations.cs @@ -282,6 +282,235 @@ internal RecoveryPointsCrrOperations(RecoveryServicesBackupClient client) return _result; } + /// + /// Provides the information of the backed up data identified using + /// RecoveryPointID. + /// + /// + /// The name of the recovery services vault. + /// + /// + /// The name of the resource group where the recovery services vault is + /// present. + /// + /// + /// Fabric name associated with backed up item. + /// + /// + /// Container name associated with backed up item. + /// + /// + /// Backed up item name whose backup data needs to be fetched. + /// + /// + /// RecoveryPointID represents the backed up data to be fetched. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> GetWithHttpMessagesAsync(string vaultName, string resourceGroupName, string fabricName, string containerName, string protectedItemName, string recoveryPointId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.ApiVersion == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ApiVersion"); + } + if (vaultName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "vaultName"); + } + if (resourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "resourceGroupName"); + } + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (fabricName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "fabricName"); + } + if (containerName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "containerName"); + } + if (protectedItemName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "protectedItemName"); + } + if (recoveryPointId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "recoveryPointId"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("vaultName", vaultName); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("fabricName", fabricName); + tracingParameters.Add("containerName", containerName); + tracingParameters.Add("protectedItemName", protectedItemName); + tracingParameters.Add("recoveryPointId", recoveryPointId); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Get", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.RecoveryServices/vaults/{vaultName}/backupFabrics/{fabricName}/protectionContainers/{containerName}/protectedItems/{protectedItemName}/recoveryPoints/{recoveryPointId}").ToString(); + _url = _url.Replace("{vaultName}", System.Uri.EscapeDataString(vaultName)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(resourceGroupName)); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{fabricName}", System.Uri.EscapeDataString(fabricName)); + _url = _url.Replace("{containerName}", System.Uri.EscapeDataString(containerName)); + _url = _url.Replace("{protectedItemName}", System.Uri.EscapeDataString(protectedItemName)); + _url = _url.Replace("{recoveryPointId}", System.Uri.EscapeDataString(recoveryPointId)); + List _queryParameters = new List(); + if (Client.ApiVersion != null) + { + _queryParameters.Add(string.Format("api-version={0}", System.Uri.EscapeDataString(Client.ApiVersion))); + } + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new NewErrorResponseException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + NewErrorResponse _errorBody = Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + /// /// Lists the backup copies for the backed up item. /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperationsExtensions.cs b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperationsExtensions.cs index 004aa6f7cbf1..45fd1ea5979b 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperationsExtensions.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/Generated/RecoveryPointsCrrOperationsExtensions.cs @@ -88,6 +88,74 @@ public static partial class RecoveryPointsCrrOperationsExtensions } } + /// + /// Provides the information of the backed up data identified using + /// RecoveryPointID. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The name of the recovery services vault. + /// + /// + /// The name of the resource group where the recovery services vault is + /// present. + /// + /// + /// Fabric name associated with backed up item. + /// + /// + /// Container name associated with backed up item. + /// + /// + /// Backed up item name whose backup data needs to be fetched. + /// + /// + /// RecoveryPointID represents the backed up data to be fetched. + /// + public static RecoveryPointResource Get(this IRecoveryPointsCrrOperations operations, string vaultName, string resourceGroupName, string fabricName, string containerName, string protectedItemName, string recoveryPointId) + { + return operations.GetAsync(vaultName, resourceGroupName, fabricName, containerName, protectedItemName, recoveryPointId).GetAwaiter().GetResult(); + } + + /// + /// Provides the information of the backed up data identified using + /// RecoveryPointID. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The name of the recovery services vault. + /// + /// + /// The name of the resource group where the recovery services vault is + /// present. + /// + /// + /// Fabric name associated with backed up item. + /// + /// + /// Container name associated with backed up item. + /// + /// + /// Backed up item name whose backup data needs to be fetched. + /// + /// + /// RecoveryPointID represents the backed up data to be fetched. + /// + /// + /// The cancellation token. + /// + public static async Task GetAsync(this IRecoveryPointsCrrOperations operations, string vaultName, string resourceGroupName, string fabricName, string containerName, string protectedItemName, string recoveryPointId, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.GetWithHttpMessagesAsync(vaultName, resourceGroupName, fabricName, containerName, protectedItemName, recoveryPointId, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + /// /// Lists the backup copies for the backed up item. /// diff --git a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/README.md b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/README.md index 64ea47b98aca..d231425eade5 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/README.md +++ b/src/RecoveryServices/RecoveryServices.Backup.CrossRegionRestore.Management.Sdk/README.md @@ -24,7 +24,7 @@ payload-flattening-threshold: 2 ### ``` yaml -commit: 0e20dd2e4e2a40e83840c30cce2efc4847fd9cb9 +commit: 1f9b94d9f01369d1438a80aaf6a658e27209c594 input-file: - https://github.com/Azure/azure-rest-api-specs/blob/$(commit)/specification/recoveryservicesbackup/resource-manager/Microsoft.RecoveryServices/stable/2021-11-15/bms.json diff --git a/src/RecoveryServices/RecoveryServices.Backup.Helpers/Conversions/RecoveryPointConversions.cs b/src/RecoveryServices/RecoveryServices.Backup.Helpers/Conversions/RecoveryPointConversions.cs index 53536558986d..92e2e3a9821f 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Helpers/Conversions/RecoveryPointConversions.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Helpers/Conversions/RecoveryPointConversions.cs @@ -26,7 +26,25 @@ namespace Microsoft.Azure.Commands.RecoveryServices.Backup.Helpers /// Recovery Point conversion helper. /// public class RecoveryPointConversions - { + { + /// + /// Tries parsing string to DateTime + /// + /// time in string format to be converted to DateTime + /// + public static DateTime? ParseStringToDateTime(string inputTime) + { + try + { + DateTime convertedTime = DateTime.Parse(inputTime); + return convertedTime; + } + catch (Exception e) + { + Logger.Instance.WriteDebug(e.Message); + return ((DateTime?)null); + } + } /// /// filter RPs based on tier @@ -365,7 +383,7 @@ public static RecoveryPointBase GetPSAzureVMRecoveryPoint( { isRehydrated = true; - rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? DateTime.Parse(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; + rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? ParseStringToDateTime(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; } } @@ -428,7 +446,7 @@ public static RecoveryPointBase GetPSAzureVMRecoveryPoint( if(recoveryPoint.RecoveryPointProperties != null) { - rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? DateTime.Parse(recoveryPoint.RecoveryPointProperties.ExpiryTime): (DateTime?)null; + rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? ParseStringToDateTime(recoveryPoint.RecoveryPointProperties.ExpiryTime): (DateTime?)null; rpBase.RuleName = recoveryPoint.RecoveryPointProperties.RuleName; } @@ -485,7 +503,7 @@ public static RecoveryPointBase GetPSAzureFileRecoveryPoint( if (recoveryPoint.RecoveryPointProperties != null) { - rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? DateTime.Parse(recoveryPoint.RecoveryPointProperties.ExpiryTime) : (DateTime?)null; + rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? ParseStringToDateTime(recoveryPoint.RecoveryPointProperties.ExpiryTime) : (DateTime?)null; rpBase.RuleName = recoveryPoint.RecoveryPointProperties.RuleName; } @@ -556,7 +574,7 @@ public static RecoveryPointBase GetPSAzureWorkloadRecoveryPoint( if (tierInfo.Type == ServiceClientModel.RecoveryPointTierType.ArchivedRP) { isRehydrated = true; - rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? DateTime.Parse(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; + rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? ParseStringToDateTime(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; } } @@ -619,7 +637,7 @@ public static RecoveryPointBase GetPSAzureWorkloadRecoveryPoint( if (recoveryPoint.RecoveryPointProperties != null) { - rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? DateTime.Parse(recoveryPoint.RecoveryPointProperties.ExpiryTime) : (DateTime?)null; + rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? ParseStringToDateTime(recoveryPoint.RecoveryPointProperties.ExpiryTime) : (DateTime?)null; rpBase.RuleName = recoveryPoint.RecoveryPointProperties.RuleName; } @@ -733,7 +751,7 @@ public static RecoveryPointBase GetPSAzureVMRecoveryPointForSecondaryRegion( { isRehydrated = true; - rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? DateTime.Parse(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; + rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? ParseStringToDateTime(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; } } @@ -797,7 +815,7 @@ public static RecoveryPointBase GetPSAzureVMRecoveryPointForSecondaryRegion( // to uncomment while adding expiry time for CRR RPs /*if (recoveryPoint.RecoveryPointProperties != null) { - rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? DateTime.Parse(recoveryPoint.RecoveryPointProperties.ExpiryTime): (DateTime?)null; + rpBase.RecoveryPointExpiryTime = (recoveryPoint.RecoveryPointProperties.ExpiryTime != null) ? ParseStringToDateTime(recoveryPoint.RecoveryPointProperties.ExpiryTime): (DateTime?)null; rpBase.RuleName = recoveryPoint.RecoveryPointProperties.RuleName; }*/ @@ -928,7 +946,7 @@ public static RecoveryPointBase GetPSAzureWorkloadRecoveryPointForSecondaryRegio if (tierInfo.Type == CrrModel.RecoveryPointTierType.ArchivedRP) { isRehydrated = true; - rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? DateTime.Parse(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; + rpBase.RehydrationExpiryTime = (tierInfo.ExtendedInfo.ContainsKey("RehydratedRPExpiryTime")) ? ParseStringToDateTime(tierInfo.ExtendedInfo["RehydratedRPExpiryTime"]) : (DateTime?)null; } } diff --git a/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs b/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs index a39a73c5ec9d..db513adc4f32 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.Providers/AzureWorkloadProviderHelper.cs @@ -914,6 +914,7 @@ public RecoveryPointBase GetRecoveryPointDetails(Dictionary Provid string vaultName = (string)ProviderData[VaultParams.VaultName]; string resourceGroupName = (string)ProviderData[VaultParams.ResourceGroupName]; ItemBase item = ProviderData[RecoveryPointParams.Item] as ItemBase; + bool secondaryRegion = (bool)ProviderData[CRRParams.UseSecondaryRegion]; string recoveryPointId = ProviderData[RecoveryPointParams.RecoveryPointId].ToString(); @@ -921,14 +922,28 @@ public RecoveryPointBase GetRecoveryPointDetails(Dictionary Provid string containerUri = HelperUtils.GetContainerUri(uriDict, item.Id); string protectedItemName = HelperUtils.GetProtectedItemUri(uriDict, item.Id); - var rpResponse = ServiceClientAdapter.GetRecoveryPointDetails( + if (secondaryRegion) + { + var rpResponse = ServiceClientAdapter.GetRecoveryPointDetailsFromSecondaryRegion( containerUri, protectedItemName, recoveryPointId, vaultName: vaultName, resourceGroupName: resourceGroupName); - return RecoveryPointConversions.GetPSAzureRecoveryPoints(rpResponse, item); + return RecoveryPointConversions.GetPSAzureRecoveryPointsFromSecondaryRegion(rpResponse, item); + } + else + { + var rpResponse = ServiceClientAdapter.GetRecoveryPointDetails( + containerUri, + protectedItemName, + recoveryPointId, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + + return RecoveryPointConversions.GetPSAzureRecoveryPoints(rpResponse, item); + } } public static CmdletModel.DailyRetentionFormat GetDailyRetentionFormat() diff --git a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs index 8e5bf2ee9f02..85ad4f593c7f 100644 --- a/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs +++ b/src/RecoveryServices/RecoveryServices.Backup.ServiceClientAdapter/BMSAPIs/RecoveryPointsAPIs.cs @@ -123,6 +123,34 @@ public List GetRecoveryPoints( return response; } + /// + /// Gets detail about the recovery point from Secondary region for CRR + /// + /// Name of the container which the item belongs to + /// Name of the item + /// ID of the recovery point + /// + /// + /// Recovery point response returned by the service + public CrrModel.RecoveryPointResource GetRecoveryPointDetailsFromSecondaryRegion( + string containerName, + string protectedItemName, + string recoveryPointId, + string vaultName = null, + string resourceGroupName = null) + { + var response = CrrAdapter.Client.RecoveryPointsCrr.GetWithHttpMessagesAsync( + vaultName ?? BmsAdapter.GetResourceName(), + resourceGroupName ?? BmsAdapter.GetResourceGroupName(), + AzureFabricName, + containerName, + protectedItemName, + recoveryPointId, + cancellationToken: BmsAdapter.CmdletCancellationToken).Result.Body; + + return response; + } + /// /// Lists recovery points recommended for Archive move /// @@ -188,7 +216,6 @@ public RestAzureNS.AzureOperationResponse MoveRecoveryPoint( ).Result; } - /// /// provision item level recovery connection identified by the input parameters /// diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Backup/BackupAzureRmRecoveryServicesBackupItem.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Backup/BackupAzureRmRecoveryServicesBackupItem.cs index bf29efcc5077..d50ab78a6945 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Backup/BackupAzureRmRecoveryServicesBackupItem.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Backup/BackupAzureRmRecoveryServicesBackupItem.cs @@ -71,6 +71,11 @@ public override void ExecuteCmdlet() string vaultName = resourceIdentifier.ResourceName; string resourceGroupName = resourceIdentifier.ResourceGroupName; + if(ExpiryDateTimeUTC == null) + { + ExpiryDateTimeUTC = DateTime.UtcNow.AddDays(30); + } + PsBackupProviderManager providerManager = new PsBackupProviderManager(new Dictionary() { diff --git a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/GetAzureRmRecoveryServicesBackupWorkloadRecoveryConfig.cs b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/GetAzureRmRecoveryServicesBackupWorkloadRecoveryConfig.cs index df12332ba022..9f05af44adf7 100644 --- a/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/GetAzureRmRecoveryServicesBackupWorkloadRecoveryConfig.cs +++ b/src/RecoveryServices/RecoveryServices.Backup/Cmdlets/Restore/GetAzureRmRecoveryServicesBackupWorkloadRecoveryConfig.cs @@ -18,6 +18,7 @@ using Microsoft.Azure.Commands.RecoveryServices.Backup.Properties; using Microsoft.Azure.Management.Internal.Resources.Utilities.Models; using Microsoft.Azure.Management.RecoveryServices.Backup.Models; +using CrrModel = Microsoft.Azure.Management.RecoveryServices.Backup.CrossRegionRestore.Models; using Microsoft.Rest.Azure.OData; using System; using System.Collections.Generic; @@ -105,6 +106,12 @@ public class GetAzureRmRecoveryServicesBackupWorkloadRecoveryConfig : RSBackupVa [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.RecoveryPointConfig.FilePath)] public string FilePath { get; set; } + /// + /// Switch parameter to specify fetching resources from Secondary Region. + /// + [Parameter(Mandatory = false, HelpMessage = ParamHelpMsgs.Common.UseSecondaryReg)] + public SwitchParameter UseSecondaryRegion; + public override void ExecuteCmdlet() { ExecutionBlock(() => @@ -218,22 +225,44 @@ public override void ExecuteCmdlet() ((AzureWorkloadProtectableItem)TargetItem).ServerName) == 0) { string itemId = GetItemId(RecoveryPoint.Id); - IList dataDirectoryPaths = GetRpDetails(vaultName, resourceGroupName); - foreach (var dataDirectoryPath in dataDirectoryPaths) + + if(UseSecondaryRegion) { - targetPhysicalPath.Add(new SQLDataDirectoryMapping() + IList dataDirectoryPathsCrr = GetRpDetailsFromSecondaryRegion(vaultName, resourceGroupName); + + foreach (var dataDirectoryPath in dataDirectoryPathsCrr) { - MappingType = dataDirectoryPath.Type, - SourceLogicalName = dataDirectoryPath.LogicalName, - SourcePath = dataDirectoryPath.Path, - TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, - ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths - as List, offset) - }); + targetPhysicalPath.Add(new SQLDataDirectoryMapping() + { + MappingType = dataDirectoryPath.Type, + SourceLogicalName = dataDirectoryPath.LogicalName, + SourcePath = dataDirectoryPath.Path, + TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, + ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths // RsvRef: do we need to call it in Crr model ? + as List, offset) + }); + } } + else + { + IList dataDirectoryPaths = GetRpDetails(vaultName, resourceGroupName); + foreach (var dataDirectoryPath in dataDirectoryPaths) + { + targetPhysicalPath.Add(new SQLDataDirectoryMapping() + { + MappingType = dataDirectoryPath.Type, + SourceLogicalName = dataDirectoryPath.LogicalName, + SourcePath = dataDirectoryPath.Path, + TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, + ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths + as List, offset) + }); + } + } break; } } + azureWorkloadRecoveryConfig.targetPhysicalPath = targetPhysicalPath; azureWorkloadRecoveryConfig.ContainerId = GetContainerId(TargetItem.Id); } @@ -282,18 +311,37 @@ public override void ExecuteCmdlet() string.Compare(((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).ServerName, ((AzureWorkloadProtectableItem)TargetItem).ServerName) == 0) { - List dataDirectory = GetDataDirectory(vaultName, resourceGroupName, Item.Id, PointInTime); - foreach (var dataDirectoryPath in dataDirectory) + if (UseSecondaryRegion) + { + List dataDirectory = GetDataDirectoryFromSecondaryRegion(vaultName, resourceGroupName, Item.Id, PointInTime); + foreach (var dataDirectoryPath in dataDirectory) + { + targetPhysicalPath.Add(new SQLDataDirectoryMapping() + { + MappingType = dataDirectoryPath.Type, + SourceLogicalName = dataDirectoryPath.LogicalName, + SourcePath = dataDirectoryPath.Path, + TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, + ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths + as List, offset) + }); + } + } + else { - targetPhysicalPath.Add(new SQLDataDirectoryMapping() + List dataDirectory = GetDataDirectory(vaultName, resourceGroupName, Item.Id, PointInTime); + foreach (var dataDirectoryPath in dataDirectory) { - MappingType = dataDirectoryPath.Type, - SourceLogicalName = dataDirectoryPath.LogicalName, - SourcePath = dataDirectoryPath.Path, - TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, - ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths - as List, offset) - }); + targetPhysicalPath.Add(new SQLDataDirectoryMapping() + { + MappingType = dataDirectoryPath.Type, + SourceLogicalName = dataDirectoryPath.LogicalName, + SourcePath = dataDirectoryPath.Path, + TargetPath = GetTargetPath(dataDirectoryPath.Path, dataDirectoryPath.LogicalName, dataDirectoryPath.Type, + ((AzureVmWorkloadSQLInstanceWorkloadItem)itemResponse.Properties).DataDirectoryPaths + as List, offset) + }); + } } break; } @@ -401,19 +449,80 @@ public List GetDataDirectory(string vaultName, string resource return dataDirectoryPaths; } + public List GetDataDirectoryFromSecondaryRegion(string vaultName, string resourceGroupName, string itemId, DateTime pointInTime) + { + Dictionary uriDict = HelperUtils.ParseUri(itemId); + string containerUri = HelperUtils.GetContainerUri(uriDict, itemId); + string protectedItemName = HelperUtils.GetProtectedItemUri(uriDict, itemId); + var queryFilterString = QueryBuilder.Instance.GetQueryString(new BMSRPQueryObject() + { + RestorePointQueryType = RestorePointQueryType.Log, + ExtendedInfo = true + }); + + ODataQuery queryFilter = new ODataQuery(); + queryFilter.Filter = queryFilterString; + + // RsvRef : fetching recovery points from primary region + var rpResponse = ServiceClientAdapter.GetRecoveryPointsFromSecondaryRegion( + containerUri, + protectedItemName, + queryFilter, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + + List dataDirectoryPaths = new List(); + + if (rpResponse[0].Properties.GetType() == typeof(CrrModel.AzureWorkloadSQLPointInTimeRecoveryPoint)) + { + CrrModel.AzureWorkloadSQLPointInTimeRecoveryPoint recoveryPoint = + rpResponse[0].Properties as CrrModel.AzureWorkloadSQLPointInTimeRecoveryPoint; + if (recoveryPoint.ExtendedInfo != null) + { + foreach(CrrModel.SQLDataDirectory dataDirectoryPath in recoveryPoint.ExtendedInfo.DataDirectoryPaths) + { + dataDirectoryPaths.Add(dataDirectoryPath); + } + } + } + return dataDirectoryPaths; + } + public IList GetRpDetails(string vaultName, string resourceGroupName) { Dictionary uriDict = HelperUtils.ParseUri(RecoveryPoint.Id); string containerUri = HelperUtils.GetContainerUri(uriDict, RecoveryPoint.Id); string protectedItemName = HelperUtils.GetProtectedItemUri(uriDict, RecoveryPoint.Id); + AzureWorkloadSQLRecoveryPoint recoveryPoint = null; + var rpResponse = ServiceClientAdapter.GetRecoveryPointDetails( - containerUri, - protectedItemName, - RecoveryPoint.RecoveryPointId, - vaultName: vaultName, - resourceGroupName: resourceGroupName); - AzureWorkloadSQLRecoveryPoint recoveryPoint = rpResponse.Properties as AzureWorkloadSQLRecoveryPoint; + containerUri, + protectedItemName, + RecoveryPoint.RecoveryPointId, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + + recoveryPoint = rpResponse.Properties as AzureWorkloadSQLRecoveryPoint; + return recoveryPoint.ExtendedInfo.DataDirectoryPaths; + } + + public IList GetRpDetailsFromSecondaryRegion(string vaultName, string resourceGroupName) + { + Dictionary uriDict = HelperUtils.ParseUri(RecoveryPoint.Id); + string containerUri = HelperUtils.GetContainerUri(uriDict, RecoveryPoint.Id); + string protectedItemName = HelperUtils.GetProtectedItemUri(uriDict, RecoveryPoint.Id); + + CrrModel.AzureWorkloadSQLRecoveryPoint recoveryPoint = null; + + var rpResponse = ServiceClientAdapter.GetRecoveryPointDetailsFromSecondaryRegion( + containerUri, + protectedItemName, + RecoveryPoint.RecoveryPointId, + vaultName: vaultName, + resourceGroupName: resourceGroupName); + + recoveryPoint = rpResponse.Properties as CrrModel.AzureWorkloadSQLRecoveryPoint; return recoveryPoint.ExtendedInfo.DataDirectoryPaths; } diff --git a/src/RecoveryServices/RecoveryServices/ChangeLog.md b/src/RecoveryServices/RecoveryServices/ChangeLog.md index d9b989da1483..44be3c42da07 100644 --- a/src/RecoveryServices/RecoveryServices/ChangeLog.md +++ b/src/RecoveryServices/RecoveryServices/ChangeLog.md @@ -19,6 +19,10 @@ --> ## Upcoming Release * Added CRR support for new regions malaysiasouth, chinanorth3, chinaeast3, jioindiacentral, jioindiawest. +* Regenerated CRR SDK. Fixed issues with SQL CRR. +* Fixed bug with rp expiry time, making 30 days expiry time for adhoc backup as default from client side. +* Added example to fetch pruned recovery points after modify policy. +* Fixed the documentation for suspend backups with immutability. ## Version 6.4.0 * Added support for updating CrossSubscriptionRestoreState of the vault diff --git a/src/RecoveryServices/RecoveryServices/help/Backup-AzRecoveryServicesBackupItem.md b/src/RecoveryServices/RecoveryServices/help/Backup-AzRecoveryServicesBackupItem.md index 96e0a86c1135..ce4952b1e673 100644 --- a/src/RecoveryServices/RecoveryServices/help/Backup-AzRecoveryServicesBackupItem.md +++ b/src/RecoveryServices/RecoveryServices/help/Backup-AzRecoveryServicesBackupItem.md @@ -34,7 +34,7 @@ This cmdlet can also be used for custom retention with or without expiry date - $vault = Get-AzRecoveryServicesVault -ResourceGroupName "resourceGroup" -Name "vaultName" $NamedContainer = Get-AzRecoveryServicesBackupContainer -ContainerType AzureVM -FriendlyName "pstestv2vm1" -VaultId $vault.ID $Item = Get-AzRecoveryServicesBackupItem -Container $NamedContainer -WorkloadType AzureVM -VaultId $vault.ID -$Job = Backup-AzRecoveryServicesBackupItem -Item $Item -VaultId $vault.ID +$Job = Backup-AzRecoveryServicesBackupItem -Item $Item -VaultId $vault.ID -ExpiryDateTimeUTC (Get-Date).ToUniversalTime().AddDays(60) $Job ``` @@ -46,7 +46,7 @@ pstestv2vm1 Backup InProgress 4/23/2016 5:00:30 PM The first command gets the Backup container of type AzureVM named pstestv2vm1, and then stores it in the $NamedContainer variable. The second command gets the Backup item corresponding to the container in $NamedContainer, and then stores it in the $Item variable. -The last command triggers the backup job for the Backup item in $Item. +The last command triggers the backup job for the Backup item in $Item with an expiry time of 60 days from now, default value for expiry time is 30 days if not specified. ### Example 2 diff --git a/src/RecoveryServices/RecoveryServices/help/Disable-AzRecoveryServicesBackupProtection.md b/src/RecoveryServices/RecoveryServices/help/Disable-AzRecoveryServicesBackupProtection.md index 26156def09a3..7eac82a9d115 100644 --- a/src/RecoveryServices/RecoveryServices/help/Disable-AzRecoveryServicesBackupProtection.md +++ b/src/RecoveryServices/RecoveryServices/help/Disable-AzRecoveryServicesBackupProtection.md @@ -21,8 +21,9 @@ Disable-AzRecoveryServicesBackupProtection [-Item] [-RemoveRecoveryPo ## DESCRIPTION The **Disable-AzRecoveryServicesBackupProtection** cmdlet disables protection for an Azure Backup-protected item. -This cmdlet stops regular scheduled backup of an item. +This cmdlet stops regular scheduled backup of an item and retain forever. This cmdlet can also delete existing recovery points for the backup item if executed with RemoveRecoveryPoints parameter. +This cmdlet can suspend backup of an item and retain recovery points as per backup policy if used with RetainRecoveryPointsAsPerPolicy parameter. One condition with this scenario is that backups can't be suspended until immutability is enabled on the vault. To enable immutability on a recovery services vault, pls follow Update-AzRecoveryServicesVault cmdlet. Set the vault context by using the Set-AzRecoveryServicesVaultContext cmdlet before you use the current cmdlet. ## EXAMPLES @@ -62,7 +63,7 @@ BackupsSuspended ``` The first cmdlet fetches the AzureVM backup items for the recovery services vault. -The second cmdlet is used to suspend backup for $item[0] of the recovery services vault. +The second cmdlet is used to suspend backup for $item[0] of the recovery services vault. One condition with this scenario is that backups can't be suspended until immutability is enabled on the vault. To enable immutability on a recovery services vault, pls follow Update-AzRecoveryServicesVault cmdlet. The third and fourth command are used to fetch the updated backup item and its protection state. To resume protection back, please use Enable-AzRecoveryServicesBackupProtection with parameter -Item. diff --git a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md index 090e60ae741f..161a3b9d870b 100644 --- a/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md +++ b/src/RecoveryServices/RecoveryServices/help/Get-AzRecoveryServicesBackupRecoveryPoint.md @@ -96,6 +96,51 @@ The fourth command gets backup items based on backupManagementType and workloadT The last command gets an array of recovery points for the item in $backupItem which are ready to be moved to VaultArchive tier and then stores them in the $rp variable. +### Example 4: Getting pruned recovery points in last year after modify policy opertaion + +```powershell +$vault = Get-AzRecoveryServicesVault -ResourceGroupName "resourceGroup" -Name "vaultName" +$startDate = (Get-Date).AddDays(-365).ToUniversalTime() +$endDate = (Get-Date).ToUniversalTime() +$item = Get-AzRecoveryServicesBackupItem -BackupManagementType "AzureVM" -WorkloadType "AzureVM" -VaultId $vault.ID +$rpsBefore = Get-AzRecoveryServicesBackupRecoveryPoint -Item $item[0] -StartDate $startDate -EndDate $endDate -VaultId $vault.ID + +# update policy +$pol = Get-AzRecoveryServicesBackupProtectionPolicy -VaultId $vault.ID -Name "policyName" +$pol.RetentionPolicy.IsWeeklyScheduleEnabled = $false +$pol.RetentionPolicy.IsMonthlyScheduleEnabled = $false +$pol.RetentionPolicy.IsYearlyScheduleEnabled = $false +Set-AzRecoveryServicesBackupProtectionPolicy -Policy $pol -VaultId $vault.ID -RetentionPolicy $pol.RetentionPolicy -Debug + +# wait until policy changes are applied to recovery points and they are pruned +$rpsAfter = Get-AzRecoveryServicesBackupRecoveryPoint -Item $item[0] -StartDate $startDate -EndDate $endDate -VaultId $vault.ID + +# compare the recovery points list before and after +$diff = Compare-Object $rpsBefore $rpsAfter +$rpsRemoved = $diff | Where-Object{ $_.SideIndicator -eq'<='} | Select-Object -ExpandProperty InputObject +$rpsRemoved +``` + +```output +RecoveryPointId RecoveryPointType RecoveryPointTime ContainerName ContainerType +--------------- ----------------- ----------------- ------------- ------------- +7397781054902 CrashConsistent 5/2/2023 3:28:35 AM iaasvmcontainerv2;test-rg;test-vm AzureVM +9722704411921 CrashConsistent 4/1/2023 3:32:26 AM iaasvmcontainerv2;test-rg;test-vm AzureVM +6543100104464 CrashConsistent 3/1/2023 3:26:27 AM iaasvmcontainerv2;test-rg;test-vm AzureVM +``` + +The first command gets vault object based on vaultName. The second command gets the date from one year days ago, and then stores it in the $startDate variable. +The third command gets today's date, and then stores it in the $endDate variable. +The fourth command gets backup items based on backupManagementType and workloadType, vaultId and then stores it in the $item variable. +The fifth command gets an array of recovery points for the item in $item which are present before the modify policy operation in last one year. +Now we move on to update the policy. The sixth command fetches the policy to be updated which is used to protect the backup item $item[0]. +The seventh, eight and ninth commands disable the yearly and monthly retention in the policy to prune the older recovery points. +The tenth command finally updates the retention policy. +The eleventh command waits in the same powershell session until the recovery points are pruned and fetches the recovery points within the same time range, after the policy changes are applied. +The twelth command takes a diff between recovery point list before and after pruning occurs. +The thirteenth command read the recovery points, from the diff, which were present before and are now pruned. +The last command displays the list of pruned recovery points. + ## PARAMETERS ### -DefaultProfile