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
{
public class BackupUtilities
/// <summary>
/// This class implements backup operations
/// </summary>
public class BackupUtilities : IBackupUtilities
{
private CDataContainer dataContainer;
private ServerConnection serverConnection;
private CommonUtilities backupRestoreUtil = null;
private CommonUtilities backupRestoreUtil = null;
private Backup backup = null;
/// <summary>
/// Constants
@@ -33,12 +37,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// UI input values
/// </summary>
private BackupInfo backupInfo;
public BackupComponent backupComponent { get; set; }
public BackupType backupType { get; set; } // 0 for Full, 1 for Differential, 2 for Log
public BackupDeviceType backupDeviceType { get; set; }
private BackupComponent backupComponent;
private BackupType backupType;
private BackupDeviceType backupDeviceType;
private BackupActionType backupActionType = BackupActionType.Database;
private bool IsBackupIncremental = false;
private bool isBackupIncremental = false;
private bool isLocalPrimaryReplica;
/// 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>
/// Initialize variables
/// </summary>
@@ -107,6 +113,10 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.backupRestoreUtil = new CommonUtilities(this.dataContainer, this.serverConnection);
}
/// <summary>
/// Set backup input properties
/// </summary>
/// <param name="input"></param>
public void SetBackupInput(BackupInfo input)
{
this.backupInfo = input;
@@ -121,11 +131,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
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)
{
BackupConfigInfo databaseInfo = new BackupConfigInfo();
@@ -135,6 +146,109 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
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>
/// Return recovery model of the database
/// </summary>
@@ -171,11 +285,11 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
private string GetDefaultBackupSetName()
{
string bkpsetName = this.backupInfo.DatabaseName + "-"
string backupName = this.backupInfo.DatabaseName + "-"
+ this.backupType.ToString() + " "
+ this.backupComponent.ToString() + " "
+ BackupConstants.Backup;
return bkpsetName;
return backupName;
}
private void SetBackupProps()
@@ -185,7 +299,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
switch (this.backupType)
{
case BackupType.Full:
if (this.backupComponent == BackupComponent.Database) // define the value as const!!
if (this.backupComponent == BackupComponent.Database)
{
this.backupActionType = BackupActionType.Database;
}
@@ -193,23 +307,23 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{
this.backupActionType = BackupActionType.Files;
}
this.IsBackupIncremental = false;
this.isBackupIncremental = false;
break;
case BackupType.Differential:
if ((this.backupComponent == BackupComponent.Files) && (0 != this.backupInfo.SelectedFiles.Length))
{
this.backupActionType = BackupActionType.Files;
this.IsBackupIncremental = true;
this.isBackupIncremental = true;
}
else
{
this.backupActionType = BackupActionType.Database;
this.IsBackupIncremental = true;
this.isBackupIncremental = true;
}
break;
case BackupType.TransactionLog:
this.backupActionType = BackupActionType.Log;
this.IsBackupIncremental = false;
this.isBackupIncremental = false;
break;
default:
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)
{
Enumerator en = new Enumerator();
Request req = new Request();
DataSet ds = new DataSet();
ds.Locale = System.Globalization.CultureInfo.InvariantCulture;
int Result = -1;
SqlExecutionModes execMode = this.serverConnection.SqlExecutionModes;
Enumerator enumerator = new Enumerator();
Request request = new Request();
DataSet dataset = new DataSet();
dataset.Locale = System.Globalization.CultureInfo.InvariantCulture;
int result = -1;
SqlExecutionModes executionMode = this.serverConnection.SqlExecutionModes;
this.serverConnection.SqlExecutionModes = SqlExecutionModes.ExecuteSql;
try
{
req.Urn = "Server/BackupDevice[@Name='" + Urn.EscapeString(deviceName) + "']";
req.Fields = new string[1];
req.Fields[0] = "BackupDeviceType";
ds = en.Process(this.serverConnection, req);
int iCount = ds.Tables[0].Rows.Count;
if (iCount > 0)
request.Urn = "Server/BackupDevice[@Name='" + Urn.EscapeString(deviceName) + "']";
request.Fields = new string[1];
request.Fields[0] = "BackupDeviceType";
dataset = enumerator.Process(this.serverConnection, request);
if (dataset.Tables[0].Rows.Count > 0)
{
Result = Convert.ToInt16(ds.Tables[0].Rows[0]["BackupDeviceType"],
result = Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"],
System.Globalization.CultureInfo.InvariantCulture);
return Result;
return result;
}
else
{
@@ -350,9 +366,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
}
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.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{
/// <summary>
/// Service for Backup and Restore
/// </summary>
public class DisasterRecoveryService
{
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
private static ConnectionService connectionService = null;
private BackupUtilities backupUtilities;
private IBackupUtilities backupUtilities;
private ManualResetEvent backupCompletedEvent = new ManualResetEvent(initialState: false);
/// <summary>
/// Default, parameterless constructor.
@@ -28,6 +34,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.backupUtilities = new BackupUtilities();
}
/// <summary>
/// For testing purpose only
/// </summary>
internal DisasterRecoveryService(IBackupUtilities backupUtilities)
{
this.backupUtilities = backupUtilities;
}
/// <summary>
/// Gets the singleton instance object
/// </summary>
@@ -66,6 +80,12 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
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(
DefaultDatabaseInfoParams optionsParams,
RequestContext<BackupConfigInfoResponse> requestContext)
@@ -113,9 +133,20 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
SqlConnection sqlConn = GetSqlConnection(connInfo);
if (sqlConn != null)
{
// initialize backup
DisasterRecoveryService.Instance.InitializeBackup(helper.DataContainer, sqlConn);
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)
{
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
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 Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts;
using Microsoft.SqlTools.Utility;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
@@ -37,18 +38,24 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// Creates new instance of SQL task
/// </summary>
/// <param name="taskMetdata">Task Metadata</param>
/// <param name="testToRun">The function to run to start the task</param>
public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> testToRun)
/// <param name="taskToRun">The function to run to start the task</param>
public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> taskToRun)
{
Validate.IsNotNull(nameof(taskMetdata), taskMetdata);
Validate.IsNotNull(nameof(testToRun), testToRun);
Validate.IsNotNull(nameof(taskToRun), taskToRun);
TaskMetadata = taskMetdata;
TaskToRun = testToRun;
TaskToRun = taskToRun;
StartTime = DateTime.UtcNow;
TaskId = Guid.NewGuid();
TokenSource = new CancellationTokenSource();
}
/// <summary>
/// Cancellation token
/// </summary>
public CancellationTokenSource TokenSource { get; private set; }
/// <summary>
/// Task Metadata
/// </summary>
@@ -99,9 +106,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
//Run Task synchronously
public void Run()
{
RunAsync().ContinueWith(task =>
{
});
Task.Run(() => RunAsync());
}
/// <summary>
@@ -254,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
}
/// <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
/// </summary>
public void Cancel()
@@ -376,6 +381,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
private void OnTaskCancelRequested()
{
TokenSource.Cancel();
var handler = TaskCanceled;
if (handler != null)
{
@@ -387,10 +393,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
//Dispose
isDisposed = true;
TokenSource.Dispose();
}
protected void ValidateNotDisposed()
{
if (isDisposed)

View File

@@ -133,6 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
Status = e.TaskData
};
if (sqlTask.IsCompleted)
{
progressInfo.Duration = sqlTask.Duration;
@@ -149,7 +150,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
TaskProgressInfo progressInfo = new TaskProgressInfo
{
TaskId = sqlTask.TaskId.ToString(),
Message = e.TaskData.Description
Message = e.TaskData.Description,
Status = sqlTask.TaskStatus
};
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.
//
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Moq;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
{
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()
{
}
}
}