Register backup to task service for execution (#381)

* Create backup task for execution

* Register backup to task service

* Fix backup task service

* Fix async methods

* Add backup unit test

* Add cancellation token to task service and fix other PR comments

* Add SR and fix other pr comments

* Add comments to methods

* Fixed backup cancel test and casing

* Change sleep time in test
This commit is contained in:
Kate Shin
2017-06-16 14:01:09 -07:00
committed by GitHub
parent 0c7533b5b9
commit a646d627c6
22 changed files with 14122 additions and 7482 deletions

View File

@@ -16,11 +16,15 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
public class BackupUtilities /// <summary>
/// This class implements backup operations
/// </summary>
public class BackupUtilities : IBackupUtilities
{ {
private CDataContainer dataContainer; private CDataContainer dataContainer;
private ServerConnection serverConnection; private ServerConnection serverConnection;
private CommonUtilities backupRestoreUtil = null; private CommonUtilities backupRestoreUtil = null;
private Backup backup = null;
/// <summary> /// <summary>
/// Constants /// Constants
@@ -33,12 +37,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// UI input values /// UI input values
/// </summary> /// </summary>
private BackupInfo backupInfo; private BackupInfo backupInfo;
public BackupComponent backupComponent { get; set; } private BackupComponent backupComponent;
public BackupType backupType { get; set; } // 0 for Full, 1 for Differential, 2 for Log private BackupType backupType;
public BackupDeviceType backupDeviceType { get; set; } private BackupDeviceType backupDeviceType;
private BackupActionType backupActionType = BackupActionType.Database; private BackupActionType backupActionType = BackupActionType.Database;
private bool IsBackupIncremental = false; private bool isBackupIncremental = false;
private bool isLocalPrimaryReplica; private bool isLocalPrimaryReplica;
/// this is used when the backup dialog is launched in the context of a backup device /// this is used when the backup dialog is launched in the context of a backup device
@@ -94,6 +98,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
} }
#endregion
/// <summary> /// <summary>
/// Initialize variables /// Initialize variables
/// </summary> /// </summary>
@@ -107,6 +113,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.backupRestoreUtil = new CommonUtilities(this.dataContainer, this.serverConnection); this.backupRestoreUtil = new CommonUtilities(this.dataContainer, this.serverConnection);
} }
/// <summary>
/// Set backup input properties
/// </summary>
/// <param name="input"></param>
public void SetBackupInput(BackupInfo input) public void SetBackupInput(BackupInfo input)
{ {
this.backupInfo = input; this.backupInfo = input;
@@ -121,11 +131,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.isLocalPrimaryReplica = this.backupRestoreUtil.IsLocalPrimaryReplica(this.backupInfo.DatabaseName); this.isLocalPrimaryReplica = this.backupRestoreUtil.IsLocalPrimaryReplica(this.backupInfo.DatabaseName);
} }
} }
#endregion
#region Methods for UI logic /// <summary>
/// Return backup configuration data
/// </summary>
/// <param name="databaseName"></param>
/// <returns></returns>
public BackupConfigInfo GetBackupConfigInfo(string databaseName) public BackupConfigInfo GetBackupConfigInfo(string databaseName)
{ {
BackupConfigInfo databaseInfo = new BackupConfigInfo(); BackupConfigInfo databaseInfo = new BackupConfigInfo();
@@ -135,6 +146,109 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return databaseInfo; return databaseInfo;
} }
/// <summary>
/// Execute backup
/// </summary>
public void PerformBackup()
{
this.backup = new Backup();
this.SetBackupProps();
backup.Database = this.backupInfo.DatabaseName;
backup.Action = this.backupActionType;
backup.Incremental = this.isBackupIncremental;
if (backup.Action == BackupActionType.Files)
{
IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator();
filegroupEnumerator.Reset();
while (filegroupEnumerator.MoveNext())
{
string currentKey = Convert.ToString(filegroupEnumerator.Key,
System.Globalization.CultureInfo.InvariantCulture);
string currentValue = Convert.ToString(filegroupEnumerator.Value,
System.Globalization.CultureInfo.InvariantCulture);
if (currentKey.IndexOf(",", StringComparison.Ordinal) < 0)
{
// is a file group
backup.DatabaseFileGroups.Add(currentValue);
}
else
{
// is a file
int idx = currentValue.IndexOf(".", StringComparison.Ordinal);
currentValue = currentValue.Substring(idx + 1, currentValue.Length - idx - 1);
backup.DatabaseFiles.Add(currentValue);
}
}
}
bool isBackupToUrl = false;
if (this.backupDeviceType == BackupDeviceType.Url)
{
isBackupToUrl = true;
}
backup.BackupSetName = this.backupInfo.BackupsetName;
if (false == isBackupToUrl)
{
for (int i = 0; i < this.backupInfo.BackupPathList.Count; i++)
{
string destName = Convert.ToString(this.backupInfo.BackupPathList[i], System.Globalization.CultureInfo.InvariantCulture);
int deviceType = (int)(this.backupInfo.BackupPathDevices[destName]);
switch (deviceType)
{
case (int)DeviceType.LogicalDevice:
int backupDeviceType =
GetDeviceType(Convert.ToString(destName,
System.Globalization.CultureInfo.InvariantCulture));
if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile)
|| (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape))
{
backup.Devices.AddDevice(destName, DeviceType.LogicalDevice);
}
break;
case (int)DeviceType.File:
if (this.backupDeviceType == BackupDeviceType.Disk)
{
backup.Devices.AddDevice(destName, DeviceType.File);
}
break;
case (int)DeviceType.Tape:
if (this.backupDeviceType == BackupDeviceType.Tape)
{
backup.Devices.AddDevice(destName, DeviceType.Tape);
}
break;
}
}
}
//TODO: This should be changed to get user inputs
backup.FormatMedia = false;
backup.Initialize = false;
backup.SkipTapeHeader = true;
backup.Checksum = false;
backup.ContinueAfterError = false;
backup.LogTruncation = BackupTruncateLogType.Truncate;
// Execute backup
backup.SqlBackup(this.dataContainer.Server);
}
/// <summary>
/// Cancel backup
/// </summary>
public void CancelBackup()
{
if (this.backup != null)
{
this.backup.Abort();
}
}
#region Methods for UI logic
/// <summary> /// <summary>
/// Return recovery model of the database /// Return recovery model of the database
/// </summary> /// </summary>
@@ -171,11 +285,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
private string GetDefaultBackupSetName() private string GetDefaultBackupSetName()
{ {
string bkpsetName = this.backupInfo.DatabaseName + "-" string backupName = this.backupInfo.DatabaseName + "-"
+ this.backupType.ToString() + " " + this.backupType.ToString() + " "
+ this.backupComponent.ToString() + " " + this.backupComponent.ToString() + " "
+ BackupConstants.Backup; + BackupConstants.Backup;
return bkpsetName; return backupName;
} }
private void SetBackupProps() private void SetBackupProps()
@@ -185,7 +299,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
switch (this.backupType) switch (this.backupType)
{ {
case BackupType.Full: case BackupType.Full:
if (this.backupComponent == BackupComponent.Database) // define the value as const!! if (this.backupComponent == BackupComponent.Database)
{ {
this.backupActionType = BackupActionType.Database; this.backupActionType = BackupActionType.Database;
} }
@@ -193,23 +307,23 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
this.backupActionType = BackupActionType.Files; this.backupActionType = BackupActionType.Files;
} }
this.IsBackupIncremental = false; this.isBackupIncremental = false;
break; break;
case BackupType.Differential: case BackupType.Differential:
if ((this.backupComponent == BackupComponent.Files) && (0 != this.backupInfo.SelectedFiles.Length)) if ((this.backupComponent == BackupComponent.Files) && (0 != this.backupInfo.SelectedFiles.Length))
{ {
this.backupActionType = BackupActionType.Files; this.backupActionType = BackupActionType.Files;
this.IsBackupIncremental = true; this.isBackupIncremental = true;
} }
else else
{ {
this.backupActionType = BackupActionType.Database; this.backupActionType = BackupActionType.Database;
this.IsBackupIncremental = true; this.isBackupIncremental = true;
} }
break; break;
case BackupType.TransactionLog: case BackupType.TransactionLog:
this.backupActionType = BackupActionType.Log; this.backupActionType = BackupActionType.Log;
this.IsBackupIncremental = false; this.isBackupIncremental = false;
break; break;
default: default:
break; break;
@@ -220,125 +334,27 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
} }
} }
/// <summary>
/// Sets the backup properties from the general tab
/// </summary>
public void PerformBackup()
{
// Set backup action
this.SetBackupProps();
Backup bk = new Backup();
try
{
bk.Database = this.backupInfo.DatabaseName;
bk.Action = this.backupActionType;
bk.Incremental = this.IsBackupIncremental;
if (bk.Action == BackupActionType.Files)
{
IDictionaryEnumerator IEnum = this.backupInfo.SelectedFileGroup.GetEnumerator();
IEnum.Reset();
while (IEnum.MoveNext())
{
string CurrentKey = Convert.ToString(IEnum.Key,
System.Globalization.CultureInfo.InvariantCulture);
string CurrentValue = Convert.ToString(IEnum.Value,
System.Globalization.CultureInfo.InvariantCulture);
if (CurrentKey.IndexOf(",", StringComparison.Ordinal) < 0)
{
// is a file group
bk.DatabaseFileGroups.Add(CurrentValue);
}
else
{
// is a file
int Idx = CurrentValue.IndexOf(".", StringComparison.Ordinal);
CurrentValue = CurrentValue.Substring(Idx + 1, CurrentValue.Length - Idx - 1);
bk.DatabaseFiles.Add(CurrentValue);
}
}
}
bool bBackupToUrl = false;
if (this.backupDeviceType == BackupDeviceType.Url)
{
bBackupToUrl = true;
}
bk.BackupSetName = this.backupInfo.BackupsetName;
if (false == bBackupToUrl)
{
for (int i = 0; i < this.backupInfo.BackupPathList.Count; i++)
{
string DestName = Convert.ToString(this.backupInfo.BackupPathList[i], System.Globalization.CultureInfo.InvariantCulture);
int deviceType = (int)(this.backupInfo.BackupPathDevices[DestName]);
switch (deviceType)
{
case (int)DeviceType.LogicalDevice:
int backupDeviceType =
GetDeviceType(Convert.ToString(DestName,
System.Globalization.CultureInfo.InvariantCulture));
if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile)
|| (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape))
{
bk.Devices.AddDevice(DestName, DeviceType.LogicalDevice);
}
break;
case (int)DeviceType.File:
if (this.backupDeviceType == BackupDeviceType.Disk)
{
bk.Devices.AddDevice(DestName, DeviceType.File);
}
break;
case (int)DeviceType.Tape:
if (this.backupDeviceType == BackupDeviceType.Tape)
{
bk.Devices.AddDevice(DestName, DeviceType.Tape);
}
break;
}
}
}
//TODO: This should be changed to get user inputs
bk.FormatMedia = false;
bk.Initialize = false;
bk.SkipTapeHeader = true;
bk.Checksum = false;
bk.ContinueAfterError = false;
bk.LogTruncation = BackupTruncateLogType.Truncate;
// Execute backup
bk.SqlBackup(this.dataContainer.Server);
}
catch
{
}
}
private int GetDeviceType(string deviceName) private int GetDeviceType(string deviceName)
{ {
Enumerator en = new Enumerator(); Enumerator enumerator = new Enumerator();
Request req = new Request(); Request request = new Request();
DataSet ds = new DataSet(); DataSet dataset = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture; dataset.Locale = System.Globalization.CultureInfo.InvariantCulture;
int Result = -1; int result = -1;
SqlExecutionModes execMode = this.serverConnection.SqlExecutionModes; SqlExecutionModes executionMode = this.serverConnection.SqlExecutionModes;
this.serverConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql; this.serverConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql;
try try
{ {
req.Urn = "Server/BackupDevice[@Name='" + Urn.EscapeString(deviceName) + "']"; request.Urn = "Server/BackupDevice[@Name='" + Urn.EscapeString(deviceName) + "']";
req.Fields = new string[1]; request.Fields = new string[1];
req.Fields[0] = "BackupDeviceType"; request.Fields[0] = "BackupDeviceType";
ds = en.Process(this.serverConnection, req); dataset = enumerator.Process(this.serverConnection, request);
int iCount = ds.Tables[0].Rows.Count; if (dataset.Tables[0].Rows.Count > 0)
if (iCount > 0)
{ {
Result = Convert.ToInt16(ds.Tables[0].Rows[0]["BackupDeviceType"], result = Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"],
System.Globalization.CultureInfo.InvariantCulture); System.Globalization.CultureInfo.InvariantCulture);
return Result; return result;
} }
else else
{ {
@@ -350,9 +366,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
finally finally
{ {
this.serverConnection.SqlExecutionModes = execMode; this.serverConnection.SqlExecutionModes = executionMode;
} }
return Result; return result;
} }
} }
} }

View File

@@ -11,14 +11,20 @@ using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
/// <summary>
/// Service for Backup and Restore
/// </summary>
public class DisasterRecoveryService public class DisasterRecoveryService
{ {
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 BackupUtilities backupUtilities; private IBackupUtilities backupUtilities;
private ManualResetEvent backupCompletedEvent = new ManualResetEvent(initialState: false);
/// <summary> /// <summary>
/// Default, parameterless constructor. /// Default, parameterless constructor.
@@ -28,6 +34,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.backupUtilities = new BackupUtilities(); this.backupUtilities = new BackupUtilities();
} }
/// <summary>
/// For testing purpose only
/// </summary>
internal DisasterRecoveryService(IBackupUtilities backupUtilities)
{
this.backupUtilities = backupUtilities;
}
/// <summary> /// <summary>
/// Gets the singleton instance object /// Gets the singleton instance object
/// </summary> /// </summary>
@@ -66,6 +80,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest); serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
} }
/// <summary>
/// Handle request to get backup configuration info
/// </summary>
/// <param name="optionsParams"></param>
/// <param name="requestContext"></param>
/// <returns></returns>
public static async Task HandleBackupConfigInfoRequest( public static async Task HandleBackupConfigInfoRequest(
DefaultDatabaseInfoParams optionsParams, DefaultDatabaseInfoParams optionsParams,
RequestContext<BackupConfigInfoResponse> requestContext) RequestContext<BackupConfigInfoResponse> requestContext)
@@ -113,9 +133,20 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
SqlConnection sqlConn = GetSqlConnection(connInfo); SqlConnection sqlConn = GetSqlConnection(connInfo);
if (sqlConn != null) if (sqlConn != null)
{ {
// initialize backup
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn); DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
DisasterRecoveryService.Instance.SetBackupInput(backupParams.BackupInfo); DisasterRecoveryService.Instance.SetBackupInput(backupParams.BackupInfo);
DisasterRecoveryService.Instance.PerformBackup();
// create task metadata
TaskMetadata metadata = new TaskMetadata();
metadata.ServerName = connInfo.ConnectionDetails.ServerName;
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
metadata.Name = SR.Backup_TaskName;
metadata.IsCancelable = true;
// create backup task and perform
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTask);
sqlTask.Run();
} }
} }
@@ -166,7 +197,83 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
internal BackupConfigInfo GetBackupConfigInfo(string databaseName) internal BackupConfigInfo GetBackupConfigInfo(string databaseName)
{ {
return this.backupUtilities.GetBackupConfigInfo(databaseName); return this.backupUtilities.GetBackupConfigInfo(databaseName);
} }
/// <summary>
/// Create a backup task for execution and cancellation
/// </summary>
/// <param name="sqlTask"></param>
/// <returns></returns>
internal async Task<TaskResult> BackupTask(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)
{
this.backupCompletedEvent.Set();
}
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
completedTask.Result.TaskStatus);
return completedTask.Result;
}
private async Task<TaskResult> PerformTask()
{
// Create a task to perform backup
return await Task.Factory.StartNew(() =>
{
TaskResult result = new TaskResult();
try
{
this.backupUtilities.PerformBackup();
result.TaskStatus = SqlTaskStatus.Succeeded;
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
if (ex.InnerException != null)
{
result.ErrorMessage += System.Environment.NewLine + ex.InnerException.Message;
}
}
return result;
});
}
private async Task<TaskResult> CancelTask(SqlTask sqlTask)
{
// 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,
token.WaitHandle
};
WaitHandle.WaitAny(waitHandles);
if (token.IsCancellationRequested)
{
try
{
this.backupUtilities.CancelBackup();
result.TaskStatus = SqlTaskStatus.Canceled;
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
}
}
return result;
});
}
} }
} }

View File

@@ -0,0 +1,46 @@
//
// 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.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{
/// <summary>
/// Interface for backup operations
/// </summary>
public interface IBackupUtilities
{
/// <summary>
/// Initialize
/// </summary>
/// <param name="dataContainer"></param>
/// <param name="sqlConnection"></param>
void Initialize(CDataContainer dataContainer, SqlConnection sqlConnection);
/// <summary>
/// Return database metadata for backup
/// </summary>
/// <param name="databaseName"></param>
/// <returns></returns>
BackupConfigInfo GetBackupConfigInfo(string databaseName);
/// <summary>
/// Set backup input properties
/// </summary>
/// <param name="input"></param>
void SetBackupInput(BackupInfo input);
/// <summary>
/// Execute backup
/// </summary>
void PerformBackup();
/// <summary>
/// Cancel backup
/// </summary>
void CancelBackup();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -799,5 +799,12 @@ general_containmentType_Partial = Partial
filegroups_filestreamFiles = FILESTREAM Files filegroups_filestreamFiles = FILESTREAM Files
prototype_file_noApplicableFileGroup = No Applicable Filegroup prototype_file_noApplicableFileGroup = No Applicable Filegroup
############################################################################
# Backup Service
Backup_TaskName = Backup Database
############################################################################
# Task Service
Task_InProgress = In progress
Task_Completed = Completed

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{ {
@@ -37,18 +38,24 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// Creates new instance of SQL task /// Creates new instance of SQL task
/// </summary> /// </summary>
/// <param name="taskMetdata">Task Metadata</param> /// <param name="taskMetdata">Task Metadata</param>
/// <param name="testToRun">The function to run to start the task</param> /// <param name="taskToRun">The function to run to start the task</param>
public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> testToRun) public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> taskToRun)
{ {
Validate.IsNotNull(nameof(taskMetdata), taskMetdata); Validate.IsNotNull(nameof(taskMetdata), taskMetdata);
Validate.IsNotNull(nameof(testToRun), testToRun); Validate.IsNotNull(nameof(taskToRun), taskToRun);
TaskMetadata = taskMetdata; TaskMetadata = taskMetdata;
TaskToRun = testToRun; TaskToRun = taskToRun;
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
TaskId = Guid.NewGuid(); TaskId = Guid.NewGuid();
TokenSource = new CancellationTokenSource();
} }
/// <summary>
/// Cancellation token
/// </summary>
public CancellationTokenSource TokenSource { get; private set; }
/// <summary> /// <summary>
/// Task Metadata /// Task Metadata
/// </summary> /// </summary>
@@ -99,9 +106,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
//Run Task synchronously //Run Task synchronously
public void Run() public void Run()
{ {
RunAsync().ContinueWith(task => Task.Run(() => RunAsync());
{
});
} }
/// <summary> /// <summary>
@@ -254,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
} }
/// <summary> /// <summary>
/// Try to cancel the task, and even to cancel the task will be raised /// Try to cancel the task, and event to cancel the task will be raised
/// but the status won't change until that task actually get canceled by it's owner /// but the status won't change until that task actually get canceled by it's owner
/// </summary> /// </summary>
public void Cancel() public void Cancel()
@@ -376,6 +381,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
private void OnTaskCancelRequested() private void OnTaskCancelRequested()
{ {
TokenSource.Cancel();
var handler = TaskCanceled; var handler = TaskCanceled;
if (handler != null) if (handler != null)
{ {
@@ -387,10 +393,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{ {
//Dispose //Dispose
isDisposed = true; isDisposed = true;
TokenSource.Dispose();
} }
protected void ValidateNotDisposed() protected void ValidateNotDisposed()
{ {
if (isDisposed) if (isDisposed)

View File

@@ -133,6 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
Status = e.TaskData Status = e.TaskData
}; };
if (sqlTask.IsCompleted) if (sqlTask.IsCompleted)
{ {
progressInfo.Duration = sqlTask.Duration; progressInfo.Duration = sqlTask.Duration;
@@ -149,7 +150,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
TaskProgressInfo progressInfo = new TaskProgressInfo TaskProgressInfo progressInfo = new TaskProgressInfo
{ {
TaskId = sqlTask.TaskId.ToString(), TaskId = sqlTask.TaskId.ToString(),
Message = e.TaskData.Description Message = e.TaskData.Description,
Status = sqlTask.TaskStatus
}; };
await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo); await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo);
} }

View File

@@ -3,14 +3,61 @@
// 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 System; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using System.Collections.Generic; using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System.Linq; using Moq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
{ {
public class BackupTests public class BackupTests
{ {
private TaskMetadata taskMetaData = new TaskMetadata
{
ServerName = "server name",
DatabaseName = "database name",
Name = "Backup Database",
IsCancelable = true
};
[Fact]
public async Task VerifyCreateAndRunningBackupTask()
{
using (SqlTaskManager manager = new SqlTaskManager())
{
var mockUtility = new Mock<IBackupUtilities>();
DisasterRecoveryService service = new DisasterRecoveryService(mockUtility.Object);
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTask);
Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
{
Assert.Equal(SqlTaskStatus.Succeeded, sqlTask.TaskStatus);
});
await taskToVerify;
}
}
[Fact]
public async Task CancelBackupTask()
{
using (SqlTaskManager manager = new SqlTaskManager())
{
IBackupUtilities backupUtility = new BackupUtilitiesStub();
DisasterRecoveryService service = new DisasterRecoveryService(backupUtility);
SqlTask sqlTask = manager.CreateTask(this.taskMetaData, service.BackupTask);
Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
{
Assert.Equal(SqlTaskStatus.Canceled, sqlTask.TaskStatus);
Assert.Equal(sqlTask.IsCancelRequested, true);
manager.Reset();
});
manager.CancelTask(sqlTask.TaskId);
await taskToVerify;
}
}
} }
} }

View File

@@ -0,0 +1,61 @@
//
// 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.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using System.Data.SqlClient;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
{
/// <summary>
/// Stub class that implements IBackupUtilities
/// </summary>
public class BackupUtilitiesStub : IBackupUtilities
{
/// <summary>
/// Initialize
/// </summary>
/// <param name="dataContainer"></param>
/// <param name="sqlConnection"></param>
public void Initialize(CDataContainer dataContainer, SqlConnection sqlConnection)
{
}
/// <summary>
/// Return database metadata for backup
/// </summary>
/// <param name="databaseName"></param>
/// <returns></returns>
public BackupConfigInfo GetBackupConfigInfo(string databaseName)
{
return null;
}
/// <summary>
/// Set backup input properties
/// </summary>
/// <param name="input"></param>
public void SetBackupInput(BackupInfo input)
{
}
/// <summary>
/// Execute backup
/// </summary>
public void PerformBackup()
{
Thread.Sleep(500);
}
/// <summary>
/// Cancel backup
/// </summary>
public void CancelBackup()
{
}
}
}