-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event driven script orchestrator #1058
Changes from all commits
d470d9f
c912d08
269dce8
720b9c4
4336c4c
254bd55
acac7fc
3f0dfac
d80bc62
11b96d4
6a4b1f6
e99bc2a
a5e0589
e4ee43c
9c0f285
4796b05
33bb1b4
9003b32
28f0ccc
87ccc83
cd5086b
d8c2f32
228af09
5bd1329
0702813
9a00c66
af4d603
e00a9d2
015fdb8
a754c9a
a377da4
3ed6087
d16a942
a13b162
051a74d
8ba4e0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using Octopus.Tentacle.Client.Scripts; | ||
using Octopus.Tentacle.Contracts; | ||
|
||
namespace Octopus.Tentacle.Client.EventDriven | ||
{ | ||
/// <summary> | ||
/// This class holds the context of where we are up to within the script execution life cycle. | ||
/// When executing a script, there are several stages it goes through (e.g. starting the script, periodically checking status for completion, completing the script). | ||
/// To be able to progress through these cycles in an event-driven environment, we need to remember some state, and then keep passing that state back into the script executor. | ||
/// </summary> | ||
public class CommandContext | ||
{ | ||
public CommandContext(ScriptTicket scriptTicket, | ||
long nextLogSequence, | ||
ScriptServiceVersion scripServiceVersionUsed) | ||
{ | ||
ScriptTicket = scriptTicket; | ||
NextLogSequence = nextLogSequence; | ||
ScripServiceVersionUsed = scripServiceVersionUsed; | ||
} | ||
|
||
public ScriptTicket ScriptTicket { get; } | ||
public long NextLogSequence { get; } | ||
public ScriptServiceVersion ScripServiceVersionUsed { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Octopus.Tentacle.Client.EventDriven; | ||
using Octopus.Tentacle.Client.Execution; | ||
using Octopus.Tentacle.Client.Observability; | ||
using Octopus.Tentacle.Client.Scripts; | ||
using Octopus.Tentacle.Client.Scripts.Models; | ||
using Octopus.Tentacle.Client.ServiceHelpers; | ||
using Octopus.Tentacle.Contracts; | ||
using Octopus.Tentacle.Contracts.Logging; | ||
using Octopus.Tentacle.Contracts.Observability; | ||
|
||
namespace Octopus.Tentacle.Client | ||
{ | ||
/// <summary> | ||
/// Executes scripts, on the best available script service. | ||
/// </summary> | ||
public class ScriptExecutor : IScriptExecutor | ||
{ | ||
readonly ITentacleClientTaskLog logger; | ||
readonly ClientOperationMetricsBuilder operationMetricsBuilder; | ||
readonly TentacleClientOptions clientOptions; | ||
readonly AllClients allClients; | ||
readonly RpcCallExecutor rpcCallExecutor; | ||
readonly TimeSpan onCancellationAbandonCompleteScriptAfter; | ||
|
||
public ScriptExecutor(AllClients allClients, | ||
ITentacleClientTaskLog logger, | ||
ITentacleClientObserver tentacleClientObserver, | ||
TentacleClientOptions clientOptions, | ||
TimeSpan onCancellationAbandonCompleteScriptAfter) | ||
: this( | ||
allClients, | ||
logger, | ||
tentacleClientObserver, | ||
// For now, we do not support operation based metrics when used outside the TentacleClient. So just plug in a builder to discard. | ||
ClientOperationMetricsBuilder.Start(), | ||
clientOptions, | ||
onCancellationAbandonCompleteScriptAfter) | ||
{ | ||
} | ||
|
||
internal ScriptExecutor(AllClients allClients, | ||
ITentacleClientTaskLog logger, | ||
ITentacleClientObserver tentacleClientObserver, | ||
ClientOperationMetricsBuilder operationMetricsBuilder, | ||
TentacleClientOptions clientOptions, | ||
TimeSpan onCancellationAbandonCompleteScriptAfter) | ||
{ | ||
this.allClients = allClients; | ||
this.logger = logger; | ||
this.clientOptions = clientOptions; | ||
this.onCancellationAbandonCompleteScriptAfter = onCancellationAbandonCompleteScriptAfter; | ||
this.operationMetricsBuilder = operationMetricsBuilder; | ||
rpcCallExecutor = RpcCallExecutorFactory.Create(this.clientOptions.RpcRetrySettings.RetryDuration, tentacleClientObserver); | ||
} | ||
|
||
public async Task<ScriptOperationExecutionResult> StartScript(ExecuteScriptCommand executeScriptCommand, | ||
StartScriptIsBeingReAttempted startScriptIsBeingReAttempted, | ||
CancellationToken cancellationToken) | ||
{ | ||
var scriptServiceVersionToUse = await DetermineScriptServiceVersionToUse(cancellationToken); | ||
|
||
var scriptExecutorFactory = CreateScriptExecutorFactory(); | ||
var scriptExecutor = scriptExecutorFactory.CreateScriptExecutor(scriptServiceVersionToUse); | ||
|
||
return await scriptExecutor.StartScript(executeScriptCommand, startScriptIsBeingReAttempted, cancellationToken); | ||
} | ||
|
||
public async Task<ScriptOperationExecutionResult> GetStatus(CommandContext ticketForNextNextStatus, CancellationToken cancellationToken) | ||
{ | ||
var scriptExecutorFactory = CreateScriptExecutorFactory(); | ||
var scriptExecutor = scriptExecutorFactory.CreateScriptExecutor(ticketForNextNextStatus.ScripServiceVersionUsed); | ||
|
||
return await scriptExecutor.GetStatus(ticketForNextNextStatus, cancellationToken); | ||
} | ||
|
||
public async Task<ScriptOperationExecutionResult> CancelScript(CommandContext ticketForNextNextStatus) | ||
{ | ||
var scriptExecutorFactory = CreateScriptExecutorFactory(); | ||
var scriptExecutor = scriptExecutorFactory.CreateScriptExecutor(ticketForNextNextStatus.ScripServiceVersionUsed); | ||
|
||
return await scriptExecutor.CancelScript(ticketForNextNextStatus); | ||
} | ||
|
||
public async Task<ScriptStatus?> CompleteScript(CommandContext ticketForNextNextStatus, CancellationToken cancellationToken) | ||
{ | ||
var scriptExecutorFactory = CreateScriptExecutorFactory(); | ||
var scriptExecutor = scriptExecutorFactory.CreateScriptExecutor(ticketForNextNextStatus.ScripServiceVersionUsed); | ||
|
||
return await scriptExecutor.CompleteScript(ticketForNextNextStatus, cancellationToken); | ||
} | ||
|
||
ScriptExecutorFactory CreateScriptExecutorFactory() | ||
{ | ||
return new ScriptExecutorFactory(allClients, | ||
rpcCallExecutor, | ||
operationMetricsBuilder, | ||
onCancellationAbandonCompleteScriptAfter, | ||
clientOptions, | ||
logger); | ||
} | ||
|
||
async Task<ScriptServiceVersion> DetermineScriptServiceVersionToUse(CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
var scriptServiceVersionSelector = new ScriptServiceVersionSelector(allClients.CapabilitiesServiceV2, logger, rpcCallExecutor, clientOptions, operationMetricsBuilder); | ||
return await scriptServiceVersionSelector.DetermineScriptServiceVersionToUse(cancellationToken); | ||
} | ||
catch (Exception ex) when (cancellationToken.IsCancellationRequested) | ||
{ | ||
throw new OperationCanceledException("Script execution was cancelled", ex); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Octopus.Tentacle.Client.EventDriven; | ||
using Octopus.Tentacle.Client.Scripts.Models; | ||
using Octopus.Tentacle.Contracts; | ||
|
||
namespace Octopus.Tentacle.Client.Scripts | ||
{ | ||
public interface IScriptExecutor | ||
{ | ||
/// <summary> | ||
/// Start the script. | ||
/// </summary> | ||
/// <returns>The result, which includes the CommandContext for the next command</returns> | ||
Task<ScriptOperationExecutionResult> StartScript(ExecuteScriptCommand command, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might make sense to document that the resulting CommandContext should be used for the next call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
StartScriptIsBeingReAttempted startScriptIsBeingReAttempted, | ||
CancellationToken scriptExecutionCancellationToken); | ||
|
||
/// <summary> | ||
/// Get the status. | ||
/// </summary> | ||
/// <param name="commandContext">The CommandContext from the previous command</param> | ||
/// <param name="scriptExecutionCancellationToken"></param> | ||
/// <returns>The result, which includes the CommandContext for the next command</returns> | ||
Task<ScriptOperationExecutionResult> GetStatus(CommandContext commandContext, CancellationToken scriptExecutionCancellationToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think I actually preferred the tuple because the responses where not something that are really coupled AND I think it improved readability. ie it was clear each method was returning a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🤔 I would have thought the property named ContextForNextCommand would be good enough. Plus, adding the documentation from the previous comment should be more than enough. I personally don't like tuples at all, and feel this is much easier to understand. For now, let's leave it the way it is, and if we find it unhelpful we can address it then. |
||
|
||
/// <summary> | ||
/// Cancel the script. | ||
/// </summary> | ||
/// <param name="commandContext">The CommandContext from the previous command</param> | ||
/// <returns>The result, which includes the CommandContext for the next command</returns> | ||
Task<ScriptOperationExecutionResult> CancelScript(CommandContext commandContext); | ||
|
||
/// <summary> | ||
/// Complete the script. | ||
/// </summary> | ||
/// <param name="commandContext">The CommandContext from the previous command</param> | ||
/// <param name="scriptExecutionCancellationToken"></param> | ||
Task<ScriptStatus?> CompleteScript(CommandContext commandContext, CancellationToken scriptExecutionCancellationToken); | ||
} | ||
} |
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider documenting what this class is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Done