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