Integrate generate script with task service (#426)

* Support generate script for backup

* change

* update task service data contract for Generate Script

* more changes

* update test

* add comments

* Add missing files

* update stub backup operation for testing

* pr comments

* remove empty space

* Fix tests

* Add unit/integration tests and isCancelable to TaskInfo

* address pr comments

* pr comments - fix tests

* fix minor issue

* fix minor issues

* remove unused variable
This commit is contained in:
Kate Shin
2017-08-09 19:59:40 -07:00
committed by GitHub
parent 6696b7e72f
commit cd870e6f15
26 changed files with 845 additions and 386 deletions

View File

@@ -14,6 +14,8 @@ using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using System.Globalization; using System.Globalization;
using System.Text;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
@@ -26,6 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
private ServerConnection serverConnection; private ServerConnection serverConnection;
private CommonUtilities backupRestoreUtil = null; private CommonUtilities backupRestoreUtil = null;
private Backup backup = null; private Backup backup = null;
private string scriptContent = "";
/// <summary> /// <summary>
/// Constants /// Constants
@@ -148,52 +151,62 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return configInfo; return configInfo;
} }
public string ScriptContent
{
get
{
return this.scriptContent;
}
set
{
this.scriptContent = value;
}
}
/// <summary> /// <summary>
/// Execute backup /// Execute backup
/// </summary> /// </summary>
public void PerformBackup() public void Execute(TaskExecutionMode mode)
{ {
StringBuilder sb = new StringBuilder();
SqlExecutionModes oldExecutionMode = this.dataContainer.Server.ConnectionContext.SqlExecutionModes;
this.dataContainer.Server.ConnectionContext.SqlExecutionModes = (mode == TaskExecutionMode.Script) ? SqlExecutionModes.CaptureSql: SqlExecutionModes.ExecuteAndCaptureSql;
this.dataContainer.Server.ConnectionContext.CapturedSql.Clear();
this.backup = new Backup(); this.backup = new Backup();
this.backup.Database = this.backupInfo.DatabaseName; this.backup.Database = this.backupInfo.DatabaseName;
this.backup.Action = this.backupActionType; this.backup.Action = this.backupActionType;
this.backup.Incremental = this.isBackupIncremental; this.backup.Incremental = this.isBackupIncremental;
this.SetBackupProps(); this.SetBackupProps();
if (this.backup.Action == BackupActionType.Files) try
{ {
IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator(); if (this.backup.Action == BackupActionType.Files)
filegroupEnumerator.Reset();
while (filegroupEnumerator.MoveNext())
{ {
string currentKey = Convert.ToString(filegroupEnumerator.Key, IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator();
System.Globalization.CultureInfo.InvariantCulture); filegroupEnumerator.Reset();
string currentValue = Convert.ToString(filegroupEnumerator.Value, while (filegroupEnumerator.MoveNext())
System.Globalization.CultureInfo.InvariantCulture);
if (currentKey.IndexOf(",", StringComparison.Ordinal) < 0)
{ {
// is a file group string currentKey = Convert.ToString(filegroupEnumerator.Key,
this.backup.DatabaseFileGroups.Add(currentValue); System.Globalization.CultureInfo.InvariantCulture);
} string currentValue = Convert.ToString(filegroupEnumerator.Value,
else System.Globalization.CultureInfo.InvariantCulture);
{ if (currentKey.IndexOf(",", StringComparison.Ordinal) < 0)
// is a file {
int idx = currentValue.IndexOf(".", StringComparison.Ordinal); // is a file group
currentValue = currentValue.Substring(idx + 1, currentValue.Length - idx - 1); this.backup.DatabaseFileGroups.Add(currentValue);
this.backup.DatabaseFiles.Add(currentValue); }
else
{
// is a file
int idx = currentValue.IndexOf(".", StringComparison.Ordinal);
currentValue = currentValue.Substring(idx + 1, currentValue.Length - idx - 1);
this.backup.DatabaseFiles.Add(currentValue);
}
} }
} }
}
bool isBackupToUrl = false; this.backup.BackupSetName = this.backupInfo.BackupsetName;
if (this.backupDeviceType == BackupDeviceType.Url)
{
isBackupToUrl = true;
}
this.backup.BackupSetName = this.backupInfo.BackupsetName;
if (false == isBackupToUrl)
{
for (int i = 0; i < this.backupInfo.BackupPathList.Count; i++) for (int i = 0; i < this.backupInfo.BackupPathList.Count; i++)
{ {
string destName = Convert.ToString(this.backupInfo.BackupPathList[i], System.Globalization.CultureInfo.InvariantCulture); string destName = Convert.ToString(this.backupInfo.BackupPathList[i], System.Globalization.CultureInfo.InvariantCulture);
@@ -205,8 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
GetDeviceType(Convert.ToString(destName, GetDeviceType(Convert.ToString(destName,
System.Globalization.CultureInfo.InvariantCulture)); System.Globalization.CultureInfo.InvariantCulture));
if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile) if (this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile)
|| (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape))
{ {
this.backup.Devices.AddDevice(destName, DeviceType.LogicalDevice); this.backup.Devices.AddDevice(destName, DeviceType.LogicalDevice);
} }
@@ -217,95 +229,104 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
this.backup.Devices.AddDevice(destName, DeviceType.File); this.backup.Devices.AddDevice(destName, DeviceType.File);
} }
break; break;
case (int)DeviceType.Tape:
if (this.backupDeviceType == BackupDeviceType.Tape)
{
this.backup.Devices.AddDevice(destName, DeviceType.Tape);
}
break;
} }
} }
}
this.backup.CopyOnly = this.backupInfo.IsCopyOnly; this.backup.CopyOnly = this.backupInfo.IsCopyOnly;
this.backup.FormatMedia = this.backupInfo.FormatMedia; this.backup.FormatMedia = this.backupInfo.FormatMedia;
this.backup.Initialize = this.backupInfo.Initialize; this.backup.Initialize = this.backupInfo.Initialize;
this.backup.SkipTapeHeader = this.backupInfo.SkipTapeHeader; this.backup.SkipTapeHeader = this.backupInfo.SkipTapeHeader;
this.backup.Checksum = this.backupInfo.Checksum; this.backup.Checksum = this.backupInfo.Checksum;
this.backup.ContinueAfterError = this.backupInfo.ContinueAfterError; this.backup.ContinueAfterError = this.backupInfo.ContinueAfterError;
if (!string.IsNullOrEmpty(this.backupInfo.MediaName)) if (!string.IsNullOrEmpty(this.backupInfo.MediaName))
{
this.backup.MediaName = this.backupInfo.MediaName;
}
if (!string.IsNullOrEmpty(this.backupInfo.MediaDescription))
{
this.backup.MediaDescription = this.backupInfo.MediaDescription;
}
if (this.backupInfo.TailLogBackup
&& !this.backupRestoreUtil.IsHADRDatabase(this.backupInfo.DatabaseName)
&& !this.backupRestoreUtil.IsMirroringEnabled(this.backupInfo.DatabaseName))
{
this.backup.NoRecovery = true;
}
if (this.backupInfo.LogTruncation)
{
this.backup.LogTruncation = BackupTruncateLogType.Truncate;
}
else
{
this.backup.LogTruncation = BackupTruncateLogType.NoTruncate;
}
if (!string.IsNullOrEmpty(this.backupInfo.BackupSetDescription))
{
this.backup.BackupSetDescription = this.backupInfo.BackupSetDescription;
}
if (this.backupInfo.RetainDays >= 0)
{
this.backup.RetainDays = this.backupInfo.RetainDays;
}
else
{
this.backup.ExpirationDate = this.backupInfo.ExpirationDate;
}
this.backup.CompressionOption = (BackupCompressionOptions)this.backupInfo.CompressionOption;
if (!string.IsNullOrEmpty(this.backupInfo.EncryptorName))
{
this.backup.EncryptionOption = new BackupEncryptionOptions((BackupEncryptionAlgorithm)this.backupInfo.EncryptionAlgorithm,
(BackupEncryptorType)this.backupInfo.EncryptorType,
this.backupInfo.EncryptorName);
}
// Execute backup
this.backup.SqlBackup(this.dataContainer.Server);
// Verify backup if required
if (this.backupInfo.VerifyBackupRequired)
{
Restore restore = new Restore();
restore.Devices.AddRange(this.backup.Devices);
restore.Database = this.backup.Database;
string errorMessage = null;
restore.SqlVerifyLatest(this.dataContainer.Server, out errorMessage);
if (errorMessage != null)
{ {
throw new Exception(errorMessage); this.backup.MediaName = this.backupInfo.MediaName;
} }
if (!string.IsNullOrEmpty(this.backupInfo.MediaDescription))
{
this.backup.MediaDescription = this.backupInfo.MediaDescription;
}
if (this.backupInfo.TailLogBackup
&& !this.backupRestoreUtil.IsHADRDatabase(this.backupInfo.DatabaseName)
&& !this.backupRestoreUtil.IsMirroringEnabled(this.backupInfo.DatabaseName))
{
this.backup.NoRecovery = true;
}
if (this.backupInfo.LogTruncation)
{
this.backup.LogTruncation = BackupTruncateLogType.Truncate;
}
else
{
this.backup.LogTruncation = BackupTruncateLogType.NoTruncate;
}
if (!string.IsNullOrEmpty(this.backupInfo.BackupSetDescription))
{
this.backup.BackupSetDescription = this.backupInfo.BackupSetDescription;
}
if (this.backupInfo.RetainDays >= 0)
{
this.backup.RetainDays = this.backupInfo.RetainDays;
}
else
{
this.backup.ExpirationDate = this.backupInfo.ExpirationDate;
}
this.backup.CompressionOption = (BackupCompressionOptions)this.backupInfo.CompressionOption;
if (!string.IsNullOrEmpty(this.backupInfo.EncryptorName))
{
this.backup.EncryptionOption = new BackupEncryptionOptions((BackupEncryptionAlgorithm)this.backupInfo.EncryptionAlgorithm,
(BackupEncryptorType)this.backupInfo.EncryptorType,
this.backupInfo.EncryptorName);
}
if (this.dataContainer.Server.ConnectionContext != null)
{
// Execute backup
this.backup.SqlBackup(this.dataContainer.Server);
// Verify backup if required
if (this.backupInfo.VerifyBackupRequired)
{
Restore restore = new Restore();
restore.Devices.AddRange(this.backup.Devices);
restore.Database = this.backup.Database;
string errorMessage = null;
restore.SqlVerifyLatest(this.dataContainer.Server, out errorMessage);
if (errorMessage != null)
{
throw new DisasterRecoveryException(errorMessage);
}
}
}
foreach (String s in this.dataContainer.Server.ConnectionContext.CapturedSql.Text)
{
sb.Append(s);
sb.Append(Environment.NewLine);
}
this.ScriptContent = sb.ToString();
}
finally
{
this.dataContainer.Server.ConnectionContext.CapturedSql.Clear();
this.dataContainer.Server.ConnectionContext.SqlExecutionModes = oldExecutionMode;
} }
} }
/// <summary> /// <summary>
/// Cancel backup /// Cancel backup
/// </summary> /// </summary>
public void CancelBackup() public void Cancel()
{ {
if (this.backup != null) if (this.backup != null)
{ {
@@ -374,43 +395,37 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
private void SetBackupProps() private void SetBackupProps()
{ {
try switch (this.backupType)
{
switch (this.backupType)
{
case BackupType.Full:
if (this.backupComponent == BackupComponent.Database)
{
this.backupActionType = BackupActionType.Database;
}
else if ((this.backupComponent == BackupComponent.Files) && (null != this.backupInfo.SelectedFileGroup) && (this.backupInfo.SelectedFileGroup.Count > 0))
{
this.backupActionType = BackupActionType.Files;
}
this.isBackupIncremental = false;
break;
case BackupType.Differential:
if ((this.backupComponent == BackupComponent.Files) && (0 != this.backupInfo.SelectedFiles.Length))
{
this.backupActionType = BackupActionType.Files;
this.isBackupIncremental = true;
}
else
{
this.backupActionType = BackupActionType.Database;
this.isBackupIncremental = true;
}
break;
case BackupType.TransactionLog:
this.backupActionType = BackupActionType.Log;
this.isBackupIncremental = false;
break;
default:
break;
}
}
catch
{ {
case BackupType.Full:
if (this.backupComponent == BackupComponent.Database)
{
this.backupActionType = BackupActionType.Database;
}
else if ((this.backupComponent == BackupComponent.Files) && (this.backupInfo.SelectedFileGroup != null) && (this.backupInfo.SelectedFileGroup.Count > 0))
{
this.backupActionType = BackupActionType.Files;
}
this.isBackupIncremental = false;
break;
case BackupType.Differential:
if ((this.backupComponent == BackupComponent.Files) && (this.backupInfo.SelectedFiles != null) && (this.backupInfo.SelectedFiles.Length > 0))
{
this.backupActionType = BackupActionType.Files;
this.isBackupIncremental = true;
}
else
{
this.backupActionType = BackupActionType.Database;
this.isBackupIncremental = true;
}
break;
case BackupType.TransactionLog:
this.backupActionType = BackupActionType.Log;
this.isBackupIncremental = false;
break;
default:
break;
} }
} }
@@ -433,20 +448,21 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
result = Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"], result = Convert.ToInt16(dataset.Tables[0].Rows[0]["BackupDeviceType"],
System.Globalization.CultureInfo.InvariantCulture); System.Globalization.CultureInfo.InvariantCulture);
return result;
} }
else else
{ {
return constDeviceTypeMediaSet; result = constDeviceTypeMediaSet;
} }
} }
catch catch (Exception ex)
{ {
throw ex;
} }
finally finally
{ {
this.serverConnection.SqlExecutionModes = executionMode; this.serverConnection.SqlExecutionModes = executionMode;
} }
return result; return result;
} }
} }

View File

@@ -5,6 +5,7 @@
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System.Data.SqlClient; using System.Data.SqlClient;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
@@ -12,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// <summary> /// <summary>
/// Interface for backup operations /// Interface for backup operations
/// </summary> /// </summary>
public interface IBackupOperation public interface IBackupOperation: IScriptableTaskOperation
{ {
/// <summary> /// <summary>
/// Initialize /// Initialize
@@ -33,15 +34,5 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
void SetBackupInput(BackupInfo input); void SetBackupInput(BackupInfo input);
/// <summary>
/// Execute backup
/// </summary>
void PerformBackup();
/// <summary>
/// Cancel backup
/// </summary>
void CancelBackup();
} }
} }

View File

@@ -7,12 +7,17 @@ using Microsoft.SqlTools.ServiceLayer.Admin.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
{ {
/// <summary>
/// Response class which returns backup configuration information
/// </summary>
public class BackupConfigInfoResponse public class BackupConfigInfoResponse
{ {
public BackupConfigInfo BackupConfigInfo { get; set; } public BackupConfigInfo BackupConfigInfo { get; set; }
} }
/// <summary>
/// Request class to get backup configuration information
/// </summary>
public class BackupConfigInfoRequest public class BackupConfigInfoRequest
{ {
public static readonly public static readonly

View File

@@ -7,13 +7,30 @@ using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
{ {
/// <summary>
/// Backup parameters passed for execution and scripting
/// </summary>
public class BackupParams public class BackupParams
{ {
/// <summary>
/// Connection uri
/// </summary>
public string OwnerUri { get; set; } public string OwnerUri { get; set; }
/// <summary>
/// Backup metrics selected from the UI
/// </summary>
public BackupInfo BackupInfo { get; set; } public BackupInfo BackupInfo { get; set; }
/// <summary>
/// True for generating script, false for execution
/// </summary>
public bool IsScripting { get; set; }
} }
/// <summary>
/// Response class for backup execution
/// </summary>
public class BackupResponse public class BackupResponse
{ {
public bool Result { get; set; } public bool Result { get; set; }
@@ -21,6 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts
public int TaskId { get; set; } public int TaskId { get; set; }
} }
/// <summary>
/// Request class for backup execution
/// </summary>
public class BackupRequest public class BackupRequest
{ {
public static readonly public static readonly

View File

@@ -0,0 +1,26 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{
/// <summary>
/// Exception raised from disaster recovery operations
/// </summary>
internal sealed class DisasterRecoveryException : Exception
{
internal DisasterRecoveryException() : base()
{
}
internal DisasterRecoveryException(string m) : base(m)
{
}
}
}

View File

@@ -13,6 +13,7 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System.Threading; using System.Threading;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation;
using System.Globalization;
namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
@@ -23,6 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService()); private static readonly Lazy<DisasterRecoveryService> instance = new Lazy<DisasterRecoveryService>(() => new DisasterRecoveryService());
private static ConnectionService connectionService = null; private static ConnectionService connectionService = null;
private SqlTaskManager sqlTaskManagerInstance = null;
private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper(); private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper();
/// <summary> /// <summary>
@@ -59,6 +61,22 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
} }
} }
private SqlTaskManager SqlTaskManagerInstance
{
get
{
if (sqlTaskManagerInstance == null)
{
sqlTaskManagerInstance = SqlTaskManager.Instance;
}
return sqlTaskManagerInstance;
}
set
{
sqlTaskManagerInstance = value;
}
}
/// <summary> /// <summary>
/// Initializes the service instance /// Initializes the service instance
/// </summary> /// </summary>
@@ -66,6 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
{ {
// Get database info // Get database info
serviceHost.SetRequestHandler(BackupConfigInfoRequest.Type, HandleBackupConfigInfoRequest); serviceHost.SetRequestHandler(BackupConfigInfoRequest.Type, HandleBackupConfigInfoRequest);
// Create backup // Create backup
serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest); serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest);
@@ -85,7 +104,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// <param name="optionsParams"></param> /// <param name="optionsParams"></param>
/// <param name="requestContext"></param> /// <param name="requestContext"></param>
/// <returns></returns> /// <returns></returns>
public static async Task HandleBackupConfigInfoRequest( internal async Task HandleBackupConfigInfoRequest(
DefaultDatabaseInfoParams optionsParams, DefaultDatabaseInfoParams optionsParams,
RequestContext<BackupConfigInfoResponse> requestContext) RequestContext<BackupConfigInfoResponse> requestContext)
{ {
@@ -103,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
SqlConnection sqlConn = GetSqlConnection(connInfo); SqlConnection sqlConn = GetSqlConnection(connInfo);
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure) if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
{ {
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); BackupConfigInfo backupConfigInfo = this.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo); backupConfigInfo.DatabaseInfo = AdminService.GetDatabaseInfo(connInfo);
response.BackupConfigInfo = backupConfigInfo; response.BackupConfigInfo = backupConfigInfo;
} }
@@ -213,7 +232,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
metadata.Data = restoreDataObject; metadata.Data = restoreDataObject;
// create restore task and perform // create restore task and perform
SqlTask sqlTask = SqlTaskManager.Instance.CreateAndRun(metadata, this.restoreDatabaseService.RestoreTaskAsync, restoreDatabaseService.CancelTaskAsync); SqlTask sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata, this.restoreDatabaseService.RestoreTaskAsync, restoreDatabaseService.CancelTaskAsync);
response.TaskId = sqlTask.TaskId.ToString(); response.TaskId = sqlTask.TaskId.ToString();
} }
else else
@@ -244,40 +263,50 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
/// <summary> /// <summary>
/// Handles a backup request /// Handles a backup request
/// </summary> /// </summary>
internal static async Task HandleBackupRequest( internal async Task HandleBackupRequest(
BackupParams backupParams, BackupParams backupParams,
RequestContext<BackupResponse> requestContext) RequestContext<BackupResponse> requestContext)
{ {
try try
{ {
BackupResponse response = new BackupResponse();
ConnectionInfo connInfo; ConnectionInfo connInfo;
DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection( bool supported = IsBackupRestoreOperationSupported(backupParams.OwnerUri, out connInfo);
backupParams.OwnerUri,
out connInfo);
if (connInfo != null) if (supported && connInfo != null)
{ {
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true); DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true);
SqlConnection sqlConn = GetSqlConnection(connInfo); SqlConnection sqlConn = GetSqlConnection(connInfo);
if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure)
BackupOperation backupOperation = CreateBackupOperation(helper.DataContainer, sqlConn, backupParams.BackupInfo);
SqlTask sqlTask = null;
// create task metadata
TaskMetadata metadata = new TaskMetadata();
metadata.ServerName = connInfo.ConnectionDetails.ServerName;
metadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
metadata.Data = backupOperation;
metadata.IsCancelable = true;
if (backupParams.IsScripting)
{ {
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); metadata.Name = string.Format("{0} {1}", SR.BackupTaskName, SR.ScriptTaskName);
metadata.TaskExecutionMode = TaskExecutionMode.Script;
// 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;
metadata.Data = backupOperation;
// create backup task and perform
SqlTask sqlTask = SqlTaskManager.Instance.CreateTask(metadata, Instance.BackupTaskAsync);
sqlTask.Run();
} }
else
{
metadata.Name = SR.BackupTaskName;
metadata.TaskExecutionMode = TaskExecutionMode.ExecuteAndScript;
}
sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata, this.PerformBackupTaskAsync, this.CancelBackupTaskAsync);
}
else
{
response.Result = false;
} }
await requestContext.SendResult(new BackupResponse()); await requestContext.SendResult(response);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -342,128 +371,123 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery
return false; return false;
} }
internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName) private BackupOperation CreateBackupOperation(CDataContainer dataContainer, SqlConnection sqlConnection)
{ {
BackupOperation backupOperation = new BackupOperation(); BackupOperation backupOperation = new BackupOperation();
backupOperation.Initialize(dataContainer, sqlConnection); backupOperation.Initialize(dataContainer, sqlConnection);
return backupOperation.CreateBackupConfigInfo(databaseName); return backupOperation;
} }
internal BackupOperation SetBackupInput(CDataContainer dataContainer, SqlConnection sqlConnection, BackupInfo input) internal BackupOperation CreateBackupOperation(CDataContainer dataContainer, SqlConnection sqlConnection, BackupInfo input)
{ {
BackupOperation backupOperation = new BackupOperation(); BackupOperation backupOperation = CreateBackupOperation(dataContainer, sqlConnection);
backupOperation.Initialize(dataContainer, sqlConnection);
backupOperation.SetBackupInput(input); backupOperation.SetBackupInput(input);
return backupOperation; return backupOperation;
} }
internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName)
{
BackupOperation backupOperation = CreateBackupOperation(dataContainer, sqlConnection);
return backupOperation.CreateBackupConfigInfo(databaseName);
}
/// <summary> /// <summary>
/// For testing purpose only /// For testing purpose only
/// </summary> /// </summary>
internal void PerformBackup(BackupOperation backupOperation) internal void PerformBackup(BackupOperation backupOperation)
{ {
backupOperation.PerformBackup(); backupOperation.Execute(TaskExecutionMode.ExecuteAndScript);
} }
/// <summary> /// <summary>
/// Create a backup task for execution and cancellation /// For testing purpose only
/// </summary> /// </summary>
/// <param name="sqlTask"></param> internal void ScriptBackup(BackupOperation backupOperation)
/// <returns></returns>
internal async Task<TaskResult> BackupTaskAsync(SqlTask sqlTask)
{ {
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true); backupOperation.Execute(TaskExecutionMode.Script);
IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation;
TaskResult taskResult = null;
if (backupOperation != null)
{
AutoResetEvent backupCompletedEvent = new AutoResetEvent(initialState: false);
Task<TaskResult> performTask = PerformTaskAsync(backupOperation);
Task<TaskResult> cancelTask = CancelTaskAsync(backupOperation, sqlTask.TokenSource.Token, backupCompletedEvent);
Task<TaskResult> completedTask = await Task.WhenAny(performTask, cancelTask);
// Release the cancelTask
if (completedTask == performTask)
{
backupCompletedEvent.Set();
}
sqlTask.AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed,
completedTask.Result.TaskStatus);
taskResult = completedTask.Result;
}
else
{
taskResult = new TaskResult();
taskResult.TaskStatus = SqlTaskStatus.Failed;
}
return taskResult;
} }
/// <summary> /// <summary>
/// Async task to execute backup /// Async task to execute backup
/// </summary> /// </summary>
/// <param name="backupOperation"></param> /// <param name="sqlTask"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TaskResult> PerformTaskAsync(IBackupOperation backupOperation) internal async Task<TaskResult> PerformBackupTaskAsync(SqlTask sqlTask)
{ {
IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation;
TaskResult result = new TaskResult();
// Create a task to perform backup // Create a task to perform backup
return await Task.Factory.StartNew(() => await Task.Factory.StartNew(() =>
{ {
TaskResult result = new TaskResult(); if (backupOperation != null)
try
{ {
backupOperation.PerformBackup(); try
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; sqlTask.AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true);
// Execute backup
backupOperation.Execute(sqlTask.TaskMetadata.TaskExecutionMode);
// Set result
result.TaskStatus = SqlTaskStatus.Succeeded;
// Send generated script to client
if (!String.IsNullOrEmpty(backupOperation.ScriptContent))
{
sqlTask.AddScript(result.TaskStatus, backupOperation.ScriptContent);
}
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = string.Format(CultureInfo.InvariantCulture, "error:{0} inner:{1} stacktrace:{2}",
ex.Message,
ex.InnerException != null ? ex.InnerException.Message : "",
ex.StackTrace);
} }
} }
return result; else
{
result.TaskStatus = SqlTaskStatus.Failed;
}
}); });
return result;
} }
/// <summary> /// <summary>
/// Async task to cancel backup /// Async task to cancel backup
/// </summary> /// </summary>
/// <param name="backupOperation"></param> /// <param name="sqlTask"></param>
/// <param name="token"></param>
/// <param name="backupCompletedEvent"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TaskResult> CancelTaskAsync(IBackupOperation backupOperation, CancellationToken token, AutoResetEvent backupCompletedEvent) internal async Task<TaskResult> CancelBackupTaskAsync(SqlTask sqlTask)
{ {
// Create a task for backup cancellation request IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation;
return await Task.Factory.StartNew(() => TaskResult result = new TaskResult();
{
TaskResult result = new TaskResult();
WaitHandle[] waitHandles = new WaitHandle[2]
{
backupCompletedEvent,
token.WaitHandle
};
WaitHandle.WaitAny(waitHandles); await Task.Factory.StartNew(() =>
try {
if (backupOperation != null)
{ {
backupOperation.CancelBackup(); try
result.TaskStatus = SqlTaskStatus.Canceled; {
backupOperation.Cancel();
result.TaskStatus = SqlTaskStatus.Canceled;
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
}
} }
catch (Exception ex) else
{ {
result.TaskStatus = SqlTaskStatus.Failed; result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
} }
return result;
}); });
return result;
} }
} }
} }

View File

@@ -32,7 +32,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation
/// <returns></returns> /// <returns></returns>
internal async Task<TaskResult> RestoreTaskAsync(SqlTask sqlTask) internal async Task<TaskResult> RestoreTaskAsync(SqlTask sqlTask)
{ {
sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true); sqlTask.AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true);
RestoreDatabaseTaskDataObject restoreDataObject = sqlTask.TaskMetadata.Data as RestoreDatabaseTaskDataObject; RestoreDatabaseTaskDataObject restoreDataObject = sqlTask.TaskMetadata.Data as RestoreDatabaseTaskDataObject;
TaskResult taskResult = null; TaskResult taskResult = null;

View File

@@ -3277,27 +3277,27 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string Backup_TaskName public static string BackupTaskName
{ {
get get
{ {
return Keys.GetString(Keys.Backup_TaskName); return Keys.GetString(Keys.BackupTaskName);
} }
} }
public static string Task_InProgress public static string TaskInProgress
{ {
get get
{ {
return Keys.GetString(Keys.Task_InProgress); return Keys.GetString(Keys.TaskInProgress);
} }
} }
public static string Task_Completed public static string TaskCompleted
{ {
get get
{ {
return Keys.GetString(Keys.Task_Completed); return Keys.GetString(Keys.TaskCompleted);
} }
} }
@@ -3485,6 +3485,14 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string ScriptTaskName
{
get
{
return Keys.GetString(Keys.ScriptTaskName);
}
}
public static string ConnectionServiceListDbErrorNotConnected(string uri) public static string ConnectionServiceListDbErrorNotConnected(string uri)
{ {
return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri);
@@ -4823,13 +4831,13 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup"; public const string prototype_file_noApplicableFileGroup = "prototype_file_noApplicableFileGroup";
public const string Backup_TaskName = "Backup_TaskName"; public const string BackupTaskName = "BackupTaskName";
public const string Task_InProgress = "Task_InProgress"; public const string TaskInProgress = "TaskInProgress";
public const string Task_Completed = "Task_Completed"; public const string TaskCompleted = "TaskCompleted";
public const string ConflictWithNoRecovery = "ConflictWithNoRecovery"; public const string ConflictWithNoRecovery = "ConflictWithNoRecovery";
@@ -4901,6 +4909,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string TheLastBackupTaken = "TheLastBackupTaken"; public const string TheLastBackupTaken = "TheLastBackupTaken";
public const string ScriptTaskName = "ScriptTaskName";
private Keys() private Keys()
{ } { }

View File

@@ -1811,15 +1811,15 @@
<value>No Applicable Filegroup</value> <value>No Applicable Filegroup</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="Backup_TaskName" xml:space="preserve"> <data name="BackupTaskName" xml:space="preserve">
<value>Backup Database</value> <value>Backup Database</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="Task_InProgress" xml:space="preserve"> <data name="TaskInProgress" xml:space="preserve">
<value>In progress</value> <value>In progress</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="Task_Completed" xml:space="preserve"> <data name="TaskCompleted" xml:space="preserve">
<value>Completed</value> <value>Completed</value>
<comment></comment> <comment></comment>
</data> </data>
@@ -1915,4 +1915,8 @@
<value>The last backup taken ({0})</value> <value>The last backup taken ({0})</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="ScriptTaskName" xml:space="preserve">
<value>scripting</value>
<comment></comment>
</data>
</root> </root>

View File

@@ -805,12 +805,12 @@ prototype_file_noApplicableFileGroup = No Applicable Filegroup
############################################################################ ############################################################################
# Backup Service # Backup Service
Backup_TaskName = Backup Database BackupTaskName = Backup Database
############################################################################ ############################################################################
# Task Service # Task Service
Task_InProgress = In progress TaskInProgress = In progress
Task_Completed = Completed TaskCompleted = Completed
########################################################################### ###########################################################################
# Restore # Restore
@@ -837,3 +837,7 @@ RestoreBackupSetSize = Size
RestoreBackupSetUserName = User Name RestoreBackupSetUserName = User Name
RestoreBackupSetExpiration = Expiration RestoreBackupSetExpiration = Expiration
TheLastBackupTaken = The last backup taken ({0}) TheLastBackupTaken = The last backup taken ({0})
############################################################################
# Generate Script
ScriptTaskName = scripting

View File

@@ -2110,20 +2110,6 @@
<source>Result set has too many rows to be safely loaded</source> <source>Result set has too many rows to be safely loaded</source>
<target state="new">Result set has too many rows to be safely loaded</target> <target state="new">Result set has too many rows to be safely loaded</target>
</trans-unit> </trans-unit>
<trans-unit id="Backup_TaskName">
<source>Backup Database</source>
<target state="new">Backup Database</target>
<note></note>
</trans-unit>
<trans-unit id="Task_InProgress">
<source>In progress</source>
<target state="new">In progress</target>
<note></note>
</trans-unit>
<trans-unit id="Task_Completed">
<source>Completed</source>
<target state="new">Completed</target>
</trans-unit>
<trans-unit id="prototype_db_prop_parameterization"> <trans-unit id="prototype_db_prop_parameterization">
<source>Parameterization</source> <source>Parameterization</source>
<target state="new">Parameterization</target> <target state="new">Parameterization</target>
@@ -2244,6 +2230,26 @@
<target state="new">The last backup taken ({0})</target> <target state="new">The last backup taken ({0})</target>
<note></note> <note></note>
</trans-unit> </trans-unit>
<trans-unit id="BackupTaskName">
<source>Backup Database</source>
<target state="new">Backup Database</target>
<note></note>
</trans-unit>
<trans-unit id="TaskInProgress">
<source>In progress</source>
<target state="new">In progress</target>
<note></note>
</trans-unit>
<trans-unit id="TaskCompleted">
<source>Completed</source>
<target state="new">Completed</target>
<note></note>
</trans-unit>
<trans-unit id="ScriptTaskName">
<source>scripting</source>
<target state="new">scripting</target>
<note></note>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@@ -20,6 +20,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
/// </summary> /// </summary>
public SqlTaskStatus Status { get; set; } public SqlTaskStatus Status { get; set; }
/// <summary>
/// Task execution mode
/// </summary>
public TaskExecutionMode TaskExecutionMode { get; set; }
/// <summary> /// <summary>
/// Database server name this task is created for /// Database server name this task is created for
/// </summary> /// </summary>
@@ -30,7 +35,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
/// </summary> /// </summary>
public string DatabaseName { get; set; } public string DatabaseName { get; set; }
/// <summary> /// <summary>
/// Task name which defines the type of the task (e.g. CreateDatabase, Backup) /// Task name which defines the type of the task (e.g. CreateDatabase, Backup)
/// </summary> /// </summary>
@@ -52,5 +56,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
/// </summary> /// </summary>
public string Description { get; set; } public string Description { get; set; }
/// <summary>
/// Defines if the task can be canceled
/// </summary>
public bool IsCancelable { get; set; }
} }
} }

View File

@@ -18,10 +18,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts
public SqlTaskStatus Status { get; set; } public SqlTaskStatus Status { get; set; }
/// <summary> /// <summary>
/// Database server name this task is created for /// Progress message
/// </summary> /// </summary>
public string Message { get; set; } public string Message { get; set; }
/// <summary>
/// Script for the task execution
/// </summary>
public string Script { get; set; }
/// <summary> /// <summary>
/// The number of millisecond the task was running /// The number of millisecond the task was running
/// </summary> /// </summary>

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
/// <summary>
/// Defines interface for task operations
/// </summary>
public interface ITaskOperation
{
/// <summary>
/// Execute a task
/// </summary>
/// <param name="mode">Task execution mode (e.g. script or execute)</param>
void Execute(TaskExecutionMode mode);
/// <summary>
/// Cancel a task
/// </summary>
void Cancel();
}
/// <summary>
/// Defines interface for scriptable task operations
/// </summary>
public interface IScriptableTaskOperation: ITaskOperation
{
/// <summary>
/// Script for the task operation
/// </summary>
string ScriptContent { get; set; }
}
}

View File

@@ -30,6 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
private SqlTaskStatus status = SqlTaskStatus.NotStarted; private SqlTaskStatus status = SqlTaskStatus.NotStarted;
private DateTime stopTime; private DateTime stopTime;
public event EventHandler<TaskEventArgs<TaskScript>> ScriptAdded;
public event EventHandler<TaskEventArgs<TaskMessage>> MessageAdded; public event EventHandler<TaskEventArgs<TaskMessage>> MessageAdded;
public event EventHandler<TaskEventArgs<SqlTaskStatus>> StatusChanged; public event EventHandler<TaskEventArgs<SqlTaskStatus>> StatusChanged;
@@ -119,7 +120,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <returns></returns> /// <returns></returns>
internal async Task<TaskResult> RunAndCancel() internal async Task<TaskResult> RunAndCancel()
{ {
AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true); AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true);
TaskResult taskResult = new TaskResult(); TaskResult taskResult = new TaskResult();
Task<TaskResult> performTask = TaskToRun(this); Task<TaskResult> performTask = TaskToRun(this);
@@ -129,15 +130,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{ {
if (TaskToCancel != null) if (TaskToCancel != null)
{ {
AutoResetEvent backupCompletedEvent = new AutoResetEvent(initialState: false); AutoResetEvent onCompletedEvent = new AutoResetEvent(initialState: false);
Task<TaskResult> cancelTask = Task.Run(() => CancelTaskAsync(TokenSource.Token, backupCompletedEvent)); Task<TaskResult> cancelTask = Task.Run(() => CancelTaskAsync(TokenSource.Token, onCompletedEvent));
completedTask = await Task.WhenAny(performTask, cancelTask); completedTask = await Task.WhenAny(performTask, cancelTask);
// Release the cancelTask // Release the cancelTask
if (completedTask == performTask) if (completedTask == performTask)
{ {
backupCompletedEvent.Set(); onCompletedEvent.Set();
} }
} }
else else
@@ -145,7 +146,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
completedTask = await Task.WhenAny(performTask); completedTask = await Task.WhenAny(performTask);
} }
AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.Task_Completed, AddMessage(completedTask.Result.TaskStatus == SqlTaskStatus.Failed ? completedTask.Result.ErrorMessage : SR.TaskCompleted,
completedTask.Result.TaskStatus); completedTask.Result.TaskStatus);
taskResult = completedTask.Result; taskResult = completedTask.Result;
@@ -174,16 +175,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// </summary> /// </summary>
/// <param name="backupOperation"></param> /// <param name="backupOperation"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <param name="backupCompletedEvent"></param> /// <param name="onCompletedEvent"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TaskResult> CancelTaskAsync(CancellationToken token, AutoResetEvent backupCompletedEvent) private async Task<TaskResult> CancelTaskAsync(CancellationToken token, AutoResetEvent onCompletedEvent)
{ {
// Create a task for backup cancellation request // Create a task for backup cancellation request
TaskResult result = new TaskResult(); TaskResult result = new TaskResult();
WaitHandle[] waitHandles = new WaitHandle[2] WaitHandle[] waitHandles = new WaitHandle[2]
{ {
backupCompletedEvent, onCompletedEvent,
token.WaitHandle token.WaitHandle
}; };
@@ -371,6 +372,26 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
IsCancelRequested = true; IsCancelRequested = true;
} }
/// <summary>
/// Add script result of the operation
/// </summary>
/// <param name="script">Script content</param>
/// <param name="errorMessage">Error occured during script</param>
/// <param name="status">Status of the script</param>
/// <returns></returns>
public TaskScript AddScript(SqlTaskStatus status, string script, string errorMessage = null)
{
var newScript = new TaskScript
{
Status = status,
Script = script,
ErrorMessage = errorMessage
};
OnScriptAdded(new TaskEventArgs<TaskScript>(newScript, this));
return newScript;
}
/// <summary> /// <summary>
/// Adds a new message to the task messages /// Adds a new message to the task messages
/// </summary> /// </summary>
@@ -430,6 +451,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
ServerName = TaskMetadata.ServerName, ServerName = TaskMetadata.ServerName,
Name = TaskMetadata.Name, Name = TaskMetadata.Name,
Description = TaskMetadata.Description, Description = TaskMetadata.Description,
TaskExecutionMode = TaskMetadata.TaskExecutionMode,
IsCancelable = TaskMetadata.IsCancelable,
}; };
} }
@@ -465,6 +488,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
} }
} }
private void OnScriptAdded(TaskEventArgs<TaskScript> e)
{
var handler = ScriptAdded;
if (handler != null)
{
handler(this, e);
}
}
private void OnMessageAdded(TaskEventArgs<TaskMessage> e) private void OnMessageAdded(TaskEventArgs<TaskMessage> e)
{ {
var handler = MessageAdded; var handler = MessageAdded;

View File

@@ -0,0 +1,29 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
/// <summary>
/// Task execution mode
/// </summary>
public enum TaskExecutionMode
{
/// <summary>
/// Execute task
/// </summary>
Execute,
/// <summary>
/// Script task
/// </summary>
Script,
/// <summary>
/// Execute and script task
/// Needed for tasks that will show the script when execution completes
/// </summary>
ExecuteAndScript
}
}

View File

@@ -17,6 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Task execution mode (e.g. execute or script)
/// </summary>
public TaskExecutionMode TaskExecutionMode { get; set; }
/// <summary> /// <summary>
/// The number of seconds to wait before canceling the task. /// The number of seconds to wait before canceling the task.
/// This is a optional field and 0 or negative numbers means no timeout /// This is a optional field and 0 or negative numbers means no timeout

View File

@@ -0,0 +1,28 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
/// <summary>
/// Task script message
/// </summary>
public class TaskScript
{
/// <summary>
/// Status of script
/// </summary>
public SqlTaskStatus Status { get; set; }
/// <summary>
/// Script content
/// </summary>
public string Script { get; set; }
/// <summary>
/// Error occurred during script
/// </summary>
public string ErrorMessage { get; set; }
}
}

View File

@@ -116,6 +116,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
if (sqlTask != null) if (sqlTask != null)
{ {
TaskInfo taskInfo = sqlTask.ToTaskInfo(); TaskInfo taskInfo = sqlTask.ToTaskInfo();
sqlTask.ScriptAdded += OnTaskScriptAdded;
sqlTask.MessageAdded += OnTaskMessageAdded; sqlTask.MessageAdded += OnTaskMessageAdded;
sqlTask.StatusChanged += OnTaskStatusChanged; sqlTask.StatusChanged += OnTaskStatusChanged;
await serviceHost.SendEvent(TaskCreatedNotification.Type, taskInfo); await serviceHost.SendEvent(TaskCreatedNotification.Type, taskInfo);
@@ -131,7 +132,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{ {
TaskId = sqlTask.TaskId.ToString(), TaskId = sqlTask.TaskId.ToString(),
Status = e.TaskData Status = e.TaskData
}; };
if (sqlTask.IsCompleted) if (sqlTask.IsCompleted)
@@ -142,6 +142,23 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
} }
} }
private async void OnTaskScriptAdded(object sender, TaskEventArgs<TaskScript> e)
{
SqlTask sqlTask = e.SqlTask;
if (sqlTask != null)
{
TaskProgressInfo progressInfo = new TaskProgressInfo
{
TaskId = sqlTask.TaskId.ToString(),
Status = e.TaskData.Status,
Script = e.TaskData.Script,
Message = e.TaskData.ErrorMessage,
};
await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo);
}
}
private async void OnTaskMessageAdded(object sender, TaskEventArgs<TaskMessage> e) private async void OnTaskMessageAdded(object sender, TaskEventArgs<TaskMessage> e)
{ {
SqlTask sqlTask = e.SqlTask; SqlTask sqlTask = e.SqlTask;

View File

@@ -53,7 +53,8 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
OwnerUri = liveConnection.ConnectionInfo.OwnerUri OwnerUri = liveConnection.ConnectionInfo.OwnerUri
}; };
await DisasterRecoveryService.HandleBackupConfigInfoRequest(dbParams, requestContext.Object); DisasterRecoveryService service = new DisasterRecoveryService();
await service.HandleBackupConfigInfoRequest(dbParams, requestContext.Object);
requestContext.Verify(x => x.SendResult(It.Is<BackupConfigInfoResponse> requestContext.Verify(x => x.SendResult(It.Is<BackupConfigInfoResponse>
(p => p.BackupConfigInfo.RecoveryModel != string.Empty (p => p.BackupConfigInfo.RecoveryModel != string.Empty
@@ -63,81 +64,85 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
testDb.Cleanup(); testDb.Cleanup();
} }
/// Test is failing in code coverage runs. Reenable when stable. /// <summary>
///[Fact] /// Create simple backup test
/// </summary>
[Fact]
public void CreateBackupTest() public void CreateBackupTest()
{ {
DisasterRecoveryService service = new DisasterRecoveryService();
string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); string databaseName = "testbackup_" + new Random().Next(10000000, 99999999);
SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName);
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
// Initialize backup service
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
// Get default backup path string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
BackupInfo backupInfo = CreateBackupInfo(databaseName, BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
BackupType.Full, BackupType.Full,
new List<string>(){ backupPath }, new List<string>() { backupPath },
new Dictionary<string, int>(){{ backupPath, (int)DeviceType.File }}); new Dictionary<string, int>() { { backupPath, (int)DeviceType.File } });
BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn);
var backupParams = new BackupParams
{
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
BackupInfo = backupInfo
};
// Backup the database // Backup the database
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); service.PerformBackup(backupOperation);
DisasterRecoveryService.Instance.PerformBackup(backupOperation);
// Remove the backup file VerifyAndCleanBackup(backupPath);
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
// Clean up the database
testDb.Cleanup(); testDb.Cleanup();
} }
[Fact]
public void ScriptBackupTest()
{
DisasterRecoveryService service = new DisasterRecoveryService();
string databaseName = "testbackup_" + new Random().Next(10000000, 99999999);
SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName);
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
BackupType.Full,
new List<string>() { backupPath },
new Dictionary<string, int>() { { backupPath, (int)DeviceType.File } });
BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn);
// Generate script for backup
service.ScriptBackup(backupOperation);
string script = backupOperation.ScriptContent;
Assert.True(!string.IsNullOrEmpty(script));
// Execute the script
testDb.RunQuery(script);
VerifyAndCleanBackup(backupPath);
testDb.Cleanup();
}
/// <summary> /// <summary>
/// Test creating backup with advanced options set. /// Test creating backup with advanced options set.
/// </summary> /// </summary>
[Fact] [Fact]
public void CreateBackupWithAdvancedOptionsTest() public void CreateBackupWithAdvancedOptionsTest()
{ {
DisasterRecoveryService service = new DisasterRecoveryService();
string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); string databaseName = "testbackup_" + new Random().Next(10000000, 99999999);
SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName); SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName);
string certificateName = "backupcertificate" + new Random().Next(10000000, 99999999);
string masterkeyPassword = Guid.NewGuid().ToString();
string createCertificateQuery = string.Format(CreateCertificateQueryFormat, masterkeyPassword, certificateName);
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
// create master key and certificate
Console.WriteLine("Create master key and certificate..");
testDb.RunQuery(createCertificateQuery);
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName); var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
// Initialize backup service
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
// Get default backup path string certificateName = CreateCertificate(testDb);
Console.WriteLine("Get default backup path.."); string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database);
string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
BackupInfo backupInfo = CreateBackupInfo(databaseName, BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
BackupType.Full, BackupType.Full,
new List<string>(){ backupPath }, new List<string>(){ backupPath },
new Dictionary<string, int>(){{ backupPath, (int)DeviceType.File }}); new Dictionary<string, int>(){{ backupPath, (int)DeviceType.File }});
// Set advanced options
backupInfo.ContinueAfterError = true; backupInfo.ContinueAfterError = true;
backupInfo.FormatMedia = true; backupInfo.FormatMedia = true;
backupInfo.SkipTapeHeader = true; backupInfo.SkipTapeHeader = true;
@@ -146,32 +151,19 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
backupInfo.MediaDescription = "backup test"; backupInfo.MediaDescription = "backup test";
backupInfo.RetainDays = 90; backupInfo.RetainDays = 90;
backupInfo.CompressionOption = (int)BackupCompressionOptions.On; backupInfo.CompressionOption = (int)BackupCompressionOptions.On;
// Set encryption
backupInfo.EncryptionAlgorithm = (int)BackupEncryptionAlgorithm.Aes128; backupInfo.EncryptionAlgorithm = (int)BackupEncryptionAlgorithm.Aes128;
backupInfo.EncryptorType = (int)BackupEncryptorType.ServerCertificate; backupInfo.EncryptorType = (int)BackupEncryptorType.ServerCertificate;
backupInfo.EncryptorName = certificateName; backupInfo.EncryptorName = certificateName;
var backupParams = new BackupParams BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn);
{
OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
BackupInfo = backupInfo
};
// Backup the database // Backup the database
Console.WriteLine("Perform backup operation.."); Console.WriteLine("Perform backup operation..");
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); service.PerformBackup(backupOperation);
DisasterRecoveryService.Instance.PerformBackup(backupOperation);
// Verify backup file is created
Assert.True(File.Exists(backupPath));
// Remove the backup file // Remove the backup file
Console.WriteLine("Remove backup file.."); Console.WriteLine("Verify the backup file exists and remove..");
if (File.Exists(backupPath)) VerifyAndCleanBackup(backupPath);
{
File.Delete(backupPath);
}
// Delete certificate and master key // Delete certificate and master key
Console.WriteLine("Remove certificate and master key.."); Console.WriteLine("Remove certificate and master key..");
@@ -182,7 +174,76 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
testDb.Cleanup(); testDb.Cleanup();
} }
private BackupInfo CreateBackupInfo(string databaseName, BackupType backupType, List<string> backupPathList, Dictionary<string, int> backupPathDevices) /// <summary>
/// Test creating backup with advanced options set.
/// </summary>
[Fact]
public void ScriptBackupWithAdvancedOptionsTest()
{
DisasterRecoveryService service = new DisasterRecoveryService();
string databaseName = "testbackup_" + new Random().Next(10000000, 99999999);
SqlTestDb testDb = SqlTestDb.CreateNew(TestServerType.OnPrem, false, databaseName);
var liveConnection = LiveConnectionHelper.InitLiveConnectionInfo(databaseName);
DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true);
SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo);
string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn);
string certificateName = CreateCertificate(testDb);
string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName);
BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName,
BackupType.Full,
new List<string>() { backupPath },
new Dictionary<string, int>() { { backupPath, (int)DeviceType.File } });
backupInfo.FormatMedia = true;
backupInfo.SkipTapeHeader = true;
backupInfo.Initialize = true;
backupInfo.MediaName = "backup test media";
backupInfo.MediaDescription = "backup test";
backupInfo.EncryptionAlgorithm = (int)BackupEncryptionAlgorithm.Aes128;
backupInfo.EncryptorType = (int)BackupEncryptorType.ServerCertificate;
backupInfo.EncryptorName = certificateName;
BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn);
// Backup the database
Console.WriteLine("Generate script for backup operation..");
service.ScriptBackup(backupOperation);
string script = backupOperation.ScriptContent;
// Run the script
Console.WriteLine("Execute the script..");
testDb.RunQuery(script);
// Remove the backup file
Console.WriteLine("Verify the backup file exists and remove..");
VerifyAndCleanBackup(backupPath);
// Delete certificate and master key
Console.WriteLine("Remove certificate and master key..");
testDb.RunQuery(cleanupCertificateQuery);
// Clean up the database
Console.WriteLine("Clean up database..");
testDb.Cleanup();
}
#region private methods
private string CreateCertificate(SqlTestDb testDb)
{
string certificateName = "backupcertificate" + new Random().Next(10000000, 99999999);
string masterkeyPassword = Guid.NewGuid().ToString();
string createCertificateQuery = string.Format(CreateCertificateQueryFormat, masterkeyPassword, certificateName);
// create master key and certificate
Console.WriteLine("Create master key and certificate..");
testDb.RunQuery(createCertificateQuery);
return certificateName;
}
private BackupInfo CreateDefaultBackupInfo(string databaseName, BackupType backupType, List<string> backupPathList, Dictionary<string, int> backupPathDevices)
{ {
BackupInfo backupInfo = new BackupInfo(); BackupInfo backupInfo = new BackupInfo();
backupInfo.BackupComponent = (int)BackupComponent.Database; backupInfo.BackupComponent = (int)BackupComponent.Database;
@@ -196,5 +257,36 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; ";
backupInfo.SelectedFiles = ""; backupInfo.SelectedFiles = "";
return backupInfo; return backupInfo;
} }
private string GetDefaultBackupPath(DisasterRecoveryService service, string databaseName, CDataContainer dataContainer, SqlConnection sqlConn)
{
BackupConfigInfo backupConfigInfo = service.GetBackupConfigInfo(dataContainer, sqlConn, sqlConn.Database);
return Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak");
}
private BackupOperation CreateBackupOperation(DisasterRecoveryService service, string uri, BackupInfo backupInfo, CDataContainer dataContainer, SqlConnection sqlConn)
{
var backupParams = new BackupParams
{
OwnerUri = uri,
BackupInfo = backupInfo,
};
return service.CreateBackupOperation(dataContainer, sqlConn, backupParams.BackupInfo);
}
private void VerifyAndCleanBackup(string backupPath)
{
// Verify it created backup
Assert.True(File.Exists(backupPath));
// Remove the backup file
if (File.Exists(backupPath))
{
File.Delete(backupPath);
}
}
#endregion
} }
} }

View File

@@ -664,11 +664,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery
var backupParams = new BackupParams var backupParams = new BackupParams
{ {
OwnerUri = liveConnection.ConnectionInfo.OwnerUri, OwnerUri = liveConnection.ConnectionInfo.OwnerUri,
BackupInfo = backupInfo BackupInfo = backupInfo,
IsScripting = false
}; };
// Backup the database // Backup the database
BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); BackupOperation backupOperation = DisasterRecoveryService.Instance.CreateBackupOperation(helper.DataContainer, sqlConn, backupParams.BackupInfo);
DisasterRecoveryService.Instance.PerformBackup(backupOperation); DisasterRecoveryService.Instance.PerformBackup(backupOperation);
// Clean up the database // Clean up the database

View File

@@ -7,6 +7,7 @@ using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using System; using System;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Threading; using System.Threading;
@@ -25,6 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
this.BackupSemaphore = new SemaphoreSlim(0, 1); this.BackupSemaphore = new SemaphoreSlim(0, 1);
} }
public string ScriptContent { get; set; }
/// <summary> /// <summary>
/// Initialize /// Initialize
@@ -56,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
/// <summary> /// <summary>
/// Execute backup /// Execute backup
/// </summary> /// </summary>
public void PerformBackup() public void Execute(TaskExecutionMode mode)
{ {
this.BackupSemaphore.Wait(TimeSpan.FromSeconds(5)); this.BackupSemaphore.Wait(TimeSpan.FromSeconds(5));
} }
@@ -64,7 +66,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
/// <summary> /// <summary>
/// Cancel backup /// Cancel backup
/// </summary> /// </summary>
public void CancelBackup() public void Cancel()
{ {
} }
} }

View File

@@ -25,7 +25,32 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
DisasterRecoveryService service = new DisasterRecoveryService(); DisasterRecoveryService service = new DisasterRecoveryService();
var mockBackupOperation = new Mock<IBackupOperation>(); var mockBackupOperation = new Mock<IBackupOperation>();
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object); TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
{
Assert.Equal(SqlTaskStatus.Succeeded, sqlTask.TaskStatus);
});
await taskToVerify;
}
}
/// <summary>
/// Generate script for backup task
/// </summary>
/// <returns></returns>
[Fact]
public async Task VerifyScriptBackupTask()
{
using (SqlTaskManager manager = new SqlTaskManager())
{
DisasterRecoveryService service = new DisasterRecoveryService();
var mockBackupOperation = new Mock<IBackupOperation>();
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
taskMetaData.TaskExecutionMode = TaskExecutionMode.Script;
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync);
Assert.NotNull(sqlTask); Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task => Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
{ {
@@ -49,8 +74,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
var mockBackupOperation = new Mock<IBackupOperation>(); var mockBackupOperation = new Mock<IBackupOperation>();
TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object); TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object);
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
SqlTask sqlTask2 = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask2 = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
Assert.NotNull(sqlTask); Assert.NotNull(sqlTask);
Assert.NotNull(sqlTask2); Assert.NotNull(sqlTask2);
@@ -80,7 +105,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
IBackupOperation backupOperation = new BackupOperationStub(); IBackupOperation backupOperation = new BackupOperationStub();
DisasterRecoveryService service = new DisasterRecoveryService(); DisasterRecoveryService service = new DisasterRecoveryService();
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation); TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
Assert.NotNull(sqlTask); Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task => Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task =>
{ {
@@ -110,8 +135,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation); TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
TaskMetadata taskMetaData2 = this.CreateTaskMetaData(backupOperation2); TaskMetadata taskMetaData2 = this.CreateTaskMetaData(backupOperation2);
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
SqlTask sqlTask2 = manager.CreateTask(taskMetaData2, service.BackupTaskAsync); SqlTask sqlTask2 = manager.CreateTask(taskMetaData2, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
Assert.NotNull(sqlTask); Assert.NotNull(sqlTask);
Assert.NotNull(sqlTask2); Assert.NotNull(sqlTask2);
@@ -149,11 +174,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery
DisasterRecoveryService service = new DisasterRecoveryService(); DisasterRecoveryService service = new DisasterRecoveryService();
IBackupOperation backupOperation = new BackupOperationStub(); IBackupOperation backupOperation = new BackupOperationStub();
TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation); TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation);
SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
var mockBackupOperation = new Mock<IBackupOperation>(); var mockBackupOperation = new Mock<IBackupOperation>();
TaskMetadata taskMetaData2 = this.CreateTaskMetaData(mockBackupOperation.Object); TaskMetadata taskMetaData2 = this.CreateTaskMetaData(mockBackupOperation.Object);
SqlTask sqlTask2 = manager.CreateTask(taskMetaData2, service.BackupTaskAsync); SqlTask sqlTask2 = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync);
Assert.NotNull(sqlTask); Assert.NotNull(sqlTask);
Assert.NotNull(sqlTask2); Assert.NotNull(sqlTask2);

View File

@@ -23,6 +23,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
Failed = true; Failed = true;
} }
public TaskScript TaskScript { get; set; }
public TaskResult TaskResult { get; set; } public TaskResult TaskResult { get; set; }
public bool IsStopped { get; set; } public bool IsStopped { get; set; }
@@ -62,5 +64,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
}; };
}); });
} }
public async Task<TaskResult> FunctionToScript(SqlTask sqlTask)
{
return await Task.Factory.StartNew(() =>
{
sqlTask.AddMessage("start scripting", SqlTaskStatus.InProgress, true);
TaskScript = sqlTask.AddScript(SqlTaskStatus.Succeeded, "script generated!");
sqlTask.AddMessage("done", SqlTaskStatus.Succeeded);
return new TaskResult
{
TaskStatus = SqlTaskStatus.Succeeded,
};
});
}
} }
} }

View File

@@ -147,5 +147,26 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
operation.FailTheOperation(); operation.FailTheOperation();
await taskToVerify; await taskToVerify;
} }
[Fact]
public async Task RunScriptShouldReturnScriptContent()
{
SqlTaskStatus expectedStatus = SqlTaskStatus.Succeeded;
DatabaseOperationStub operation = new DatabaseOperationStub();
operation.TaskResult = new TaskResult
{
TaskStatus = expectedStatus
};
SqlTask sqlTask = new SqlTask(new TaskMetadata(), operation.FunctionToScript, null);
Assert.Equal(sqlTask.TaskStatus, SqlTaskStatus.NotStarted);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(task => {
Assert.Equal(sqlTask.TaskStatus, expectedStatus);
Assert.Equal(sqlTask.IsCompleted, true);
Assert.NotNull(operation.TaskScript);
Assert.True(!string.IsNullOrEmpty(operation.TaskScript.Script));
});
await taskToVerify;
}
} }
} }

View File

@@ -86,5 +86,40 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices
} }
} }
[Fact]
public async Task VerifyScriptTask()
{
using (SqlTaskManager manager = new SqlTaskManager())
{
DatabaseOperationStub operation = new DatabaseOperationStub();
operation.TaskResult = new TaskResult
{
TaskStatus = SqlTaskStatus.Succeeded
};
SqlTask sqlTask = manager.CreateTask(taskMetaData, operation.FunctionToScript);
bool scriptAddedEventRaised = false;
string script = null;
sqlTask.ScriptAdded += (object sender, TaskEventArgs<TaskScript> e) =>
{
scriptAddedEventRaised = true;
script = e.TaskData.Script;
};
Assert.NotNull(sqlTask);
Task taskToVerify = sqlTask.RunAsync().ContinueWith(task =>
{
Assert.True(scriptAddedEventRaised);
Assert.True(!string.IsNullOrEmpty(script));
Assert.True(manager.HasCompletedTasks());
manager.RemoveCompletedTask(sqlTask);
});
operation.Stop();
await taskToVerify;
}
}
} }
} }