Skip to content

Commit

Permalink
Event driven script orchestrator (#1058)
Browse files Browse the repository at this point in the history
Event driven  script orchestrator

Co-authored-by: Luke Butters <[email protected]>
Co-authored-by: Samdanae Imran <[email protected]>
  • Loading branch information
3 people authored Jan 21, 2025
1 parent 930b260 commit 67b07ca
Show file tree
Hide file tree
Showing 20 changed files with 648 additions and 459 deletions.
26 changes: 26 additions & 0 deletions source/Octopus.Tentacle.Client/EventDriven/CommandContext.cs
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; }
}
}
118 changes: 118 additions & 0 deletions source/Octopus.Tentacle.Client/ScriptExecutor.cs
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);
}
}
}
}
42 changes: 42 additions & 0 deletions source/Octopus.Tentacle.Client/Scripts/IScriptExecutor.cs
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,
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);

/// <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);
}
}
12 changes: 0 additions & 12 deletions source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs

This file was deleted.

This file was deleted.

Loading

0 comments on commit 67b07ca

Please sign in to comment.