mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user