diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreConfigInfoRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreConfigInfoRequest.cs new file mode 100644 index 00000000..8de0931c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreConfigInfoRequest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts +{ + public class RestoreConfigInfoRequestParams + { + /// + /// The Uri to find the connection to do the restore operations + /// + public string OwnerUri { get; set; } + } + + public class RestoreConfigInfoResponse + { + public RestoreConfigInfoResponse() + { + ConfigInfo = new Dictionary(); + } + + /// + /// Config Info + /// + public Dictionary ConfigInfo { get; set; } + + /// + /// Errors occurred while creating the restore config info + /// + public string ErrorMessage { get; set; } + } + + public class RestoreConfigInfoRequest + { + public static readonly + RequestType Type = + RequestType.Create("disasterrecovery/restoreconfiginfo"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs new file mode 100644 index 00000000..ea0d3757 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanDetailInfo.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts +{ + /// + /// Class to include the plan detail + /// + public class RestorePlanDetailInfo + { + /// + /// The name of the option from RestoreOptionsHelper + /// + public string Name { get; set; } + + /// + /// The current value of the option + /// + public object CurrentValue { get; set; } + + /// + /// Indicates whether the option is read only or can be changed in client + /// + public bool IsReadOnly { get; set; } + + /// + /// Indicates whether the option should be visibile in client + /// + public bool IsVisiable { get; set; } + + /// + /// The default value of the option + /// + public object DefaultValue { get; set; } + + internal static RestorePlanDetailInfo Create(string name, object currentValue, bool isReadOnly = false, bool isVisible = true, object defaultValue = null) + { + return new RestorePlanDetailInfo + { + CurrentValue = currentValue, + IsReadOnly = isReadOnly, + Name = name, + IsVisiable = isVisible, + DefaultValue = defaultValue + }; + } + } + + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanRequest.cs new file mode 100644 index 00000000..b7987407 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestorePlanRequest.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts +{ + /// + /// Database file info + /// + public class RestoreDatabaseFileInfo + { + /// + /// File type (Rows Data, Log ...) + /// + public string FileType { get; set; } + + /// + /// Logical Name + /// + public string LogicalFileName { get; set; } + + /// + /// Original location of the file to restore to + /// + public string OriginalFileName { get; set; } + + /// + /// The file to restore to + /// + public string RestoreAsFileName { get; set; } + } + + /// + /// Restore Plan Response + /// + public class RestorePlanResponse + { + /// + /// Restore session id, can be used in restore request to use an existing restore plan + /// + public string SessionId { get; set; } + + + /// + /// The list of backup sets to restore + /// + public DatabaseFileInfo[] BackupSetsToRestore { get; set; } + + /// + /// Indicates whether the restore operation is supported + /// + public bool CanRestore { get; set; } + + /// + /// Errors occurred while creating restore plan + /// + public string ErrorMessage { get; set; } + + /// + /// The db files included in the backup file + /// + public IEnumerable DbFiles { get; set; } + + /// + /// Database names extracted from backup sets + /// + public string[] DatabaseNamesFromBackupSets { get; set; } + + /// + /// For testing purpose to verify the target database + /// + internal string DatabaseName { get; set; } + + /// + /// Plan details + /// + public Dictionary PlanDetails { get; set; } + } + + public class RestorePlanRequest + { + public static readonly + RequestType Type = + RequestType.Create("disasterrecovery/restoreplan"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequest.cs index a3281333..2f376d1f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequest.cs @@ -3,113 +3,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System.Collections.Generic; using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts { - /// - /// Restore request parameters - /// - public class RestoreParams : GeneralRequestDetails - { - /// - /// Restore session id. The parameter is optional and if passed, an existing plan will be used - /// - internal string SessionId - { - get - { - return GetOptionValue(RestoreOptionsHelper.SessionId); - } - set - { - SetOptionValue(RestoreOptionsHelper.SessionId, value); - } - } - - /// - /// The Uri to find the connection to do the restore operations - /// - public string OwnerUri { get; set; } - - /// - /// Comma delimited list of backup files - /// - internal string BackupFilePaths - { - get - { - return GetOptionValue(RestoreOptionsHelper.BackupFilePaths); - } - set - { - SetOptionValue(RestoreOptionsHelper.BackupFilePaths, value); - } - } - - /// - /// Target Database name to restore to - /// - internal string TargetDatabaseName - { - get - { - return GetOptionValue(RestoreOptionsHelper.TargetDatabaseName); - } - set - { - SetOptionValue(RestoreOptionsHelper.TargetDatabaseName, value); - } - } - - /// - /// Source Database name to restore from - /// - internal string SourceDatabaseName - { - get - { - return GetOptionValue(RestoreOptionsHelper.SourceDatabaseName); - } - set - { - SetOptionValue(RestoreOptionsHelper.SourceDatabaseName, value); - } - } - - /// - /// If set to true, the db files will be relocated to default data location in the server - /// - internal bool RelocateDbFiles - { - get - { - return GetOptionValue(RestoreOptionsHelper.RelocateDbFiles); - } - set - { - SetOptionValue(RestoreOptionsHelper.RelocateDbFiles, value); - } - } - - /// - /// Ids of the backup set to restore - /// - internal string[] SelectedBackupSets - { - get - { - return GetOptionValue(RestoreOptionsHelper.SelectedBackupSets); - } - set - { - SetOptionValue(RestoreOptionsHelper.SelectedBackupSets, value); - } - } - } - /// /// Restore response /// @@ -132,78 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts public string ErrorMessage { get; set; } } - /// - /// Database file info - /// - public class RestoreDatabaseFileInfo - { - /// - /// File type (Rows Data, Log ...) - /// - public string FileType { get; set; } - - /// - /// Logical Name - /// - public string LogicalFileName { get; set; } - - /// - /// Original location of the file to restore to - /// - public string OriginalFileName { get; set; } - - /// - /// The file to restore to - /// - public string RestoreAsFileName { get; set; } - } - - /// - /// Restore Plan Response - /// - public class RestorePlanResponse - { - /// - /// Restore session id, can be used in restore request to use an existing restore plan - /// - public string SessionId { get; set; } - - - /// - /// The list of backup sets to restore - /// - public DatabaseFileInfo[] BackupSetsToRestore { get; set; } - - /// - /// Indicates whether the restore operation is supported - /// - public bool CanRestore { get; set; } - - /// - /// Errors occurred while creating restore plan - /// - public string ErrorMessage { get; set; } - - /// - /// The db files included in the backup file - /// - public IEnumerable DbFiles { get; set; } - - /// - /// Database names extracted from backup sets - /// - public string[] DatabaseNamesFromBackupSets { get; set; } - - /// - /// For testing purpose to verify the target database - /// - internal string DatabaseName { get; set; } - - /// - /// Plan details - /// - public Dictionary PlanDetails { get; set; } - } + public class RestoreRequest { @@ -212,10 +38,5 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts RequestType.Create("disasterrecovery/restore"); } - public class RestorePlanRequest - { - public static readonly - RequestType Type = - RequestType.Create("disasterrecovery/restoreplan"); - } + } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs new file mode 100644 index 00000000..b761ced1 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Newtonsoft.Json.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts +{ + /// + /// Restore request parameters + /// + public class RestoreParams : GeneralRequestDetails + { + /// + /// Restore session id. The parameter is optional and if passed, an existing plan will be used + /// + internal string SessionId + { + get + { + return GetOptionValue(RestoreOptionsHelper.SessionId); + } + set + { + SetOptionValue(RestoreOptionsHelper.SessionId, value); + } + } + + /// + /// The Uri to find the connection to do the restore operations + /// + public string OwnerUri { get; set; } + + /// + /// Comma delimited list of backup files + /// + internal string BackupFilePaths + { + get + { + return GetOptionValue(RestoreOptionsHelper.BackupFilePaths); + } + set + { + SetOptionValue(RestoreOptionsHelper.BackupFilePaths, value); + } + } + + /// + /// Target Database name to restore to + /// + internal string TargetDatabaseName + { + get + { + return GetOptionValue(RestoreOptionsHelper.TargetDatabaseName); + } + set + { + SetOptionValue(RestoreOptionsHelper.TargetDatabaseName, value); + } + } + + /// + /// Source Database name to restore from + /// + internal string SourceDatabaseName + { + get + { + return GetOptionValue(RestoreOptionsHelper.SourceDatabaseName); + } + set + { + SetOptionValue(RestoreOptionsHelper.SourceDatabaseName, value); + } + } + + /// + /// If set to true, the db files will be relocated to default data location in the server + /// + internal bool RelocateDbFiles + { + get + { + return GetOptionValue(RestoreOptionsHelper.RelocateDbFiles); + } + set + { + SetOptionValue(RestoreOptionsHelper.RelocateDbFiles, value); + } + } + + /// + /// If set to true, the backup files will be used to create restore plan otehrwise the source database name will be used + /// + internal bool ReadHeaderFromMedia + { + get + { + //Default is true for now for backward compatibility + return Options.ContainsKey(RestoreOptionsHelper.ReadHeaderFromMedia) ? GetOptionValue(RestoreOptionsHelper.ReadHeaderFromMedia) : true; + } + set + { + SetOptionValue(RestoreOptionsHelper.ReadHeaderFromMedia, value); + } + } + + /// + /// Ids of the selected backup set to restore. If null, all backup sets will be selected. If empty list, + /// no backup sets will be selected + /// + internal IEnumerable SelectedBackupSets + { + get + { + var selectedBackupSets = GetOptionValue(RestoreOptionsHelper.SelectedBackupSets); + if (selectedBackupSets != null) + { + JArray array = selectedBackupSets as JArray; + if(array != null) + { + return array.ToObject>(); + } + else + { + IEnumerable list = selectedBackupSets as IEnumerable; + return list; + } + + } + return null; + } + set + { + SetOptionValue(RestoreOptionsHelper.SelectedBackupSets, value); + } + } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs index e01a7d79..ffef5151 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs @@ -10,12 +10,9 @@ using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; -using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.TaskServices; using System.Threading; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlServer.Management.Common; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { @@ -26,7 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { private static readonly Lazy instance = new Lazy(() => new DisasterRecoveryService()); private static ConnectionService connectionService = null; - private RestoreDatabaseHelper restoreDatabaseService = RestoreDatabaseHelper.Instance; + private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper(); /// /// Default, parameterless constructor. @@ -74,8 +71,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery // Create respore task serviceHost.SetRequestHandler(RestoreRequest.Type, HandleRestoreRequest); + // Create respore plan serviceHost.SetRequestHandler(RestorePlanRequest.Type, HandleRestorePlanRequest); + + // Create respore config + serviceHost.SetRequestHandler(RestoreConfigInfoRequest.Type, HandleRestoreConfigInfoRequest); } /// @@ -128,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery try { ConnectionInfo connInfo; - bool supported = IsBackupRestoreOperationSupported(restoreParams, out connInfo); + bool supported = IsBackupRestoreOperationSupported(restoreParams.OwnerUri, out connInfo); if (supported && connInfo != null) { @@ -150,6 +151,37 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery } } + /// + /// Handles a restore config info request + /// + internal async Task HandleRestoreConfigInfoRequest( + RestoreConfigInfoRequestParams restoreConfigInfoParams, + RequestContext requestContext) + { + RestoreConfigInfoResponse response = new RestoreConfigInfoResponse(); + + try + { + ConnectionInfo connInfo; + bool supported = IsBackupRestoreOperationSupported(restoreConfigInfoParams.OwnerUri, out connInfo); + + if (supported && connInfo != null) + { + response = this.restoreDatabaseService.CreateConfigInfoResponse(restoreConfigInfoParams); + } + else + { + response.ErrorMessage = SR.RestoreNotSupported; + } + await requestContext.SendResult(response); + } + catch (Exception ex) + { + response.ErrorMessage = ex.Message; + await requestContext.SendResult(response); + } + } + /// /// Handles a restore request /// @@ -162,7 +194,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery try { ConnectionInfo connInfo; - bool supported = IsBackupRestoreOperationSupported(restoreParams, out connInfo); + bool supported = IsBackupRestoreOperationSupported(restoreParams.OwnerUri, out connInfo); if (supported && connInfo != null) { @@ -175,7 +207,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery // create task metadata TaskMetadata metadata = new TaskMetadata(); metadata.ServerName = connInfo.ConnectionDetails.ServerName; - metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName; + metadata.DatabaseName = restoreParams.TargetDatabaseName; metadata.Name = SR.RestoreTaskName; metadata.IsCancelable = true; metadata.Data = restoreDataObject; @@ -279,14 +311,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery return null; } - private bool IsBackupRestoreOperationSupported(RestoreParams restoreParams, out ConnectionInfo connectionInfo) + private bool IsBackupRestoreOperationSupported(string ownerUri, out ConnectionInfo connectionInfo) { SqlConnection sqlConn = null; try { ConnectionInfo connInfo; DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection( - restoreParams.OwnerUri, + ownerUri, out connInfo); if (connInfo != null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs index 511ce104..ac8690b9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs @@ -15,7 +15,6 @@ using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; -using System.Collections.Concurrent; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { @@ -25,21 +24,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation public class RestoreDatabaseHelper { public const string LastBackupTaken = "lastBackupTaken"; - private static RestoreDatabaseHelper instance = new RestoreDatabaseHelper(); - private ConcurrentDictionary restoreSessions = new ConcurrentDictionary(); - - internal RestoreDatabaseHelper() - { - - } - - public static RestoreDatabaseHelper Instance - { - get - { - return instance; - } - } /// /// Create a backup task for execution and cancellation @@ -142,6 +126,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return taskResult; } + /// + /// Creates response which includes information about the server given to restore (default data location, db names with backupsets) + /// + public RestoreConfigInfoResponse CreateConfigInfoResponse(RestoreConfigInfoRequestParams restoreConfigInfoRequest) + { + RestoreConfigInfoResponse response = new RestoreConfigInfoResponse(); + RestoreDatabaseTaskDataObject restoreTaskObject = CreateRestoreForNewSession(restoreConfigInfoRequest.OwnerUri); + if (restoreTaskObject != null) + { + // Default Data folder path in the target server + response.ConfigInfo.Add(RestoreOptionsHelper.DataFileFolder, restoreTaskObject.DefaultDataFileFolder); + // Default log folder path in the target server + response.ConfigInfo.Add(RestoreOptionsHelper.LogFileFolder, restoreTaskObject.DefaultLogFileFolder); + // The db names with backup set + response.ConfigInfo.Add(RestoreOptionsHelper.SourceDatabaseNamesWithBackupSets, restoreTaskObject.GetDatabaseNamesWithBackupSets()); + } + + return response; + } + /// /// Creates a restore plan, The result includes the information about the backup set, /// the files and the database to restore to @@ -153,7 +157,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation RestorePlanResponse response = new RestorePlanResponse() { DatabaseName = restoreDataObject.RestoreParams.TargetDatabaseName, - PlanDetails = new System.Collections.Generic.Dictionary() + PlanDetails = new System.Collections.Generic.Dictionary() }; try { @@ -179,25 +183,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation response.ErrorMessage = SR.RestoreNotSupported; } - response.PlanDetails.Add(LastBackupTaken, restoreDataObject.GetLastBackupTaken()); + response.PlanDetails.Add(LastBackupTaken, RestorePlanDetailInfo.Create(LastBackupTaken, restoreDataObject.GetLastBackupTaken())); response.BackupSetsToRestore = restoreDataObject.GetSelectedBakupSets(); var dbNames = restoreDataObject.GetSourceDbNames(); response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray(); - - // Adding the default values for some of the options in the plan details - bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(restoreDataObject.RestorePlanner.DatabaseName); - // Default backup tail-log. It's true when tail-log backup is possible for the source database - response.PlanDetails.Add(RestoreOptionsHelper.DefaultBackupTailLog, isTailLogBackupPossible); - // Default backup file for tail-log bacup when Tail-Log bachup is set to true - response.PlanDetails.Add(RestoreOptionsHelper.DefaultTailLogBackupFile, - restoreDataObject.Util.GetDefaultTailLogbackupFile(restoreDataObject.RestorePlan.DatabaseName)); - // Default stand by file path for when RESTORE WITH STANDBY is selected - response.PlanDetails.Add(RestoreOptionsHelper.DefaultStandbyFile, restoreDataObject.Util.GetDefaultStandbyFile(restoreDataObject.RestorePlan.DatabaseName)); - // Default Data folder path in the target server - response.PlanDetails.Add(RestoreOptionsHelper.DefaultDataFileFolder, restoreDataObject.DefaultDataFileFolder); - // Default log folder path in the target server - response.PlanDetails.Add(RestoreOptionsHelper.DefaultLogFileFolder, restoreDataObject.DefaultLogFileFolder); + + RestoreOptionsHelper.AddOptions(response, restoreDataObject); + } else { @@ -248,30 +241,18 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation public RestoreDatabaseTaskDataObject CreateRestoreDatabaseTaskDataObject(RestoreParams restoreParams) { RestoreDatabaseTaskDataObject restoreTaskObject = null; - if (!string.IsNullOrWhiteSpace(restoreParams.SessionId)) - { - this.restoreSessions.TryGetValue(restoreParams.SessionId, out restoreTaskObject); - } - - if (restoreTaskObject == null) - { - restoreTaskObject = CreateRestoreForNewSession(restoreParams); - string sessionId = string.IsNullOrWhiteSpace(restoreParams.SessionId) ? Guid.NewGuid().ToString() : restoreParams.SessionId; - this.restoreSessions.AddOrUpdate(sessionId, restoreTaskObject, (key, oldSession) => restoreTaskObject); - restoreTaskObject.SessionId = sessionId; - } - else - { - restoreTaskObject.RestoreParams = restoreParams; - } + restoreTaskObject = CreateRestoreForNewSession(restoreParams.OwnerUri, restoreParams.TargetDatabaseName); + string sessionId = string.IsNullOrWhiteSpace(restoreParams.SessionId) ? Guid.NewGuid().ToString() : restoreParams.SessionId; + restoreTaskObject.SessionId = sessionId; + restoreTaskObject.RestoreParams = restoreParams; return restoreTaskObject; } - private RestoreDatabaseTaskDataObject CreateRestoreForNewSession(RestoreParams restoreParams) + private RestoreDatabaseTaskDataObject CreateRestoreForNewSession(string ownerUri, string targetDatabaseName = null) { ConnectionInfo connInfo; DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection( - restoreParams.OwnerUri, + ownerUri, out connInfo); if (connInfo != null) @@ -295,8 +276,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } Server server = new Server(new ServerConnection(connection)); - RestoreDatabaseTaskDataObject restoreDataObject = new RestoreDatabaseTaskDataObject(server, restoreParams.TargetDatabaseName); - restoreDataObject.RestoreParams = restoreParams; + RestoreDatabaseTaskDataObject restoreDataObject = new RestoreDatabaseTaskDataObject(server, targetDatabaseName); return restoreDataObject; } return null; @@ -313,7 +293,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { restoreDataObject.AddFiles(restoreDataObject.RestoreParams.BackupFilePaths); } - restoreDataObject.RestorePlanner.ReadHeaderFromMedia = !string.IsNullOrEmpty(restoreDataObject.RestoreParams.BackupFilePaths); + restoreDataObject.RestorePlanner.ReadHeaderFromMedia = restoreDataObject.RestoreParams.ReadHeaderFromMedia; if (string.IsNullOrWhiteSpace(restoreDataObject.RestoreParams.SourceDatabaseName)) { @@ -325,30 +305,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName; - restoreDataObject.RestoreOptions.KeepReplication = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.KeepReplication); - restoreDataObject.RestoreOptions.ReplaceDatabase = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.ReplaceDatabase); - restoreDataObject.RestoreOptions.SetRestrictedUser = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.SetRestrictedUser); - string recoveryState = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.RecoveryState); - object databaseRecoveryState; - if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState)) - { - restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState; - } - bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(restoreDataObject.RestorePlanner.DatabaseName); - if (isTailLogBackupPossible) - { - restoreDataObject.RestorePlanner.BackupTailLog = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.BackupTailLog); - restoreDataObject.TailLogBackupFile = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogBackupFile); - restoreDataObject.TailLogWithNoRecovery = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.TailLogWithNoRecovery); - } - else - { - restoreDataObject.RestorePlanner.BackupTailLog = false; - } + RestoreOptionsHelper.UpdateOptionsInPlan(restoreDataObject); - restoreDataObject.CloseExistingConnections = restoreDataObject.RestoreParams.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections); - - restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles); + restoreDataObject.UpdateRestorePlan(); } /// @@ -366,8 +325,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { restoreDataObject.SqlTask = sqlTask; restoreDataObject.Execute(); - RestoreDatabaseTaskDataObject cachedRestoreDataObject; - this.restoreSessions.TryRemove(restoreDataObject.SessionId, out cachedRestoreDataObject); } catch(Exception ex) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs index e76accf9..59e6da0c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseTaskDataObject.cs @@ -8,16 +8,46 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { + public interface IRestoreDatabaseTaskDataObject + { + string DataFilesFolder { get; set; } + string DefaultDataFileFolder { get; } + bool RelocateAllFiles { get; set; } + string LogFilesFolder { get; set; } + string DefaultLogFileFolder { get; } + List DbFiles { get; } + + RestoreOptions RestoreOptions { get; } + + string GetDefaultStandbyFile(string databaseName); + bool IsTailLogBackupPossible(string databaseName); + bool IsTailLogBackupWithNoRecoveryPossible(string databaseName); + + bool TailLogWithNoRecovery { get; set; } + + string TailLogBackupFile { get; set; } + + string GetDefaultTailLogbackupFile(string databaseName); + + RestorePlan RestorePlan { get; } + + bool CloseExistingConnections { get; set; } + + RestoreParams RestoreParams { get; set; } + + bool BackupTailLog { get; set; } + } /// /// Includes the plan with all the data required to do a restore operation on server /// - public class RestoreDatabaseTaskDataObject + public class RestoreDatabaseTaskDataObject : IRestoreDatabaseTaskDataObject { private const char BackupMediaNameSeparator = ','; @@ -48,6 +78,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation this.restoreOptions.PercentCompleteNotification = 5; } + /// + /// Boolean indicating whether the relocate all files checkbox was checked + /// + public bool RelocateAllFiles { get; set; } + /// /// Restore session id /// @@ -89,6 +124,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName); } + /// + /// Returns the db names that have backupsets + /// + /// + public List GetDatabaseNamesWithBackupSets() + { + return Util.GetSourceDbNames(); + } + /// /// Current sqlserver instance /// @@ -375,7 +419,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// /// true if [is tail log backup possible]; otherwise, false. /// - internal bool IsTailLogBackupPossible(string databaseName) + public bool IsTailLogBackupPossible(string databaseName) { if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName)) { @@ -403,6 +447,45 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return false; } + /// + /// Determines whether [is tail log backup with NORECOVERY possible]. + /// + /// + /// true if [is tail log backup with NORECOVERY possible]; otherwise, false. + /// + public bool IsTailLogBackupWithNoRecoveryPossible(string databaseName) + { + if (!IsTailLogBackupPossible(databaseName)) + { + return false; + } + + Database db = this.Server.Databases[databaseName]; + if (db == null) + { + return false; + } + if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName)) + { + return false; + } + if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled) + { + return false; + } + return true; + } + + public string GetDefaultStandbyFile(string databaseName) + { + return Util.GetDefaultStandbyFile(databaseName); + } + + public string GetDefaultTailLogbackupFile(string databaseName) + { + return Util.GetDefaultTailLogbackupFile(databaseName); + } + /// /// Gets or sets a value indicating whether [prompt before each backup]. /// @@ -500,7 +583,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { if (this.restorePlan == null) { - this.UpdateRestorePlan(false); + this.UpdateRestorePlan(); } return this.restorePlan; } @@ -681,7 +764,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// /// Updates restore plan /// - public void UpdateRestorePlan(bool relocateAllFiles = false) + public void UpdateRestorePlan() { this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated. //Clear any existing exceptions as new plan is getting recreated. @@ -703,7 +786,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation this.dbFiles = this.GetDbFiles(); UpdateDBFilesPhysicalRelocate(); - if (relocateAllFiles) + if (RelocateAllFiles) { UpdateDbFiles(); } @@ -992,47 +1075,4 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return true; } } - - public class RestoreDatabaseRecoveryState - { - public RestoreDatabaseRecoveryState(DatabaseRecoveryState recoveryState) - { - this.RecoveryState = recoveryState; - } - - public DatabaseRecoveryState RecoveryState; - private static string RestoreWithRecovery = "RESTORE WITH RECOVERY"; - private static string RestoreWithNoRecovery = "RESTORE WITH NORECOVERY"; - private static string RestoreWithStandby = "RESTORE WITH STANDBY"; - - public override string ToString() - { - switch (this.RecoveryState) - { - case DatabaseRecoveryState.WithRecovery: - return RestoreDatabaseRecoveryState.RestoreWithRecovery; - case DatabaseRecoveryState.WithNoRecovery: - return RestoreDatabaseRecoveryState.RestoreWithNoRecovery; - case DatabaseRecoveryState.WithStandBy: - return RestoreDatabaseRecoveryState.RestoreWithStandby; - } - return RestoreDatabaseRecoveryState.RestoreWithRecovery; - } - - /* - public string Info() - { - switch (this.RecoveryState) - { - case DatabaseRecoveryState.WithRecovery: - return SR.RestoreWithRecoveryInfo; - case DatabaseRecoveryState.WithNoRecovery: - return SR.RestoreWithNoRecoveryInfo; - case DatabaseRecoveryState.WithStandBy: - return SR.RestoreWithStandbyInfo; - } - return SR.RestoreWithRecoveryInfo; - } - */ - } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreUtil.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreUtil.cs index 681a7130..e3972342 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreUtil.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreUtil.cs @@ -70,9 +70,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation return dt; } - //TODO: the code is moved from ssms and used for restore differential backups - //Uncomment when restore operation for differential backups is supported - /* + /// /// Queries msdb for source database names /// @@ -87,7 +85,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation req.OrderByList[0] = new OrderBy(); req.OrderByList[0].Field = "DatabaseName"; req.OrderByList[0].Dir = OrderBy.Direction.Asc; - DataTable dt = server.ExecutionManager.GetEnumeratorData(req); + DataTable dt = GetEnumeratorData(req); string last = ""; foreach (DataRow row in dt.Rows) { @@ -97,7 +95,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation bool found = false; foreach (string str in databaseNames) { - if (StrEqual(str, dbName)) + if (string.Compare(str, dbName, StringComparison.InvariantCultureIgnoreCase) == 0) { found = true; break; @@ -112,7 +110,16 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation } return databaseNames; } - */ + + /// + /// make enumerator data request + /// + /// + /// + internal DataTable GetEnumeratorData(Request req) + { + return new Enumerator().Process(this.server.ConnectionContext.SqlConnectionObject, req); + } /// /// Reads backup file header to get source database names diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs index 4fd8d57e..1df22074 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOptionsHelper.cs @@ -3,34 +3,76 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Collections.Generic; +using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.Hosting.Contracts; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { public class RestoreOptionsHelper { + //The key names of restore info in the resquest of response + + //Option name keepReplication internal const string KeepReplication = "keepReplication"; + + //Option name replaceDatabase internal const string ReplaceDatabase = "replaceDatabase"; + + //Option name setRestrictedUser internal const string SetRestrictedUser = "setRestrictedUser"; + + //Option name recoveryState internal const string RecoveryState = "recoveryState"; + + //Option name backupTailLog internal const string BackupTailLog = "backupTailLog"; - internal const string DefaultBackupTailLog = "defaultBackupTailLog"; + + //Option name tailLogBackupFile internal const string TailLogBackupFile = "tailLogBackupFile"; - internal const string DefaultTailLogBackupFile = "defaultTailLogBackupFile"; + + //Option name tailLogWithNoRecovery internal const string TailLogWithNoRecovery = "tailLogWithNoRecovery"; + + //Option name closeExistingConnections internal const string CloseExistingConnections = "closeExistingConnections"; + + //Option name relocateDbFiles internal const string RelocateDbFiles = "relocateDbFiles"; + + //Option name dataFileFolder internal const string DataFileFolder = "dataFileFolder"; - internal const string DefaultDataFileFolder = "defaultDataFileFolder"; + + //Option name logFileFolder internal const string LogFileFolder = "logFileFolder"; - internal const string DefaultLogFileFolder = "defaultLogFileFolder"; + + //The key name to use to set the session id in the request internal const string SessionId = "sessionId"; + + //The key name to use to set the backup file paths in the request internal const string BackupFilePaths = "backupFilePaths"; + + //The key name to use to set the target database name in the request internal const string TargetDatabaseName = "targetDatabaseName"; + + //The key name to use to set the source database name in the request internal const string SourceDatabaseName = "sourceDatabaseName"; + + //The key name to use to set the selected backup sets in the request internal const string SelectedBackupSets = "selectedBackupSets"; + + //The key name to use to set the standby file sets in the request internal const string StandbyFile = "standbyFile"; - internal const string DefaultStandbyFile = "defaultStandbyFile"; + + //The key name to use to set source db names in restore response + internal const string SourceDatabaseNamesWithBackupSets = "sourceDatabaseNamesWithBackupSets"; + + //The key name to use to set in the requst. If set to true, the backup files will be used to restore otherwise the source database name + internal const string ReadHeaderFromMedia = "readHeaderFromMedia"; /// /// Creates the options metadata available for restore operations @@ -187,5 +229,201 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery return options; } + + internal static Dictionary CreateRestorePlanOptions(IRestoreDatabaseTaskDataObject restoreDataObject) + { + Validate.IsNotNull(nameof(restoreDataObject), restoreDataObject); + + Dictionary options = new Dictionary(); + string databaseName = restoreDataObject.RestorePlan == null ? string.Empty : restoreDataObject.RestorePlan.DatabaseName; + //Files + + // Default Data folder path in the target server + options.Add(RestoreOptionsHelper.DataFileFolder, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.DataFileFolder, + currentValue: restoreDataObject.DataFilesFolder, + defaultValue: restoreDataObject.DefaultDataFileFolder, + isReadOnly: !restoreDataObject.RelocateAllFiles, + isVisible: true + )); + + // Default log folder path in the target server + options.Add(RestoreOptionsHelper.LogFileFolder, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.LogFileFolder, + currentValue: restoreDataObject.LogFilesFolder, + defaultValue: restoreDataObject.DefaultLogFileFolder, + isReadOnly: !restoreDataObject.RelocateAllFiles, + isVisible: true + )); + + // Relocate all files + options.Add(RestoreOptionsHelper.RelocateDbFiles, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.RelocateDbFiles, + currentValue: restoreDataObject.RelocateAllFiles, + defaultValue: false, + isReadOnly: restoreDataObject.DbFiles.Count == 0, + isVisible: true + )); + + + //Options + + //With Replace + options.Add(RestoreOptionsHelper.ReplaceDatabase, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.ReplaceDatabase, + currentValue: restoreDataObject.RestoreOptions.ReplaceDatabase, + defaultValue: false, + isReadOnly: false, + isVisible: true + )); + + //Keep replication + options.Add(RestoreOptionsHelper.KeepReplication, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.KeepReplication, + currentValue: restoreDataObject.RestoreOptions.KeepReplication, + defaultValue: false, + isReadOnly: restoreDataObject.RestoreOptions.RecoveryState == DatabaseRecoveryState.WithNoRecovery, + isVisible: true + )); + + //Restricted user + options.Add(RestoreOptionsHelper.SetRestrictedUser, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.SetRestrictedUser, + currentValue: restoreDataObject.RestoreOptions.SetRestrictedUser, + defaultValue: false, + isReadOnly: false, + isVisible: true + )); + + //State recovery + options.Add(RestoreOptionsHelper.RecoveryState, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.RecoveryState, + currentValue: restoreDataObject.RestoreOptions.RecoveryState.ToString(), + defaultValue: DatabaseRecoveryState.WithRecovery.ToString(), + isReadOnly: false, + isVisible: true + )); + + // stand by file path for when RESTORE WITH STANDBY is selected + options.Add(RestoreOptionsHelper.StandbyFile, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.StandbyFile, + currentValue: restoreDataObject.RestoreOptions.StandByFile, + defaultValue: restoreDataObject.GetDefaultStandbyFile(databaseName), + isReadOnly: restoreDataObject.RestoreOptions.RecoveryState != DatabaseRecoveryState.WithStandBy, + isVisible: true + )); + + // Tail-log backup + // TODO:These methods are internal in SMO. after making them public, they can be removed from RestoreDatabaseTaskDataObject + bool isTailLogBackupPossible = restoreDataObject.IsTailLogBackupPossible(databaseName); + bool isTailLogBackupWithNoRecoveryPossible = restoreDataObject.IsTailLogBackupWithNoRecoveryPossible(databaseName); + + options.Add(RestoreOptionsHelper.BackupTailLog, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.BackupTailLog, + currentValue: restoreDataObject.BackupTailLog, + defaultValue: isTailLogBackupPossible, + isReadOnly: !isTailLogBackupPossible, + isVisible: true + )); + + options.Add(RestoreOptionsHelper.TailLogBackupFile, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.TailLogBackupFile, + currentValue: restoreDataObject.TailLogBackupFile, + defaultValue: restoreDataObject.GetDefaultTailLogbackupFile(databaseName), + isReadOnly: !isTailLogBackupPossible, + isVisible: true + )); + + options.Add(RestoreOptionsHelper.TailLogWithNoRecovery, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.TailLogWithNoRecovery, + currentValue: restoreDataObject.TailLogWithNoRecovery, + defaultValue: isTailLogBackupWithNoRecoveryPossible, + isReadOnly: !isTailLogBackupWithNoRecoveryPossible, + isVisible: true + )); + + + //TODO: make the method public in SMO bool canDropExistingConnections = restoreDataObject.RestorePlan.CanDropExistingConnections(this.Data.RestorePlanner.DatabaseName); + options.Add(RestoreOptionsHelper.CloseExistingConnections, RestorePlanDetailInfo.Create( + name: RestoreOptionsHelper.CloseExistingConnections, + currentValue: restoreDataObject.CloseExistingConnections, + defaultValue: false, + isReadOnly: false, //TODO: !canDropExistingConnections + isVisible: true + )); + + return options; + } + /// + /// Add options to restore plan response + /// + internal static void AddOptions(RestorePlanResponse response, RestoreDatabaseTaskDataObject restoreDataObject) + { + Validate.IsNotNull(nameof(response), response); + Validate.IsNotNull(nameof(restoreDataObject), restoreDataObject); + Validate.IsNotNull(nameof(restoreDataObject.RestorePlanner), restoreDataObject.RestorePlanner); + + + var options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDataObject); + + foreach (var item in options) + { + response.PlanDetails.Add(item.Key, item.Value); + } + } + + internal static T GetOptionValue(string optionkey, Dictionary optionsMetadata, IRestoreDatabaseTaskDataObject restoreDataObject) + { + RestorePlanDetailInfo optionMetadata = null; + if(optionsMetadata.TryGetValue(optionkey, out optionMetadata)) + { + if (!optionMetadata.IsReadOnly) + { + return restoreDataObject.RestoreParams.GetOptionValue(optionkey); + } + else + { + return (T)Convert.ChangeType(optionMetadata.DefaultValue, typeof(T)); + } + } + else + { + return default(T); + } + } + + /// + /// Load options in restore plan + /// + internal static void UpdateOptionsInPlan(IRestoreDatabaseTaskDataObject restoreDataObject) + { + var options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDataObject); + + //Files + restoreDataObject.LogFilesFolder = GetOptionValue(RestoreOptionsHelper.LogFileFolder, options, restoreDataObject); + restoreDataObject.DataFilesFolder = GetOptionValue(RestoreOptionsHelper.DataFileFolder, options, restoreDataObject); + restoreDataObject.RelocateAllFiles = GetOptionValue(RestoreOptionsHelper.RelocateDbFiles, options, restoreDataObject); + + //Options + object databaseRecoveryState; + + string recoveryState = GetOptionValue(RestoreOptionsHelper.RecoveryState, options, restoreDataObject); + if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState)) + { + restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState; + } + restoreDataObject.RestoreOptions.KeepReplication = GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDataObject); + restoreDataObject.RestoreOptions.ReplaceDatabase = GetOptionValue(RestoreOptionsHelper.ReplaceDatabase, options, restoreDataObject); + restoreDataObject.RestoreOptions.SetRestrictedUser = GetOptionValue(RestoreOptionsHelper.SetRestrictedUser, options, restoreDataObject); + restoreDataObject.RestoreOptions.StandByFile = GetOptionValue(RestoreOptionsHelper.StandbyFile, options, restoreDataObject); + + + + restoreDataObject.BackupTailLog = GetOptionValue(RestoreOptionsHelper.BackupTailLog, options, restoreDataObject); + restoreDataObject.TailLogBackupFile = GetOptionValue(RestoreOptionsHelper.TailLogBackupFile, options, restoreDataObject); + restoreDataObject.TailLogWithNoRecovery = GetOptionValue(RestoreOptionsHelper.TailLogWithNoRecovery, options, restoreDataObject); + + restoreDataObject.CloseExistingConnections = GetOptionValue(RestoreOptionsHelper.CloseExistingConnections, options, restoreDataObject); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs index 771a7b13..798b729b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs @@ -35,6 +35,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery private DisasterRecoveryService service; private string fullBackupFilePath; private string[] backupFilesToRecoverDatabase; + private string databaseNameToRestoreFrom; //The table names used in the script to create backup files for a database //Each table is created after a backup script to verify recovering to different states @@ -84,6 +85,27 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery await VerifyRestoreMultipleBackupSets(backupFiles, indexToDelete, expectedTable); } + [Fact] + public async void RestoreShouldRestoreFromAnotherDatabase() + { + await GetBackupFilesToRecoverDatabaseCreated(); + + var testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "RestoreTest"); + try + { + Dictionary options = new Dictionary(); + options.Add(RestoreOptionsHelper.ReplaceDatabase, true); + await VerifyRestore(null, databaseNameToRestoreFrom, true, true, testDb.DatabaseName, null, options, (database) => + { + return database.Tables.Contains("tb1", "test"); + }); + } + finally + { + testDb.Cleanup(); + } + } + [Fact] public async void RestoreShouldRestoreTheBackupSetsThatAreSelected() { @@ -114,7 +136,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery { string targetDbName = testDb.DatabaseName; bool canRestore = true; - var response = await VerifyRestore(backupFiles, canRestore, false, targetDbName, null, null); + var response = await VerifyRestore(backupFiles, null, canRestore, false, targetDbName, null, null); Assert.True(response.BackupSetsToRestore.Count() >= 2); var allIds = response.BackupSetsToRestore.Select(x => x.Id).ToList(); if (backupSetIndexToDelete >= 0) @@ -124,7 +146,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery string[] selectedIds = allIds.ToArray(); Dictionary options = new Dictionary(); options.Add(RestoreOptionsHelper.ReplaceDatabase, true); - response = await VerifyRestore(backupFiles, canRestore, true, targetDbName, selectedIds, options, (database) => + response = await VerifyRestore(backupFiles, null, canRestore, true, targetDbName, selectedIds, options, (database) => { bool tablesFound = true; for (int i = 0; i < tableNames.Length; i++) @@ -168,7 +190,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery Dictionary options = new Dictionary(); options.Add(RestoreOptionsHelper.ReplaceDatabase, true); - await VerifyRestore(new string[] { fullBackupFilePath }, canRestore, true, testDb.DatabaseName, null, options); + await VerifyRestore(new string[] { fullBackupFilePath }, null, canRestore, true, testDb.DatabaseName, null, options); } finally { @@ -190,7 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery await VerifyBackupFileCreated(); bool canRestore = true; - await VerifyRestore(new string[] { fullBackupFilePath }, canRestore, false, testDb.DatabaseName, null, null); + await VerifyRestore(new string[] { fullBackupFilePath }, null, canRestore, false, testDb.DatabaseName, null, null); } finally { @@ -207,7 +229,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery string[] backupFileNames = new string[] { "FullBackup.bak", "DiffBackup.bak" }; bool canRestore = true; - var response = await VerifyRestore(backupFileNames, canRestore, false, "RestoredFromTwoBackupFile"); + var response = await VerifyRestore(backupFileNames, null, canRestore, false, "RestoredFromTwoBackupFile"); Assert.True(response.BackupSetsToRestore.Count() == 2); } @@ -217,13 +239,13 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery string[] backupFileNames = new string[] { "FullBackup.bak", "DiffBackup.bak" }; bool canRestore = true; - var response = await VerifyRestore(backupFileNames, canRestore, false, "RestoredFromTwoBackupFile"); + var response = await VerifyRestore(backupFileNames, null, canRestore, false, "RestoredFromTwoBackupFile"); Assert.True(response.BackupSetsToRestore.Count() == 2); var fileInfo = response.BackupSetsToRestore.FirstOrDefault(x => x.GetPropertyValueAsString(BackupSetInfo.BackupTypePropertyName) != RestoreConstants.TypeFull); if(fileInfo != null) { var selectedBackupSets = new string[] { fileInfo.Id }; - await VerifyRestore(backupFileNames, true, false, "RestoredFromTwoBackupFile", selectedBackupSets); + await VerifyRestore(backupFileNames, null, true, false, "RestoredFromTwoBackupFile", selectedBackupSets); } } @@ -233,13 +255,13 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery string[] backupFileNames = new string[] { "FullBackup.bak", "DiffBackup.bak" }; bool canRestore = true; - var response = await VerifyRestore(backupFileNames, canRestore, false, "RestoredFromTwoBackupFile"); + var response = await VerifyRestore(backupFileNames, null, canRestore, false, "RestoredFromTwoBackupFile"); Assert.True(response.BackupSetsToRestore.Count() == 2); var fileInfo = response.BackupSetsToRestore.FirstOrDefault(x => x.GetPropertyValueAsString(BackupSetInfo.BackupTypePropertyName) == RestoreConstants.TypeFull); if (fileInfo != null) { var selectedBackupSets = new string[] { fileInfo.Id }; - await VerifyRestore(backupFileNames, true, false, "RestoredFromTwoBackupFile2", selectedBackupSets); + await VerifyRestore(backupFileNames, null, true, false, "RestoredFromTwoBackupFile2", selectedBackupSets); } } @@ -306,6 +328,32 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery } } + [Fact] + public async Task RestoreConfigInfoRequestShouldReturnResponse() + { + await VerifyBackupFileCreated(); + + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + + string filePath = GetBackupFilePath(fullBackupFilePath); + + RestoreConfigInfoRequestParams restoreParams = new RestoreConfigInfoRequestParams + { + OwnerUri = queryTempFile.FilePath + }; + + await RunAndVerify( + test: (requestContext) => service.HandleRestoreConfigInfoRequest(restoreParams, requestContext), + verify: ((result) => + { + Assert.True(result.ConfigInfo.Any()); + Assert.True(result.ConfigInfo.ContainsKey(RestoreOptionsHelper.SourceDatabaseNamesWithBackupSets)); + })); + } + } + [Fact] public async Task RestoreDatabaseRequestShouldStartTheRestoreTask() { @@ -368,36 +416,53 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery private async Task VerifyRestore(string backupFileName, bool canRestore, bool execute = false, string targetDatabase = null) { - return await VerifyRestore(new string[] { backupFileName }, canRestore, execute, targetDatabase); + return await VerifyRestore(new string[] { backupFileName }, null, canRestore, execute, targetDatabase); } private async Task VerifyRestore( - string[] backupFileNames, - bool canRestore, + string[] backupFileNames = null, + string sourceDbName = null, + bool canRestore = true, bool execute = false, string targetDatabase = null, string[] selectedBackupSets = null, Dictionary options = null, Func verifyDatabase = null) { - var filePaths = backupFileNames.Select(x => GetBackupFilePath(x)); - string backUpFilePath = filePaths.Aggregate((current, next) => current + " ," + next); - + string backUpFilePath = string.Empty; + if (backupFileNames != null) + { + var filePaths = backupFileNames.Select(x => GetBackupFilePath(x)); + backUpFilePath = filePaths.Aggregate((current, next) => current + " ," + next); + } using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) { TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); RestoreDatabaseHelper service = new RestoreDatabaseHelper(); + + // If source database is sepecified verfiy it's part of source db list + if(!string.IsNullOrEmpty(sourceDbName)) + { + RestoreConfigInfoResponse configInfoResponse = service.CreateConfigInfoResponse(new RestoreConfigInfoRequestParams + { + OwnerUri = queryTempFile.FilePath + }); + IEnumerable dbNames = configInfoResponse.ConfigInfo[RestoreOptionsHelper.SourceDatabaseNamesWithBackupSets] as IEnumerable; + Assert.True(dbNames.Any(x => x == sourceDbName)); + } var request = new RestoreParams { BackupFilePaths = backUpFilePath, TargetDatabaseName = targetDatabase, OwnerUri = queryTempFile.FilePath, - SelectedBackupSets = selectedBackupSets + SelectedBackupSets = selectedBackupSets, + SourceDatabaseName = sourceDbName }; + request.Options[RestoreOptionsHelper.ReadHeaderFromMedia] = backupFileNames != null; - if(options != null) + if (options != null) { foreach (var item in options) { @@ -424,35 +489,45 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery Assert.Equal(response.DatabaseName, targetDatabase); Assert.NotNull(response.PlanDetails); Assert.True(response.PlanDetails.Any()); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultBackupTailLog]); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultTailLogBackupFile]); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultDataFileFolder]); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultLogFileFolder]); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultStandbyFile]); - Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DefaultStandbyFile]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.BackupTailLog]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.TailLogBackupFile]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.DataFileFolder]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.LogFileFolder]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.StandbyFile]); + Assert.NotNull(response.PlanDetails[RestoreOptionsHelper.StandbyFile]); if(execute) { - request.SessionId = response.SessionId; - restoreDataObject = service.CreateRestoreDatabaseTaskDataObject(request); - Assert.Equal(response.SessionId, restoreDataObject.SessionId); - request.RelocateDbFiles = !restoreDataObject.DbFilesLocationAreValid(); - service.ExecuteRestore(restoreDataObject); - Assert.True(restoreDataObject.Server.Databases.Contains(targetDatabase)); - - if(verifyDatabase != null) + try { - Assert.True(verifyDatabase(restoreDataObject.Server.Databases[targetDatabase])); - } + request.SessionId = response.SessionId; + restoreDataObject = service.CreateRestoreDatabaseTaskDataObject(request); + Assert.Equal(response.SessionId, restoreDataObject.SessionId); + request.RelocateDbFiles = !restoreDataObject.DbFilesLocationAreValid(); + service.ExecuteRestore(restoreDataObject); + Assert.True(restoreDataObject.Server.Databases.Contains(targetDatabase)); + + if (verifyDatabase != null) + { + Assert.True(verifyDatabase(restoreDataObject.Server.Databases[targetDatabase])); + } + + //To verify the backupset that are restored, verifying the database is a better options. + //Some tests still verify the number of backup sets that are executed which in some cases can be less than the selected list + if (verifyDatabase == null && selectedBackupSets != null) + { + Assert.Equal(selectedBackupSets.Count(), restoreDataObject.RestorePlanToExecute.RestoreOperations.Count()); + } - //To verify the backupset that are restored, verifying the database is a better options. - //Some tests still verify the number of backup sets that are executed which in some cases can be less than the selected list - if (verifyDatabase == null && selectedBackupSets != null) - { - Assert.Equal(selectedBackupSets.Count(), restoreDataObject.RestorePlanToExecute.RestoreOperations.Count()); } - - await DropDatabase(targetDatabase); + catch(Exception ex) + { + Assert.False(true, ex.Message); + } + finally + { + await DropDatabase(targetDatabase); + } } } @@ -514,8 +589,6 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery List backupFiles = new List(); using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) { - - string query = $"CREATE SCHEMA [test]"; SqlTestDb testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, query, "RestoreTest"); string databaseName = testDb.DatabaseName; @@ -560,6 +633,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery await TestServiceProvider.Instance.RunQueryAsync(TestServerType.OnPrem, "master", query); backupFiles.Add(backupPath); + databaseNameToRestoreFrom = testDb.DatabaseName; // Clean up the database testDb.Cleanup(); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs new file mode 100644 index 00000000..20b34420 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs @@ -0,0 +1,275 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery +{ + public class RestoreOptionsHelperTests + { + [Fact] + public void VerifyOptionsCreatedSuccessfullyIsResponse() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + } + + [Fact] + public void RelocateAllFilesShouldBeReadOnlyGivenNoDbFiles() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options["DbFiles"] = new List(); + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + Assert.True(result[RestoreOptionsHelper.RelocateDbFiles].IsReadOnly); + } + + [Fact] + public void BackupTailLogShouldBeReadOnlyTailLogBackupNotPossible() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options["IsTailLogBackupPossible"] = false; + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + Assert.True(result[RestoreOptionsHelper.BackupTailLog].IsReadOnly); + Assert.True(result[RestoreOptionsHelper.TailLogBackupFile].IsReadOnly); + } + + [Fact] + public void TailLogWithNoRecoveryShouldBeReadOnlyTailLogBackupWithNoRecoveryNotPossible() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options["IsTailLogBackupWithNoRecoveryPossible"] = false; + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + Assert.True(result[RestoreOptionsHelper.TailLogWithNoRecovery].IsReadOnly); + } + + [Fact] + public void StandbyFileShouldNotBeReadOnlyGivenRecoveryStateWithStandBy() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithStandBy; + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + Assert.False(result[RestoreOptionsHelper.StandbyFile].IsReadOnly); + } + + [Fact] + public void KeepReplicationShouldNotBeReadOnlyGivenRecoveryStateWithNoRecovery() + { + GeneralRequestDetails optionValues = CreateOptionsTestData(); + optionValues.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues); + + Dictionary result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + Assert.NotNull(result); + VerifyOptions(result, optionValues); + Assert.True(result[RestoreOptionsHelper.KeepReplication].IsReadOnly); + } + + [Fact] + public void KeepReplicationShouldSetToDefaultValueGivenRecoveryStateWithNoRecovery() + { + RestoreParams restoreParams = CreateOptionsTestData(); + restoreParams.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithNoRecovery; + + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + + restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true; + + bool actual = RestoreOptionsHelper.GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDatabaseTaskDataObject.Object); + bool expected = (bool)options[RestoreOptionsHelper.KeepReplication].DefaultValue; + + Assert.Equal(actual, expected); + } + + [Fact] + public void KeepReplicationShouldSetToValueInRequestGivenRecoveryStateWithRecovery() + { + RestoreParams restoreParams = CreateOptionsTestData(); + + restoreParams.Options[RestoreOptionsHelper.RecoveryState] = DatabaseRecoveryState.WithRecovery; + Mock restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams); + Dictionary options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object); + + restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true; + + bool actual = RestoreOptionsHelper.GetOptionValue(RestoreOptionsHelper.KeepReplication, options, restoreDatabaseTaskDataObject.Object); + bool expected = true; + Assert.Equal(actual, expected); + + } + + + private RestoreParams CreateOptionsTestData() + { + RestoreParams optionValues = new RestoreParams(); + optionValues.Options.Add(RestoreOptionsHelper.CloseExistingConnections, false); + optionValues.Options.Add(RestoreOptionsHelper.DataFileFolder, "Data file folder"); + optionValues.Options.Add("DbFiles", new List() { new DbFile("", '1', "") }); + optionValues.Options.Add("DefaultDataFileFolder", "Default data file folder"); + optionValues.Options.Add("DefaultLogFileFolder", "Default log file folder"); + optionValues.Options.Add("IsTailLogBackupPossible", true); + optionValues.Options.Add("IsTailLogBackupWithNoRecoveryPossible", true); + optionValues.Options.Add("GetDefaultStandbyFile", "default standby file"); + optionValues.Options.Add("GetDefaultTailLogbackupFile", "default tail log backup file"); + optionValues.Options.Add("LogFilesFolder", "Log file folder"); + optionValues.Options.Add("RelocateAllFiles", false); + optionValues.Options.Add("TailLogBackupFile", "tail log backup file"); + optionValues.Options.Add("TailLogWithNoRecovery", false); + optionValues.Options.Add("BackupTailLog", false); + optionValues.Options.Add(RestoreOptionsHelper.KeepReplication, false); + optionValues.Options.Add("ReplaceDatabase", false); + optionValues.Options.Add("SetRestrictedUser", false); + optionValues.Options.Add("StandbyFile", "Stand by file"); + optionValues.Options.Add(RestoreOptionsHelper.RecoveryState, DatabaseRecoveryState.WithNoRecovery.ToString()); + return optionValues; + } + + private Mock CreateRestoreDatabaseTaskDataObject(GeneralRequestDetails optionValues) + { + var restoreDataObject = new Mock(); + restoreDataObject.Setup(x => x.CloseExistingConnections).Returns(optionValues.GetOptionValue(RestoreOptionsHelper.CloseExistingConnections)); + restoreDataObject.Setup(x => x.DataFilesFolder).Returns(optionValues.GetOptionValue(RestoreOptionsHelper.DataFileFolder)); + restoreDataObject.Setup(x => x.DbFiles).Returns(optionValues.GetOptionValue>("DbFiles")); + restoreDataObject.Setup(x => x.DefaultDataFileFolder).Returns(optionValues.GetOptionValue("DefaultDataFileFolder")); + restoreDataObject.Setup(x => x.DefaultLogFileFolder).Returns(optionValues.GetOptionValue("DefaultLogFileFolder")); + restoreDataObject.Setup(x => x.IsTailLogBackupPossible(It.IsAny())).Returns(optionValues.GetOptionValue("IsTailLogBackupPossible")); + restoreDataObject.Setup(x => x.IsTailLogBackupWithNoRecoveryPossible(It.IsAny())).Returns(optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); + restoreDataObject.Setup(x => x.GetDefaultStandbyFile(It.IsAny())).Returns(optionValues.GetOptionValue("GetDefaultStandbyFile")); + restoreDataObject.Setup(x => x.GetDefaultTailLogbackupFile(It.IsAny())).Returns(optionValues.GetOptionValue("GetDefaultTailLogbackupFile")); + restoreDataObject.Setup(x => x.LogFilesFolder).Returns(optionValues.GetOptionValue("LogFilesFolder")); + restoreDataObject.Setup(x => x.RelocateAllFiles).Returns(optionValues.GetOptionValue("RelocateAllFiles")); + restoreDataObject.Setup(x => x.TailLogBackupFile).Returns(optionValues.GetOptionValue("TailLogBackupFile")); + restoreDataObject.Setup(x => x.TailLogWithNoRecovery).Returns(optionValues.GetOptionValue("TailLogWithNoRecovery")); + restoreDataObject.Setup(x => x.BackupTailLog).Returns(optionValues.GetOptionValue("BackupTailLog")); + restoreDataObject.Setup(x => x.RestoreParams).Returns(optionValues as RestoreParams); + restoreDataObject.Setup(x => x.RestorePlan).Returns(() => null); + RestoreOptions restoreOptions = new RestoreOptions(); + restoreOptions.KeepReplication = optionValues.GetOptionValue(RestoreOptionsHelper.KeepReplication); + restoreOptions.ReplaceDatabase = optionValues.GetOptionValue("ReplaceDatabase"); + restoreOptions.SetRestrictedUser = optionValues.GetOptionValue("SetRestrictedUser"); + restoreOptions.StandByFile = optionValues.GetOptionValue("StandbyFile"); + restoreOptions.RecoveryState = optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState); + restoreDataObject.Setup(x => x.RestoreOptions).Returns(restoreOptions); + + + return restoreDataObject; + } + + private void VerifyOptions(Dictionary optionInResponse, GeneralRequestDetails optionValues) + { + RestorePlanDetailInfo planDetailInfo = optionInResponse[RestoreOptionsHelper.DataFileFolder]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.DataFileFolder); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("RelocateAllFiles")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.DataFileFolder)); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("DefaultDataFileFolder")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.LogFileFolder]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.LogFileFolder); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("RelocateAllFiles")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("LogFilesFolder")); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("DefaultLogFileFolder")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.RelocateDbFiles]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.RelocateDbFiles); + Assert.Equal(planDetailInfo.IsReadOnly, (optionValues.GetOptionValue>("DbFiles").Count == 0)); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("LogFilesFolder")); + Assert.Equal(planDetailInfo.DefaultValue, false); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.ReplaceDatabase]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.ReplaceDatabase); + Assert.Equal(planDetailInfo.IsReadOnly, false); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("ReplaceDatabase")); + Assert.Equal(planDetailInfo.DefaultValue, false); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.KeepReplication]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.KeepReplication); + Assert.Equal(planDetailInfo.IsReadOnly, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState) == DatabaseRecoveryState.WithNoRecovery); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.KeepReplication)); + Assert.Equal(planDetailInfo.DefaultValue, false); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.SetRestrictedUser]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.SetRestrictedUser); + Assert.Equal(planDetailInfo.IsReadOnly, false); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("SetRestrictedUser")); + Assert.Equal(planDetailInfo.DefaultValue, false); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.RecoveryState]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.RecoveryState); + Assert.Equal(planDetailInfo.IsReadOnly, false); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState).ToString()); + Assert.Equal(planDetailInfo.DefaultValue, DatabaseRecoveryState.WithRecovery.ToString()); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.StandbyFile]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.StandbyFile); + Assert.Equal(planDetailInfo.IsReadOnly, optionValues.GetOptionValue(RestoreOptionsHelper.RecoveryState) != DatabaseRecoveryState.WithStandBy); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("StandbyFile")); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("GetDefaultStandbyFile")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.BackupTailLog]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.BackupTailLog); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupPossible")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("BackupTailLog")); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("IsTailLogBackupPossible")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogBackupFile]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogBackupFile); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupPossible")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("TailLogBackupFile")); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("GetDefaultTailLogbackupFile")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogWithNoRecovery]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogWithNoRecovery); + Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("TailLogWithNoRecovery")); + Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue("IsTailLogBackupWithNoRecoveryPossible")); + Assert.Equal(planDetailInfo.IsVisiable, true); + + planDetailInfo = optionInResponse[RestoreOptionsHelper.CloseExistingConnections]; + Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.CloseExistingConnections); + Assert.Equal(planDetailInfo.IsReadOnly, false); + Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue("CloseExistingConnections")); + Assert.Equal(planDetailInfo.DefaultValue, false); + Assert.Equal(planDetailInfo.IsVisiable, true); + } + + } +}