mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
Fix backup service to have multiple backup instances (#396)
* Allow multiple backups per backup service instance * Add test to run multiple backups * Update backup cancelTask method signature * Fix to have multiple backup instances and add more tests * Address PR comments * Remove double new lines * Add Azure check for backup operations
This commit is contained in:
@@ -19,7 +19,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class implements backup operations
|
/// This class implements backup operations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BackupUtilities : IBackupUtilities
|
public class BackupOperation : IBackupOperation
|
||||||
{
|
{
|
||||||
private CDataContainer dataContainer;
|
private CDataContainer dataContainer;
|
||||||
private ServerConnection serverConnection;
|
private ServerConnection serverConnection;
|
||||||
@@ -94,7 +94,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ctor
|
/// Ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BackupUtilities()
|
public BackupOperation()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +152,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
public void PerformBackup()
|
public void PerformBackup()
|
||||||
{
|
{
|
||||||
this.backup = new Backup();
|
this.backup = new Backup();
|
||||||
|
this.backup.Database = this.backupInfo.DatabaseName;
|
||||||
|
this.backup.Action = this.backupActionType;
|
||||||
|
this.backup.Incremental = this.isBackupIncremental;
|
||||||
this.SetBackupProps();
|
this.SetBackupProps();
|
||||||
backup.Database = this.backupInfo.DatabaseName;
|
|
||||||
backup.Action = this.backupActionType;
|
if (this.backup.Action == BackupActionType.Files)
|
||||||
backup.Incremental = this.isBackupIncremental;
|
|
||||||
if (backup.Action == BackupActionType.Files)
|
|
||||||
{
|
{
|
||||||
IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator();
|
IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator();
|
||||||
filegroupEnumerator.Reset();
|
filegroupEnumerator.Reset();
|
||||||
@@ -169,14 +170,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
if (currentKey.IndexOf(",", StringComparison.Ordinal) < 0)
|
if (currentKey.IndexOf(",", StringComparison.Ordinal) < 0)
|
||||||
{
|
{
|
||||||
// is a file group
|
// is a file group
|
||||||
backup.DatabaseFileGroups.Add(currentValue);
|
this.backup.DatabaseFileGroups.Add(currentValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// is a file
|
// is a file
|
||||||
int idx = currentValue.IndexOf(".", StringComparison.Ordinal);
|
int idx = currentValue.IndexOf(".", StringComparison.Ordinal);
|
||||||
currentValue = currentValue.Substring(idx + 1, currentValue.Length - idx - 1);
|
currentValue = currentValue.Substring(idx + 1, currentValue.Length - idx - 1);
|
||||||
backup.DatabaseFiles.Add(currentValue);
|
this.backup.DatabaseFiles.Add(currentValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +188,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
isBackupToUrl = true;
|
isBackupToUrl = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
backup.BackupSetName = this.backupInfo.BackupsetName;
|
this.backup.BackupSetName = this.backupInfo.BackupsetName;
|
||||||
|
|
||||||
if (false == isBackupToUrl)
|
if (false == isBackupToUrl)
|
||||||
{
|
{
|
||||||
@@ -205,19 +206,19 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile)
|
if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile)
|
||||||
|| (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape))
|
|| (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape))
|
||||||
{
|
{
|
||||||
backup.Devices.AddDevice(destName, DeviceType.LogicalDevice);
|
this.backup.Devices.AddDevice(destName, DeviceType.LogicalDevice);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (int)DeviceType.File:
|
case (int)DeviceType.File:
|
||||||
if (this.backupDeviceType == BackupDeviceType.Disk)
|
if (this.backupDeviceType == BackupDeviceType.Disk)
|
||||||
{
|
{
|
||||||
backup.Devices.AddDevice(destName, DeviceType.File);
|
this.backup.Devices.AddDevice(destName, DeviceType.File);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (int)DeviceType.Tape:
|
case (int)DeviceType.Tape:
|
||||||
if (this.backupDeviceType == BackupDeviceType.Tape)
|
if (this.backupDeviceType == BackupDeviceType.Tape)
|
||||||
{
|
{
|
||||||
backup.Devices.AddDevice(destName, DeviceType.Tape);
|
this.backup.Devices.AddDevice(destName, DeviceType.Tape);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -225,15 +226,15 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO: This should be changed to get user inputs
|
//TODO: This should be changed to get user inputs
|
||||||
backup.FormatMedia = false;
|
this.backup.FormatMedia = false;
|
||||||
backup.Initialize = false;
|
this.backup.Initialize = false;
|
||||||
backup.SkipTapeHeader = true;
|
this.backup.SkipTapeHeader = true;
|
||||||
backup.Checksum = false;
|
this.backup.Checksum = false;
|
||||||
backup.ContinueAfterError = false;
|
this.backup.ContinueAfterError = false;
|
||||||
backup.LogTruncation = BackupTruncateLogType.Truncate;
|
this.backup.LogTruncation = BackupTruncateLogType.Truncate;
|
||||||
|
|
||||||
// Execute backup
|
// Execute backup
|
||||||
backup.SqlBackup(this.dataContainer.Server);
|
this.backup.SqlBackup(this.dataContainer.Server);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -278,20 +279,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool BackupToUrlSupported()
|
private bool BackupToUrlSupported()
|
||||||
{
|
{
|
||||||
return BackupRestoreBase.IsBackupUrlDeviceSupported(this.dataContainer.Server.PingSqlServerVersion(this.dataContainer.ServerName)); //@@ originally, DataContainer.Server.ServerVersion
|
return BackupRestoreBase.IsBackupUrlDeviceSupported(this.dataContainer.Server.PingSqlServerVersion(this.dataContainer.ServerName));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private string GetDefaultBackupSetName()
|
|
||||||
{
|
|
||||||
string backupName = this.backupInfo.DatabaseName + "-"
|
|
||||||
+ this.backupType.ToString() + " "
|
|
||||||
+ this.backupComponent.ToString() + " "
|
|
||||||
+ BackupConstants.Backup;
|
|
||||||
return backupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetBackupProps()
|
private void SetBackupProps()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -327,7 +319,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
//throw new Exception("Unexpected error");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -202,7 +202,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool ServerHasLogicalDevices()
|
public bool ServerHasLogicalDevices()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -253,7 +252,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return new string(result);
|
return new string(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public RecoveryModel GetRecoveryModel(string databaseName)
|
public RecoveryModel GetRecoveryModel(string databaseName)
|
||||||
{
|
{
|
||||||
Enumerator en = null;
|
Enumerator en = null;
|
||||||
@@ -275,7 +273,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return recoveryModel;
|
return recoveryModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public string GetRecoveryModelAsString(RecoveryModel recoveryModel)
|
public string GetRecoveryModelAsString(RecoveryModel recoveryModel)
|
||||||
{
|
{
|
||||||
string recoveryModelString = string.Empty;
|
string recoveryModelString = string.Empty;
|
||||||
@@ -296,7 +293,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return recoveryModelString;
|
return recoveryModelString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public string GetDefaultBackupFolder()
|
public string GetDefaultBackupFolder()
|
||||||
{
|
{
|
||||||
string backupFolder = "";
|
string backupFolder = "";
|
||||||
@@ -571,7 +567,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool IsDatabaseOnServer(string databaseName)
|
public bool IsDatabaseOnServer(string databaseName)
|
||||||
{
|
{
|
||||||
Enumerator en = new Enumerator();
|
Enumerator en = new Enumerator();
|
||||||
@@ -699,7 +694,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void GetBackupSetTypeAndComponent(string strType, ref string backupType, ref string backupComponent)
|
public void GetBackupSetTypeAndComponent(string strType, ref string backupType, ref string backupComponent)
|
||||||
{
|
{
|
||||||
string type = strType.ToUpperInvariant();
|
string type = strType.ToUpperInvariant();
|
||||||
@@ -816,7 +810,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DataSet GetBackupSetFiles(int backupsetId)
|
public DataSet GetBackupSetFiles(int backupsetId)
|
||||||
{
|
{
|
||||||
Enumerator en = new Enumerator();
|
Enumerator en = new Enumerator();
|
||||||
@@ -853,7 +846,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return backupset;
|
return backupset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ArrayList GetBackupSetPhysicalSources(int backupsetId)
|
public ArrayList GetBackupSetPhysicalSources(int backupsetId)
|
||||||
{
|
{
|
||||||
SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes;
|
SqlExecutionModes executionMode = this.sqlConnection.SqlExecutionModes;
|
||||||
@@ -902,7 +894,6 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public RestoreActionType GetRestoreTaskFromBackupSetType(BackupsetType type)
|
public RestoreActionType GetRestoreTaskFromBackupSetType(BackupsetType type)
|
||||||
{
|
{
|
||||||
RestoreActionType result = RestoreActionType.Database;
|
RestoreActionType result = RestoreActionType.Database;
|
||||||
@@ -952,7 +943,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RestoreItemSource> GetLatestBackupLocations(string DatabaseName)
|
public List<RestoreItemSource> GetLatestBackupLocations(string databaseName)
|
||||||
{
|
{
|
||||||
List<RestoreItemSource> latestLocations = new List<RestoreItemSource>();
|
List<RestoreItemSource> latestLocations = new List<RestoreItemSource>();
|
||||||
Enumerator en = null;
|
Enumerator en = null;
|
||||||
@@ -961,7 +952,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
Request req = new Request();
|
Request req = new Request();
|
||||||
en = new Enumerator();
|
en = new Enumerator();
|
||||||
|
|
||||||
req.Urn = "Server/BackupSet[@DatabaseName='" + Urn.EscapeString(DatabaseName) + "']";
|
req.Urn = "Server/BackupSet[@DatabaseName='" + Urn.EscapeString(databaseName) + "']";
|
||||||
req.OrderByList = new OrderBy[1];
|
req.OrderByList = new OrderBy[1];
|
||||||
req.OrderByList[0] = new OrderBy();
|
req.OrderByList[0] = new OrderBy();
|
||||||
req.OrderByList[0].Field = "BackupFinishDate";
|
req.OrderByList[0].Field = "BackupFinishDate";
|
||||||
@@ -1015,7 +1006,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return latestLocations;
|
return latestLocations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetDefaultDatabaseForLogin(string LoginName)
|
public string GetDefaultDatabaseForLogin(string loginName)
|
||||||
{
|
{
|
||||||
string defaultDatabase = string.Empty;
|
string defaultDatabase = string.Empty;
|
||||||
Enumerator en = new Enumerator();
|
Enumerator en = new Enumerator();
|
||||||
@@ -1023,7 +1014,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
|
ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
|
||||||
Request req = new Request();
|
Request req = new Request();
|
||||||
|
|
||||||
req.Urn = "Server/Login[@Name='"+Urn.EscapeString(LoginName)+"']";
|
req.Urn = "Server/Login[@Name='"+Urn.EscapeString(loginName)+"']";
|
||||||
req.Fields = new string[1];
|
req.Fields = new string[1];
|
||||||
req.Fields[0] = "DefaultDatabase";
|
req.Fields[0] = "DefaultDatabase";
|
||||||
ds = en.Process(this.sqlConnection, req);
|
ds = en.Process(this.sqlConnection, req);
|
||||||
|
|||||||
@@ -23,23 +23,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
{
|
{
|
||||||
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
||||||
private static ConnectionService connectionService = null;
|
private static ConnectionService connectionService = null;
|
||||||
private IBackupUtilities backupUtilities;
|
|
||||||
private ManualResetEvent backupCompletedEvent = new ManualResetEvent(initialState: false);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default, parameterless constructor.
|
/// Default, parameterless constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal DisasterRecoveryService()
|
internal DisasterRecoveryService()
|
||||||
{
|
{
|
||||||
this.backupUtilities = new BackupUtilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For testing purpose only
|
|
||||||
/// </summary>
|
|
||||||
internal DisasterRecoveryService(IBackupUtilities backupUtilities)
|
|
||||||
{
|
|
||||||
this.backupUtilities = backupUtilities;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,17 +89,13 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
{
|
{
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
||||||
if (sqlConn != null)
|
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
|
||||||
{
|
{
|
||||||
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
|
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
|
||||||
if (!connInfo.IsSqlDW)
|
|
||||||
{
|
|
||||||
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(sqlConn.Database);
|
|
||||||
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
|
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
|
||||||
response.BackupConfigInfo = backupConfigInfo;
|
response.BackupConfigInfo = backupConfigInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await requestContext.SendResult(response);
|
await requestContext.SendResult(response);
|
||||||
}
|
}
|
||||||
@@ -131,11 +116,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
{
|
{
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
||||||
if (sqlConn != null)
|
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
|
||||||
{
|
{
|
||||||
// initialize backup
|
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo);
|
||||||
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
|
|
||||||
DisasterRecoveryService.Instance.SetBackupInput(backupParams.BackupInfo);
|
|
||||||
|
|
||||||
// create task metadata
|
// create task metadata
|
||||||
TaskMetadata metadata = new TaskMetadata();
|
TaskMetadata metadata = new TaskMetadata();
|
||||||
@@ -143,9 +126,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
|
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
|
||||||
metadata.Name = SR.Backup_TaskName;
|
metadata.Name = SR.Backup_TaskName;
|
||||||
metadata.IsCancelable = true;
|
metadata.IsCancelable = true;
|
||||||
|
metadata.Data = backupOperation;
|
||||||
|
|
||||||
// create backup task and perform
|
// create backup task and perform
|
||||||
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTask);
|
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTaskAsync);
|
||||||
sqlTask.Run();
|
sqlTask.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,24 +163,27 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InitializeBackup(CDataContainer dataContainer, SqlConnection sqlConnection)
|
internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName)
|
||||||
{
|
{
|
||||||
this.backupUtilities.Initialize(dataContainer, sqlConnection);
|
BackupOperation backupOperation = new BackupOperation();
|
||||||
|
backupOperation.Initialize(dataContainer, sqlConnection);
|
||||||
|
return backupOperation.GetBackupConfigInfo(databaseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetBackupInput(BackupInfo input)
|
internal BackupOperation SetBackupInput(CDataContainer dataContainer, SqlConnection sqlConnection, BackupInfo input)
|
||||||
{
|
{
|
||||||
this.backupUtilities.SetBackupInput(input);
|
BackupOperation backupOperation = new BackupOperation();
|
||||||
|
backupOperation.Initialize(dataContainer, sqlConnection);
|
||||||
|
backupOperation.SetBackupInput(input);
|
||||||
|
return backupOperation;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PerformBackup()
|
/// <summary>
|
||||||
|
/// For testing purpose only
|
||||||
|
/// </summary>
|
||||||
|
internal void PerformBackup(BackupOperation backupOperation)
|
||||||
{
|
{
|
||||||
this.backupUtilities.PerformBackup();
|
backupOperation.PerformBackup();
|
||||||
}
|
|
||||||
|
|
||||||
internal BackupConfigInfo GetBackupConfigInfo(string databaseName)
|
|
||||||
{
|
|
||||||
return this.backupUtilities.GetBackupConfigInfo(databaseName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -204,23 +191,44 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sqlTask"></param>
|
/// <param name="sqlTask"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task<TaskResult> BackupTask(SqlTask sqlTask)
|
internal async Task<TaskResult> BackupTaskAsync(SqlTask sqlTask)
|
||||||
{
|
{
|
||||||
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true);
|
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true);
|
||||||
Task<TaskResult> performTask = this.PerformTask();
|
IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation;
|
||||||
Task<TaskResult> cancelTask = this.CancelTask(sqlTask);
|
TaskResult taskResult = null;
|
||||||
|
|
||||||
|
if (backupOperation != null)
|
||||||
|
{
|
||||||
|
AutoResetEvent backupCompletedEvent = new AutoResetEvent(initialState: false);
|
||||||
|
Task<TaskResult> performTask = PerformTaskAsync(backupOperation);
|
||||||
|
Task<TaskResult> cancelTask = CancelTaskAsync(backupOperation, sqlTask.TokenSource.Token, backupCompletedEvent);
|
||||||
Task<TaskResult> completedTask = await Task.WhenAny(performTask, cancelTask);
|
Task<TaskResult> completedTask = await Task.WhenAny(performTask, cancelTask);
|
||||||
|
|
||||||
|
// Release the cancelTask
|
||||||
if (completedTask == performTask)
|
if (completedTask == performTask)
|
||||||
{
|
{
|
||||||
this.backupCompletedEvent.Set();
|
backupCompletedEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
|
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
|
||||||
completedTask.Result.TaskStatus);
|
completedTask.Result.TaskStatus);
|
||||||
return completedTask.Result;
|
taskResult = completedTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
taskResult = new TaskResult();
|
||||||
|
taskResult.TaskStatus = SqlTaskStatus.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TaskResult> PerformTask()
|
return taskResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Async task to execute backup
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backupOperation"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<TaskResult> PerformTaskAsync(IBackupOperation backupOperation)
|
||||||
{
|
{
|
||||||
// Create a task to perform backup
|
// Create a task to perform backup
|
||||||
return await Task.Factory.StartNew(() =>
|
return await Task.Factory.StartNew(() =>
|
||||||
@@ -228,7 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
TaskResult result = new TaskResult();
|
TaskResult result = new TaskResult();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.backupUtilities.PerformBackup();
|
backupOperation.PerformBackup();
|
||||||
result.TaskStatus = SqlTaskStatus.Succeeded;
|
result.TaskStatus = SqlTaskStatus.Succeeded;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -244,14 +252,19 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TaskResult> CancelTask(SqlTask sqlTask)
|
/// <summary>
|
||||||
|
/// Async task to cancel backup
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="backupOperation"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <param name="backupCompletedEvent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<TaskResult> CancelTaskAsync(IBackupOperation backupOperation, CancellationToken token, AutoResetEvent backupCompletedEvent)
|
||||||
{
|
{
|
||||||
// Create a task for backup cancellation request
|
// Create a task for backup cancellation request
|
||||||
return await Task.Factory.StartNew(() =>
|
return await Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
TaskResult result = new TaskResult();
|
TaskResult result = new TaskResult();
|
||||||
|
|
||||||
CancellationToken token = sqlTask.TokenSource.Token;
|
|
||||||
WaitHandle[] waitHandles = new WaitHandle[2]
|
WaitHandle[] waitHandles = new WaitHandle[2]
|
||||||
{
|
{
|
||||||
backupCompletedEvent,
|
backupCompletedEvent,
|
||||||
@@ -259,11 +272,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
};
|
};
|
||||||
|
|
||||||
WaitHandle.WaitAny(waitHandles);
|
WaitHandle.WaitAny(waitHandles);
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.backupUtilities.CancelBackup();
|
backupOperation.CancelBackup();
|
||||||
result.TaskStatus = SqlTaskStatus.Canceled;
|
result.TaskStatus = SqlTaskStatus.Canceled;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -271,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
result.TaskStatus = SqlTaskStatus.Failed;
|
result.TaskStatus = SqlTaskStatus.Failed;
|
||||||
result.ErrorMessage = ex.Message;
|
result.ErrorMessage = ex.Message;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Copyright (c) Microsoft. All rights reserved.
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
using Microsoft.SqlServer.Management.Smo;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
@@ -11,7 +12,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for backup operations
|
/// Interface for backup operations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBackupUtilities
|
public interface IBackupOperation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize
|
/// Initialize
|
||||||
@@ -37,5 +37,10 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
|
|||||||
/// Database name this task is created for
|
/// Database name this task is created for
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DatabaseName { get; set; }
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data required to perform the task
|
||||||
|
/// </summary>
|
||||||
|
public object Data { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
|
|||||||
// Initialize backup service
|
// Initialize backup service
|
||||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
|
||||||
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
|
||||||
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
|
|
||||||
|
|
||||||
// Get default backup path
|
// Get default backup path
|
||||||
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(sqlConn.Database);
|
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
|
||||||
string backupPath = backupConfigInfo.DefaultBackupFolder + "\\" + databaseName + ".bak";
|
string backupPath = backupConfigInfo.DefaultBackupFolder + "\\" + databaseName + ".bak";
|
||||||
|
|
||||||
var backupInfo = new BackupInfo();
|
var backupInfo = new BackupInfo();
|
||||||
@@ -81,8 +80,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Backup the database
|
// Backup the database
|
||||||
DisasterRecoveryService.Instance.SetBackupInput(backupParams.BackupInfo);
|
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo);
|
||||||
DisasterRecoveryService.Instance.PerformBackup();
|
DisasterRecoveryService.Instance.PerformBackup(backupOperation);
|
||||||
|
|
||||||
// Remove the backup file
|
// Remove the backup file
|
||||||
if (File.Exists(backupPath))
|
if (File.Exists(backupPath))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlServer.Management.Smo;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
|
||||||
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
|
||||||
@@ -12,9 +13,9 @@ using System.Threading;
|
|||||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub class that implements IBackupUtilities
|
/// Stub class that implements IBackupOperation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BackupUtilitiesStub : IBackupUtilities
|
public class BackupOperationStub : IBackupOperation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize
|
/// Initialize
|
||||||
@@ -21,14 +21,20 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
IsCancelable = true
|
IsCancelable = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create and run a backup task
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task VerifyCreateAndRunningBackupTask()
|
public async Task VerifyRunningBackupTask()
|
||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
var mockUtility = new Mock<IBackupUtilities>();
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService(mockUtility.Object);
|
var mockBackupOperation = new Mock<IBackupOperation>();
|
||||||
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTask);
|
this.taskMetaData.Data = mockBackupOperation.Object;
|
||||||
|
|
||||||
|
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
Assert.NotNull(sqlTask);
|
Assert.NotNull(sqlTask);
|
||||||
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
||||||
{
|
{
|
||||||
@@ -39,14 +45,51 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create and run multiple backup tasks
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CancelBackupTask()
|
public async Task VerifyRunningMultipleBackupTasks()
|
||||||
{
|
{
|
||||||
using (SqlTaskManager manager = new SqlTaskManager())
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
{
|
{
|
||||||
IBackupUtilities backupUtility = new BackupUtilitiesStub();
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
DisasterRecoveryService service = new DisasterRecoveryService(backupUtility);
|
var mockUtility = new Mock<IBackupOperation>();
|
||||||
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTask);
|
this.taskMetaData.Data = mockUtility.Object;
|
||||||
|
|
||||||
|
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
SqlTask sqlTask2 = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
Assert.NotNull(sqlTask);
|
||||||
|
Assert.NotNull(sqlTask2);
|
||||||
|
|
||||||
|
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Succeeded, sqlTask.TaskStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task taskToVerify2 = sqlTask2.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Succeeded, sqlTask2.TaskStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(taskToVerify, taskToVerify2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel a backup task
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[Fact]
|
||||||
|
public async Task VerifyCancelBackupTask()
|
||||||
|
{
|
||||||
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
|
{
|
||||||
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
|
this.taskMetaData.Data = backupOperation;
|
||||||
|
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
Assert.NotNull(sqlTask);
|
Assert.NotNull(sqlTask);
|
||||||
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
||||||
{
|
{
|
||||||
@@ -59,5 +102,77 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
|
|||||||
await taskToVerify;
|
await taskToVerify;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel multiple backup tasks
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[Fact]
|
||||||
|
public async Task VerifyCancelMultipleBackupTasks()
|
||||||
|
{
|
||||||
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
|
{
|
||||||
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
|
this.taskMetaData.Data = backupOperation;
|
||||||
|
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
SqlTask sqlTask2 = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
Assert.NotNull(sqlTask);
|
||||||
|
Assert.NotNull(sqlTask2);
|
||||||
|
|
||||||
|
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Canceled, sqlTask.TaskStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCancelRequested, true);
|
||||||
|
manager.Reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
Task taskToVerify2 = sqlTask2.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Canceled, sqlTask2.TaskStatus);
|
||||||
|
Assert.Equal(sqlTask2.IsCancelRequested, true);
|
||||||
|
manager.Reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.CancelTask(sqlTask.TaskId);
|
||||||
|
manager.CancelTask(sqlTask2.TaskId);
|
||||||
|
|
||||||
|
await Task.WhenAll(taskToVerify, taskToVerify2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create two backup tasks and cancel one task
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[Fact]
|
||||||
|
public async Task VerifyCombinationRunAndCancelBackupTasks()
|
||||||
|
{
|
||||||
|
using (SqlTaskManager manager = new SqlTaskManager())
|
||||||
|
{
|
||||||
|
IBackupOperation backupOperation = new BackupOperationStub();
|
||||||
|
DisasterRecoveryService service = new DisasterRecoveryService();
|
||||||
|
this.taskMetaData.Data = backupOperation;
|
||||||
|
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
SqlTask sqlTask2 = manager.CreateTask(this.taskMetaData, service.BackupTaskAsync);
|
||||||
|
Assert.NotNull(sqlTask);
|
||||||
|
Assert.NotNull(sqlTask2);
|
||||||
|
|
||||||
|
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Canceled, sqlTask.TaskStatus);
|
||||||
|
Assert.Equal(sqlTask.IsCancelRequested, true);
|
||||||
|
manager.Reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
Task taskToVerify2 = sqlTask2.RunAsync().ContinueWith(Task =>
|
||||||
|
{
|
||||||
|
Assert.Equal(SqlTaskStatus.Succeeded, sqlTask2.TaskStatus);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager.CancelTask(sqlTask.TaskId);
|
||||||
|
await Task.WhenAll(taskToVerify, taskToVerify2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user