mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{ }
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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})
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user