Skip to content

Commit d9b3baf

Browse files
Adds IExternalInvoker (#776)
* Define a contract for the external invoker * Remove extraneous comments and variables
1 parent 7232e58 commit d9b3baf

File tree

10 files changed

+168
-95
lines changed

10 files changed

+168
-95
lines changed

src/DurableSDK/ExternalInvoker.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
7+
{
8+
using System;
9+
using System.Management.Automation;
10+
11+
internal class ExternalInvoker : IExternalInvoker
12+
{
13+
private readonly Func<PowerShell, object> _externalSDKInvokerFunction;
14+
private readonly IPowerShellServices _powerShellServices;
15+
16+
public ExternalInvoker(Func<PowerShell, object> invokerFunction, IPowerShellServices powerShellServices)
17+
{
18+
_externalSDKInvokerFunction = invokerFunction;
19+
_powerShellServices = powerShellServices;
20+
}
21+
22+
public void Invoke()
23+
{
24+
_externalSDKInvokerFunction.Invoke(_powerShellServices.GetPowerShell());
25+
}
26+
}
27+
}

src/DurableSDK/IExternalInvoker.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
7+
{
8+
// Represents a contract for the
9+
internal interface IExternalInvoker
10+
{
11+
// Method to invoke an orchestration using the external Durable SDK
12+
void Invoke();
13+
}
14+
}

src/DurableSDK/IOrchestrationInvoker.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55

66
namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
77
{
8-
using System;
98
using System.Collections;
10-
using System.Management.Automation;
119

1210
internal interface IOrchestrationInvoker
1311
{
1412
Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh);
15-
void SetExternalInvoker(Action<PowerShell> externalInvoker);
13+
void SetExternalInvoker(IExternalInvoker externalInvoker);
1614
}
1715
}

src/DurableSDK/IPowerShellServices.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal interface IPowerShellServices
1717

1818
void SetDurableClient(object durableClient);
1919

20-
OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding orchestrationContext, out Action<object> externalInvoker);
20+
OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding context, out IExternalInvoker externalInvoker);
2121

2222
void ClearOrchestrationContext();
2323

src/DurableSDK/OrchestrationContext.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ public class OrchestrationContext
3535

3636
internal OrchestrationActionCollector OrchestrationActionCollector { get; } = new OrchestrationActionCollector();
3737

38-
internal object ExternalResult;
39-
internal bool ExternalIsError;
38+
internal object ExternalSDKResult;
39+
40+
internal bool ExternalSDKIsError;
4041

4142
// Called by the External DF SDK to communicate its orchestration result
4243
// back to the worker.
4344
internal void SetExternalResult(object result, bool isError)
4445
{
45-
this.ExternalResult = result;
46-
this.ExternalIsError = isError;
46+
this.ExternalSDKResult = result;
47+
this.ExternalSDKIsError = isError;
4748
}
4849

4950
internal object CustomStatus { get; set; }

src/DurableSDK/OrchestrationInvoker.cs

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,80 +11,95 @@ 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-
private Action<PowerShell> externalInvoker = null;
18+
private IExternalInvoker _externalInvoker;
2019

21-
public Hashtable Invoke(OrchestrationBindingInfo orchestrationBindingInfo, IPowerShellServices pwsh)
20+
public Hashtable Invoke(
21+
OrchestrationBindingInfo orchestrationBindingInfo,
22+
IPowerShellServices powerShellServices)
2223
{
23-
2424
try
2525
{
26-
if (pwsh.UseExternalDurableSDK())
26+
if (powerShellServices.UseExternalDurableSDK())
2727
{
28-
externalInvoker.Invoke(pwsh.GetPowerShell());
29-
var result = orchestrationBindingInfo.Context.ExternalResult;
30-
var isError = orchestrationBindingInfo.Context.ExternalIsError;
31-
if (isError)
32-
{
33-
throw (Exception)result;
34-
}
35-
else
36-
{
37-
return (Hashtable)result;
38-
}
28+
return InvokeExternalDurableSDK(orchestrationBindingInfo, powerShellServices);
3929
}
30+
return InvokeInternalDurableSDK(orchestrationBindingInfo, powerShellServices);
31+
}
32+
finally
33+
{
34+
powerShellServices.ClearStreamsAndCommands();
35+
}
36+
}
4037

41-
var outputBuffer = new PSDataCollection<object>();
42-
var context = orchestrationBindingInfo.Context;
38+
public Hashtable InvokeExternalDurableSDK(
39+
OrchestrationBindingInfo orchestrationBindingInfo,
40+
IPowerShellServices powerShellServices)
41+
{
4342

44-
// context.History should never be null when initializing CurrentUtcDateTime
45-
var orchestrationStart = context.History.First(
46-
e => e.EventType == HistoryEventType.OrchestratorStarted);
47-
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();
43+
_externalInvoker.Invoke();
44+
var result = orchestrationBindingInfo.Context.ExternalSDKResult;
45+
var isError = orchestrationBindingInfo.Context.ExternalSDKIsError;
46+
if (isError)
47+
{
48+
throw (Exception)result;
49+
}
50+
else
51+
{
52+
return (Hashtable)result;
53+
}
54+
}
55+
56+
public Hashtable InvokeInternalDurableSDK(
57+
OrchestrationBindingInfo orchestrationBindingInfo,
58+
IPowerShellServices powerShellServices)
59+
{
60+
var outputBuffer = new PSDataCollection<object>();
61+
var context = orchestrationBindingInfo.Context;
4862

49-
// Marks the first OrchestratorStarted event as processed
50-
orchestrationStart.IsProcessed = true;
63+
// context.History should never be null when initializing CurrentUtcDateTime
64+
var orchestrationStart = context.History.First(
65+
e => e.EventType == HistoryEventType.OrchestratorStarted);
66+
context.CurrentUtcDateTime = orchestrationStart.Timestamp.ToUniversalTime();
5167

52-
// Finish initializing the Function invocation
53-
pwsh.AddParameter(orchestrationBindingInfo.ParameterName, context);
54-
pwsh.TracePipelineObject();
68+
// Marks the first OrchestratorStarted event as processed
69+
orchestrationStart.IsProcessed = true;
5570

56-
var asyncResult = pwsh.BeginInvoke(outputBuffer);
71+
// Finish initializing the Function invocation
72+
powerShellServices.AddParameter(orchestrationBindingInfo.ParameterName, context);
73+
powerShellServices.TracePipelineObject();
5774

58-
var (shouldStop, actions) =
59-
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);
75+
var asyncResult = powerShellServices.BeginInvoke(outputBuffer);
6076

61-
if (shouldStop)
77+
var (shouldStop, actions) =
78+
orchestrationBindingInfo.Context.OrchestrationActionCollector.WaitForActions(asyncResult.AsyncWaitHandle);
79+
80+
if (shouldStop)
81+
{
82+
// The orchestration function should be stopped and restarted
83+
powerShellServices.StopInvoke();
84+
// return (Hashtable)orchestrationBindingInfo.Context.OrchestrationActionCollector.output;
85+
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
86+
}
87+
else
88+
{
89+
try
6290
{
63-
// The orchestration function should be stopped and restarted
64-
pwsh.StopInvoke();
65-
return CreateOrchestrationResult(isDone: false, actions, output: null, context.CustomStatus);
91+
// The orchestration function completed
92+
powerShellServices.EndInvoke(asyncResult);
93+
var result = CreateReturnValueFromFunctionOutput(outputBuffer);
94+
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
6695
}
67-
else
96+
catch (Exception e)
6897
{
69-
try
70-
{
71-
// The orchestration function completed
72-
pwsh.EndInvoke(asyncResult);
73-
var result = CreateReturnValueFromFunctionOutput(outputBuffer);
74-
return CreateOrchestrationResult(isDone: true, actions, output: result, context.CustomStatus);
75-
}
76-
catch (Exception e)
77-
{
78-
// The orchestrator code has thrown an unhandled exception:
79-
// this should be treated as an entire orchestration failure
80-
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
81-
}
98+
// The orchestrator code has thrown an unhandled exception:
99+
// this should be treated as an entire orchestration failure
100+
throw new OrchestrationFailureException(actions, context.CustomStatus, e);
82101
}
83102
}
84-
finally
85-
{
86-
pwsh.ClearStreamsAndCommands();
87-
}
88103
}
89104

90105
public static object CreateReturnValueFromFunctionOutput(IList<object> pipelineItems)
@@ -107,9 +122,9 @@ private static Hashtable CreateOrchestrationResult(
107122
return new Hashtable { { "$return", orchestrationMessage } };
108123
}
109124

110-
public void SetExternalInvoker(Action<PowerShell> externalInvoker)
125+
public void SetExternalInvoker(IExternalInvoker externalInvoker)
111126
{
112-
this.externalInvoker = externalInvoker;
127+
_externalInvoker = externalInvoker;
113128
}
114129
}
115130
}

src/DurableSDK/PowerShellServices.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ internal class PowerShellServices : IPowerShellServices
2525

2626
public PowerShellServices(PowerShell pwsh)
2727
{
28-
// Attempt to import the external SDK
28+
// We attempt to import the external SDK upon construction of the PowerShellServices object.
29+
// We maintain the boolean member _useExternalDurableSDK in this object rather than
30+
// DurableController because the expected input and functionality of SetFunctionInvocationContextCommand
31+
// may differ between the internal and external implementations.
2932
try
3033
{
3134
pwsh.AddCommand(Utils.ImportModuleCmdletInfo)
@@ -46,7 +49,7 @@ public PowerShellServices(PowerShell pwsh)
4649
if (availableModules.Count() > 0)
4750
{
4851
// TODO: evaluate if there is a better error message or exception type to be throwing.
49-
// Ideally, this should never happen
52+
// Ideally, this should never happen.
5053
throw new InvalidOperationException("The external Durable SDK was detected, but unable to be imported.", e);
5154
}
5255
_useExternalDurableSDK = false;
@@ -78,38 +81,46 @@ public void SetDurableClient(object durableClient)
7881
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
7982
.AddParameter("DurableClient", durableClient)
8083
.InvokeAndClearCommands();
81-
84+
// TODO: is _hasSetOrchestrationContext properly named?
8285
_hasSetOrchestrationContext = true;
8386
}
8487

85-
public OrchestrationBindingInfo SetOrchestrationContext(ParameterBinding context, out Action<object> externalInvoker)
88+
public OrchestrationBindingInfo SetOrchestrationContext(
89+
ParameterBinding context,
90+
out IExternalInvoker externalInvoker)
8691
{
8792
externalInvoker = null;
88-
var orchBindingInfo = new OrchestrationBindingInfo(
93+
OrchestrationBindingInfo orchestrationBindingInfo = new OrchestrationBindingInfo(
8994
context.Name,
9095
JsonConvert.DeserializeObject<OrchestrationContext>(context.Data.String));
9196

9297
if (_useExternalDurableSDK)
9398
{
94-
Collection<Action<object>> output = _pwsh.AddCommand(SetFunctionInvocationContextCommand)
99+
Collection<Func<PowerShell, object>> output = _pwsh.AddCommand(SetFunctionInvocationContextCommand)
100+
// The external SetFunctionInvocationContextCommand expects a .json string to deserialize
101+
// and writes an invoker function to the output pipeline.
95102
.AddParameter("OrchestrationContext", context.Data.String)
96-
.AddParameter("SetResult", (Action<object, bool>) orchBindingInfo.Context.SetExternalResult)
97-
.InvokeAndClearCommands<Action<object>>();
103+
.AddParameter("SetResult", (Action<object, bool>) orchestrationBindingInfo.Context.SetExternalResult)
104+
.InvokeAndClearCommands<Func<PowerShell, object>>();
98105
if (output.Count() == 1)
99106
{
100-
externalInvoker = output[0];
107+
externalInvoker = new ExternalInvoker(output[0], this);
108+
}
109+
else
110+
{
111+
throw new InvalidOperationException($"Only a single output was expected for an invocation of {SetFunctionInvocationContextCommand}");
101112
}
102113
}
103114
else
104115
{
105116
_pwsh.AddCommand(SetFunctionInvocationContextCommand)
106-
.AddParameter("OrchestrationContext", orchBindingInfo.Context)
107-
.InvokeAndClearCommands<Action<object>>();
117+
.AddParameter("OrchestrationContext", orchestrationBindingInfo.Context)
118+
.InvokeAndClearCommands();
108119
}
109-
110120
_hasSetOrchestrationContext = true;
111-
return orchBindingInfo;
121+
return orchestrationBindingInfo;
112122
}
123+
113124

114125
public void AddParameter(string name, object value)
115126
{

src/DurableWorker/DurableController.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ public void InitializeBindings(IList<ParameterBinding> inputData)
6767
}
6868
else if (_durableFunctionInfo.IsOrchestrationFunction)
6969
{
70-
var contextBindingData = inputData[0];
71-
_orchestrationBindingInfo = _powerShellServices.SetOrchestrationContext(contextBindingData, out var externalInvoker);
72-
if (externalInvoker != null)
73-
{
74-
this._orchestrationInvoker.SetExternalInvoker(externalInvoker);
75-
}
70+
_orchestrationBindingInfo = _powerShellServices.SetOrchestrationContext(
71+
inputData[0],
72+
out IExternalInvoker externalInvoker);
73+
_orchestrationInvoker.SetExternalInvoker(externalInvoker);
7674
}
7775
}
7876

src/Modules/Microsoft.Azure.Functions.PowerShellWorker/Microsoft.Azure.Functions.PowerShellWorker.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Set-Alias -Name Start-NewOrchestration -Value Start-DurableOrchestration
1111

1212
function GetDurableClientFromModulePrivateData {
1313
$PrivateData = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData
14-
if ($PrivateData -eq $null -or $PrivateData['DurableClient'] -eq $null) {
14+
if ($null -eq $PrivateData -or $null -eq $PrivateData['DurableClient']) {
1515
throw "No binding of the type 'durableClient' was defined."
1616
}
1717
else {

0 commit comments

Comments
 (0)