supporting restore from db (#429)

* supporting restore from db
This commit is contained in:
Leila Lali
2017-08-09 11:01:52 -07:00
committed by GitHub
parent a4a27f9559
commit 6696b7e72f
12 changed files with 1143 additions and 369 deletions

View File

@@ -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
{
/// <summary>
/// The Uri to find the connection to do the restore operations
/// </summary>
public string OwnerUri { get; set; }
}
public class RestoreConfigInfoResponse
{
public RestoreConfigInfoResponse()
{
ConfigInfo = new Dictionary<string, object>();
}
/// <summary>
/// Config Info
/// </summary>
public Dictionary<string, object> ConfigInfo { get; set; }
/// <summary>
/// Errors occurred while creating the restore config info
/// </summary>
public string ErrorMessage { get; set; }
}
public class RestoreConfigInfoRequest
{
public static readonly
RequestType<RestoreConfigInfoRequestParams, RestoreConfigInfoResponse> Type =
RequestType<RestoreConfigInfoRequestParams, RestoreConfigInfoResponse>.Create("disasterrecovery/restoreconfiginfo");
}
}

View File

@@ -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
{
/// <summary>
/// Class to include the plan detail
/// </summary>
public class RestorePlanDetailInfo
{
/// <summary>
/// The name of the option from RestoreOptionsHelper
/// </summary>
public string Name { get; set; }
/// <summary>
/// The current value of the option
/// </summary>
public object CurrentValue { get; set; }
/// <summary>
/// Indicates whether the option is read only or can be changed in client
/// </summary>
public bool IsReadOnly { get; set; }
/// <summary>
/// Indicates whether the option should be visibile in client
/// </summary>
public bool IsVisiable { get; set; }
/// <summary>
/// The default value of the option
/// </summary>
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
};
}
}
}

View File

@@ -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
{
/// <summary>
/// Database file info
/// </summary>
public class RestoreDatabaseFileInfo
{
/// <summary>
/// File type (Rows Data, Log ...)
/// </summary>
public string FileType { get; set; }
/// <summary>
/// Logical Name
/// </summary>
public string LogicalFileName { get; set; }
/// <summary>
/// Original location of the file to restore to
/// </summary>
public string OriginalFileName { get; set; }
/// <summary>
/// The file to restore to
/// </summary>
public string RestoreAsFileName { get; set; }
}
/// <summary>
/// Restore Plan Response
/// </summary>
public class RestorePlanResponse
{
/// <summary>
/// Restore session id, can be used in restore request to use an existing restore plan
/// </summary>
public string SessionId { get; set; }
/// <summary>
/// The list of backup sets to restore
/// </summary>
public DatabaseFileInfo[] BackupSetsToRestore { get; set; }
/// <summary>
/// Indicates whether the restore operation is supported
/// </summary>
public bool CanRestore { get; set; }
/// <summary>
/// Errors occurred while creating restore plan
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// The db files included in the backup file
/// </summary>
public IEnumerable<RestoreDatabaseFileInfo> DbFiles { get; set; }
/// <summary>
/// Database names extracted from backup sets
/// </summary>
public string[] DatabaseNamesFromBackupSets { get; set; }
/// <summary>
/// For testing purpose to verify the target database
/// </summary>
internal string DatabaseName { get; set; }
/// <summary>
/// Plan details
/// </summary>
public Dictionary<string, RestorePlanDetailInfo> PlanDetails { get; set; }
}
public class RestorePlanRequest
{
public static readonly
RequestType<RestoreParams, RestorePlanResponse> Type =
RequestType<RestoreParams, RestorePlanResponse>.Create("disasterrecovery/restoreplan");
}
}

View File

@@ -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
{
/// <summary>
/// Restore request parameters
/// </summary>
public class RestoreParams : GeneralRequestDetails
{
/// <summary>
/// Restore session id. The parameter is optional and if passed, an existing plan will be used
/// </summary>
internal string SessionId
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.SessionId);
}
set
{
SetOptionValue(RestoreOptionsHelper.SessionId, value);
}
}
/// <summary>
/// The Uri to find the connection to do the restore operations
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// Comma delimited list of backup files
/// </summary>
internal string BackupFilePaths
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.BackupFilePaths);
}
set
{
SetOptionValue(RestoreOptionsHelper.BackupFilePaths, value);
}
}
/// <summary>
/// Target Database name to restore to
/// </summary>
internal string TargetDatabaseName
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.TargetDatabaseName);
}
set
{
SetOptionValue(RestoreOptionsHelper.TargetDatabaseName, value);
}
}
/// <summary>
/// Source Database name to restore from
/// </summary>
internal string SourceDatabaseName
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.SourceDatabaseName);
}
set
{
SetOptionValue(RestoreOptionsHelper.SourceDatabaseName, value);
}
}
/// <summary>
/// If set to true, the db files will be relocated to default data location in the server
/// </summary>
internal bool RelocateDbFiles
{
get
{
return GetOptionValue<bool>(RestoreOptionsHelper.RelocateDbFiles);
}
set
{
SetOptionValue(RestoreOptionsHelper.RelocateDbFiles, value);
}
}
/// <summary>
/// Ids of the backup set to restore
/// </summary>
internal string[] SelectedBackupSets
{
get
{
return GetOptionValue<string[]>(RestoreOptionsHelper.SelectedBackupSets);
}
set
{
SetOptionValue(RestoreOptionsHelper.SelectedBackupSets, value);
}
}
}
/// <summary>
/// Restore response
/// </summary>
@@ -132,78 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
public string ErrorMessage { get; set; }
}
/// <summary>
/// Database file info
/// </summary>
public class RestoreDatabaseFileInfo
{
/// <summary>
/// File type (Rows Data, Log ...)
/// </summary>
public string FileType { get; set; }
/// <summary>
/// Logical Name
/// </summary>
public string LogicalFileName { get; set; }
/// <summary>
/// Original location of the file to restore to
/// </summary>
public string OriginalFileName { get; set; }
/// <summary>
/// The file to restore to
/// </summary>
public string RestoreAsFileName { get; set; }
}
/// <summary>
/// Restore Plan Response
/// </summary>
public class RestorePlanResponse
{
/// <summary>
/// Restore session id, can be used in restore request to use an existing restore plan
/// </summary>
public string SessionId { get; set; }
/// <summary>
/// The list of backup sets to restore
/// </summary>
public DatabaseFileInfo[] BackupSetsToRestore { get; set; }
/// <summary>
/// Indicates whether the restore operation is supported
/// </summary>
public bool CanRestore { get; set; }
/// <summary>
/// Errors occurred while creating restore plan
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// The db files included in the backup file
/// </summary>
public IEnumerable<RestoreDatabaseFileInfo> DbFiles { get; set; }
/// <summary>
/// Database names extracted from backup sets
/// </summary>
public string[] DatabaseNamesFromBackupSets { get; set; }
/// <summary>
/// For testing purpose to verify the target database
/// </summary>
internal string DatabaseName { get; set; }
/// <summary>
/// Plan details
/// </summary>
public Dictionary<string, object> PlanDetails { get; set; }
}
public class RestoreRequest
{
@@ -212,10 +38,5 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
RequestType<RestoreParams, RestoreResponse>.Create("disasterrecovery/restore");
}
public class RestorePlanRequest
{
public static readonly
RequestType<RestoreParams, RestorePlanResponse> Type =
RequestType<RestoreParams, RestorePlanResponse>.Create("disasterrecovery/restoreplan");
}
}

View File

@@ -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
{
/// <summary>
/// Restore request parameters
/// </summary>
public class RestoreParams : GeneralRequestDetails
{
/// <summary>
/// Restore session id. The parameter is optional and if passed, an existing plan will be used
/// </summary>
internal string SessionId
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.SessionId);
}
set
{
SetOptionValue(RestoreOptionsHelper.SessionId, value);
}
}
/// <summary>
/// The Uri to find the connection to do the restore operations
/// </summary>
public string OwnerUri { get; set; }
/// <summary>
/// Comma delimited list of backup files
/// </summary>
internal string BackupFilePaths
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.BackupFilePaths);
}
set
{
SetOptionValue(RestoreOptionsHelper.BackupFilePaths, value);
}
}
/// <summary>
/// Target Database name to restore to
/// </summary>
internal string TargetDatabaseName
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.TargetDatabaseName);
}
set
{
SetOptionValue(RestoreOptionsHelper.TargetDatabaseName, value);
}
}
/// <summary>
/// Source Database name to restore from
/// </summary>
internal string SourceDatabaseName
{
get
{
return GetOptionValue<string>(RestoreOptionsHelper.SourceDatabaseName);
}
set
{
SetOptionValue(RestoreOptionsHelper.SourceDatabaseName, value);
}
}
/// <summary>
/// If set to true, the db files will be relocated to default data location in the server
/// </summary>
internal bool RelocateDbFiles
{
get
{
return GetOptionValue<bool>(RestoreOptionsHelper.RelocateDbFiles);
}
set
{
SetOptionValue(RestoreOptionsHelper.RelocateDbFiles, value);
}
}
/// <summary>
/// If set to true, the backup files will be used to create restore plan otehrwise the source database name will be used
/// </summary>
internal bool ReadHeaderFromMedia
{
get
{
//Default is true for now for backward compatibility
return Options.ContainsKey(RestoreOptionsHelper.ReadHeaderFromMedia) ? GetOptionValue<bool>(RestoreOptionsHelper.ReadHeaderFromMedia) : true;
}
set
{
SetOptionValue(RestoreOptionsHelper.ReadHeaderFromMedia, value);
}
}
/// <summary>
/// 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
/// </summary>
internal IEnumerable<string> SelectedBackupSets
{
get
{
var selectedBackupSets = GetOptionValue<object>(RestoreOptionsHelper.SelectedBackupSets);
if (selectedBackupSets != null)
{
JArray array = selectedBackupSets as JArray;
if(array != null)
{
return array.ToObject<IEnumerable<string>>();
}
else
{
IEnumerable<string> list = selectedBackupSets as IEnumerable<string>;
return list;
}
}
return null;
}
set
{
SetOptionValue(RestoreOptionsHelper.SelectedBackupSets, value);
}
}
}
}

View File

@@ -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<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
private static ConnectionService connectionService = null;
private RestoreDatabaseHelper restoreDatabaseService = RestoreDatabaseHelper.Instance;
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
/// <summary>
/// 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);
}
/// <summary>
@@ -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
}
}
/// <summary>
/// Handles a restore config info request
/// </summary>
internal async Task HandleRestoreConfigInfoRequest(
RestoreConfigInfoRequestParams restoreConfigInfoParams,
RequestContext<RestoreConfigInfoResponse> 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);
}
}
/// <summary>
/// Handles a restore request
/// </summary>
@@ -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)

View File

@@ -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<string, RestoreDatabaseTaskDataObject> restoreSessions = new ConcurrentDictionary<string, RestoreDatabaseTaskDataObject>();
internal RestoreDatabaseHelper()
{
}
public static RestoreDatabaseHelper Instance
{
get
{
return instance;
}
}
/// <summary>
/// Create a backup task for execution and cancellation
@@ -142,6 +126,26 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
return taskResult;
}
/// <summary>
/// Creates response which includes information about the server given to restore (default data location, db names with backupsets)
/// </summary>
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;
}
/// <summary>
/// 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<string, object>()
PlanDetails = new System.Collections.Generic.Dictionary<string, RestorePlanDetailInfo>()
};
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<bool>(RestoreOptionsHelper.KeepReplication);
restoreDataObject.RestoreOptions.ReplaceDatabase = restoreDataObject.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.ReplaceDatabase);
restoreDataObject.RestoreOptions.SetRestrictedUser = restoreDataObject.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.SetRestrictedUser);
string recoveryState = restoreDataObject.RestoreParams.GetOptionValue<string>(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<bool>(RestoreOptionsHelper.BackupTailLog);
restoreDataObject.TailLogBackupFile = restoreDataObject.RestoreParams.GetOptionValue<string>(RestoreOptionsHelper.TailLogBackupFile);
restoreDataObject.TailLogWithNoRecovery = restoreDataObject.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.TailLogWithNoRecovery);
}
else
{
restoreDataObject.RestorePlanner.BackupTailLog = false;
}
RestoreOptionsHelper.UpdateOptionsInPlan(restoreDataObject);
restoreDataObject.CloseExistingConnections = restoreDataObject.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.CloseExistingConnections);
restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles);
restoreDataObject.UpdateRestorePlan();
}
/// <summary>
@@ -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)
{

View File

@@ -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<DbFile> 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; }
}
/// <summary>
/// Includes the plan with all the data required to do a restore operation on server
/// </summary>
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;
}
/// <summary>
/// Boolean indicating whether the relocate all files checkbox was checked
/// </summary>
public bool RelocateAllFiles { get; set; }
/// <summary>
/// Restore session id
/// </summary>
@@ -89,6 +124,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName);
}
/// <summary>
/// Returns the db names that have backupsets
/// </summary>
/// <returns></returns>
public List<String> GetDatabaseNamesWithBackupSets()
{
return Util.GetSourceDbNames();
}
/// <summary>
/// Current sqlserver instance
/// </summary>
@@ -375,7 +419,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
/// <returns>
/// <c>true</c> if [is tail log backup possible]; otherwise, <c>false</c>.
/// </returns>
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;
}
/// <summary>
/// Determines whether [is tail log backup with NORECOVERY possible].
/// </summary>
/// <returns>
/// <c>true</c> if [is tail log backup with NORECOVERY possible]; otherwise, <c>false</c>.
/// </returns>
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);
}
/// <summary>
/// Gets or sets a value indicating whether [prompt before each backup].
/// </summary>
@@ -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
/// <summary>
/// Updates restore plan
/// </summary>
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;
}
*/
}
}

View File

@@ -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
/*
/// <summary>
/// Queries msdb for source database names
/// </summary>
@@ -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;
}
*/
/// <summary>
/// make enumerator data request
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
internal DataTable GetEnumeratorData(Request req)
{
return new Enumerator().Process(this.server.ConnectionContext.SqlConnectionObject, req);
}
/// <summary>
/// Reads backup file header to get source database names

View File

@@ -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";
/// <summary>
/// Creates the options metadata available for restore operations
@@ -187,5 +229,201 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return options;
}
internal static Dictionary<string, RestorePlanDetailInfo> CreateRestorePlanOptions(IRestoreDatabaseTaskDataObject restoreDataObject)
{
Validate.IsNotNull(nameof(restoreDataObject), restoreDataObject);
Dictionary<string, RestorePlanDetailInfo> options = new Dictionary<string, RestorePlanDetailInfo>();
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;
}
/// <summary>
/// Add options to restore plan response
/// </summary>
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<T>(string optionkey, Dictionary<string, RestorePlanDetailInfo> optionsMetadata, IRestoreDatabaseTaskDataObject restoreDataObject)
{
RestorePlanDetailInfo optionMetadata = null;
if(optionsMetadata.TryGetValue(optionkey, out optionMetadata))
{
if (!optionMetadata.IsReadOnly)
{
return restoreDataObject.RestoreParams.GetOptionValue<T>(optionkey);
}
else
{
return (T)Convert.ChangeType(optionMetadata.DefaultValue, typeof(T));
}
}
else
{
return default(T);
}
}
/// <summary>
/// Load options in restore plan
/// </summary>
internal static void UpdateOptionsInPlan(IRestoreDatabaseTaskDataObject restoreDataObject)
{
var options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDataObject);
//Files
restoreDataObject.LogFilesFolder = GetOptionValue<string>(RestoreOptionsHelper.LogFileFolder, options, restoreDataObject);
restoreDataObject.DataFilesFolder = GetOptionValue<string>(RestoreOptionsHelper.DataFileFolder, options, restoreDataObject);
restoreDataObject.RelocateAllFiles = GetOptionValue<bool>(RestoreOptionsHelper.RelocateDbFiles, options, restoreDataObject);
//Options
object databaseRecoveryState;
string recoveryState = GetOptionValue<string>(RestoreOptionsHelper.RecoveryState, options, restoreDataObject);
if (Enum.TryParse(typeof(DatabaseRecoveryState), recoveryState, out databaseRecoveryState))
{
restoreDataObject.RestoreOptions.RecoveryState = (DatabaseRecoveryState)databaseRecoveryState;
}
restoreDataObject.RestoreOptions.KeepReplication = GetOptionValue<bool>(RestoreOptionsHelper.KeepReplication, options, restoreDataObject);
restoreDataObject.RestoreOptions.ReplaceDatabase = GetOptionValue<bool>(RestoreOptionsHelper.ReplaceDatabase, options, restoreDataObject);
restoreDataObject.RestoreOptions.SetRestrictedUser = GetOptionValue<bool>(RestoreOptionsHelper.SetRestrictedUser, options, restoreDataObject);
restoreDataObject.RestoreOptions.StandByFile = GetOptionValue<string>(RestoreOptionsHelper.StandbyFile, options, restoreDataObject);
restoreDataObject.BackupTailLog = GetOptionValue<bool>(RestoreOptionsHelper.BackupTailLog, options, restoreDataObject);
restoreDataObject.TailLogBackupFile = GetOptionValue<string>(RestoreOptionsHelper.TailLogBackupFile, options, restoreDataObject);
restoreDataObject.TailLogWithNoRecovery = GetOptionValue<bool>(RestoreOptionsHelper.TailLogWithNoRecovery, options, restoreDataObject);
restoreDataObject.CloseExistingConnections = GetOptionValue<bool>(RestoreOptionsHelper.CloseExistingConnections, options, restoreDataObject);
}
}
}

View File

@@ -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<string, object> options = new Dictionary<string, object>();
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<string, object> options = new Dictionary<string, object>();
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<string, object> options = new Dictionary<string, object>();
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<RestoreConfigInfoResponse>(
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<RestorePlanResponse> 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<RestorePlanResponse> VerifyRestore(
string[] backupFileNames,
bool canRestore,
string[] backupFileNames = null,
string sourceDbName = null,
bool canRestore = true,
bool execute = false,
string targetDatabase = null,
string[] selectedBackupSets = null,
Dictionary<string, object> options = null,
Func<Database, bool> 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<string> dbNames = configInfoResponse.ConfigInfo[RestoreOptionsHelper.SourceDatabaseNamesWithBackupSets] as IEnumerable<string>;
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<string> backupFiles = new List<string>();
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();
}

View File

@@ -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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> result = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object);
Assert.NotNull(result);
VerifyOptions(result, optionValues);
}
[Fact]
public void RelocateAllFilesShouldBeReadOnlyGivenNoDbFiles()
{
GeneralRequestDetails optionValues = CreateOptionsTestData();
optionValues.Options["DbFiles"] = new List<DbFile>();
Mock <IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> 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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> 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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> 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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> 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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(optionValues);
Dictionary<string, RestorePlanDetailInfo> 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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams);
Dictionary<string, RestorePlanDetailInfo> options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object);
restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true;
bool actual = RestoreOptionsHelper.GetOptionValue<bool>(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<IRestoreDatabaseTaskDataObject> restoreDatabaseTaskDataObject = CreateRestoreDatabaseTaskDataObject(restoreParams);
Dictionary<string, RestorePlanDetailInfo> options = RestoreOptionsHelper.CreateRestorePlanOptions(restoreDatabaseTaskDataObject.Object);
restoreParams.Options[RestoreOptionsHelper.KeepReplication] = true;
bool actual = RestoreOptionsHelper.GetOptionValue<bool>(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<DbFile>() { 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<IRestoreDatabaseTaskDataObject> CreateRestoreDatabaseTaskDataObject(GeneralRequestDetails optionValues)
{
var restoreDataObject = new Mock<IRestoreDatabaseTaskDataObject>();
restoreDataObject.Setup(x => x.CloseExistingConnections).Returns(optionValues.GetOptionValue<bool>(RestoreOptionsHelper.CloseExistingConnections));
restoreDataObject.Setup(x => x.DataFilesFolder).Returns(optionValues.GetOptionValue<string>(RestoreOptionsHelper.DataFileFolder));
restoreDataObject.Setup(x => x.DbFiles).Returns(optionValues.GetOptionValue<List<DbFile>>("DbFiles"));
restoreDataObject.Setup(x => x.DefaultDataFileFolder).Returns(optionValues.GetOptionValue<string>("DefaultDataFileFolder"));
restoreDataObject.Setup(x => x.DefaultLogFileFolder).Returns(optionValues.GetOptionValue<string>("DefaultLogFileFolder"));
restoreDataObject.Setup(x => x.IsTailLogBackupPossible(It.IsAny<string>())).Returns(optionValues.GetOptionValue<bool>("IsTailLogBackupPossible"));
restoreDataObject.Setup(x => x.IsTailLogBackupWithNoRecoveryPossible(It.IsAny<string>())).Returns(optionValues.GetOptionValue<bool>("IsTailLogBackupWithNoRecoveryPossible"));
restoreDataObject.Setup(x => x.GetDefaultStandbyFile(It.IsAny<string>())).Returns(optionValues.GetOptionValue<string>("GetDefaultStandbyFile"));
restoreDataObject.Setup(x => x.GetDefaultTailLogbackupFile(It.IsAny<string>())).Returns(optionValues.GetOptionValue<string>("GetDefaultTailLogbackupFile"));
restoreDataObject.Setup(x => x.LogFilesFolder).Returns(optionValues.GetOptionValue<string>("LogFilesFolder"));
restoreDataObject.Setup(x => x.RelocateAllFiles).Returns(optionValues.GetOptionValue<bool>("RelocateAllFiles"));
restoreDataObject.Setup(x => x.TailLogBackupFile).Returns(optionValues.GetOptionValue<string>("TailLogBackupFile"));
restoreDataObject.Setup(x => x.TailLogWithNoRecovery).Returns(optionValues.GetOptionValue<bool>("TailLogWithNoRecovery"));
restoreDataObject.Setup(x => x.BackupTailLog).Returns(optionValues.GetOptionValue<bool>("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<bool>(RestoreOptionsHelper.KeepReplication);
restoreOptions.ReplaceDatabase = optionValues.GetOptionValue<bool>("ReplaceDatabase");
restoreOptions.SetRestrictedUser = optionValues.GetOptionValue<bool>("SetRestrictedUser");
restoreOptions.StandByFile = optionValues.GetOptionValue<string>("StandbyFile");
restoreOptions.RecoveryState = optionValues.GetOptionValue<DatabaseRecoveryState>(RestoreOptionsHelper.RecoveryState);
restoreDataObject.Setup(x => x.RestoreOptions).Returns(restoreOptions);
return restoreDataObject;
}
private void VerifyOptions(Dictionary<string, RestorePlanDetailInfo> optionInResponse, GeneralRequestDetails optionValues)
{
RestorePlanDetailInfo planDetailInfo = optionInResponse[RestoreOptionsHelper.DataFileFolder];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.DataFileFolder);
Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue<bool>("RelocateAllFiles"));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<string>(RestoreOptionsHelper.DataFileFolder));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<string>("DefaultDataFileFolder"));
Assert.Equal(planDetailInfo.IsVisiable, true);
planDetailInfo = optionInResponse[RestoreOptionsHelper.LogFileFolder];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.LogFileFolder);
Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue<bool>("RelocateAllFiles"));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<string>("LogFilesFolder"));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<string>("DefaultLogFileFolder"));
Assert.Equal(planDetailInfo.IsVisiable, true);
planDetailInfo = optionInResponse[RestoreOptionsHelper.RelocateDbFiles];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.RelocateDbFiles);
Assert.Equal(planDetailInfo.IsReadOnly, (optionValues.GetOptionValue<List<DbFile>>("DbFiles").Count == 0));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<bool>("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<bool>("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<DatabaseRecoveryState>(RestoreOptionsHelper.RecoveryState) == DatabaseRecoveryState.WithNoRecovery);
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<bool>(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<bool>("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<DatabaseRecoveryState>(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<DatabaseRecoveryState>(RestoreOptionsHelper.RecoveryState) != DatabaseRecoveryState.WithStandBy);
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<string>("StandbyFile"));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<string>("GetDefaultStandbyFile"));
Assert.Equal(planDetailInfo.IsVisiable, true);
planDetailInfo = optionInResponse[RestoreOptionsHelper.BackupTailLog];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.BackupTailLog);
Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue<bool>("IsTailLogBackupPossible"));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<bool>("BackupTailLog"));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<bool>("IsTailLogBackupPossible"));
Assert.Equal(planDetailInfo.IsVisiable, true);
planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogBackupFile];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogBackupFile);
Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue<bool>("IsTailLogBackupPossible"));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<string>("TailLogBackupFile"));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<string>("GetDefaultTailLogbackupFile"));
Assert.Equal(planDetailInfo.IsVisiable, true);
planDetailInfo = optionInResponse[RestoreOptionsHelper.TailLogWithNoRecovery];
Assert.Equal(planDetailInfo.Name, RestoreOptionsHelper.TailLogWithNoRecovery);
Assert.Equal(planDetailInfo.IsReadOnly, !optionValues.GetOptionValue<bool>("IsTailLogBackupWithNoRecoveryPossible"));
Assert.Equal(planDetailInfo.CurrentValue, optionValues.GetOptionValue<bool>("TailLogWithNoRecovery"));
Assert.Equal(planDetailInfo.DefaultValue, optionValues.GetOptionValue<bool>("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<bool>("CloseExistingConnections"));
Assert.Equal(planDetailInfo.DefaultValue, false);
Assert.Equal(planDetailInfo.IsVisiable, true);
}
}
}