generic way to support scriptable operations (#438)

* implemented a generic way to support scriptable operations
This commit is contained in:
Leila Lali
2017-08-18 16:12:00 -07:00
committed by GitHub
parent 3915688332
commit 39dedd88e0
25 changed files with 953 additions and 289 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
//
// 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
{
@@ -19,6 +20,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// Cancel a task
/// </summary>
void Cancel();
/// <summary>
/// If an error occurred during task execution, this field contains the error message text
/// </summary>
string ErrorMessage { get; }
/// <summary>
/// The sql task that's executing the operation
/// </summary>
SqlTask SqlTask { get; set; }
}
/// <summary>

View File

@@ -0,0 +1,146 @@
//
// 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.Text;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
/// <summary>
/// Any SMO operation that supports scripting should implement this class.
/// It provides all of the configuration needed to choose between scripting or execution mode,
/// hook into the Task manager framework, and send success / completion notifications to the caller.
/// </summary>
public abstract class SmoScriptableTaskOperation : IScriptableTaskOperation
{
/// <summary>
/// Script content
/// </summary>
public string ScriptContent
{
get; set;
}
/// <summary>
/// If an error occurred during task execution, this field contains the error message text
/// </summary>
public abstract string ErrorMessage { get; }
/// <summary>
/// SMO Server instance used for the operation
/// </summary>
public abstract Server Server { get; }
/// <summary>
/// Cancels the operation
/// </summary>
public abstract void Cancel();
/// <summary>
/// Updates messages in sql task given new progress message
/// </summary>
/// <param name="message"></param>
public void OnMessageAdded(TaskMessage message)
{
if (this.SqlTask != null)
{
this.SqlTask.AddMessage(message.Description, message.Status);
}
}
/// <summary>
/// Updates scripts in sql task given new script
/// </summary>
/// <param name="script"></param>
public void OnScriptAdded(TaskScript script)
{
this.SqlTask.AddScript(script.Status, script.Script, script.ErrorMessage);
}
/// <summary>
/// Executes the operations
/// </summary>
public abstract void Execute();
/// <summary>
/// Execute the operation for given execution mode
/// </summary>
/// <param name="mode"></param>
public virtual void Execute(TaskExecutionMode mode)
{
var currentExecutionMode = Server.ConnectionContext.SqlExecutionModes;
try
{
if (Server != null)
{
Server.ConnectionContext.CapturedSql.Clear();
switch (mode)
{
case TaskExecutionMode.Execute:
{
Server.ConnectionContext.SqlExecutionModes = SqlExecutionModes.ExecuteSql;
break;
}
case TaskExecutionMode.ExecuteAndScript:
{
Server.ConnectionContext.SqlExecutionModes = SqlExecutionModes.ExecuteAndCaptureSql;
break;
}
case TaskExecutionMode.Script:
{
Server.ConnectionContext.SqlExecutionModes = SqlExecutionModes.CaptureSql;
break;
}
}
}
Execute();
if (mode == TaskExecutionMode.Script || mode == TaskExecutionMode.ExecuteAndScript)
{
this.ScriptContent = GetScriptContent();
if (SqlTask != null)
{
OnScriptAdded(new TaskScript
{
Status = SqlTaskStatus.Succeeded,
Script = this.ScriptContent
});
}
}
}
catch
{
throw;
}
finally
{
Server.ConnectionContext.CapturedSql.Clear();
Server.ConnectionContext.SqlExecutionModes = currentExecutionMode;
}
}
private string GetScriptContent()
{
StringBuilder sb = new StringBuilder();
foreach (String s in this.Server.ConnectionContext.CapturedSql.Text)
{
sb.Append(s);
sb.Append(Environment.NewLine);
}
return sb.ToString();
}
/// <summary>
/// The sql task to run the operations
/// </summary>
public SqlTask SqlTask { get; set; }
}
}

View File

@@ -34,6 +34,14 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
public event EventHandler<TaskEventArgs<TaskMessage>> MessageAdded;
public event EventHandler<TaskEventArgs<SqlTaskStatus>> StatusChanged;
/// <summary>
/// Default constructor to create the geenric type. calling Initialize method is required after creating
/// the insance
/// </summary>
public SqlTask()
{
}
/// <summary>
/// Creates new instance of SQL task
/// </summary>
@@ -41,10 +49,21 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <param name="taskToRun">The function to run to start the task</param>
public SqlTask(TaskMetadata taskMetdata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
{
Validate.IsNotNull(nameof(taskMetdata), taskMetdata);
Init(taskMetdata, taskToRun, taskToCancel);
}
/// <summary>
/// Initializes the Sql task
/// </summary>
/// <param name="taskMetadata">Task metadata</param>
/// <param name="taskToRun">The function to execute the operation</param>
/// <param name="taskToCancel">The function to cancel the operation</param>
public virtual void Init(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
{
Validate.IsNotNull(nameof(taskMetadata), taskMetadata);
Validate.IsNotNull(nameof(taskToRun), taskToRun);
TaskMetadata = taskMetdata;
TaskMetadata = taskMetadata;
TaskToRun = taskToRun;
TaskToCancel = taskToCancel;
StartTime = DateTime.UtcNow;
@@ -120,8 +139,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <returns></returns>
internal async Task<TaskResult> RunAndCancel()
{
TokenSource = new CancellationTokenSource();
AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true);
TaskResult taskResult = new TaskResult();
Task<TaskResult> performTask = TaskToRun(this);
Task<TaskResult> completedTask = null;
@@ -452,7 +472,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
Name = TaskMetadata.Name,
Description = TaskMetadata.Description,
TaskExecutionMode = TaskMetadata.TaskExecutionMode,
IsCancelable = TaskMetadata.IsCancelable,
IsCancelable = this.TaskToCancel != null,
};
}

View File

@@ -8,6 +8,7 @@ using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
@@ -83,19 +84,58 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <param name="taskMetadata">Task Metadata</param>
/// <param name="taskToRun">The function to run the operation</param>
/// <param name="taskToCancel">The function to cancel the operation</param>
/// <returns></returns>
public SqlTask CreateTask(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
/// <returns>The new sql task</returns>
public SqlTask CreateTask(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
{
return CreateTask<SqlTask>(taskMetadata, taskToRun, taskToCancel);
}
/// <summary>
/// Creates a new task
/// </summary>
/// <param name="taskMetadata">Task Metadata</param>
/// <returns>The new sql task</returns>
public SqlTask CreateTask<T>(TaskMetadata taskMetadata) where T : SqlTask, new()
{
Validate.IsNotNull(nameof(taskMetadata), taskMetadata);
return CreateTask<T>(taskMetadata, TaskOperationHelper.ExecuteTaskAsync, TaskOperationHelper.CancelTaskAsync);
}
/// <summary>
/// Creates a new task
/// </summary>
/// <param name="taskMetadata">Task Metadata</param>
/// <param name="taskToRun">The function to run the operation</param>
/// <param name="taskToCancel">The function to cancel the operation</param>
/// <returns>The new sql task</returns>
public SqlTask CreateTask<T>(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel) where T : SqlTask, new()
{
ValidateNotDisposed();
var newtask = new SqlTask(taskMetadata, taskToRun, taskToCancel);
var newTask = new T();
newTask.Init(taskMetadata, taskToRun, taskToCancel);
if (taskMetadata != null && taskMetadata.TaskOperation != null)
{
taskMetadata.TaskOperation.SqlTask = newTask;
}
lock (lockObject)
{
tasks.AddOrUpdate(newtask.TaskId, newtask, (key, oldValue) => newtask);
tasks.AddOrUpdate(newTask.TaskId, newTask, (key, oldValue) => newTask);
}
OnTaskAdded(new TaskEventArgs<SqlTask>(newtask));
return newtask;
OnTaskAdded(new TaskEventArgs<SqlTask>(newTask));
return newTask;
}
/// <summary>
/// Creates a new task
/// </summary>
/// <param name="taskMetadata">Task Metadata</param>
/// <param name="taskToRun">The function to run the operation</param>
/// <returns>The new sql task</returns>
public SqlTask CreateTask(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun)
{
return CreateTask<SqlTask>(taskMetadata, taskToRun);
}
/// <summary>
@@ -104,9 +144,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <param name="taskMetadata">Task Metadata</param>
/// <param name="taskToRun">The function to run the operation</param>
/// <returns></returns>
public SqlTask CreateTask(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun)
public SqlTask CreateTask<T>(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun) where T : SqlTask, new()
{
return CreateTask(taskMetadata, taskToRun, null);
return CreateTask<T>(taskMetadata, taskToRun, null);
}
/// <summary>
@@ -118,7 +158,26 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <returns></returns>
public SqlTask CreateAndRun(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel)
{
var sqlTask = CreateTask(taskMetadata, taskToRun, taskToCancel);
return CreateAndRun<SqlTask>(taskMetadata, taskToRun, taskToCancel);
}
public SqlTask CreateAndRun<T>(TaskMetadata taskMetadata) where T : SqlTask, new()
{
var sqlTask = CreateTask<T>(taskMetadata);
sqlTask.Run();
return sqlTask;
}
/// <summary>
/// Creates a new task and starts the task
/// </summary>
/// <param name="taskMetadata">Task Metadata</param>
/// <param name="taskToRun">The function to run the operation</param>
/// <param name="taskToCancel">The function to cancel the operation</param>
/// <returns></returns>
public SqlTask CreateAndRun<T>(TaskMetadata taskMetadata, Func<SqlTask, Task<TaskResult>> taskToRun, Func<SqlTask, Task<TaskResult>> taskToCancel) where T : SqlTask, new()
{
var sqlTask = CreateTask<T>(taskMetadata, taskToRun, taskToCancel);
sqlTask.Run();
return sqlTask;
}

View File

@@ -3,6 +3,9 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
public class TaskMetadata
@@ -28,11 +31,6 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// </summary>
public int Timeout { get; set; }
/// <summary>
/// Defines if the task can be canceled
/// </summary>
public bool IsCancelable { get; set; }
/// <summary>
/// Database server name this task is created for
/// </summary>
@@ -46,6 +44,52 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
/// <summary>
/// Data required to perform the task
/// </summary>
public object Data { get; set; }
public ITaskOperation TaskOperation { get; set; }
/// <summary>
/// Creates task metadata given the request parameters
/// </summary>
/// <param name="requestParam">Request parameters</param>
/// <param name="taskName">Task name</param>
/// <param name="taskOperation">Task operation</param>
/// <param name="connectionService">Connection Service</param>
/// <returns>Task metadata</returns>
public static TaskMetadata Create(IRequestParams requestParam, string taskName, ITaskOperation taskOperation, ConnectionService connectionService)
{
TaskMetadata taskMetadata = new TaskMetadata();
ConnectionInfo connInfo;
connectionService.TryFindConnection(
requestParam.OwnerUri,
out connInfo);
if (connInfo != null)
{
taskMetadata.ServerName = connInfo.ConnectionDetails.ServerName;
}
if (!string.IsNullOrEmpty(requestParam.DatabaseName))
{
taskMetadata.DatabaseName = requestParam.DatabaseName;
}
else if (connInfo != null)
{
taskMetadata.DatabaseName = connInfo.ConnectionDetails.DatabaseName;
}
IScriptableRequestParams scriptableRequestParams = requestParam as IScriptableRequestParams;
if (scriptableRequestParams != null && scriptableRequestParams.TaskExecutionMode != TaskExecutionMode.Execute)
{
taskMetadata.Name = string.Format("{0} {1}", taskName, SR.ScriptTaskName);
}
else
{
taskMetadata.Name = taskName;
}
taskMetadata.TaskExecutionMode = scriptableRequestParams.TaskExecutionMode;
taskMetadata.TaskOperation = taskOperation;
return taskMetadata;
}
}
}

View File

@@ -0,0 +1,113 @@
//
// 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.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
/// <summary>
/// Helper class for task operations
/// </summary>
public static class TaskOperationHelper
{
/// <summary>
/// Async method to execute the operation
/// </summary>
/// <param name="sqlTask">Sql Task</param>
/// <returns>Task Result</returns>
public static async Task<TaskResult> ExecuteTaskAsync(SqlTask sqlTask)
{
sqlTask.AddMessage(SR.TaskInProgress, SqlTaskStatus.InProgress, true);
ITaskOperation taskOperation = sqlTask.TaskMetadata.TaskOperation as ITaskOperation;
TaskResult taskResult = null;
if (taskOperation != null)
{
taskOperation.SqlTask = sqlTask;
return await Task.Factory.StartNew(() =>
{
TaskResult result = new TaskResult();
try
{
if (string.IsNullOrEmpty(taskOperation.ErrorMessage))
{
taskOperation.Execute(sqlTask.TaskMetadata.TaskExecutionMode);
result.TaskStatus = SqlTaskStatus.Succeeded;
}
else
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = taskOperation.ErrorMessage;
}
}
catch (Exception ex)
{
result.TaskStatus = SqlTaskStatus.Failed;
result.ErrorMessage = ex.Message;
if (ex.InnerException != null)
{
result.ErrorMessage += Environment.NewLine + ex.InnerException.Message;
}
if (taskOperation != null && taskOperation.ErrorMessage != null)
{
result.ErrorMessage += Environment.NewLine + taskOperation.ErrorMessage;
}
}
return result;
});
}
else
{
taskResult = new TaskResult();
taskResult.TaskStatus = SqlTaskStatus.Failed;
}
return taskResult;
}
/// <summary>
/// Async method to cancel the operations
/// </summary>
public static async Task<TaskResult> CancelTaskAsync(SqlTask sqlTask)
{
ITaskOperation taskOperation = sqlTask.TaskMetadata.TaskOperation as ITaskOperation;
TaskResult taskResult = null;
if (taskOperation != null)
{
return await Task.Factory.StartNew(() =>
{
try
{
taskOperation.Cancel();
return new TaskResult
{
TaskStatus = SqlTaskStatus.Canceled
};
}
catch (Exception ex)
{
return new TaskResult
{
TaskStatus = SqlTaskStatus.Failed,
ErrorMessage = ex.Message
};
}
});
}
else
{
taskResult = new TaskResult();
taskResult.TaskStatus = SqlTaskStatus.Failed;
}
return taskResult;
}
}
}

View File

@@ -17,18 +17,9 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
public class TaskService: HostedService<TaskService>, IComposableService
{
private static readonly Lazy<TaskService> instance = new Lazy<TaskService>(() => new TaskService());
private SqlTaskManager taskManager = SqlTaskManager.Instance;
private SqlTaskManager taskManager = null;
private IProtocolEndpoint serviceHost;
/// <summary>
/// Default, parameterless constructor.
/// </summary>
public TaskService()
{
taskManager.TaskAdded += OnTaskAdded;
}
/// <summary>
/// Gets the singleton instance object
/// </summary>
@@ -44,8 +35,16 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
{
get
{
if(taskManager == null)
{
taskManager = SqlTaskManager.Instance;
}
return taskManager;
}
set
{
taskManager = value;
}
}
/// <summary>
@@ -57,6 +56,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
Logger.Write(LogLevel.Verbose, "TaskService initialized");
serviceHost.SetRequestHandler(ListTasksRequest.Type, HandleListTasksRequest);
serviceHost.SetRequestHandler(CancelTaskRequest.Type, HandleCancelTaskRequest);
TaskManager.TaskAdded += OnTaskAdded;
}
/// <summary>
@@ -74,7 +74,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
return Task.Factory.StartNew(() =>
{
ListTasksResponse response = new ListTasksResponse();
response.Tasks = taskManager.Tasks.Select(x => x.ToTaskInfo()).ToArray();
response.Tasks = TaskManager.Tasks.Select(x => x.ToTaskInfo()).ToArray();
return response;
});
@@ -96,7 +96,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
Guid taskId;
if (Guid.TryParse(cancelTaskParams.TaskId, out taskId))
{
taskManager.CancelTask(taskId);
TaskManager.CancelTask(taskId);
return true;
}
else
@@ -176,8 +176,8 @@ namespace Microsoft.SqlTools.ServiceLayer.TaskServices
public void Dispose()
{
taskManager.TaskAdded -= OnTaskAdded;
taskManager.Dispose();
TaskManager.TaskAdded -= OnTaskAdded;
TaskManager.Dispose();
}
}
}