diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/BackupOperation.cs similarity index 61% rename from src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs rename to src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/BackupOperation.cs index 8022122e..8c2a3c59 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/BackupOperation.cs @@ -14,6 +14,8 @@ using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using System.Globalization; +using System.Text; +using Microsoft.SqlTools.ServiceLayer.TaskServices; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { @@ -26,6 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery private ServerConnection serverConnection; private CommonUtilities backupRestoreUtil = null; private Backup backup = null; + private string scriptContent = ""; /// /// Constants @@ -111,7 +114,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { this.dataContainer = dataContainer; this.serverConnection = new ServerConnection(sqlConnection); - this.backupRestoreUtil = new CommonUtilities(this.dataContainer, this.serverConnection); + this.backupRestoreUtil = new CommonUtilities(this.dataContainer, this.serverConnection); } /// @@ -122,7 +125,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { this.backupInfo = input; - // convert the types + // convert the types this.backupComponent = (BackupComponent)input.BackupComponent; this.backupType = (BackupType)input.BackupType; this.backupDeviceType = (BackupDeviceType)input.BackupDeviceType; @@ -148,52 +151,62 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery return configInfo; } + public string ScriptContent + { + get + { + return this.scriptContent; + } + set + { + this.scriptContent = value; + } + } + /// /// Execute backup /// - 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.Database = this.backupInfo.DatabaseName; this.backup.Action = this.backupActionType; this.backup.Incremental = this.isBackupIncremental; this.SetBackupProps(); - if (this.backup.Action == BackupActionType.Files) + try { - IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator(); - filegroupEnumerator.Reset(); - while (filegroupEnumerator.MoveNext()) + if (this.backup.Action == BackupActionType.Files) { - 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) + IDictionaryEnumerator filegroupEnumerator = this.backupInfo.SelectedFileGroup.GetEnumerator(); + filegroupEnumerator.Reset(); + while (filegroupEnumerator.MoveNext()) { - // is a file group - this.backup.DatabaseFileGroups.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); + 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 + this.backup.DatabaseFileGroups.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; - if (this.backupDeviceType == BackupDeviceType.Url) - { - isBackupToUrl = true; - } + this.backup.BackupSetName = this.backupInfo.BackupsetName; - this.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); @@ -205,8 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery GetDeviceType(Convert.ToString(destName, System.Globalization.CultureInfo.InvariantCulture)); - if ((this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile) - || (this.backupDeviceType == BackupDeviceType.Tape && backupDeviceType == constDeviceTypeTape)) + if (this.backupDeviceType == BackupDeviceType.Disk && backupDeviceType == constDeviceTypeFile) { this.backup.Devices.AddDevice(destName, DeviceType.LogicalDevice); } @@ -217,95 +229,104 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery this.backup.Devices.AddDevice(destName, DeviceType.File); } 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.FormatMedia = this.backupInfo.FormatMedia; - this.backup.Initialize = this.backupInfo.Initialize; - this.backup.SkipTapeHeader = this.backupInfo.SkipTapeHeader; - this.backup.Checksum = this.backupInfo.Checksum; - this.backup.ContinueAfterError = this.backupInfo.ContinueAfterError; + this.backup.CopyOnly = this.backupInfo.IsCopyOnly; + this.backup.FormatMedia = this.backupInfo.FormatMedia; + this.backup.Initialize = this.backupInfo.Initialize; + this.backup.SkipTapeHeader = this.backupInfo.SkipTapeHeader; + this.backup.Checksum = this.backupInfo.Checksum; + this.backup.ContinueAfterError = this.backupInfo.ContinueAfterError; - 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) + if (!string.IsNullOrEmpty(this.backupInfo.MediaName)) { - 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; } } /// /// Cancel backup /// - public void CancelBackup() + public void Cancel() { if (this.backup != null) { @@ -374,43 +395,37 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery private void SetBackupProps() { - try - { - 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 + switch (this.backupType) { + 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"], System.Globalization.CultureInfo.InvariantCulture); - return result; } else { - return constDeviceTypeMediaSet; + result = constDeviceTypeMediaSet; } } - catch - { + catch (Exception ex) + { + throw ex; } finally { this.serverConnection.SqlExecutionModes = executionMode; } + return result; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/IBackupOperation.cs similarity index 81% rename from src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs rename to src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/IBackupOperation.cs index 8358e68a..84d33012 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/IBackupOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/BackupOperation/IBackupOperation.cs @@ -5,6 +5,7 @@ using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; using System.Data.SqlClient; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery @@ -12,7 +13,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// Interface for backup operations /// - public interface IBackupOperation + public interface IBackupOperation: IScriptableTaskOperation { /// /// Initialize @@ -33,15 +34,5 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// void SetBackupInput(BackupInfo input); - - /// - /// Execute backup - /// - void PerformBackup(); - - /// - /// Cancel backup - /// - void CancelBackup(); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfoRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfoRequest.cs index 353b85c7..58c0498d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfoRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupConfigInfoRequest.cs @@ -7,12 +7,17 @@ using Microsoft.SqlTools.ServiceLayer.Admin.Contracts; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts { - + /// + /// Response class which returns backup configuration information + /// public class BackupConfigInfoResponse { public BackupConfigInfo BackupConfigInfo { get; set; } } + /// + /// Request class to get backup configuration information + /// public class BackupConfigInfoRequest { public static readonly diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupRequest.cs index 981bce84..6a6ab212 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/BackupRequest.cs @@ -7,13 +7,30 @@ using Microsoft.SqlTools.Hosting.Protocol.Contracts; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts { + /// + /// Backup parameters passed for execution and scripting + /// public class BackupParams { + /// + /// Connection uri + /// public string OwnerUri { get; set; } + /// + /// Backup metrics selected from the UI + /// public BackupInfo BackupInfo { get; set; } + + /// + /// True for generating script, false for execution + /// + public bool IsScripting { get; set; } } + /// + /// Response class for backup execution + /// public class BackupResponse { public bool Result { get; set; } @@ -21,6 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts public int TaskId { get; set; } } + /// + /// Request class for backup execution + /// public class BackupRequest { public static readonly diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryException.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryException.cs new file mode 100644 index 00000000..cd7f37dc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryException.cs @@ -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 +{ + + /// + /// Exception raised from disaster recovery operations + /// + internal sealed class DisasterRecoveryException : Exception + { + internal DisasterRecoveryException() : base() + { + } + + internal DisasterRecoveryException(string m) : base(m) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs index ffef5151..46c713f6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/DisasterRecoveryService.cs @@ -13,6 +13,7 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using System.Threading; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; +using System.Globalization; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { @@ -23,6 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { private static readonly Lazy instance = new Lazy(() => new DisasterRecoveryService()); private static ConnectionService connectionService = null; + private SqlTaskManager sqlTaskManagerInstance = null; private RestoreDatabaseHelper restoreDatabaseService = new RestoreDatabaseHelper(); /// @@ -59,6 +61,22 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery } } + private SqlTaskManager SqlTaskManagerInstance + { + get + { + if (sqlTaskManagerInstance == null) + { + sqlTaskManagerInstance = SqlTaskManager.Instance; + } + return sqlTaskManagerInstance; + } + set + { + sqlTaskManagerInstance = value; + } + } + /// /// Initializes the service instance /// @@ -66,6 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery { // Get database info serviceHost.SetRequestHandler(BackupConfigInfoRequest.Type, HandleBackupConfigInfoRequest); + // Create backup serviceHost.SetRequestHandler(BackupRequest.Type, HandleBackupRequest); @@ -85,7 +104,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// /// - public static async Task HandleBackupConfigInfoRequest( + internal async Task HandleBackupConfigInfoRequest( DefaultDatabaseInfoParams optionsParams, RequestContext requestContext) { @@ -103,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery SqlConnection sqlConn = GetSqlConnection(connInfo); 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); response.BackupConfigInfo = backupConfigInfo; } @@ -213,7 +232,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery metadata.Data = restoreDataObject; // 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(); } else @@ -244,40 +263,50 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery /// /// Handles a backup request /// - internal static async Task HandleBackupRequest( + internal async Task HandleBackupRequest( BackupParams backupParams, RequestContext requestContext) - { + { try - { + { + BackupResponse response = new BackupResponse(); ConnectionInfo connInfo; - DisasterRecoveryService.ConnectionServiceInstance.TryFindConnection( - backupParams.OwnerUri, - out connInfo); + bool supported = IsBackupRestoreOperationSupported(backupParams.OwnerUri, out connInfo); - if (connInfo != null) + if (supported && connInfo != null) { DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(connInfo, databaseExists: true); SqlConnection sqlConn = GetSqlConnection(connInfo); - if ((sqlConn != null) && !connInfo.IsSqlDW && !connInfo.IsAzure) - { - BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); - // 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(); + 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) + { + metadata.Name = string.Format("{0} {1}", SR.BackupTaskName, SR.ScriptTaskName); + metadata.TaskExecutionMode = TaskExecutionMode.Script; } + else + { + metadata.Name = SR.BackupTaskName; + metadata.TaskExecutionMode = TaskExecutionMode.ExecuteAndScript; + } + + sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata, this.PerformBackupTaskAsync, this.CancelBackupTaskAsync); } - - await requestContext.SendResult(new BackupResponse()); + else + { + response.Result = false; + } + + await requestContext.SendResult(response); } catch (Exception ex) { @@ -342,128 +371,123 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery return false; } - internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName) + private BackupOperation CreateBackupOperation(CDataContainer dataContainer, SqlConnection sqlConnection) { BackupOperation backupOperation = new BackupOperation(); 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.Initialize(dataContainer, sqlConnection); + BackupOperation backupOperation = CreateBackupOperation(dataContainer, sqlConnection); backupOperation.SetBackupInput(input); return backupOperation; } + internal BackupConfigInfo GetBackupConfigInfo(CDataContainer dataContainer, SqlConnection sqlConnection, string databaseName) + { + BackupOperation backupOperation = CreateBackupOperation(dataContainer, sqlConnection); + return backupOperation.CreateBackupConfigInfo(databaseName); + } + /// /// For testing purpose only /// internal void PerformBackup(BackupOperation backupOperation) { - backupOperation.PerformBackup(); + backupOperation.Execute(TaskExecutionMode.ExecuteAndScript); } - + /// - /// Create a backup task for execution and cancellation + /// For testing purpose only /// - /// - /// - internal async Task BackupTaskAsync(SqlTask sqlTask) + internal void ScriptBackup(BackupOperation backupOperation) { - sqlTask.AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true); - IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation; - TaskResult taskResult = null; - - if (backupOperation != null) - { - AutoResetEvent backupCompletedEvent = new AutoResetEvent(initialState: false); - Task performTask = PerformTaskAsync(backupOperation); - Task cancelTask = CancelTaskAsync(backupOperation, sqlTask.TokenSource.Token, backupCompletedEvent); - Task 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; + backupOperation.Execute(TaskExecutionMode.Script); } /// /// Async task to execute backup /// - /// + /// /// - private async Task PerformTaskAsync(IBackupOperation backupOperation) + internal async Task PerformBackupTaskAsync(SqlTask sqlTask) { + IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation; + TaskResult result = new TaskResult(); + // Create a task to perform backup - return await Task.Factory.StartNew(() => + await Task.Factory.StartNew(() => { - TaskResult result = new TaskResult(); - try + if (backupOperation != null) { - backupOperation.PerformBackup(); - result.TaskStatus = SqlTaskStatus.Succeeded; - } - catch (Exception ex) - { - result.TaskStatus = SqlTaskStatus.Failed; - result.ErrorMessage = ex.Message; - if (ex.InnerException != null) + try { - 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; } /// /// Async task to cancel backup /// - /// - /// - /// + /// /// - private async Task CancelTaskAsync(IBackupOperation backupOperation, CancellationToken token, AutoResetEvent backupCompletedEvent) + internal async Task CancelBackupTaskAsync(SqlTask sqlTask) { - // Create a task for backup cancellation request - return await Task.Factory.StartNew(() => - { - TaskResult result = new TaskResult(); - WaitHandle[] waitHandles = new WaitHandle[2] - { - backupCompletedEvent, - token.WaitHandle - }; + IBackupOperation backupOperation = sqlTask.TaskMetadata.Data as IBackupOperation; + TaskResult result = new TaskResult(); - WaitHandle.WaitAny(waitHandles); - try + await Task.Factory.StartNew(() => + { + if (backupOperation != null) { - backupOperation.CancelBackup(); - result.TaskStatus = SqlTaskStatus.Canceled; + try + { + 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.ErrorMessage = ex.Message; } - - return result; }); + + return result; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs index ac8690b9..7c3d51bd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/RestoreOperation/RestoreDatabaseHelper.cs @@ -32,7 +32,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation /// internal async Task 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; TaskResult taskResult = null; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index d5c0e055..7e974337 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -3277,27 +3277,27 @@ namespace Microsoft.SqlTools.ServiceLayer } } - public static string Backup_TaskName + public static string BackupTaskName { get { - return Keys.GetString(Keys.Backup_TaskName); + return Keys.GetString(Keys.BackupTaskName); } } - public static string Task_InProgress + public static string TaskInProgress { get { - return Keys.GetString(Keys.Task_InProgress); + return Keys.GetString(Keys.TaskInProgress); } } - public static string Task_Completed + public static string TaskCompleted { 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) { 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 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"; @@ -4901,6 +4909,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string TheLastBackupTaken = "TheLastBackupTaken"; + public const string ScriptTaskName = "ScriptTaskName"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index bfd644fa..0822ea24 100755 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1811,15 +1811,15 @@ No Applicable Filegroup - + Backup Database - + In progress - + Completed @@ -1915,4 +1915,8 @@ The last backup taken ({0}) + + scripting + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 08c6e99b..01122b85 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -805,12 +805,12 @@ prototype_file_noApplicableFileGroup = No Applicable Filegroup ############################################################################ # Backup Service -Backup_TaskName = Backup Database +BackupTaskName = Backup Database ############################################################################ # Task Service -Task_InProgress = In progress -Task_Completed = Completed +TaskInProgress = In progress +TaskCompleted = Completed ########################################################################### # Restore @@ -836,4 +836,8 @@ RestoreBackupSetFinishDate = Finish Date RestoreBackupSetSize = Size RestoreBackupSetUserName = User Name RestoreBackupSetExpiration = Expiration -TheLastBackupTaken = The last backup taken ({0}) \ No newline at end of file +TheLastBackupTaken = The last backup taken ({0}) + +############################################################################ +# Generate Script +ScriptTaskName = scripting \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 23be7182..734bb2e8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -2110,20 +2110,6 @@ Result set has too many rows to be safely loaded Result set has too many rows to be safely loaded - - Backup Database - Backup Database - - - - In progress - In progress - - - - Completed - Completed - Parameterization Parameterization @@ -2244,6 +2230,26 @@ The last backup taken ({0}) + + Backup Database + Backup Database + + + + In progress + In progress + + + + Completed + Completed + + + + scripting + scripting + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskInfo.cs index 570efba4..2f7822a8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskInfo.cs @@ -20,6 +20,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts /// public SqlTaskStatus Status { get; set; } + /// + /// Task execution mode + /// + public TaskExecutionMode TaskExecutionMode { get; set; } + /// /// Database server name this task is created for /// @@ -30,7 +35,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts /// public string DatabaseName { get; set; } - /// /// Task name which defines the type of the task (e.g. CreateDatabase, Backup) /// @@ -52,5 +56,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts /// public string Description { get; set; } + /// + /// Defines if the task can be canceled + /// + public bool IsCancelable { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskProgress.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskProgress.cs index 26648903..23c5eb47 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskProgress.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/Contracts/TaskProgress.cs @@ -18,10 +18,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices.Contracts public SqlTaskStatus Status { get; set; } /// - /// Database server name this task is created for + /// Progress message /// public string Message { get; set; } + /// + /// Script for the task execution + /// + public string Script { get; set; } + /// /// The number of millisecond the task was running /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/ITaskOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/ITaskOperation.cs new file mode 100644 index 00000000..1f8eb862 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/ITaskOperation.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.SqlTools.ServiceLayer.TaskServices +{ + /// + /// Defines interface for task operations + /// + public interface ITaskOperation + { + /// + /// Execute a task + /// + /// Task execution mode (e.g. script or execute) + void Execute(TaskExecutionMode mode); + + /// + /// Cancel a task + /// + void Cancel(); + } + + /// + /// Defines interface for scriptable task operations + /// + public interface IScriptableTaskOperation: ITaskOperation + { + /// + /// Script for the task operation + /// + string ScriptContent { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs index 98697689..6e4628b9 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/SqlTask.cs @@ -30,6 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices private SqlTaskStatus status = SqlTaskStatus.NotStarted; private DateTime stopTime; + public event EventHandler> ScriptAdded; public event EventHandler> MessageAdded; public event EventHandler> StatusChanged; @@ -119,7 +120,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices /// internal async Task RunAndCancel() { - AddMessage(SR.Task_InProgress, SqlTaskStatus.InProgress, true); + AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true); TaskResult taskResult = new TaskResult(); Task performTask = TaskToRun(this); @@ -129,15 +130,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices { if (TaskToCancel != null) { - AutoResetEvent backupCompletedEvent = new AutoResetEvent(initialState: false); - Task cancelTask = Task.Run(() => CancelTaskAsync(TokenSource.Token, backupCompletedEvent)); + AutoResetEvent onCompletedEvent = new AutoResetEvent(initialState: false); + Task cancelTask = Task.Run(() => CancelTaskAsync(TokenSource.Token, onCompletedEvent)); completedTask = await Task.WhenAny(performTask, cancelTask); // Release the cancelTask if (completedTask == performTask) { - backupCompletedEvent.Set(); + onCompletedEvent.Set(); } } else @@ -145,7 +146,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices 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); taskResult = completedTask.Result; @@ -174,16 +175,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices /// /// /// - /// + /// /// - private async Task CancelTaskAsync(CancellationToken token, AutoResetEvent backupCompletedEvent) + private async Task CancelTaskAsync(CancellationToken token, AutoResetEvent onCompletedEvent) { // Create a task for backup cancellation request TaskResult result = new TaskResult(); WaitHandle[] waitHandles = new WaitHandle[2] { - backupCompletedEvent, + onCompletedEvent, token.WaitHandle }; @@ -371,6 +372,26 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices IsCancelRequested = true; } + /// + /// Add script result of the operation + /// + /// Script content + /// Error occured during script + /// Status of the script + /// + public TaskScript AddScript(SqlTaskStatus status, string script, string errorMessage = null) + { + var newScript = new TaskScript + { + Status = status, + Script = script, + ErrorMessage = errorMessage + }; + + OnScriptAdded(new TaskEventArgs(newScript, this)); + return newScript; + } + /// /// Adds a new message to the task messages /// @@ -430,6 +451,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices ServerName = TaskMetadata.ServerName, Name = TaskMetadata.Name, Description = TaskMetadata.Description, + TaskExecutionMode = TaskMetadata.TaskExecutionMode, + IsCancelable = TaskMetadata.IsCancelable, }; } @@ -465,6 +488,15 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices } } + private void OnScriptAdded(TaskEventArgs e) + { + var handler = ScriptAdded; + if (handler != null) + { + handler(this, e); + } + } + private void OnMessageAdded(TaskEventArgs e) { var handler = MessageAdded; diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskExecutionMode.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskExecutionMode.cs new file mode 100644 index 00000000..03da21ae --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskExecutionMode.cs @@ -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 +{ + /// + /// Task execution mode + /// + public enum TaskExecutionMode + { + /// + /// Execute task + /// + Execute, + + /// + /// Script task + /// + Script, + + /// + /// Execute and script task + /// Needed for tasks that will show the script when execution completes + /// + ExecuteAndScript + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskMetadata.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskMetadata.cs index 3481a6ca..ca485524 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskMetadata.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskMetadata.cs @@ -17,6 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices /// public string Name { get; set; } + /// + /// Task execution mode (e.g. execute or script) + /// + public TaskExecutionMode TaskExecutionMode { get; set; } + /// /// The number of seconds to wait before canceling the task. /// This is a optional field and 0 or negative numbers means no timeout @@ -41,6 +46,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices /// /// Data required to perform the task /// - public object Data { get; set; } + public object Data { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskScript.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskScript.cs new file mode 100644 index 00000000..4c40d6b3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskScript.cs @@ -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 +{ + /// + /// Task script message + /// + public class TaskScript + { + /// + /// Status of script + /// + public SqlTaskStatus Status { get; set; } + + /// + /// Script content + /// + public string Script { get; set; } + + /// + /// Error occurred during script + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskService.cs b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskService.cs index 5cb69fcf..15f9b513 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/TaskServices/TaskService.cs @@ -116,6 +116,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices if (sqlTask != null) { TaskInfo taskInfo = sqlTask.ToTaskInfo(); + sqlTask.ScriptAdded += OnTaskScriptAdded; sqlTask.MessageAdded += OnTaskMessageAdded; sqlTask.StatusChanged += OnTaskStatusChanged; await serviceHost.SendEvent(TaskCreatedNotification.Type, taskInfo); @@ -131,7 +132,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices { TaskId = sqlTask.TaskId.ToString(), Status = e.TaskData - }; if (sqlTask.IsCompleted) @@ -141,6 +141,23 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices await serviceHost.SendEvent(TaskStatusChangedNotification.Type, progressInfo); } } + + private async void OnTaskScriptAdded(object sender, TaskEventArgs 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 e) { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs index 42013e54..b214146b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/BackupServiceTests.cs @@ -53,7 +53,8 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; "; 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 (p => p.BackupConfigInfo.RecoveryModel != string.Empty @@ -63,115 +64,106 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; "; testDb.Cleanup(); } - /// Test is failing in code coverage runs. Reenable when stable. - ///[Fact] + /// + /// Create simple backup test + /// + [Fact] public void CreateBackupTest() { + 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); - - // Initialize backup service DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); - - // Get default backup path - BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); - string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak"); - BackupInfo backupInfo = CreateBackupInfo(databaseName, + string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn); + + BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName, BackupType.Full, - new List(){ backupPath }, - new Dictionary(){{ backupPath, (int)DeviceType.File }}); - - var backupParams = new BackupParams - { - OwnerUri = liveConnection.ConnectionInfo.OwnerUri, - BackupInfo = backupInfo - }; + new List() { backupPath }, + new Dictionary() { { backupPath, (int)DeviceType.File } }); + BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn); // Backup the database - BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); - DisasterRecoveryService.Instance.PerformBackup(backupOperation); - - // Remove the backup file - if (File.Exists(backupPath)) - { - File.Delete(backupPath); - } + service.PerformBackup(backupOperation); - // Clean up the database + VerifyAndCleanBackup(backupPath); 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() { backupPath }, + new Dictionary() { { 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(); + } + + /// /// Test creating backup with advanced options set. /// [Fact] public void CreateBackupWithAdvancedOptionsTest() { + DisasterRecoveryService service = new DisasterRecoveryService(); string databaseName = "testbackup_" + new Random().Next(10000000, 99999999); 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); - - // Initialize backup service DatabaseTaskHelper helper = AdminService.CreateDatabaseTaskHelper(liveConnection.ConnectionInfo, databaseExists: true); SqlConnection sqlConn = DisasterRecoveryService.GetSqlConnection(liveConnection.ConnectionInfo); + string backupPath = GetDefaultBackupPath(service, databaseName, helper.DataContainer, sqlConn); - // Get default backup path - Console.WriteLine("Get default backup path.."); - BackupConfigInfo backupConfigInfo = DisasterRecoveryService.Instance.GetBackupConfigInfo(helper.DataContainer, sqlConn, sqlConn.Database); - string backupPath = Path.Combine(backupConfigInfo.DefaultBackupFolder, databaseName + ".bak"); + string certificateName = CreateCertificate(testDb); + string cleanupCertificateQuery = string.Format(CleanupCertificateQueryFormat, certificateName); - BackupInfo backupInfo = CreateBackupInfo(databaseName, + BackupInfo backupInfo = CreateDefaultBackupInfo(databaseName, BackupType.Full, new List(){ backupPath }, new Dictionary(){{ backupPath, (int)DeviceType.File }}); - - // Set advanced options backupInfo.ContinueAfterError = true; backupInfo.FormatMedia = true; backupInfo.SkipTapeHeader = true; backupInfo.Initialize = true; backupInfo.MediaName = "backup test media"; backupInfo.MediaDescription = "backup test"; - backupInfo.RetainDays = 90; + backupInfo.RetainDays = 90; backupInfo.CompressionOption = (int)BackupCompressionOptions.On; - - // Set encryption backupInfo.EncryptionAlgorithm = (int)BackupEncryptionAlgorithm.Aes128; backupInfo.EncryptorType = (int)BackupEncryptorType.ServerCertificate; backupInfo.EncryptorName = certificateName; - var backupParams = new BackupParams - { - OwnerUri = liveConnection.ConnectionInfo.OwnerUri, - BackupInfo = backupInfo - }; - + BackupOperation backupOperation = CreateBackupOperation(service, liveConnection.ConnectionInfo.OwnerUri, backupInfo, helper.DataContainer, sqlConn); + // Backup the database Console.WriteLine("Perform backup operation.."); - BackupOperation backupOperation = DisasterRecoveryService.Instance.SetBackupInput(helper.DataContainer, sqlConn, backupParams.BackupInfo); - DisasterRecoveryService.Instance.PerformBackup(backupOperation); - - // Verify backup file is created - Assert.True(File.Exists(backupPath)); + service.PerformBackup(backupOperation); // Remove the backup file - Console.WriteLine("Remove backup file.."); - if (File.Exists(backupPath)) - { - File.Delete(backupPath); - } + Console.WriteLine("Verify the backup file exists and remove.."); + VerifyAndCleanBackup(backupPath); // Delete 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(); } - private BackupInfo CreateBackupInfo(string databaseName, BackupType backupType, List backupPathList, Dictionary backupPathDevices) + /// + /// Test creating backup with advanced options set. + /// + [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() { backupPath }, + new Dictionary() { { 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 backupPathList, Dictionary backupPathDevices) { BackupInfo backupInfo = new BackupInfo(); backupInfo.BackupComponent = (int)BackupComponent.Database; @@ -196,5 +257,36 @@ CREATE CERTIFICATE {1} WITH SUBJECT = 'Backup Encryption Certificate'; "; backupInfo.SelectedFiles = ""; 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 } } \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs index 798b729b..aa766be2 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DisasterRecovery/RestoreDatabaseServiceTests.cs @@ -664,11 +664,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DisasterRecovery var backupParams = new BackupParams { OwnerUri = liveConnection.ConnectionInfo.OwnerUri, - BackupInfo = backupInfo + BackupInfo = backupInfo, + IsScripting = false }; // 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); // Clean up the database diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs index e3feccf5..8e80f853 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupOperationStub.cs @@ -7,6 +7,7 @@ using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; using System; using System.Data.SqlClient; using System.Threading; @@ -25,6 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery this.BackupSemaphore = new SemaphoreSlim(0, 1); } + public string ScriptContent { get; set; } /// /// Initialize @@ -56,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery /// /// Execute backup /// - public void PerformBackup() + public void Execute(TaskExecutionMode mode) { this.BackupSemaphore.Wait(TimeSpan.FromSeconds(5)); } @@ -64,7 +66,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery /// /// Cancel backup /// - public void CancelBackup() + public void Cancel() { } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs index c62b7111..1a3386aa 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/BackupTests.cs @@ -25,7 +25,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery DisasterRecoveryService service = new DisasterRecoveryService(); var mockBackupOperation = new Mock(); 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 => { @@ -35,7 +35,32 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery await taskToVerify; } } - + + /// + /// Generate script for backup task + /// + /// + [Fact] + public async Task VerifyScriptBackupTask() + { + using (SqlTaskManager manager = new SqlTaskManager()) + { + DisasterRecoveryService service = new DisasterRecoveryService(); + var mockBackupOperation = new Mock(); + TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object); + taskMetaData.TaskExecutionMode = TaskExecutionMode.Script; + + SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync); + Assert.NotNull(sqlTask); + Task taskToVerify = sqlTask.RunAsync().ContinueWith(Task => + { + Assert.Equal(SqlTaskStatus.Succeeded, sqlTask.TaskStatus); + }); + + await taskToVerify; + } + } + /// /// Create and run multiple backup tasks /// @@ -49,8 +74,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery var mockBackupOperation = new Mock(); TaskMetadata taskMetaData = this.CreateTaskMetaData(mockBackupOperation.Object); - SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); - SqlTask sqlTask2 = manager.CreateTask(taskMetaData, service.BackupTaskAsync); + SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync); + SqlTask sqlTask2 = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync); Assert.NotNull(sqlTask); Assert.NotNull(sqlTask2); @@ -77,10 +102,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery { using (SqlTaskManager manager = new SqlTaskManager()) { - IBackupOperation backupOperation = new BackupOperationStub(); + IBackupOperation backupOperation = new BackupOperationStub(); DisasterRecoveryService service = new DisasterRecoveryService(); TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation); - 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 => { @@ -110,8 +135,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery TaskMetadata taskMetaData = this.CreateTaskMetaData(backupOperation); TaskMetadata taskMetaData2 = this.CreateTaskMetaData(backupOperation2); - SqlTask sqlTask = manager.CreateTask(taskMetaData, service.BackupTaskAsync); - SqlTask sqlTask2 = manager.CreateTask(taskMetaData2, service.BackupTaskAsync); + SqlTask sqlTask = manager.CreateTask(taskMetaData, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync); + SqlTask sqlTask2 = manager.CreateTask(taskMetaData2, service.PerformBackupTaskAsync, service.CancelBackupTaskAsync); Assert.NotNull(sqlTask); Assert.NotNull(sqlTask2); @@ -149,11 +174,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery DisasterRecoveryService service = new DisasterRecoveryService(); IBackupOperation backupOperation = new BackupOperationStub(); 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(); 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(sqlTask2); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/DatabaseOperationStub.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/DatabaseOperationStub.cs index 6fbfc64c..aebec4b9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/DatabaseOperationStub.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/DatabaseOperationStub.cs @@ -23,6 +23,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices Failed = true; } + public TaskScript TaskScript { get; set; } + public TaskResult TaskResult { get; set; } public bool IsStopped { get; set; } @@ -62,5 +64,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices }; }); } + + public async Task 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, + + }; + }); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/SqlTaskTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/SqlTaskTests.cs index 8b0aee6c..12c1eb84 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/SqlTaskTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/SqlTaskTests.cs @@ -147,5 +147,26 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.TaskServices operation.FailTheOperation(); 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; + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/TaskManagerTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/TaskManagerTests.cs index 720252b4..4e164360 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/TaskManagerTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/TaskServices/TaskManagerTests.cs @@ -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 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; + } + + } } }