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:
Kate Shin
2017-06-26 16:21:09 -07:00
committed by GitHub
parent 276f70ab3b
commit cdfdd7bd5a
8 changed files with 241 additions and 127 deletions

View File

@@ -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;
});
}