mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-19 09:35:36 -05:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user