Skip to content

Commit 5f862e6

Browse files
committed
restore orchestrationInvoker and powershellServices
1 parent 12ccdee commit 5f862e6

File tree

2 files changed

+186
-47
lines changed

2 files changed

+186
-47
lines changed

src/DurableSDK/OrchestrationInvoker.cs

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,58 +11,98 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
1111
using System.Linq;
1212
using System.Management.Automation;
1313

14-
using PowerShellWorker.Utility;
1514
using Microsoft.Azure.Functions.PowerShellWorker.Durable.Actions;
1615

1716
internal class OrchestrationInvoker : IOrchestrationInvoker
1817
{
19-
public Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh)
18+
private IExternalInvoker _externalInvoker;
19+
internal static string isOrchestrationFailureKey = "IsOrchestrationFailure";
20+
21+
public Hashtable Invoke(
22+
OrchestrationBindingInfo orchestrationBindingInfo,
23+
IPowerShellServices powerShellServices)
2024
{
2125
try
2226
{
23-
var outputBuffer = new PSDataCollection<object>();
24-
var context = orchestrationBindingInfo.Context;
27+
if (powerShellServices.UseExternalDurableSDK())
28+
{
29+
return InvokeExternalDurableSDK(powerShellServices);
30+
}
31+
return InvokeInternalDurableSDK(orchestrationBindingInfo, powerShellServices);
32+
}
33+
catch (Exception ex)
34+
{
35+
ex.Data.Add(isOrchestrationFailureKey, true);
36+
throw;
37+
}
38+
finally
39+
{
40+
powerShellServices.ClearStreamsAndCommands();
41+
}
42+
}
43+
44+
public Hashtable InvokeExternalDurableSDK(IPowerShellServices powerShellServices)
45+
{
46+
return _externalInvoker.Invoke(powerShellServices);
47+
}
48+
49+
public Hashtable InvokeInternalDurableSDK(
50+
OrchestrationBindingInfo orchestrationBindingInfo,
51+
IPowerShellServices powerShellServices)
52+
{
53+
var outputBuffer = new PSDataCollection<object>();
54+
var context = orchestrationBindingInfo.Context;
55+
56+
// context.History should never be null when initializing CurrentUtcDateTime
57+
var orchestrationStart = context.History.First(
58+
e => e.EventType == HistoryEventType.OrchestratorStarted);
59+
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();
2560

26-
// context.History should never be null when initializing CurrentUtcDateTime
27-
var orchestrationStart = context.History.First(
28-
e => e.EventType == HistoryEventType.OrchestratorStarted);
29-
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();
61+
// Marks the first OrchestratorStarted event as processed
62+
orchestrationStart.IsProcessed = true;
3063

31-
// Marks the first OrchestratorStarted event as processed
32-
orchestrationStart.IsProcessed = true;
33-
34-
var asyncResult = pwsh.BeginInvoke(outputBuffer);
64+
// Finish initializing the Function invocation
65+
powerShellServices.AddParameter(orchestrationBindingInfo.ParameterName, context);
66+
powerShellServices.TracePipelineObject();
3567

36-
var (shouldStop, actions) =
37-
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);
68+
var asyncResult = powerShellServices.BeginInvoke(outputBuffer);
3869

39-
if (shouldStop)
70+
var (shouldStop, actions) =
71+
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);
72+
73+
if (shouldStop)
74+
{
75+
// The orchestration function should be stopped and restarted
76+
powerShellServices.StopInvoke();
77+
// return (Hashtable)orchestrationBindingInfo.Context.OrchestrationActionCollector.output;
78+
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
79+
}
80+
else
81+
{
82+
try
4083
{
41-
// The orchestration function should be stopped and restarted
42-
pwsh.StopInvoke();
43-
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
84+
// The orchestration function completed
85+
powerShellServices.EndInvoke(asyncResult);
86+
var result = CreateReturnValueFromFunctionOutput(outputBuffer);
87+
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
4488
}
45-
else
89+
catch (Exception e)
4690
{
47-
try
48-
{
49-
// The orchestration function completed
50-
pwsh.EndInvoke(asyncResult);
51-
var result = FunctionReturnValueBuilder.CreateReturnValueFromFunctionOutput(outputBuffer);
52-
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
53-
}
54-
catch (Exception e)
55-
{
56-
// The orchestrator code has thrown an unhandled exception:
57-
// this should be treated as an entire orchestration failure
58-
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
59-
}
91+
// The orchestrator code has thrown an unhandled exception:
92+
// this should be treated as an entire orchestration failure
93+
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
6094
}
6195
}
62-
finally
96+
}
97+
98+
public static object CreateReturnValueFromFunctionOutput(IList<object> pipelineItems)
99+
{
100+
if (pipelineItems == null || pipelineItems.Count <= 0)
63101
{
64-
pwsh.ClearStreamsAndCommands();
102+
return null;
65103
}
104+
105+
return pipelineItems.Count == 1 ? pipelineItems[0] : pipelineItems.ToArray();
66106
}
67107

68108
private static Hashtable CreateOrchestrationResult(
@@ -72,7 +112,12 @@ private static Hashtable CreateOrchestrationResult(
72112
object customStatus)
73113
{
74114
var orchestrationMessage = new OrchestrationMessage(isDone, actions, output, customStatus);
75-
return new Hashtable { { AzFunctionInfo.DollarReturn, orchestrationMessage } };
115+
return new Hashtable { { "$return", orchestrationMessage } };
116+
}
117+
118+
public void SetExternalInvoker(IExternalInvoker externalInvoker)
119+
{
120+
_externalInvoker = externalInvoker;
76121
}
77122
}
78123
}

src/DurableSDK/PowerShellServices.cs

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,144 @@
66
namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
77
{
88
using System;
9+
using System.Collections.ObjectModel;
10+
using System.Linq;
911
using System.Management.Automation;
10-
using PowerShell;
12+
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
13+
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
14+
using Newtonsoft.Json;
1115

1216
internal class PowerShellServices : IPowerShellServices
1317
{
14-
private const string SetFunctionInvocationContextCommand =
15-
"Microsoft.Azure.Functions.PowerShellWorker\\Set-FunctionInvocationContext";
18+
private readonly string SetFunctionInvocationContextCommand;
19+
private const string ExternalDurableSDKName = "DurableSDK";
20+
private const string InternalDurableSDKName = "Microsoft.Azure.Functions.PowerShellWorker";
1621

1722
private readonly PowerShell _pwsh;
18-
private bool _hasSetOrchestrationContext = false;
23+
private bool _hasInitializedDurableFunction = false;
24+
private readonly bool _useExternalDurableSDK = false;
1925

2026
public PowerShellServices(PowerShell pwsh)
2127
{
28+
//This logic will be commented out until the external SDK is published on the PS Gallery
29+
30+
// We attempt to import the external SDK upon construction of the PowerShellServices object.
31+
// We maintain the boolean member _useExternalDurableSDK in this object rather than
32+
// DurableController because the expected input and functionality of SetFunctionInvocationContextCommand
33+
// may differ between the internal and external implementations.
34+
35+
try
36+
{
37+
pwsh.AddCommand(Utils.ImportModuleCmdletInfo)
38+
.AddParameter("Name", ExternalDurableSDKName)
39+
.AddParameter("ErrorAction", ActionPreference.Stop)
40+
.InvokeAndClearCommands();
41+
_useExternalDurableSDK = true;
42+
}
43+
catch (Exception e)
44+
{
45+
// Check to see if ExternalDurableSDK is among the modules imported or
46+
// available to be imported: if it is, then something went wrong with
47+
// the Import-Module statement and we should throw an Exception.
48+
// Otherwise, we use the InternalDurableSDK
49+
var availableModules = pwsh.AddCommand(Utils.GetModuleCmdletInfo)
50+
.AddParameter("Name", ExternalDurableSDKName)
51+
.InvokeAndClearCommands<PSModuleInfo>();
52+
if (availableModules.Count() > 0)
53+
{
54+
// TODO: evaluate if there is a better error message or exception type to be throwing.
55+
// Ideally, this should never happen.
56+
throw new InvalidOperationException("The external Durable SDK was detected, but unable to be imported.", e);
57+
}
58+
_useExternalDurableSDK = false;
59+
}
60+
//_useExternalDurableSDK = false;
61+
62+
if (_useExternalDurableSDK)
63+
{
64+
SetFunctionInvocationContextCommand = $"{ExternalDurableSDKName}\\Set-FunctionInvocationContext";
65+
}
66+
else
67+
{
68+
SetFunctionInvocationContextCommand = $"{InternalDurableSDKName}\\Set-FunctionInvocationContext";
69+
}
2270
_pwsh = pwsh;
2371
}
2472

73+
public bool UseExternalDurableSDK()
74+
{
75+
return _useExternalDurableSDK;
76+
}
77+
78+
public PowerShell GetPowerShell()
79+
{
80+
return this._pwsh;
81+
}
82+
2583
public void SetDurableClient(object durableClient)
2684
{
2785
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
2886
.AddParameter("DurableClient", durableClient)
2987
.InvokeAndClearCommands();
30-
31-
_hasSetOrchestrationContext = true;
88+
_hasInitializedDurableFunction = true;
3289
}
3390

34-
public void SetOrchestrationContext(OrchestrationContext orchestrationContext)
91+
public OrchestrationBindingInfo SetOrchestrationContext(
92+
ParameterBinding context,
93+
out IExternalInvoker externalInvoker)
3594
{
36-
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
37-
.AddParameter("OrchestrationContext", orchestrationContext)
38-
.InvokeAndClearCommands();
95+
externalInvoker = null;
96+
OrchestrationBindingInfo orchestrationBindingInfo = new OrchestrationBindingInfo(
97+
context.Name,
98+
JsonConvert.DeserializeObject<OrchestrationContext>(context.Data.String));
99+
100+
if (_useExternalDurableSDK)
101+
{
102+
Collection<Func<PowerShell, object>> output = _pwsh.AddCommand(SetFunctionInvocationContextCommand)
103+
// The external SetFunctionInvocationContextCommand expects a .json string to deserialize
104+
// and writes an invoker function to the output pipeline.
105+
.AddParameter("OrchestrationContext", context.Data.String)
106+
.InvokeAndClearCommands<Func<PowerShell, object>>();
107+
if (output.Count() == 1)
108+
{
109+
externalInvoker = new ExternalInvoker(output[0]);
110+
}
111+
else
112+
{
113+
throw new InvalidOperationException($"Only a single output was expected for an invocation of {SetFunctionInvocationContextCommand}");
114+
}
115+
}
116+
else
117+
{
118+
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
119+
.AddParameter("OrchestrationContext", orchestrationBindingInfo.Context)
120+
.InvokeAndClearCommands();
121+
}
122+
_hasInitializedDurableFunction = true;
123+
return orchestrationBindingInfo;
124+
}
125+
39126

40-
_hasSetOrchestrationContext = true;
127+
public void AddParameter(string name, object value)
128+
{
129+
_pwsh.AddParameter(name, value);
41130
}
42131

43132
public void ClearOrchestrationContext()
44133
{
45-
if (_hasSetOrchestrationContext)
134+
if (_hasInitializedDurableFunction)
46135
{
47136
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
48137
.AddParameter("Clear", true)
49138
.InvokeAndClearCommands();
50139
}
51140
}
52141

142+
public void TracePipelineObject()
143+
{
144+
_pwsh.AddCommand("Microsoft.Azure.Functions.PowerShellWorker\\Trace-PipelineObject");
145+
}
146+
53147
public IAsyncResult BeginInvoke(PSDataCollection<object> output)
54148
{
55149
return _pwsh.BeginInvoke<object, object>(input: null, output);

0 commit comments

Comments
 (0)