added restore options (#423)

* added restore options
This commit is contained in:
Leila Lali
2017-07-28 12:54:08 -07:00
committed by GitHub
parent e1395cbd7d
commit e453a19d00
15 changed files with 505 additions and 64 deletions

View File

@@ -3,6 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Hosting.Contracts;
namespace Microsoft.SqlTools.Hosting.Contracts
{
/// <summary>
@@ -19,5 +21,10 @@ namespace Microsoft.SqlTools.Hosting.Contracts
public ConnectionProviderOptions ConnectionProvider { get; set; }
public AdminServicesProviderOptions AdminServicesProvider { get; set; }
/// <summary>
/// List of features
/// </summary>
public FeatureMetadataProvider[] Features { get; set; }
}
}

View File

@@ -0,0 +1,31 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Contracts;
namespace Microsoft.SqlTools.Hosting.Hosting.Contracts
{
/// <summary>
/// Includes the metadata for a feature
/// </summary>
public class FeatureMetadataProvider
{
/// <summary>
/// Indicates whether the feature is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Feature name
/// </summary>
public string FeatureName { get; set; }
/// <summary>
/// The options metadata avaialble for this feature
/// </summary>
public ServiceOption[] OptionsMetadata { get; set; }
}
}

View File

@@ -29,6 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
var idProperty = this.Properties.FirstOrDefault(x => x.PropertyName == IdPropertyName);
Id = idProperty == null || idProperty.PropertyValue == null ? string.Empty : idProperty.PropertyValue.ToString();
}
IsSelected = true;
}
/// <summary>

View File

@@ -14,15 +14,18 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
/// </summary>
public class RestoreParams : GeneralRequestDetails
{
public string SessionId
/// <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>("sessionId");
return GetOptionValue<string>(RestoreOptionsHelper.SessionId);
}
set
{
SetOptionValue("sessionId", value);
SetOptionValue(RestoreOptionsHelper.SessionId, value);
}
}
@@ -34,75 +37,75 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
/// <summary>
/// Comma delimited list of backup files
/// </summary>
public string BackupFilePaths
internal string BackupFilePaths
{
get
{
return GetOptionValue<string>("backupFilePaths");
return GetOptionValue<string>(RestoreOptionsHelper.BackupFilePaths);
}
set
{
SetOptionValue("backupFilePaths", value);
SetOptionValue(RestoreOptionsHelper.BackupFilePaths, value);
}
}
/// <summary>
/// Target Database name to restore to
/// </summary>
public string TargetDatabaseName
internal string TargetDatabaseName
{
get
{
return GetOptionValue<string>("targetDatabaseName");
return GetOptionValue<string>(RestoreOptionsHelper.TargetDatabaseName);
}
set
{
SetOptionValue("targetDatabaseName", value);
SetOptionValue(RestoreOptionsHelper.TargetDatabaseName, value);
}
}
/// <summary>
/// Source Database name to restore from
/// </summary>
public string SourceDatabaseName
internal string SourceDatabaseName
{
get
{
return GetOptionValue<string>("sourceDatabaseName");
return GetOptionValue<string>(RestoreOptionsHelper.SourceDatabaseName);
}
set
{
SetOptionValue("sourceDatabaseName", value);
SetOptionValue(RestoreOptionsHelper.SourceDatabaseName, value);
}
}
/// <summary>
/// If set to true, the db files will be relocated to default data location in the server
/// </summary>
public bool RelocateDbFiles
internal bool RelocateDbFiles
{
get
{
return GetOptionValue<bool>("relocateDbFiles");
return GetOptionValue<bool>(RestoreOptionsHelper.RelocateDbFiles);
}
set
{
SetOptionValue("relocateDbFiles", value);
SetOptionValue(RestoreOptionsHelper.RelocateDbFiles, value);
}
}
/// <summary>
/// Ids of the backup set to restore
/// </summary>
public string[] SelectedBackupSets
internal string[] SelectedBackupSets
{
get
{
return GetOptionValue<string[]>("selectedBackupSets");
return GetOptionValue<string[]>(RestoreOptionsHelper.SelectedBackupSets);
}
set
{
SetOptionValue("selectedBackupSets", value);
SetOptionValue(RestoreOptionsHelper.SelectedBackupSets, value);
}
}
}
@@ -160,8 +163,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
/// </summary>
public class RestorePlanResponse
{
public string RestoreSessionId { get; set; }
/// <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>
@@ -185,30 +195,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
public string[] DatabaseNamesFromBackupSets { get; set; }
/// <summary>
/// Server name
/// For testing purpose to verify the target database
/// </summary>
public string ServerName { get; set; }
internal string DatabaseName { get; set; }
/// <summary>
/// Database name to restore to
/// Plan details
/// </summary>
public string DatabaseName { get; set; }
/// <summary>
/// Indicates whether relocating the db files is required
/// because the original file paths are not valid in the target server
/// </summary>
public bool RelocateFilesNeeded { get; set; }
/// <summary>
/// Default Data folder path in the target server
/// </summary>
public string DefaultDataFolder { get; set; }
/// <summary>
/// Default log folder path in the target server
/// </summary>
public string DefaultLogFolder { get; set; }
public Dictionary<string, object> PlanDetails { get; set; }
}
public class RestoreRequest

View File

@@ -24,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
/// </summary>
public class RestoreDatabaseHelper
{
public const string LastBackupTaken = "lastBackupTaken";
private static RestoreDatabaseHelper instance = new RestoreDatabaseHelper();
private ConcurrentDictionary<string, RestoreDatabaseTaskDataObject> restoreSessions = new ConcurrentDictionary<string, RestoreDatabaseTaskDataObject>();
@@ -152,7 +152,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{
RestorePlanResponse response = new RestorePlanResponse()
{
DatabaseName = restoreDataObject.RestoreParams.TargetDatabaseName
DatabaseName = restoreDataObject.RestoreParams.TargetDatabaseName,
PlanDetails = new System.Collections.Generic.Dictionary<string, object>()
};
try
{
@@ -162,7 +163,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
if (restoreDataObject != null && restoreDataObject.IsValid)
{
response.RestoreSessionId = restoreDataObject.SessionId;
response.SessionId = restoreDataObject.SessionId;
response.DatabaseName = restoreDataObject.TargetDatabase;
response.DbFiles = restoreDataObject.DbFiles.Select(x => new RestoreDatabaseFileInfo
{
@@ -178,12 +179,25 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
response.ErrorMessage = SR.RestoreNotSupported;
}
response.PlanDetails.Add(LastBackupTaken, restoreDataObject.GetLastBackupTaken());
response.BackupSetsToRestore = restoreDataObject.GetBackupSetInfo().Select(x => new DatabaseFileInfo(x.ConvertPropertiesToArray())).ToArray();
var dbNames = restoreDataObject.GetSourceDbNames();
response.DatabaseNamesFromBackupSets = dbNames == null ? new string[] { } : dbNames.ToArray();
response.RelocateFilesNeeded = !restoreDataObject.DbFilesLocationAreValid();
response.DefaultDataFolder = restoreDataObject.DefaultDataFileFolder;
response.DefaultLogFolder = restoreDataObject.DefaultLogFileFolder;
// 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);
}
else
{
@@ -314,12 +328,29 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
restoreDataObject.RestorePlanner.DatabaseName = restoreDataObject.RestoreParams.SourceDatabaseName;
}
restoreDataObject.TargetDatabase = restoreDataObject.RestoreParams.TargetDatabaseName;
//TODO: used for other types of restore
/*bool isTailLogBackupPossible = restoreDataObject.RestorePlanner.IsTailLogBackupPossible(restoreDataObject.RestorePlanner.DatabaseName);
restoreDataObject.RestorePlanner.BackupTailLog = isTailLogBackupPossible;
restoreDataObject.TailLogBackupFile = restoreDataObject.Util.GetDefaultTailLogbackupFile(dbName);
restoreDataObject.RestorePlanner.TailLogBackupFile = restoreDataObject.TailLogBackupFile;
*/
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;
}
restoreDataObject.CloseExistingConnections = restoreDataObject.RestoreParams.GetOptionValue<bool>(RestoreOptionsHelper.CloseExistingConnections);
restoreDataObject.UpdateRestorePlan(restoreDataObject.RestoreParams.RelocateDbFiles);
}
@@ -340,6 +371,8 @@ 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

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.SqlServer.Management.Smo;
@@ -20,6 +21,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
{
private const char BackupMediaNameSeparator = ',';
private DatabaseRestorePlanner restorePlanner;
private string tailLogBackupFile;
public RestoreDatabaseTaskDataObject(Server server, String databaseName)
{
PlanUpdateRequired = true;
@@ -147,6 +151,35 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
/// <summary>
/// Returns the last backup taken
/// </summary>
/// <returns></returns>
public string GetLastBackupTaken()
{
string lastBackup = string.Empty;
int lastIndexSel = 0; //TODO: find the selected backup set
if (this.RestorePlanner.RestoreToLastBackup &&
this.RestorePlan.RestoreOperations[lastIndexSel] != null &&
this.RestorePlan.RestoreOperations.Count > 0 &&
this.RestorePlan.RestoreOperations[lastIndexSel].BackupSet != null)
{
int lastIndex = this.RestorePlan.RestoreOperations.Count - 1;
DateTime backupTime = this.RestorePlan.RestoreOperations[lastIndexSel].BackupSet.BackupStartDate;
string backupTimeStr = backupTime.ToLongDateString() + " " + backupTime.ToLongTimeString();
lastBackup = (lastIndexSel == lastIndex) ?
string.Format(CultureInfo.CurrentCulture, SR.TheLastBackupTaken, (backupTimeStr)) : backupTimeStr;
}
//TODO: find the selected one
else if (this.RestoreSelected[0] && !this.RestorePlanner.RestoreToLastBackup)
{
lastBackup = this.CurrentRestorePointInTime.Value.ToLongDateString() +
" " + this.CurrentRestorePointInTime.Value.ToLongTimeString();
}
return lastBackup;
}
/// <summary>
/// Executes the restore operations
/// </summary>
@@ -170,9 +203,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
/// <summary>
/// Restore Util
/// </summary>
public RestoreUtil Util { get; set; }
private DatabaseRestorePlanner restorePlanner;
/// <summary>
/// SMO database restore planner used to create a restore plan
@@ -182,7 +217,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
get { return restorePlanner; }
}
private string tailLogBackupFile;
public bool PlanUpdateRequired { get; private set; }
/// <summary>
@@ -303,6 +337,40 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
}
}
/// <summary>
/// Determines whether [is tail log backup possible].
/// </summary>
/// <returns>
/// <c>true</c> if [is tail log backup possible]; otherwise, <c>false</c>.
/// </returns>
internal bool IsTailLogBackupPossible(string databaseName)
{
if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName))
{
return false;
}
Database db = this.Server.Databases[databaseName];
if (db == null)
{
return false;
}
else
{
db.Refresh();
}
if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode)
{
return false;
}
if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged)
{
return true;
}
return false;
}
/// <summary>
/// Gets or sets a value indicating whether [prompt before each backup].
/// </summary>

View File

@@ -0,0 +1,186 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{
public class RestoreOptionsHelper
{
internal const string KeepReplication = "keepReplication";
internal const string ReplaceDatabase = "replaceDatabase";
internal const string SetRestrictedUser = "setRestrictedUser";
internal const string RecoveryState = "eecoveryState";
internal const string BackupTailLog = "backupTailLog";
internal const string DefaultBackupTailLog = "defaultBackupTailLog";
internal const string TailLogBackupFile = "tailLogBackupFile";
internal const string DefaultTailLogBackupFile = "defaultTailLogBackupFile";
internal const string TailLogWithNoRecovery = "tailLogWithNoRecovery";
internal const string CloseExistingConnections = "closeExistingConnections";
internal const string RelocateDbFiles = "relocateDbFiles";
internal const string DataFileFolder = "dataFileFolder";
internal const string DefaultDataFileFolder = "defaultDataFileFolder";
internal const string LogFileFolder = "logFileFolder";
internal const string DefaultLogFileFolder = "defaultLogFileFolder";
internal const string SessionId = "sessionId";
internal const string BackupFilePaths = "backupFilePaths";
internal const string TargetDatabaseName = "targetDatabaseName";
internal const string SourceDatabaseName = "sourceDatabaseName";
internal const string SelectedBackupSets = "selectedBackupSets";
internal const string StandbyFile = "standbyFile";
internal const string DefaultStandbyFile = "defaultStandbyFile";
/// <summary>
/// Creates the options metadata available for restore operations
/// </summary>
/// <returns></returns>
public static ServiceOption[] CreateRestoreOptions()
{
ServiceOption[] options = new ServiceOption[]
{
new ServiceOption
{
Name = RestoreOptionsHelper.KeepReplication,
DisplayName = "Keep Replication",
Description = "Preserve the replication settings (WITH KEEP_REPLICATION)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Restore options"
},
new ServiceOption
{
Name = RestoreOptionsHelper.ReplaceDatabase,
DisplayName = "ReplaceDatabase",
Description = "Overwrite the existing database (WITH REPLACE)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Restore options"
},
new ServiceOption
{
Name = RestoreOptionsHelper.SetRestrictedUser,
DisplayName = "SetRestrictedUser",
Description = "Restrict access to the restored database (WITH RESTRICTED_USER)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Restore options"
},
new ServiceOption
{
Name = RestoreOptionsHelper.RecoveryState,
DisplayName = "Recovery State",
Description = "Recovery State",
ValueType = ServiceOption.ValueTypeCategory,
IsRequired = false,
GroupName = "Restore options",
CategoryValues = new CategoryValue[]
{
new CategoryValue
{
Name = "WithRecovery",
DisplayName = "RESTORE WITH RECOVERTY"
},
new CategoryValue
{
Name = "WithNoRecovery",
DisplayName = "RESTORE WITH NORECOVERTY"
},
new CategoryValue
{
Name = "WithStandBy",
DisplayName = "RESTORE WITH STANDBY"
}
}
},
new ServiceOption
{
Name = RestoreOptionsHelper.StandbyFile,
DisplayName = "Standby file",
Description = "Standby file",
ValueType = ServiceOption.ValueTypeString,
IsRequired = false,
GroupName = "Restore options"
},
new ServiceOption
{
Name = RestoreOptionsHelper.BackupTailLog,
DisplayName = "Backup Tail Log",
Description = "Take tail-log backup before restore",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
DefaultValue = "true",
GroupName = "Tail-Log backup"
},
new ServiceOption
{
Name = RestoreOptionsHelper.BackupTailLog,
DisplayName = "Backup Tail Log",
Description = "Take tail-log backup before restore",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
DefaultValue = "true",
GroupName = "Tail-Log backup"
},
new ServiceOption
{
Name = RestoreOptionsHelper.TailLogBackupFile,
DisplayName = "Tail Log Backup File",
Description = "Tail Log Backup File",
ValueType = ServiceOption.ValueTypeString,
IsRequired = false,
GroupName = "Tail-Log backup"
},
new ServiceOption
{
Name = RestoreOptionsHelper.TailLogWithNoRecovery,
DisplayName = "Tail Log With NoRecovery",
Description = "Leave source database in the restoring state(WITH NORECOVERTY)",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Tail-Log backup"
},
new ServiceOption
{
Name = RestoreOptionsHelper.CloseExistingConnections,
DisplayName = "Close Existing Connections",
Description = "Close existing connections to destination database",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Server connections"
},
new ServiceOption
{
Name = RestoreOptionsHelper.RelocateDbFiles,
DisplayName = "Relocate all files",
Description = "Relocate all files",
ValueType = ServiceOption.ValueTypeBoolean,
IsRequired = false,
GroupName = "Restore database files as"
},
new ServiceOption
{
Name = RestoreOptionsHelper.DataFileFolder,
DisplayName = "Data file folder",
Description = "Data file folder",
ValueType = ServiceOption.ValueTypeString,
IsRequired = false,
GroupName = "Restore database files as"
},
new ServiceOption
{
Name = RestoreOptionsHelper.LogFileFolder,
DisplayName = "Log file folder",
Description = "Log file folder",
ValueType = ServiceOption.ValueTypeString,
IsRequired = false,
GroupName = "Restore database files as"
}
};
return options;
}
}
}

View File

@@ -3477,6 +3477,14 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string TheLastBackupTaken
{
get
{
return Keys.GetString(Keys.TheLastBackupTaken);
}
}
public static string ConnectionServiceListDbErrorNotConnected(string uri)
{
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
@@ -4890,6 +4898,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string RestoreBackupSetExpiration = "RestoreBackupSetExpiration";
public const string TheLastBackupTaken = "TheLastBackupTaken";
private Keys()
{ }

View File

@@ -1911,4 +1911,8 @@
<value>Expiration</value>
<comment></comment>
</data>
<data name="TheLastBackupTaken" xml:space="preserve">
<value>The last backup taken ({0})</value>
<comment></comment>
</data>
</root>

View File

@@ -835,4 +835,5 @@ RestoreBackupSetStartDate = Start Date
RestoreBackupSetFinishDate = Finish Date
RestoreBackupSetSize = Size
RestoreBackupSetUserName = User Name
RestoreBackupSetExpiration = Expiration
RestoreBackupSetExpiration = Expiration
TheLastBackupTaken = The last backup taken ({0})

View File

@@ -2239,6 +2239,11 @@
<target state="new">Name</target>
<note></note>
</trans-unit>
<trans-unit id="TheLastBackupTaken">
<source>The last backup taken ({0})</source>
<target state="new">The last backup taken ({0})</target>
<note></note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -16,6 +16,7 @@ using Microsoft.SqlTools.Hosting.Protocol.Channel;
using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Hosting
{
@@ -208,7 +209,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
ProviderName = ServiceHost.ProviderName,
ProviderDisplayName = ServiceHost.ProviderDescription,
ConnectionProvider = ConnectionProviderOptionsHelper.BuildConnectionProviderOptions(),
AdminServicesProvider = AdminServicesProviderOptionsHelper.BuildAdminServicesProviderOptions()
AdminServicesProvider = AdminServicesProviderOptionsHelper.BuildAdminServicesProviderOptions(),
Features = FeaturesMetadataProviderHelper.CreateFratureMetadataProviders()
}
}
);

View File

@@ -0,0 +1,28 @@
//
// 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.Hosting.Contracts;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
public class FeaturesMetadataProviderHelper
{
public static FeatureMetadataProvider[] CreateFratureMetadataProviders()
{
List<FeatureMetadataProvider> featues = new List<FeatureMetadataProvider>();
featues.Add(new FeatureMetadataProvider
{
FeatureName = "Restore",
Enabled = true,
OptionsMetadata = RestoreOptionsHelper.CreateRestoreOptions()
});
return featues.ToArray();
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
Options = new Dictionary<string, object>();
}
protected T GetOptionValue<T>(string name)
internal T GetOptionValue<T>(string name)
{
T result = default(T);
if (Options != null && Options.ContainsKey(name))

View File

@@ -8,7 +8,6 @@ using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Extensibility;
@@ -58,6 +57,52 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
await VerifyRestore(fullBackUpDatabase, canRestore);
}
[Fact]
public async void RestorePlanShouldCreatedSuccessfullyOnExistingDatabaseGivenReplaceOption()
{
SqlTestDb testDb = null;
try
{
testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "RestoreTest");
//Create a backup from a test db but don't delete the database
await VerifyBackupFileCreated();
bool canRestore = true;
Dictionary<string, object> options = new Dictionary<string, object>();
options.Add(RestoreOptionsHelper.ReplaceDatabase, true);
await VerifyRestore(new string[] { fullBackUpDatabase }, canRestore, true, testDb.DatabaseName, null, options);
}
finally
{
if (testDb != null)
{
testDb.Cleanup();
}
}
}
[Fact]
public async void RestorePlanShouldFailOnExistingDatabaseNotGivenReplaceOption()
{
SqlTestDb testDb = null;
try
{
testDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "RestoreTest");
//Create a backup from a test db but don't delete the database
await VerifyBackupFileCreated();
bool canRestore = true;
await VerifyRestore(new string[] { fullBackUpDatabase }, canRestore, false, testDb.DatabaseName, null, null);
}
finally
{
if (testDb != null)
{
testDb.Cleanup();
}
}
}
[Fact]
public async void RestoreShouldCreatedSuccessfullyGivenTwoBackupFiles()
{
@@ -228,7 +273,13 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
return await VerifyRestore(new string[] { backupFileName }, canRestore, execute, targetDatabase);
}
private async Task<RestorePlanResponse> VerifyRestore(string[] backupFileNames, bool canRestore, bool execute = false, string targetDatabase = null, string[] selectedBackupSets = null)
private async Task<RestorePlanResponse> VerifyRestore(
string[] backupFileNames,
bool canRestore,
bool execute = false,
string targetDatabase = null,
string[] selectedBackupSets = null,
Dictionary<string, object> options = null)
{
var filePaths = backupFileNames.Select(x => GetBackupFilePath(x));
string backUpFilePath = filePaths.Aggregate((current, next) => current + " ," + next);
@@ -247,11 +298,22 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
SelectedBackupSets = selectedBackupSets
};
if(options != null)
{
foreach (var item in options)
{
if (!request.Options.ContainsKey(item.Key))
{
request.Options.Add(item.Key, item.Value);
}
}
}
var restoreDataObject = service.CreateRestoreDatabaseTaskDataObject(request);
var response = service.CreateRestorePlanResponse(restoreDataObject);
Assert.NotNull(response);
Assert.False(string.IsNullOrWhiteSpace(response.RestoreSessionId));
Assert.False(string.IsNullOrWhiteSpace(response.SessionId));
Assert.Equal(response.CanRestore, canRestore);
if (canRestore)
{
@@ -261,15 +323,23 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
targetDatabase = response.DatabaseName;
}
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]);
if(execute)
{
request.SessionId = response.RestoreSessionId;
request.SessionId = response.SessionId;
restoreDataObject = service.CreateRestoreDatabaseTaskDataObject(request);
Assert.Equal(response.RestoreSessionId, restoreDataObject.SessionId);
await DropDatabase(targetDatabase);
Thread.Sleep(2000);
request.RelocateDbFiles = response.RelocateFilesNeeded;
Assert.Equal(response.SessionId, restoreDataObject.SessionId);
//await DropDatabase(targetDatabase);
//Thread.Sleep(2000);
request.RelocateDbFiles = !restoreDataObject.DbFilesLocationAreValid();
service.ExecuteRestore(restoreDataObject);
Assert.True(restoreDataObject.Server.Databases.Contains(targetDatabase));
if(selectedBackupSets != null)