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

@@ -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

View File

@@ -107,8 +107,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// <param name="sqlConnection"></param> /// <param name="sqlConnection"></param>
public CommonUtilities(CDataContainer dataContainer, ServerConnection sqlConnection) public CommonUtilities(CDataContainer dataContainer, ServerConnection sqlConnection)
{ {
this.dataContainer = dataContainer; this.dataContainer = dataContainer;
this.sqlConnection = sqlConnection; this.sqlConnection = sqlConnection;
this.excludedDatabases = new ArrayList(); this.excludedDatabases = new ArrayList();
this.excludedDatabases.Add("master"); this.excludedDatabases.Add("master");
this.excludedDatabases.Add("tempdb"); this.excludedDatabases.Add("tempdb");
@@ -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);

View File

@@ -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,15 +89,11 @@ 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.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
{ response.BackupConfigInfo = backupConfigInfo;
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(sqlConn.Database);
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
response.BackupConfigInfo = backupConfigInfo;
}
} }
} }
@@ -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;
Task<TaskResult> completedTask = await Task.WhenAny(performTask, cancelTask);
if (completedTask == performTask) 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, return taskResult;
completedTask.Result.TaskStatus);
return completedTask.Result;
} }
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 // 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,19 +272,17 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
}; };
WaitHandle.WaitAny(waitHandles); WaitHandle.WaitAny(waitHandles);
if (token.IsCancellationRequested) try
{ {
try backupOperation.CancelBackup();
{ result.TaskStatus = SqlTaskStatus.Canceled;
this.backupUtilities.CancelBackup();
result.TaskStatus = SqlTaskStatus.Canceled;
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
}
} }
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
}
return result; return result;
}); });
} }

View File

@@ -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

View File

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

View File

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

View File

@@ -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

View File

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