mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-16 17:23:38 -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:
@@ -23,23 +23,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
{
|
||||
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
|
||||
private static ConnectionService connectionService = null;
|
||||
private IBackupUtilities backupUtilities;
|
||||
private ManualResetEvent backupCompletedEvent = new ManualResetEvent(initialState: false);
|
||||
|
||||
/// <summary>
|
||||
/// Default, parameterless constructor.
|
||||
/// </summary>
|
||||
internal DisasterRecoveryService()
|
||||
{
|
||||
this.backupUtilities = new BackupUtilities();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For testing purpose only
|
||||
/// </summary>
|
||||
internal DisasterRecoveryService(IBackupUtilities backupUtilities)
|
||||
{
|
||||
this.backupUtilities = backupUtilities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,18 +89,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
{
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
||||
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
||||
if (sqlConn != null)
|
||||
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
|
||||
{
|
||||
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
|
||||
if (!connInfo.IsSqlDW)
|
||||
{
|
||||
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(sqlConn.Database);
|
||||
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
|
||||
response.BackupConfigInfo = backupConfigInfo;
|
||||
}
|
||||
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
|
||||
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
|
||||
response.BackupConfigInfo = backupConfigInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await requestContext.SendResult(response);
|
||||
}
|
||||
|
||||
@@ -131,11 +116,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
{
|
||||
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
|
||||
SqlConnection sqlConn = GetSqlConnection(connInfo);
|
||||
if (sqlConn != null)
|
||||
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
|
||||
{
|
||||
// initialize backup
|
||||
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
|
||||
DisasterRecoveryService.Instance.SetBackupInput(backupParams.BackupInfo);
|
||||
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo);
|
||||
|
||||
// create task metadata
|
||||
TaskMetadata metadata = new TaskMetadata();
|
||||
@@ -143,9 +126,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
|
||||
metadata.Name = SR.Backup_TaskName;
|
||||
metadata.IsCancelable = true;
|
||||
|
||||
metadata.Data = backupOperation;
|
||||
|
||||
// create backup task and perform
|
||||
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTask);
|
||||
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTaskAsync);
|
||||
sqlTask.Run();
|
||||
}
|
||||
}
|
||||
@@ -179,48 +163,72 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
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)
|
||||
{
|
||||
this.backupUtilities.SetBackupInput(input);
|
||||
}
|
||||
|
||||
internal void PerformBackup()
|
||||
internal BackupOperation SetBackupInput(CDataContainer dataContainer, SqlConnection sqlConnection, BackupInfo input)
|
||||
{
|
||||
this.backupUtilities.PerformBackup();
|
||||
BackupOperation backupOperation = new BackupOperation();
|
||||
backupOperation.Initialize(dataContainer, sqlConnection);
|
||||
backupOperation.SetBackupInput(input);
|
||||
return backupOperation;
|
||||
}
|
||||
|
||||
internal BackupConfigInfo GetBackupConfigInfo(string databaseName)
|
||||
/// <summary>
|
||||
/// For testing purpose only
|
||||
/// </summary>
|
||||
internal void PerformBackup(BackupOperation backupOperation)
|
||||
{
|
||||
return this.backupUtilities.GetBackupConfigInfo(databaseName);
|
||||
backupOperation.PerformBackup();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a backup task for execution and cancellation
|
||||
/// </summary>
|
||||
/// <param name="sqlTask"></param>
|
||||
/// <returns></returns>
|
||||
internal async Task<TaskResult> BackupTask(SqlTask sqlTask)
|
||||
internal async Task<TaskResult> BackupTaskAsync(SqlTask sqlTask)
|
||||
{
|
||||
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true);
|
||||
Task<TaskResult> performTask = this.PerformTask();
|
||||
Task<TaskResult> cancelTask = this.CancelTask(sqlTask);
|
||||
Task<TaskResult> completedTask = await Task.WhenAny(performTask, cancelTask);
|
||||
if (completedTask == performTask)
|
||||
IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation;
|
||||
TaskResult taskResult = null;
|
||||
|
||||
if (backupOperation != null)
|
||||
{
|
||||
this.backupCompletedEvent.Set();
|
||||
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);
|
||||
|
||||
// Release the cancelTask
|
||||
if (completedTask == performTask)
|
||||
{
|
||||
backupCompletedEvent.Set();
|
||||
}
|
||||
|
||||
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
|
||||
completedTask.Result.TaskStatus);
|
||||
taskResult = completedTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
taskResult = new TaskResult();
|
||||
taskResult.TaskStatus = SqlTaskStatus.Failed;
|
||||
}
|
||||
|
||||
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
|
||||
completedTask.Result.TaskStatus);
|
||||
return completedTask.Result;
|
||||
return taskResult;
|
||||
}
|
||||
|
||||
private async Task<TaskResult> PerformTask()
|
||||
/// <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
|
||||
return await Task.Factory.StartNew(() =>
|
||||
@@ -228,7 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
TaskResult result = new TaskResult();
|
||||
try
|
||||
{
|
||||
this.backupUtilities.PerformBackup();
|
||||
backupOperation.PerformBackup();
|
||||
result.TaskStatus = SqlTaskStatus.Succeeded;
|
||||
}
|
||||
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
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
TaskResult result = new TaskResult();
|
||||
|
||||
CancellationToken token = sqlTask.TokenSource.Token;
|
||||
WaitHandle[] waitHandles = new WaitHandle[2]
|
||||
{
|
||||
backupCompletedEvent,
|
||||
@@ -259,19 +272,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
|
||||
};
|
||||
|
||||
WaitHandle.WaitAny(waitHandles);
|
||||
if (token.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
this.backupUtilities.CancelBackup();
|
||||
result.TaskStatus = SqlTaskStatus.Canceled;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.TaskStatus = SqlTaskStatus.Failed;
|
||||
result.ErrorMessage = ex.Message;
|
||||
}
|
||||
backupOperation.CancelBackup();
|
||||
result.TaskStatus = SqlTaskStatus.Canceled;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.TaskStatus = SqlTaskStatus.Failed;
|
||||
result.ErrorMessage = ex.Message;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user